first commit

This commit is contained in:
ngn
2024-05-01 21:02:15 +03:00
commit 5691727d64
32 changed files with 2114 additions and 0 deletions

8
src/args.c Normal file
View File

@ -0,0 +1,8 @@
#include "args.h"
args_t args;
void args_load(int argc, char *argv[]) {
args.count = argc;
args.list = argv;
}

9
src/args.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
typedef struct Args {
int count;
char **list;
} args_t;
extern args_t args;
void args_load(int, char *[]);

173
src/config.c Normal file
View File

@ -0,0 +1,173 @@
#include <ini.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "error.h"
#include "intl.h"
#include "log.h"
#include "util.h"
bool target_is_valid(target_t *target) {
if (NULL == target->name) {
error_set(_("Configuration target is missing a name"));
return false;
}
if (NULL == target->dst) {
error_set(_("Configuration target %s does not have \"dst\" field"), target->name);
return false;
}
if (NULL == target->src) {
error_set(_("Configuration target %s does not have \"src\" field"), target->name);
return false;
}
return true;
}
bool config_add_target(config_t *config, char *name) {
target_t *new = malloc(sizeof(target_t));
clist_init(&new->requires);
new->next = NULL;
new->name = name;
new->desc = NULL;
new->dst = NULL;
new->src = NULL;
if (NULL == config->t_first || NULL == config->t_last) {
config->t_first = new;
config->t_last = new;
return true;
}
config->t_last->next = new;
config->t_last = new;
return true;
}
bool config_is_valid(config_t *config) {
if (NULL == config->name) {
error_set(_("Configuration does not have a name field"));
return false;
}
if (NULL == config->author) {
error_set(_("Configuration does not have an author field"));
return false;
}
if (NULL == config->t_last) {
error_set(_("Configuration does not have any targets"));
return false;
}
return true;
}
int config_handler(void *data, const char *section, const char *key, const char *value) {
#define MATCH(s, k) eq((char *)section, s) && eq((char *)key, k)
#define MATCHKEY(k) eq((char *)key, k)
if (strlen(section) == 0)
return 1;
config_t *config = data;
if (MATCH("details", "name")) {
config->name = strdup(value);
return 1;
} else if (MATCH("details", "author")) {
config->author = strdup(value);
return 1;
} else if (MATCH("details", "keywords")) {
clist_from_str(&config->keywords, (char *)value);
return 1;
}
if (NULL == config->t_last || (NULL != config->t_last->name && !eq(config->t_last->name, (char *)section))) {
if (NULL != config->t_last) {
if (!target_is_valid(config->t_last)) {
errno = ConfigInvalid;
debug("invalid %s %s", config->t_last->name, section);
return 0;
}
}
debug("adding new target => %s", section);
config_add_target(config, strdup(section));
}
if (MATCHKEY("desc")) {
config->t_last->desc = strdup(value);
return 1;
} else if (MATCHKEY("dst")) {
config->t_last->dst = strdup(value);
return 1;
} else if (MATCHKEY("src")) {
config->t_last->dst = strdup(value);
return 1;
} else if (MATCHKEY("requires")) {
clist_from_str(&config->t_last->requires, strdup(value));
return 1;
}
error_set(_("Key %s is unknown"), key);
errno = ConfigUnknown;
return 0;
}
void target_free(target_t *target) {
free(target->name);
free(target->desc);
free(target->dst);
free(target->src);
clist_free(&target->requires);
free(target);
}
void config_free(config_t *config) {
free(config->name);
free(config->author);
target_t *cur = config->t_first, *prev = NULL;
while (cur != NULL) {
prev = cur;
cur = cur->next;
target_free(prev);
}
clist_free(&config->keywords);
}
bool config_load(config_t *config, char *path) {
if (!exists(path) || is_dir(path)) {
errno = ConfigNotFound;
return false;
}
config->name = NULL;
config->author = NULL;
config->t_first = NULL;
config->t_last = NULL;
clist_init(&config->keywords);
if (ini_parse(path, config_handler, config) < 0) {
config_free(config);
if (errno != ConfigUnknown && errno != ConfigInvalid) {
error_set(_("Failed to parse configuration file"));
errno = ConfigParseFail;
}
return false;
}
if (!config_is_valid(config)) {
errno = ConfigInvalid;
config_free(config);
return false;
}
return true;
}

