first functional version that actually records + mirrors data
diff --git a/src/e1_recorder.c b/src/e1_recorder.c
index 5c53205..d8dccfe 100644
--- a/src/e1_recorder.c
+++ b/src/e1_recorder.c
@@ -3,6 +3,7 @@
 #include <osmocom/core/application.h>
 #include <osmocom/vty/vty.h>
 #include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
 
 #include <osmocom/abis/abis.h>
 #include <osmocom/abis/e1_input.h>
@@ -35,7 +36,15 @@
 	e1frame_store(ts, msg, cap_mode);
 
 	if (rline->mirror.enabled) {
+		struct e1inp_line *other_line =
+				e1inp_line_find(rline->mirror.line_nr);
+		struct e1inp_ts *other_ts;
+		other_ts = &other_line->ts[ts->num-1];
+		if (!other_ts)
+			return;
 		/* forward data to destination line */
+		OSMO_ASSERT(other_ts->type == ts->type);
+		msgb_enqueue(&other_ts->raw.tx_queue, msg);
 	}
 }
 
@@ -49,11 +58,10 @@
 	return 0;
 }
 
-//	e1inp_ts_config_raw(ts, line, &e1ts_raw_recv);
-
 static const struct log_info_cat recorder_categories[] = {
 	[DMAIN] = {
 		.name = "MAIN",
+		.description = "Osmocom E1 Recorder",
 		.enabled = 1, .loglevel = LOGL_DEBUG,
 	},
 };
@@ -79,6 +87,7 @@
 
 	osmo_init_logging(&info);
 	vty_init(&vty_info);
+	logging_vty_add_cmds(&info);
 	osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
 	libosmo_abis_init(rec_tall_ctx);
 	e1inp_vty_init();
diff --git a/src/recorder.h b/src/recorder.h
index eaa5eab..12bc993 100644
--- a/src/recorder.h
+++ b/src/recorder.h
@@ -2,6 +2,7 @@
 #include <stdbool.h>
 
 #include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
 #include <osmocom/vty/command.h>
 #include <osmocom/abis/e1_input.h>
 
diff --git a/src/storage.c b/src/storage.c
index 571a043..2b9fb9f 100644
--- a/src/storage.c
+++ b/src/storage.c
@@ -1,20 +1,86 @@
+#include <time.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
 #include <osmocom/core/msgb.h>
 #include <osmocom/abis/e1_input.h>
 
 #include "storage.h"
+#include "recorder.h"
+
+static int g_out_fd = -1;;
+static uint64_t g_written_bytes;
+
+static const char *storage_gen_filename(void)
+{
+	static char buf[32];
+	time_t t;
+	struct tm *tmp;
+
+	t = time(NULL);
+	tmp = localtime(&t);
+	strftime(buf, sizeof(buf), "%Y%m%d%H%M%S.e1cap", tmp);
+	return buf;
+}
+
+static int storage_reopen_if_needed(void)
+{
+	if (g_written_bytes / (1024*1024) >= g_recorder.max_file_size_mb) {
+		close(g_out_fd);
+		g_out_fd = -1;
+	}
+
+	if (g_out_fd < 0) {
+		int rc;
+		const char *fname = storage_gen_filename();
+		rc = chdir(g_recorder.storage_path);
+		if (rc < 0) {
+			LOGP(DMAIN, LOGL_ERROR, "Unable to chdir(%s): %s\n",
+				g_recorder.storage_path, strerror(errno));
+			return -1;
+		}
+		g_out_fd = open(fname, O_WRONLY|O_CREAT, 0664);
+		if (g_out_fd < 0) {
+			LOGP(DMAIN, LOGL_ERROR, "Unable to open(%s): %s\n",
+				fname, strerror(errno));
+		}
+		g_written_bytes = 0;
+	}
+
+	return g_out_fd;
+}
 
 int e1frame_store(struct e1inp_ts *ts, struct msgb *msg, enum osmo_e1cap_capture_mode mode)
 {
-	struct osmo_e1cap_pkthdr *h;
-	uint32_t len = msg->len;
+	struct osmo_e1cap_pkthdr _h, *h = &_h;
+	int rc;
+	struct iovec iov[2] = {
+		{
+			.iov_base = h,
+			.iov_len = sizeof(*h),
+		}, {
+			.iov_base = msg->data,
+			.iov_len = msg->len,
+		}
+	};
 
-	h = (struct osmo_e1cap_pkthdr *) msgb_push(msg, sizeof(*h));
-	h->len = htonl(len);
+	h->len = htonl(msg->len);
+	gettimeofday(&h->ts, NULL);
 	h->line_nr = ts->line->num;
 	h->ts_nr = ts->num;
 	h->capture_mode = mode;
 	h->flags = 0;
 
-	/* FIXME: Write */
+	storage_reopen_if_needed();
+
+	rc = writev(g_out_fd, iov, ARRAY_SIZE(iov));
+	if (rc < 0)
+		return rc;
+
+	g_written_bytes += rc;
+
 	return 0;
 }
diff --git a/src/vty.c b/src/vty.c
index 694dab1..ae032fb 100644
--- a/src/vty.c
+++ b/src/vty.c
@@ -15,8 +15,14 @@
 	return CMD_SUCCESS;
 }
 
+static const struct e1inp_line_ops dummy_e1i_line_ops = {
+	.sign_link_up = NULL,
+	.sign_link_down = NULL,
+	.sign_link = NULL,
+};
+
 DEFUN(cfg_rec_line_ts_mode, cfg_rec_line_ts_mode_cmd,
-	"line <0-255> ts <0-31> mode (none|signalling|trau|raw)",
+	"line <0-255> ts <1-31> mode (none|signalling|trau|raw)",
 	LINE_STR
 	"E1/T1 Timeslot Number\n"
 	"E1/T1 Timeslot Number\n"
@@ -46,8 +52,10 @@
 		vty_out(vty, "Timeslot %d is too large%s", ts_nr, VTY_NEWLINE);
 		return CMD_WARNING;
 	}
-	ts = &line->ts[ts_nr];
+	ts = &line->ts[ts_nr-1];
+	e1inp_line_bind_ops(line, &dummy_e1i_line_ops);
 
+	vty_out(vty, "Line %u TS %u mode %u%s", line_nr, ts_nr, mode, VTY_NEWLINE);
 	switch (mode) {
 	case E1INP_TS_TYPE_NONE:
 		/* TOOD: have eqinp_ts_config_none ? */
@@ -59,6 +67,9 @@
 	case E1INP_TS_TYPE_RAW:
 		e1inp_ts_config_raw(ts, line, &e1ts_raw_recv);
 		break;
+	default:
+		vty_out(vty, "Unknown mode %u ?!?%s", mode, VTY_NEWLINE);
+		break;
 	}
 
 	/* notify driver of change */
@@ -157,6 +168,12 @@
 
 	for (i = 0; i < line->num_ts; i++) {
 		struct e1inp_ts *ts = &line->ts[i];
+		const char *mode_str;
+
+		mode_str = get_value_string(e1inp_ts_type_names, ts->type);
+
+		vty_out(vty, " line %u ts %u mode %s%s",
+			lnr, ts->num, mode_str, VTY_NEWLINE);
 	}
 }