blob: 8077da067115c8e1b71f41f13a6f50cb72d97f0f [file] [log] [blame]
// 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_maildir = %s\n",
repo->name, url->oid_known ? url->oid_maildir :
"[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_maildir,
sizeof(url->oid_maildir) - 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;
}