| // 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 "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.maildir = %s\n", cfg->general.maildir); |
| 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.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]"); |
| } |
| } |
| } |
| |
| 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_maildir(struct config *cfg, const char *dir, bool root) |
| { |
| struct config_repo *repo = repo_last(cfg); |
| char *maildir = root ? cfg->general.maildir : repo->maildir; |
| wordexp_t p; |
| |
| wordexp(dir, &p, 0); |
| dir = p.we_wordv[0]; |
| strlcpy(maildir, dir, sizeof(repo->maildir)); |
| 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_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_maildir(cfg, cfg->general.maildir, false); |
| 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)); |
| |
| slprintf(path, sizeof(path), "%s/maildir", cfg->general.base); |
| strlcpy(cfg->general.maildir, path, sizeof(cfg->general.maildir)); |
| } |
| |
| 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", tmp, strerror(errno)); |
| |
| 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 %[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"); |
| 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) |
| config_set_basedir(cfg, tmp); |
| else |
| goto state_next; |
| break; |
| |
| case STATE_REPO: |
| if (sscanf(buff, "\turl = %s", 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, "\tinitial_import = %u", &val) == 1) |
| config_set_initial_import(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; |
| } |