#include #include #include #include #include #include #include #include #include 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 INITIAL_CONFIG_SIZE = 8; static const uint16_t MAX_PATH_LENGTH = 4096; 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[strlen(str)]; 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); return ret; } char *resolve_env_vars(const char *str) { size_t len = strlen(str); char *ret = malloc(MAX_PATH_LENGTH * sizeof(char)); size_t ret_offset = 0; bool in_env_var = false; char env_var[MAX_PATH_LENGTH]; memset(env_var, 0, sizeof(env_var)); 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; } // end of current env var 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); memset(env_var, 0, sizeof(env_var)); 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; } char *process_str(const char *str) { char *whitespaceless = remove_whitespaces(str); char *ret = resolve_env_vars(whitespaceless); free(whitespaceless); 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[MAX_MODULE_NAME_LENGTH]; Module current_module = { .path = NULL, .config = NULL, }; size_t config_index = 0; size_t config_multiplier = 1; Config config = { .modules = malloc(MAX_MODULES * sizeof(Module)), .module_count = 0, }; // TODO rewrite this to read the config char by char while ((read = getline(&line, &len, config_file)) != -1) { if (read <= 1) continue; // if config is passed to this module if (line[read - 2u] == '{') { if (in_config) { fclose(config_file); parsing_error(line_index); } in_config = true; // get rid of the '{' strncpy(current_path, line, (size_t)read - 2); current_module.path = process_str(current_path); current_module.config = malloc(INITIAL_CONFIG_SIZE * sizeof(char*)); continue; } // end of config for current module if (line[read - 2u] == '}') { if (!in_config) { fclose(config_file); parsing_error(line_index); } in_config = false; if (config_index >= INITIAL_CONFIG_SIZE * config_multiplier) { config_multiplier *= 2; current_module.config = realloc(current_module.config, INITIAL_CONFIG_SIZE * config_multiplier); } current_module.config[config_index] = NULL; config.modules[config.module_count] = current_module; ++config.module_count; config_index = 0; continue; } if (in_config) { if (config_index >= INITIAL_CONFIG_SIZE * config_multiplier) { config_multiplier *= 2; current_module.config = realloc(current_module.config, INITIAL_CONFIG_SIZE * config_multiplier); } current_module.config[config_index] = process_str(line); ++config_index; continue; } // no config passed to this module current_module.path = process_str(line); current_module.config = malloc(sizeof(void*)); current_module.config[0] = NULL; config.modules[config.module_count] = current_module; ++config.module_count; free(line); } 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; }