| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (C) 2019 Daniel Borkmann <daniel@iogearbox.net> */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <wordexp.h> |
| #include <stdbool.h> |
| |
| #include <sys/resource.h> |
| |
| #include "l2md.h" |
| |
| enum { |
| STATE_NONE = 0, |
| STATE_GENERAL, |
| STATE_REPO, |
| STATE_MAX, |
| }; |
| |
| static void config_dump(struct config *cfg) |
| { |
| struct config_repo *repo; |
| struct config_url *url; |
| uint32_t i, j; |
| |
| if (!verbose_enabled) |
| return; |
| |
| verbose("general.base = %s\n", cfg->general.base); |
| 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.%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); |
| verbose("repos.%s.oid = %s\n", repo->name, |
| url->oid_known ? url->oid : "[unknown]"); |
| } |
| } |
| |
| verbose("oneshot = %d\n", cfg->oneshot); |
| } |
| |
| static void config_probe_oids(struct config *cfg) |
| { |
| struct config_repo *repo; |
| struct config_url *url; |
| char path[PATH_MAX]; |
| uint32_t i, j; |
| int ret; |
| |
| repo_for_each(cfg, repo, i) { |
| url_for_each(repo, url, j) { |
| repo_local_oid(cfg, repo, url, path, sizeof(path)); |
| ret = xread_file(path, url->oid, sizeof(url->oid) - 1, |
| false); |
| if (!ret) |
| url->oid_known = true; |
| } |
| } |
| } |
| |
| static void config_set_basedir(struct config *cfg, const char *dir) |
| { |
| wordexp_t p; |
| |
| wordexp(dir, &p, 0); |
| dir = p.we_wordv[0]; |
| strlcpy(cfg->general.base, dir, sizeof(cfg->general.base)); |
| wordfree(&p); |
| } |
| |
| static void config_set_ops(struct config *cfg, const struct mail_ops* ops) |
| { |
| cfg->ops = ops; |
| } |
| |
| static void config_check_ops(struct config *cfg, const struct mail_ops* ops) |
| { |
| if (cfg->ops != ops) |
| panic("mode %s in [general] must match [repo *] mode %s\n", |
| cfg->ops->name, ops->name); |
| } |
| |
| static void config_set_mode(struct config *cfg, const char *mode) |
| { |
| if (!strncmp(mode, "maildir", sizeof("maildir"))) |
| config_set_ops(cfg, &ops_maildir); |
| else if (!strncmp(mode, "pipe", sizeof("pipe"))) |
| config_set_ops(cfg, &ops_pipe); |
| 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 *out = root ? cfg->general.out : repo->out; |
| wordexp_t p; |
| |
| wordexp(ctx, &p, 0); |
| ctx = p.we_wordv[0]; |
| strlcpy(out, ctx, sizeof(repo->out)); |
| wordfree(&p); |
| } |
| |
| static void config_set_initial_import(struct config *cfg, uint32_t limit) |
| { |
| struct config_repo *repo = repo_last(cfg); |
| |
| repo->initial_import = limit; |
| if (repo->initial_import > 0) |
| repo->limit = true; |
| } |
| |
| static void config_set_repo_status(struct config *cfg, bool status) |
| { |
| struct config_repo *repo = repo_last(cfg); |
| |
| repo->sync_enabled = status; |
| } |
| |
| static void config_new_url(struct config *cfg, const char *git_url) |
| { |
| struct config_repo *repo = repo_last(cfg); |
| struct config_url *url; |
| |
| repo->urls_num++; |
| repo->urls = xrealloc(repo->urls, sizeof(*repo->urls) * |
| repo->urls_num); |
| url = url_last(repo); |
| memset(url, 0, sizeof(*url)); |
| strlcpy(url->path, git_url, sizeof(url->path)); |
| } |
| |
| static void config_new_repo(struct config *cfg, const char *name) |
| { |
| struct config_repo *repo; |
| |
| cfg->repos_num++; |
| cfg->repos = xrealloc(cfg->repos, sizeof(*cfg->repos) * |
| cfg->repos_num); |
| repo = repo_last(cfg); |
| memset(repo, 0, sizeof(*repo)); |
| config_set_out(cfg, cfg->general.out, false); |
| config_set_repo_status(cfg, true); |
| strlcpy(repo->name, name, sizeof(repo->name)); |
| } |
| |
| static void config_set_defaults(struct config *cfg, const char *homedir) |
| { |
| char path[PATH_MAX]; |
| |
| cfg->general.period = 60; |
| |
| slprintf(path, sizeof(path), "%s/.l2md", homedir); |
| strlcpy(cfg->general.base, path, sizeof(cfg->general.base)); |
| |
| config_set_ops(cfg, &ops_maildir); |
| cfg->ops->set_defaults(cfg); |
| } |
| |
| static void config_ulimits(void) |
| { |
| struct rlimit limit; |
| int ret; |
| |
| ret = getrlimit(RLIMIT_NOFILE, &limit); |
| if (ret < 0) |
| panic("Cannot retrieve rlimit!\n"); |
| |
| /* The git repos we're dealing with can have a lot of |
| * pack files potentially, hence increase open file limit |
| * to help libgit2's revwalk. |
| * |
| * If there is a better API supported by native libgit2 |
| * for doing repack, we should use that instead: |
| * |
| * https://github.com/libgit2/libgit2/issues/3247 |
| */ |
| limit.rlim_cur = limit.rlim_max; |
| |
| ret = setrlimit(RLIMIT_NOFILE, &limit); |
| if (ret < 0) |
| panic("Cannot set rlimit!\n"); |
| } |
| |
| void config_uninit(struct config *cfg) |
| { |
| struct config_repo *repo; |
| uint32_t i; |
| |
| repo_for_each(cfg, repo, i) |
| xfree(repo->urls); |
| xfree(cfg->repos); |
| xfree(cfg); |
| } |
| |
| struct config *config_init(int argc, char **argv) |
| { |
| const char *homedir = getenv("HOME"); |
| char buff[1024], tmp[1024] = {}; |
| char path[PATH_MAX]; |
| bool seen[STATE_MAX] = {}; |
| int state = STATE_NONE; |
| struct config *cfg; |
| FILE *fp; |
| |
| if (argc > 1) { |
| if (argc == 2 && !strncmp(argv[1], "--verbose", |
| sizeof("--verbose"))) |
| verbose_enabled = true; |
| else |
| panic("Usage: %s [--verbose]\n", argv[0]); |
| } |
| |
| if (!homedir) |
| panic("Cannot retrieve $HOME from env!\n"); |
| |
| slprintf(path, sizeof(path), "%s/.l2mdconfig", homedir); |
| fp = fopen(path, "r"); |
| if (!fp) |
| panic("Cannot open config %s: %s\n", path, strerror(errno)); |
| |
| config_ulimits(); |
| |
| cfg = xzmalloc(sizeof(*cfg)); |
| config_set_defaults(cfg, homedir); |
| |
| while (fgets(buff, sizeof(buff), fp)) { |
| uint32_t val; |
| |
| if (buff[0] == '#' || buff[0] == '\n') |
| continue; |
| |
| seen[state] = true; |
| switch (state) { |
| case STATE_NONE: |
| state_next: |
| if (!strcmp(buff, "[general]\n")) { |
| state = STATE_GENERAL; |
| } else if (sscanf(buff, "[repo %1023[a-z0-9-]]\n", |
| tmp) == 1) { |
| state = STATE_REPO; |
| config_new_repo(cfg, tmp); |
| } else { |
| panic("Cannot parse: '%s'\n", buff); |
| } |
| break; |
| |
| case STATE_GENERAL: |
| if (seen[STATE_REPO]) { |
| panic("[general] config must be before [repo *] config\n"); |
| } else if (sscanf(buff, "\tperiod = %u", &val) == 1) { |
| cfg->general.period = val; |
| } else if (sscanf(buff, "\tmode = %1023s", tmp) == 1) { |
| config_set_mode(cfg, tmp); |
| } else if (sscanf(buff, "\tmaildir = %1023s", tmp) == 1) { |
| config_set_ops(cfg, &ops_maildir); |
| config_set_out(cfg, tmp, true); |
| } else if (sscanf(buff, "\tpipe = %1023s", tmp) == 1) { |
| config_set_ops(cfg, &ops_pipe); |
| config_set_out(cfg, tmp, true); |
| } else if (sscanf(buff, "\tbase = %1023s", tmp) == 1) { |
| config_set_basedir(cfg, tmp); |
| } else if (sscanf(buff, "\toneshot = %u", &val) == 1) { |
| cfg->oneshot = val; |
| } else { |
| goto state_next; |
| } |
| break; |
| |
| case STATE_REPO: |
| if (sscanf(buff, "\turl = %1023s", tmp) == 1) { |
| config_new_url(cfg, tmp); |
| } else if (sscanf(buff, "\tmaildir = %1023s", tmp) == 1) { |
| config_check_ops(cfg, &ops_maildir); |
| config_set_out(cfg, tmp, false); |
| } else if (sscanf(buff, "\tpipe = %1023s", tmp) == 1) { |
| config_check_ops(cfg, &ops_pipe); |
| config_set_out(cfg, tmp, false); |
| } else if (sscanf(buff, "\tinitial_import = %u", &val) == 1) { |
| config_set_initial_import(cfg, val); |
| } else if (sscanf(buff, "\tsync_enabled = %u", &val) == 1) { |
| config_set_repo_status(cfg, val); |
| } else { |
| goto state_next; |
| } |
| break; |
| |
| default: |
| panic("Invalid parser state: %d\n", state); |
| }; |
| } |
| |
| fclose(fp); |
| |
| config_probe_oids(cfg); |
| config_dump(cfg); |
| |
| return cfg; |
| } |