25
src/config.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include "util.h"
#include <stdbool.h>
#include <stddef.h>
typedef struct target {
struct target *next;
clist_t
requires;
char *name;
char *desc;
char *src;
char *dst;
} target_t;
typedef struct config {
target_t *t_first;
target_t *t_last;
char *name;
char *author;
clist_t keywords;
} config_t;
bool config_load(config_t *, char *);

23
src/env.c Normal file
View File

@ -0,0 +1,23 @@
#include "env.h"
#include <pwd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
char *env_mc_root() {
char *env = getenv("MC_ROOT");
if (env == NULL) {
struct passwd *pw = getpwuid(getuid());
return pw->pw_dir;
}
return env;
}
bool env_mc_yes() {
return NULL != getenv("MC_YES");
}
bool env_mc_debug() {
return NULL != getenv("MC_DEBUG");
}

6
src/env.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include <stdbool.h>
char *env_mc_root();
bool env_mc_yes();
bool env_mc_debug();

27
src/error.c Normal file
View File

@ -0,0 +1,27 @@
#include "error.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
char *errch = NULL;
int errno = 0;
char *error_set(char *text, ...) {
if (NULL != errch)
free(errch);
va_list args, argscp;
va_start(args, text);
va_copy(argscp, args);
int size = vsnprintf(NULL, 0, text, args);
errch = malloc(size + 1);
bzero(errch, size + 1);
vsprintf(errch, text, argscp);
va_end(argscp);
va_end(args);
return errch;
}

13
src/error.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
enum ErrorCodes {
ConfigUnknown = 955,
ConfigNotFound = 954,
ConfigInvalid = 953,
ConfigParseFail = 952,
};
extern int errno;
extern char *errch;
char *error_set(char *text, ...);

5
src/gen.c Normal file
View File

@ -0,0 +1,5 @@
#include "gen.h"
bool gen_cmd() {
return true;
}

4
src/gen.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include <stdbool.h>
bool gen_cmd();

3
src/intl.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
#include <libintl.h>
#define _(x) gettext(x)

17
src/lock.c Normal file
View File

@ -0,0 +1,17 @@
#include "lock.h"
#include "paths.h"
#include "util.h"
#include <fcntl.h>
#include <unistd.h>
lock_st lock() {
if (exists(mc_lock_path))
return ALREADY_LOCKED;
if (creat(mc_lock_path, 0600) == -1)
return LOCK_ERROR;
return LOCK_SUCCESS;
}
void unlock() {
unlink(mc_lock_path);
}

9
src/lock.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
typedef enum lock_state {
ALREADY_LOCKED = 1,
LOCK_SUCCESS = 2,
LOCK_ERROR = 3,
} lock_st;
lock_st lock();
void unlock();

114
src/log.c Normal file
View File

@ -0,0 +1,114 @@
#include "log.h"
#include "env.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
int lastprog = -1;
struct winsize barwin = {0};
void bar_init() {
lastprog = -1;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &barwin);
printf("\e[?25l");
bar(0, 1);
}
void bar_free() {
int size = barwin.ws_col;
printf("\r");
for (int i = 0; i < size; i++)
printf(" ");
printf("\r");
printf("\e[?25h");
}
bool bar(float cur, float max) {
if (cur <= 0 || max <= 0 || cur > max)
return false;
float per = 100 * (cur / max);
if (per > 100) {
return false;
}
char perc[20];
sprintf(perc, " %%%.f", per);
int size = barwin.ws_col - (3 + strlen(perc));
if (size <= 0) {
return false;
}
int prog = size * (cur / max);
if (prog == lastprog || prog > size)
return true;
lastprog = prog;
printf("\r" COLOR_BOLD "[");
for (int i = 0; i < prog; i++) {
printf("#");
}
printf(COLOR_BLUE "#" COLOR_RESET COLOR_BOLD);
for (int i = prog; i < size; i++) {
printf(" ");
}
printf("]" COLOR_RESET);
printf(COLOR_BOLD COLOR_BLUE "%s" COLOR_RESET "\r", perc);
return true;
}
void info(const char *msg, ...) {
va_list args;
va_start(args, msg);
printf(COLOR_BOLD COLOR_BLUE ">>> " COLOR_RESET COLOR_BOLD);
vprintf(msg, args);
printf(COLOR_RESET "\n");
va_end(args);
}
void error(const char *msg, ...) {
va_list args;
va_start(args, msg);
printf(COLOR_BOLD COLOR_RED ">>> " COLOR_RESET COLOR_BOLD);
vprintf(msg, args);
printf(COLOR_RESET "\n");
va_end(args);
}
void success(const char *msg, ...) {
va_list args;
va_start(args, msg);
printf(COLOR_BOLD COLOR_GREEN ">>> " COLOR_RESET COLOR_BOLD);
vprintf(msg, args);
printf(COLOR_RESET "\n");
va_end(args);
}
void debug(const char *msg, ...) {
if (!env_mc_debug())
return;
va_list args;
va_start(args, msg);
printf(COLOR_BOLD COLOR_MAGENTA "DEBUG: " COLOR_RESET COLOR_BOLD);
vprintf(msg, args);
printf(COLOR_RESET "\n");
va_end(args);
}

