l2md: make output modular to support different modes

Refactor the output handling of new mails in order to allow adding
new modes as next step.

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
diff --git a/Makefile b/Makefile
index 19b0ce8..8ef22d9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 CFLAGS = -O2 -Wall -Werror
 LDFLAGS = -lgit2
 
-l2md: l2md.o config.o env.o utils.o repo.o mail.o
+l2md: l2md.o config.o env.o utils.o repo.o mail.o maildir.o
 	$(CC) -o $@ $^ $(LDFLAGS)
 
 install:
diff --git a/config.c b/config.c
index 72eb6f8..129aa75 100644
--- a/config.c
+++ b/config.c
@@ -27,11 +27,12 @@
 		return;
 
 	verbose("general.base = %s\n", cfg->general.base);
-	verbose("general.maildir = %s\n", cfg->general.maildir);
+	verbose("general.mode = %s\n", cfg->ops->name);
+	verbose("general.%s = %s\n", cfg->ops->name, cfg->general.out);
 	verbose("general.period = %u\n", cfg->general.period);
 
 	repo_for_each(cfg, repo, i) {
-		verbose("repos.%s.maildir = %s\n", repo->name, repo->maildir);
+		verbose("repos.%s.%s = %s\n", repo->name, cfg->ops->name, repo->out);
 		verbose("repos.%s.initial_import = %u\n", repo->name, repo->initial_import);
 		url_for_each(repo, url, j) {
 			verbose("repos.%s.url = %s\n", repo->name, url->path);
@@ -70,15 +71,23 @@
 	wordfree(&p);
 }
 
-static void config_set_maildir(struct config *cfg, const char *dir, bool root)
+static void config_set_mode(struct config *cfg, const char *mode)
+{
+	if (!strncmp(mode, "maildir", sizeof("maildir")))
+		cfg->ops = &ops_maildir;
+	else
+		panic("Unknown mode: %s!\n", mode);
+}
+
+static void config_set_out(struct config *cfg, const char *ctx, bool root)
 {
 	struct config_repo *repo = repo_last(cfg);
-	char *maildir = root ? cfg->general.maildir : repo->maildir;
+	char *out = root ? cfg->general.out : repo->out;
 	wordexp_t p;
 
-	wordexp(dir, &p, 0);
-	dir = p.we_wordv[0];
-	strlcpy(maildir, dir, sizeof(repo->maildir));
+	wordexp(ctx, &p, 0);
+	ctx = p.we_wordv[0];
+	strlcpy(out, ctx, sizeof(repo->out));
 	wordfree(&p);
 }
 
@@ -113,7 +122,7 @@
 			      cfg->repos_num);
 	repo = repo_last(cfg);
 	memset(repo, 0, sizeof(*repo));
-	config_set_maildir(cfg, cfg->general.maildir, false);
+	config_set_out(cfg, cfg->general.out, false);
 	strlcpy(repo->name, name, sizeof(repo->name));
 }
 
@@ -121,13 +130,14 @@
 {
 	char path[PATH_MAX];
 
+	cfg->ops = &ops_maildir;
 	cfg->general.period = 60;
 
 	slprintf(path, sizeof(path), "%s/.l2md", homedir);
 	strlcpy(cfg->general.base, path, sizeof(cfg->general.base));
 
 	slprintf(path, sizeof(path), "%s/maildir", cfg->general.base);
-	strlcpy(cfg->general.maildir, path, sizeof(cfg->general.maildir));
+	strlcpy(cfg->general.out, path, sizeof(cfg->general.out));
 }
 
 void config_uninit(struct config *cfg)
