1
0
Fork 0
forked from poz/modfetch
modfetch/modfetch.c

259 lines
7.3 KiB
C
Raw Normal View History

2024-02-09 12:05:06 +01:00
#include <ctype.h>
#include <dlfcn.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <wordexp.h>
static const char *PNAME = "modfetch";
static const uint8_t VERSION_MAJOR = 0;
static const uint8_t VERSION_MINOR = 1;
static const uint8_t VERSION_PATCH = 0;
static const uint8_t MAX_MODULE_NAME_LENGTH = 128;
static const uint8_t MAX_MODULES = 128;
static const uint8_t MAX_MODULE_VARS = 128;
static const char *version_str(void) {
char *ver;
if (asprintf(&ver, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) < 0) {
return NULL;
}
return ver;
}
typedef struct {
// path to the binary of this module
char *path;
// name=value strings for each value in config
char **config;
} Module;
typedef struct {
size_t module_count;
Module *modules;
} Config;
void parsing_error(size_t line) {
fprintf(stderr, "error: failed parsing config at line %zu", line);
exit(EXIT_FAILURE);
}
char *remove_whitespaces(const char *str) {
char *tmp = malloc(strlen(str) * sizeof(char));
size_t i = 0;
size_t j = 0;
while (str[i] != '\0') {
if (isspace(str[i])) {
++i;
continue;
}
tmp[j] = str[i];
++i;
++j;
}
tmp[j] = '\0';
char *ret = malloc(j * sizeof(char));
strncpy(ret, tmp, j);
free(tmp);
return ret;
}
char *resolve_env_vars(const char *str) {
size_t len = strlen(str);
char *ret = malloc(4096 * sizeof(char));
size_t ret_offset = 0;
bool in_env_var = false;
char *env_var = malloc(4096 * sizeof(char));
size_t env_var_offset = 0;
for (size_t i = 0; i < len; ++i) {
if (str[i] == '~') {
char *home = getenv("HOME");
strcat(ret, home);
ret_offset += strlen(home);
continue;
}
if (str[i] == '$') {
in_env_var = true;
continue;
}
if (in_env_var && !(isalpha(str[i]) || isdigit(str[i]) || str[i] == '_')) {
in_env_var = false;
char *env = getenv(env_var);
strcat(ret, env);
ret_offset += strlen(env);
free(env_var);
env_var = malloc(4096 * sizeof(char));
env_var_offset = 0;
}
if (in_env_var) {
env_var[env_var_offset] = str[i];
++env_var_offset;
continue;
}
ret[ret_offset] = str[i];
++ret_offset;
}
return ret;
}
Config parse_config(FILE *config_file) {
size_t len = 0;
ssize_t read;
char *line = NULL;
size_t line_index = 0;
bool in_config = false;
char *current_path = malloc(MAX_MODULE_NAME_LENGTH * sizeof(char));
Module current_module = {
.path = malloc(4096 * sizeof(char)),
.config = malloc(MAX_MODULE_VARS * sizeof(char)),
};
size_t config_index = 0;
Config config = {
.modules = malloc(MAX_MODULES * sizeof(Module)),
.module_count = 0,
};
size_t module_index = 0;
while ((read = getline(&line, &len, config_file)) != -1) {
// if config is passed
if (line[read - 2u] == '{') {
if (in_config) {
fclose(config_file);
parsing_error(line_index);
}
in_config = true;
strncpy(current_path, line, (size_t)read - 2);
current_module.path = resolve_env_vars(remove_whitespaces(current_path));
free(current_path);
current_path = malloc(MAX_MODULE_NAME_LENGTH);
continue;
}
// end of config for current module
if (line[read - 2u] == '}') {
if (!in_config) {
fclose(config_file);
parsing_error(line_index);
}
in_config = false;
current_module.config[config_index] = NULL;
config.modules[module_index] = current_module;
++module_index;
current_module.path = malloc(4096 * sizeof(char));
current_module.config = malloc(MAX_MODULE_VARS * sizeof(char));
continue;
}
if (in_config) {
current_module.config[config_index] = remove_whitespaces(line);
++config_index;
continue;
}
current_module.path = resolve_env_vars(remove_whitespaces(line));
free(current_module.config);
current_module.config = malloc(sizeof(void*));
current_module.config[0] = NULL;
config.modules[module_index] = current_module;
++module_index;
current_module.path = malloc(4096 * sizeof(char));
current_module.config = malloc(MAX_MODULE_VARS * sizeof(char));
}
config.module_count = module_index;
return config;
}
int main(int argc, char *argv[]) {
char *config_path;
if (asprintf(&config_path, "%s/modfetch.conf", getenv("XDG_CONFIG_HOME")) < 0) {
fprintf(stderr, "error: failed formatting config path (this shouldn't happen)");
exit(EXIT_FAILURE);
}
for (size_t i = 1; i < (size_t)argc; ++i) {
if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) {
if (i == (size_t)argc - 1) {
fprintf(stderr, "error: no config path passed\n");
exit(EXIT_FAILURE);
}
++i;
config_path = argv[i];
continue;
}
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
fprintf(stderr, "%s: modular fetch [%s]\n", PNAME, version_str());
fprintf(stderr, "\n");
fprintf(stderr, "OPTIONS\n");
fprintf(stderr, "\t-h, --help\t\t\tdisplays this help text\n");
fprintf(stderr, "\t-c, --config /path/to/config\tchanges config path from the default ($XDG_CONFIG_HOME/%s.conf)\n", PNAME);
exit(EXIT_SUCCESS);
}
}
FILE *config_file = fopen(config_path, "r");
if (config_file == NULL) {
fprintf(stderr, "error: failed to open config at: %s\n", config_path);
exit(EXIT_FAILURE);
}
Config config = parse_config(config_file);
fclose(config_file);
for (size_t i = 0; i < config.module_count; ++i) {
Module current_module = config.modules[i];
void *handle = dlopen(current_module.path, RTLD_NOW);
if (handle == NULL) {
fprintf(stderr, "error: failed opening module %s\n", current_module.path);
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror();
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
uint8_t (*version_major)(void) = dlsym(handle, "version_major");
uint8_t (*version_minor)(void) = dlsym(handle, "version_minor");
uint8_t (*version_patch)(void) = dlsym(handle, "version_patch");
const char *(*module_name)(void) = dlsym(handle, "module_name");
const char *(*get)(void) = dlsym(handle, "get");
void (*init)(char**) = dlsym(handle, "init");
#pragma GCC diagnostic pop
init(current_module.config);
(void)module_name;
(void)version_major;
(void)version_minor;
(void)version_patch;
//printf("%s: %d.%d.%d\n", module_name(), version_major(), version_minor(), version_patch());
printf("%s\n", get());
}
return 0;
}