18
src/log.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#define COLOR_RED "\x1b[31m"
#define COLOR_BOLD "\x1b[1m"
#define COLOR_BLUE "\x1b[34m"
#define COLOR_GREEN "\x1b[32m"
#define COLOR_MAGENTA "\x1b[35m"
#define COLOR_RESET "\x1b[0m"
void bar_init();
bool bar(float, float);
void bar_free();
void info(const char *msg, ...);
void error(const char *msg, ...);
void success(const char *msg, ...);
void debug(const char *, ...);

106
src/main.c Normal file
View File

@ -0,0 +1,106 @@
// clang-format off
/*
* mc | MatterLinux Configuration Manager
* MatterLinux 2023-2024 (https://matterlinux.xyz)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// clang-format on
#include <libintl.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "args.h"
#include "gen.h"
#include "lock.h"
#include "log.h"
#include "paths.h"
#include "pull.h"
#include "util.h"
#define _(x) gettext(x)
typedef bool (*cmd)();
struct CmdMap {
char *name;
bool lock;
cmd func;
};
struct CmdMap cmdmap[] = {
{.name = "mc-pull", .lock = true, .func = pull_cmd},
{.name = "mc-gen", .lock = false, .func = gen_cmd },
};
int main(int argc, char *argv[]) {
setbuf(stdout, NULL);
setlocale(LC_ALL, "");
textdomain("mc");
for (int i = 0; i < sizeof(cmdmap) / sizeof(struct CmdMap); i++) {
if (strcmp(argv[0], cmdmap[i].name) != 0)
continue;
args_load(argc, argv);
if(!paths_load()){
error(_("Failed to access paths (%s)"), mc_dir);
return EXIT_FAILURE;
}
if (cmdmap[i].lock) {
int st = lock();
switch (st) {
case ALREADY_LOCKED:
error(_("Failed to lock, mc is already running"));
return EXIT_FAILURE;
case LOCK_ERROR:
error(_("Failed to lock, did you mess up your directory permissions?"));
return EXIT_FAILURE;
}
}
bool ret = cmdmap[i].func();
unlock();
if (!ret) {
error(_("%s: command failed"), cmdmap[i].name);
return EXIT_FAILURE;
}
success(_("%s: command successful"), cmdmap[i].name);
return EXIT_SUCCESS;
}
info(_("MatterLinux Configuration Manager (%s)"), VERSION);
info(_("Different operations are done using different commands\n"));
printf(COLOR_BOLD " mc-pull" COLOR_RESET ": ");
printf(" %s\n", _("pull down a configuration"));
printf(COLOR_BOLD " mc-gen" COLOR_RESET ": ");
printf(" %s\n\n", _("build the configuration in the current directory"));
info(_("Licensed under GPLv3, see <https://www.gnu.org/licenses/> for more "
"information"));
return EXIT_SUCCESS;
}

31
src/paths.c Normal file
View File

@ -0,0 +1,31 @@
#include <string.h>
#include <sys/stat.h>
#include "env.h"
#include "paths.h"
#include "util.h"
char *mc_lock_path;
char *mc_tmp_path;
char *mc_root;
char *mc_dir;
char *append_root(char *pth) {
char fp[strlen(mc_root) + strlen(pth) + 10];
join(fp, mc_root, pth);
return strdup(fp);
}
bool paths_load() {
mc_root = env_mc_root();
if (!exists(mc_root) || !is_dir(mc_root))
return false;
mc_dir = append_root(MC_DIR);
mc_lock_path = append_root(LOCK_PATH);
mc_tmp_path = append_root(TMP_PATH);
if (!mksubdirsp(mc_dir, 0700))
return false;
return true;
}

11
src/paths.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <stdbool.h>
#define MC_DIR ".local/share/mc"
#define TMP_PATH MC_DIR "/tmp"
#define LOCK_PATH MC_DIR "/lock"
bool paths_load();
extern char *mc_lock_path;
extern char *mc_tmp_path;
extern char *mc_root;
extern char *mc_dir;

93
src/pull.c Normal file
View File

@ -0,0 +1,93 @@
#include "pull.h"
#include "args.h"
#include "config.h"
#include "error.h"
#include "log.h"
#include "paths.h"
#include "url.h"
#include "util.h"
#include <git2.h>
#include <git2/global.h>
#include <libintl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#define _(x) gettext(x)
int fetch_progress(const git_indexer_progress *stats, void *data) {
bar(stats->indexed_objects, stats->total_objects);
return 0;
}
bool pull_cmd() {
if (args.count != 2) {
error(_("Please specify a config name or a URL"));
return false;
}
char *repo_url = NULL, *repo_root = NULL;
bool ret = false;
if (url_is_local(args.list[1])) {
repo_root = args.list[1];
goto COPY;
}
else if (url_is_name(args.list[1]))
repo_url = url_complete(args.list[1]);
else
repo_url = url_ensure_protocol(args.list[1]);
rmrf(mc_tmp_path);
repo_root = mc_tmp_path;
git_libgit2_init();
git_repository *repo = NULL;
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
opts.fetch_opts.callbacks.transfer_progress = fetch_progress;
opts.fetch_opts.callbacks.payload = NULL;
info(_("Cloning %s"), repo_url);
bar_init();
if (git_clone(&repo, repo_url, repo_root, NULL) < 0) {
const git_error *e = git_error_last();
error(_("Failed to clone the %s: %s"), repo_url, e->message);
bar_free();
goto END;
}
bar_free();
git_libgit2_shutdown();
COPY:
if (chdir(repo_root) < 0) {
error(_("Failed to chdir to %s"), repo_root);
goto END;
}
config_t repo_cfg;
if (!config_load(&repo_cfg, "mc.cfg")) {
error(_("Failed to load the configuration file (mc.cfg):"));
printf(" %s\n", errch);
goto END;
}
info("Configuration details:");
printf(COLOR_BOLD " Name" COLOR_RESET ": %s\n", repo_cfg.name);
printf(COLOR_BOLD " Author" COLOR_RESET ": %s\n", repo_cfg.author);
printf(COLOR_BOLD " Keywords" COLOR_RESET ":");
for (int i = 0; i < repo_cfg.keywords.s; i++)
printf("%s ", repo_cfg.keywords.c[i]);
printf("\n");
END:
free(repo_url);
return ret;
}

4
src/pull.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include <stdbool.h>
bool pull_cmd();

26
src/repo.c Normal file
View File

@ -0,0 +1,26 @@
#include "log.h"
#include "util.h"
#include <git2.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TMP_PATH "/tmp/confer"
char *repo_clone(char *url) {
git_libgit2_init();
git_repository *repo = NULL;
int random = randint(100, 999);
char path[strlen(TMP_PATH) + 4];
sprintf(path, "%s_%d", TMP_PATH, random);
if (git_clone(&repo, url, path, NULL) != 0) {
error("Failed to clone the repository: " COLOR_RESET "%s", git_error_last()->message);
return NULL;
}
git_libgit2_shutdown();
return strdup(path);
}

1
src/repo.h Normal file
View File

@ -0,0 +1 @@
char *repo_clone(char *);

46
src/url.c Normal file
View File

@ -0,0 +1,46 @@
#include "util.h"
#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define HUB_URL "https://configs.matterlinux.xyz"
bool url_is_name(char *name) {
for (char *c = name; *c != '\0'; c++) {
if (*c == '/' || *c == '.')
return false;
}
return true;
}
bool url_is_local(char *url) {
bool ret = false;
if (startswith(url, "http://") || startswith(url, "https://"))
return ret;
DIR *dir = NULL;
if (access(url, F_OK) == 0 && (dir = opendir(url)) != NULL)
ret = true;
closedir(dir);
return ret;
}
char *url_complete(char *name) {
char *url = malloc(strlen(name) + strlen(HUB_URL) + 1);
sprintf(url, "%s/%s", HUB_URL, name);
return url;
}
char *url_ensure_protocol(char *url) {
if (startswith(url, "http://") || startswith(url, "https://"))
return strdup(url);
char *full = malloc(strlen("https://") + strlen(url) + 1);
join(full, "https://", url);
return full;
}

6
src/url.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
bool url_is_name(char *);
char *url_complete(char *);
char *url_ensure_protocol(char *);
bool url_is_local(char *);

189
src/util.c Normal file
View File

@ -0,0 +1,189 @@
#include <dirent.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "util.h"
bool eq(char *s1, char *s2) {
if (strlen(s1) != strlen(s2))
return false;
return strcmp(s1, s2) == 0;
}
bool joinhome(char *res, char *path) {
char *homedir = getenv("HOME");
if (NULL == homedir)
return false;
snprintf(res, PATH_MAX, "%s/%s", homedir, path);
return true;
}
bool exists(char *path) {
return access(path, F_OK) == 0;
}
bool endswith(const char *str, const char *suf) {
int strl = strlen(str);
int sufl = strlen(suf);
if (sufl > strl)
return false;
return strncmp(str + strl - sufl, suf, sufl) == 0;
}
bool startswith(char *str, char *sub) {
if (strlen(sub) > strlen(str))
return false;
return strncmp(str, sub, strlen(sub)) == 0;
}
char *check_path(char *bin) {
char *path = getenv("PATH");
char *res = NULL;
if (NULL == path)
return NULL;
path = strdup(path);
char *p = strtok(path, ":");
while (NULL != p) {
char *fp = malloc(PATH_MAX);
snprintf(fp, PATH_MAX, "%s/%s", p, bin);
if (exists(fp)) {
res = fp;
break;
}
p = strtok(NULL, ":");
}
free(path);
return res;
}
int join(char *res, const char *base, const char *pth) {
int blen = strlen(base);
if ((base[blen - 1] == '/' && pth[0] != '/') || (base[blen - 1] != '/' && pth[0] == '/')) {
return sprintf(res, "%s%s", base, pth);
} else if (base[blen - 1] != '/' && pth[0] != '/') {
return sprintf(res, "%s/%s", base, pth);
} else if (base[blen - 1] == '/' && pth[0] == '/') {
char *basedup = strdup(base);
basedup[blen - 1] = '\0';
return sprintf(res, "%s%s", basedup, pth);
}
return -1;
}
int randint(int min, int max) {
srand(time(NULL));
return rand() % max + min;
}
bool is_dir(char *path) {
DIR *dir = opendir(path);
if (dir) {
closedir(dir);
return true;
}
return false;
}
bool mksubdirsp(char *path, int perms) {
char cur[PATH_MAX];
bzero(cur, PATH_MAX);
int indx = 0;
for (char *c = path; *c != '\0'; c++) {
if (*c == '/' && indx != 0) {
cur[indx] = '\0';
if (!exists(cur) && mkdir(cur, perms) < 0)
return false;
}
cur[indx] = *c;
indx++;
}
if (!exists(cur) && mkdir(cur, perms) < 0)
return false;
return true;
}
bool rmrf(char *path) {
if (!exists(path))
return true;
if (!is_dir(path))
return unlink(path) == 0;
DIR *dir = opendir(path);
struct dirent *ent = NULL;
char fp[PATH_MAX] = {};
while ((ent = readdir(dir)) != NULL) {
if (eq(ent->d_name, ".") || eq(ent->d_name, ".."))
continue;
join(fp, path, ent->d_name);
if (!rmrf(fp))
return false;
}
closedir(dir);
rmdir(path);
return true;
}
void clist_init(clist_t *l) {
l->c = NULL;
l->s = 0;
}
void clist_from_str(clist_t *l, char *str) {
char *save = NULL, *el = NULL;
char *strdp = strdup(str);
el = strtok_r(strdp, ",", &save);
while (NULL != el) {
clist_add(l, strdup(el));
el = strtok_r(NULL, ",", &save);
}
free(strdp);
}
void clist_free(clist_t *l) {
if (NULL == l->c || 0 == l->s)
return;
for (int i = 0; i < l->s; i++)
free(l->c[i]);
free(l->c);
l->s = 0;
}
void clist_add(clist_t *l, char *en) {
if (NULL == l->c || 0 == l->s) {
l->c = malloc(sizeof(char *));
l->c[l->s] = en;
l->s++;
return;
}
l->c = realloc(l->c, sizeof(char *) * (l->s + 1));
l->c[l->s] = en;
l->s++;
}

25
src/util.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
typedef struct clist {
char **c;
size_t s;
} clist_t;
bool eq(char *, char *);
bool exists(char *);
char *check_path(char *);
bool joinhome(char *, char *);
bool startswith(char *, char *);
bool endswith(const char *, const char *);
int join(char *, const char *, const char *);
int randint(int, int);
bool is_dir(char *);
bool mksubdirsp(char *, int);
bool rmrf(char *);
void clist_init(clist_t *);
void clist_from_str(clist_t *, char *);
void clist_add(clist_t *, char *);
void clist_free(clist_t *);