@@ -144,7 +154,7 @@
 struct config *config_init(int argc, char **argv)
 {
 	const char *homedir = getenv("HOME");
-	char buff[1024], tmp[1024];
+	char buff[1024], tmp[1024] = {};
 	char path[PATH_MAX];
 	bool seen[STATE_MAX] = {};
 	int state = STATE_NONE;
@@ -182,7 +192,7 @@
 		state_next:
 			if (!strcmp(buff, "[general]\n")) {
 				state = STATE_GENERAL;
-			} else if (sscanf(buff, "[repo %[a-z0-9-]]\n",
+			} else if (sscanf(buff, "[repo %1023[a-z0-9-]]\n",
 					  tmp) == 1) {
 				state = STATE_REPO;
 				config_new_repo(cfg, tmp);
@@ -196,19 +206,21 @@
 				panic("[general] config must be before [repo *] config");
 			else if (sscanf(buff, "\tperiod = %u", &val) == 1)
 				cfg->general.period = val;
-			else if (sscanf(buff, "\tmaildir = %s", tmp) == 1)
-				config_set_maildir(cfg, tmp, true);
-			else if (sscanf(buff, "\tbase = %s", tmp) == 1)
+			else if (sscanf(buff, "\tmode = %1023s", tmp) == 1)
+				config_set_mode(cfg, tmp);
+			else if (sscanf(buff, "\tmaildir = %1023s", tmp) == 1)
+				config_set_out(cfg, tmp, true);
+			else if (sscanf(buff, "\tbase = %1023s", tmp) == 1)
 				config_set_basedir(cfg, tmp);
 			else
 				goto state_next;
 			break;
 
 		case STATE_REPO:
-			if (sscanf(buff, "\turl = %s", tmp) == 1)
+			if (sscanf(buff, "\turl = %1023s", tmp) == 1)
 				config_new_url(cfg, tmp);
-			else if (sscanf(buff, "\tmaildir = %s", tmp) == 1)
-				config_set_maildir(cfg, tmp, false);
+			else if (sscanf(buff, "\tmaildir = %1023s", tmp) == 1)
+				config_set_out(cfg, tmp, false);
 			else if (sscanf(buff, "\tinitial_import = %u", &val) == 1)
 				config_set_initial_import(cfg, val);
 			else
diff --git a/env.c b/env.c
index 3139fe2..f72d15c 100644
--- a/env.c
+++ b/env.c
@@ -52,16 +52,7 @@
 
 static void bootstrap_mail(struct config *cfg)
 {
-	struct config_repo *repo;
-	uint32_t i;
-
-	repo_for_each(cfg, repo, i) {
-		xmkdir1_with_subdirs(repo->maildir);
-
-		xmkdir2(repo->maildir, "cur");
-		xmkdir2(repo->maildir, "tmp");
-		xmkdir2(repo->maildir, "new");
-	}
+	cfg->ops->bootstrap(cfg);
 }
 
 void bootstrap_env(struct config *cfg)
diff --git a/l2md.h b/l2md.h
index 45e87d5..7389d8c 100644
--- a/l2md.h
+++ b/l2md.h
@@ -34,8 +34,18 @@
 
 #define MAIL                    "m"
 
+struct config;
+struct config_repo;
+
+struct mail_ops {
+	const char *name;
+	void (*bootstrap)(struct config *cfg);
+	void (*new_mail)(struct config_repo *repo, uint32_t url,
+			 const char *oid, const void *raw, size_t len);
+};
+
 struct config_general {
-	char                    maildir[PATH_MAX];
+	char                    out[PATH_MAX];
 	char                    base[PATH_MAX];
 	uint32_t                period;
 };
@@ -48,7 +58,7 @@
 
 struct config_repo {
 	char		        name[128];
-	char                    maildir[PATH_MAX];
+	char                    out[PATH_MAX];
 	git_repository         *git;
 	struct config_url      *urls;
 	uint32_t                urls_num;
@@ -60,6 +70,7 @@
 	struct config_general   general;
 	struct config_repo     *repos;
 	uint32_t                repos_num;
+	const struct mail_ops  *ops;
 };
 
 #define repo_for_each(cfg, repo, i) \
@@ -79,6 +90,8 @@
 extern pid_t own_pid;
 extern bool verbose_enabled;
 
+extern struct mail_ops ops_maildir;
+
 struct config *config_init(int argc, char **argv);
 void config_uninit(struct config *cfg);
 
@@ -90,10 +103,10 @@
 
 void repo_clone(struct config_repo *repo, struct config_url *url, const char *target);
 void repo_pull(struct config_repo *repo, struct config_url *url, const char *target);
-void repo_walk_files(struct config *cfg, struct config_repo *repo, uint32_t which,
+void repo_walk_files(struct config *cfg, struct config_repo *repo, uint32_t url,
 		     const char *path, const char *oid_last, char *oid_done,
 		     void (*repo_walker)(struct config *cfg, struct config_repo *repo,
-				         uint32_t which, const char *oid,
+				         uint32_t url, const char *oid,
 				         const void *raw, size_t len));
 
 void repo_local_path(struct config *cfg, struct config_repo *repo,
diff --git a/mail.c b/mail.c
index fd8f783..1d2fdda 100644
--- a/mail.c
+++ b/mail.c
@@ -8,15 +8,11 @@
 
 #include "l2md.h"
 
-static void repo_walker(struct config *cfg, struct config_repo *repo, uint32_t which,
-			const char *oid, const void *raw, size_t len)
+static void repo_walker(struct config *cfg, struct config_repo *repo,
+			uint32_t url, const char *oid,
+			const void *raw, size_t len)
 {
-	char dst[PATH_MAX];
-
-	slprintf(dst, sizeof(dst), "%s/new/0.%06u.%s-%u-%s",
-		 repo->maildir, own_pid, repo->name, which, oid);
-
-	xwrite_file(dst, raw, len, true);
+	cfg->ops->new_mail(repo, url, oid, raw, len);
 }
 
 void sync_mail(struct config *cfg)
@@ -26,7 +22,7 @@
 	char path[PATH_MAX];
 	uint32_t i, j;
 
-	verbose("Resyncing maildirs.\n");
+	verbose("Resyncing mails.\n");
 
 	repo_for_each(cfg, repo, i) {
 		url_for_each(repo, url, j) {
diff --git a/maildir.c b/maildir.c
new file mode 100644
index 0000000..5228650
--- /dev/null
+++ b/maildir.c
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2019 Daniel Borkmann <daniel@iogearbox.net> */
+
+#include <unistd.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include "l2md.h"
+
+static void maildir_new_mail(struct config_repo *repo, uint32_t which,
+			     const char *oid, const void *raw, size_t len)
+{
+	char dst[PATH_MAX];
+
+	slprintf(dst, sizeof(dst), "%s/new/0.%06u.%s-%u-%s",
+		 repo->out, own_pid, repo->name, which, oid);
+
+	xwrite_file(dst, raw, len, true);
+}
+
+static void maildir_bootstrap(struct config *cfg)
+{
+	struct config_repo *repo;
+	uint32_t i;
+
+	repo_for_each(cfg, repo, i) {
+		xmkdir1_with_subdirs(repo->out);
+
+		xmkdir2(repo->out, "cur");
+		xmkdir2(repo->out, "tmp");
+		xmkdir2(repo->out, "new");
+	}
+}
+
+struct mail_ops ops_maildir = {
+	.name		= "maildir",
+	.bootstrap	= maildir_bootstrap,
+	.new_mail	= maildir_new_mail,
+};
diff --git a/repo.c b/repo.c
index 843bfbe..c5ca8e8 100644
--- a/repo.c
+++ b/repo.c
@@ -179,10 +179,10 @@
 	__repo_local_get(cfg, repo, url, OIDS, out, len);
 }
 
-void repo_walk_files(struct config *cfg, struct config_repo *repo, uint32_t which,
+void repo_walk_files(struct config *cfg, struct config_repo *repo, uint32_t url,
 		     const char *path, const char *oid_last, char *oid_done,
 		     void (*repo_walker)(struct config *cfg, struct config_repo *repo,
-					 uint32_t which, const char *oid,
+					 uint32_t url, const char *oid,
 					 const void *raw, size_t len))
 {
 	char oid_curr[GIT_OID_HEXSZ + 1];
@@ -249,7 +249,7 @@
 			panic_git("Cannot revparse object");
 
 		blob = (const git_blob *)object;
-		repo_walker(cfg, repo, which, oid_curr, git_blob_rawcontent(blob),
+		repo_walker(cfg, repo, url, oid_curr, git_blob_rawcontent(blob),
 			    (size_t)git_blob_rawsize(blob));
 
 		git_object_free(object);
@@ -266,7 +266,7 @@
 	if (have_first && count) {
 		git_oid_tostr(oid_curr, sizeof(oid_curr), &loid);
 		printf("Processed %u new mail(s) for %s. %s: %s -> %s in %ld.%02lds.\n",
-		       count, repo->name, repo->urls[which].path, oid_last ? oid_curr :
+		       count, repo->name, repo->urls[url].path, oid_last ? oid_curr :
 		       "[none]", oid_done, res.tv_sec, res.tv_usec);
 	}