From 534657dd6fc2ee98159e41d2700554fed0da2c4e Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Wed, 18 Dec 2019 21:57:51 -0500 Subject: Refactor project structure --- CMakeLists.txt | 8 +- archive.c | 76 --------- compat.c | 16 -- config.c | 163 -------------------- config_global.c | 186 ----------------------- deps.c | 188 ----------------------- find.c | 154 ------------------- fs.c | 264 -------------------------------- include/spm.h | 181 ++++++++++++++++++++++ install.c | 63 -------- relocation.c | 237 ----------------------------- rpath.c | 233 ---------------------------- shell.c | 112 -------------- spm.c | 60 -------- spm.h | 181 ---------------------- src/CMakeLists.txt | 17 +++ src/archive.c | 76 +++++++++ src/compat.c | 16 ++ src/config.c | 163 ++++++++++++++++++++ src/config_global.c | 186 +++++++++++++++++++++++ src/deps.c | 188 +++++++++++++++++++++++ src/find.c | 154 +++++++++++++++++++ src/fs.c | 264 ++++++++++++++++++++++++++++++++ src/install.c | 63 ++++++++ src/relocation.c | 237 +++++++++++++++++++++++++++++ src/rpath.c | 233 ++++++++++++++++++++++++++++ src/shell.c | 112 ++++++++++++++ src/spm.c | 60 ++++++++ src/strings.c | 431 ++++++++++++++++++++++++++++++++++++++++++++++++++++ strings.c | 431 ---------------------------------------------------- 30 files changed, 2384 insertions(+), 2369 deletions(-) delete mode 100644 archive.c delete mode 100644 compat.c delete mode 100644 config.c delete mode 100644 config_global.c delete mode 100644 deps.c delete mode 100644 find.c delete mode 100644 fs.c create mode 100644 include/spm.h delete mode 100644 install.c delete mode 100644 relocation.c delete mode 100644 rpath.c delete mode 100644 shell.c delete mode 100644 spm.c delete mode 100644 spm.h create mode 100644 src/CMakeLists.txt create mode 100644 src/archive.c create mode 100644 src/compat.c create mode 100644 src/config.c create mode 100644 src/config_global.c create mode 100644 src/deps.c create mode 100644 src/find.c create mode 100644 src/fs.c create mode 100644 src/install.c create mode 100644 src/relocation.c create mode 100644 src/rpath.c create mode 100644 src/shell.c create mode 100644 src/spm.c create mode 100644 src/strings.c delete mode 100644 strings.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e41eb88..ee2fded 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,9 @@ cmake_minimum_required(VERSION 3.15) project(spm C) include(CheckSymbolExists) - set(CMAKE_C_STANDARD 99) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) + check_symbol_exists(strsep string.h HAVE_STRSEP) -configure_file(config.h.in config.h) +configure_file(${CMAKE_SOURCE_DIR}/config.h.in ${CMAKE_BINARY_DIR}/include/config.h) -add_executable(spm spm.c config.c spm.h config.h.in compat.c deps.c fs.c rpath.c find.c shell.c archive.c strings.c relocation.c install.c config_global.c) -target_link_libraries(spm rt) +add_subdirectory(src) diff --git a/archive.c b/archive.c deleted file mode 100644 index 7ec9d04..0000000 --- a/archive.c +++ /dev/null @@ -1,76 +0,0 @@ -#include "spm.h" - -/** - * Extract a single file from a tar archive into a directory - * - * @param archive path to tar archive - * @param filename known path inside the archive to extract - * @param destination where to extract file to (must exist) - * @return - */ -int tar_extract_file(const char *archive, const char* filename, const char *destination) { - Process *proc = NULL; - int status; - char cmd[PATH_MAX]; - - sprintf(cmd, "tar xf %s -C %s %s 2>&1", archive, destination, filename); - if (exists(archive) != 0) { - fprintf(stderr, "%s :: ", archive); - fprintf(SYSERROR); - return -1; - } - - shell(&proc, SHELL_OUTPUT, cmd); - if (!proc) { - fprintf(SYSERROR); - return -1; - } - - status = proc->returncode; - shell_free(proc); - - return status; -} - -int tar_extract_archive(const char *_archive, const char *_destination) { - Process *proc = NULL; - int status; - char cmd[PATH_MAX]; - - if (exists(_archive) != 0) { - fprintf(SYSERROR); - return -1; - } - - char *archive = strdup(_archive); - if (!archive) { - fprintf(SYSERROR); - return -1; - } - char *destination = strdup(_destination); - if (!destination) { - fprintf(SYSERROR); - return -1; - } - - // sanitize archive - strchrdel(archive, "&;|"); - // sanitize destination - strchrdel(destination, "&;|"); - - sprintf(cmd, "tar xf %s -C %s 2>&1", archive, destination); - shell(&proc, SHELL_OUTPUT, cmd); - if (!proc) { - fprintf(SYSERROR); - free(archive); - free(destination); - return -1; - } - - status = proc->returncode; - shell_free(proc); - free(archive); - free(destination); - return status; -} - diff --git a/compat.c b/compat.c deleted file mode 100644 index 082a602..0000000 --- a/compat.c +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include "config.h" - -#ifndef HAVE_STRSEP -// credit: Dan Cross via https://unixpapa.com/incnote/string.html -char *strsep(char **sp, char *sep) -{ - char *p, *s; - if (sp == NULL || *sp == NULL || **sp == '\0') return(NULL); - s = *sp; - p = s + strcspn(s, sep); - if (*p != '\0') *p++ = '\0'; - *sp = p; - return(s); -} -#endif \ No newline at end of file diff --git a/config.c b/config.c deleted file mode 100644 index 93ff673..0000000 --- a/config.c +++ /dev/null @@ -1,163 +0,0 @@ -/** - * @file config.c - */ -#include "spm.h" - -/** - * Parse a basic configuration file - * - * NOTE: All values are stored as strings. You need to convert non-string values yourself. - * - * @param filename - * @return success=`ConfigItem` array, failure=NULL - */ -ConfigItem **config_read(const char *filename) { - const char sep = '='; - char *line = (char *)calloc(CONFIG_BUFFER_SIZE, sizeof(char)); - FILE *fp = fopen(filename, "r"); - if (!fp) { - // errno will be set, so die, and let the caller handle it - return NULL; - } - int record_initial = 2; - ConfigItem **config = (ConfigItem **) calloc(record_initial, sizeof(ConfigItem *)); - int record = 0; - - while (fgets(line, CONFIG_BUFFER_SIZE, fp) != NULL) { - char *lptr = line; - // Remove trailing space and newlines - lptr = strip(lptr); - - // Remove leading space and newlines - lptr = lstrip(lptr); - - // Skip empty lines - if (isempty(lptr)) { - continue; - } - // Skip comment-only lines - if (*lptr == '#' || *lptr == ';') { - continue; - } - - // Get a pointer to the key pair separator - char *sep_pos = strchr(lptr, sep); - if (!sep_pos) { - printf("invalid entry on line %d: missing '%c': '%s'\n", record, sep, lptr); - continue; - } - - // These values are approximations. The real length(s) are recorded using strlen below. - // At most we'll lose a few heap bytes to whitespace, but it's better than allocating PATH_MAX or BUFSIZ - // for a measly ten byte string. - size_t key_length = strcspn(lptr, &sep); - size_t value_length = strlen(sep_pos); - - // Allocate a ConfigItem record - config[record] = (ConfigItem *)calloc(1, sizeof(ConfigItem)); - config[record]->key = (char *)calloc(key_length + 1, sizeof(char)); - config[record]->value = (char *)calloc(value_length + 1, sizeof(char)); - - // Shortcut our array at this point. Things get pretty ugly otherwise. - char *key = config[record]->key; - char *value = config[record]->value; - - // Copy the array pointers (used to populate config->key/value_length - char *key_orig = key; - char *value_orig = value; - - // Populate the key and remove any trailing space - while (lptr != sep_pos) { - *key++ = *lptr++; - } - key = strip(key); - - // We're at the separator now, so skip over it - lptr++; - // and remove any leading space - lptr = lstrip(lptr); - - // Determine whether the string is surrounded by quotes, if so, get rid of them - if (isquoted(lptr)) { - // Move pointer beyond quote - lptr = strpbrk(lptr, "'\"") + 1; - // Terminate on closing quote - char *tmp = strpbrk(lptr, "'\""); - *tmp = '\0'; - } - - // Populate the value, and ignore any inline comments - while (*lptr) { - if (*lptr == '#' || *lptr == ';') { - // strip trailing whitespace where the comment is and stop processing - value = strip(value); - break; - } - *value++ = *lptr++; - } - - // Populate length data - config[record]->key_length = strlen(key_orig); - config[record]->value_length = strlen(value_orig); - - // Destroy contents of line buffer - memset(line, '\0', CONFIG_BUFFER_SIZE); - - // increment record count - record++; - // Expand config by another record - config = (ConfigItem **)reallocarray(config, record + record_initial + 1, sizeof(ConfigItem *)); - } - free(line); - return config; -} - -/** - * Free memory allocated by `config_read` - * @param item `ConfigItem` array - */ -void config_free(ConfigItem **item) { - for (int i = 0; item[i] != NULL; i++) { - free(item[i]); - } - free(item); -} - -/** - * If the configuration contains `key` return a pointer to that record - * @param item pointer to array of config records - * @param key search for key in config records - * @return success=pointer to record, failure=NULL - */ -ConfigItem *config_get(ConfigItem **item, const char *key) { - if (!item) { - return NULL; - } - for (int i = 0; item[i] != NULL; i++) { - if (!strcmp(item[i]->key, key)) { - return item[i]; - } - } - return NULL; -} - -void config_test(void) { - ConfigItem **config = config_read("program.conf"); - printf("Data Parsed:\n"); - for (int i = 0; config[i] != NULL; i++) { - printf("key: '%s', value: '%s'\n", config[i]->key, config[i]->value); - } - - printf("Testing config_get():\n"); - ConfigItem *cptr = NULL; - if ((cptr = config_get(config, "integer_value"))) { - printf("%s = %d\n", cptr->key, atoi(cptr->value)); - } - if ((cptr = config_get(config, "float_value"))) { - printf("%s = %.3f\n", cptr->key, atof(cptr->value)); - } - if ((cptr = config_get(config, "string_value"))) { - printf("%s = %s\n", cptr->key, cptr->value); - } - config_free(config); -} diff --git a/config_global.c b/config_global.c deleted file mode 100644 index b1c6dc2..0000000 --- a/config_global.c +++ /dev/null @@ -1,186 +0,0 @@ -#include "spm.h" - -char *get_user_conf_dir(void) { - char *result = NULL; - wordexp_t wexp; - wordexp("~/.spm", &wexp, 0); - if (wexp.we_wordc != 0) { - result = (char *)calloc(strlen(wexp.we_wordv[0]) + 1, sizeof(char)); - if (!result) { - wordfree(&wexp); - return NULL; - } - strncpy(result, wexp.we_wordv[0], strlen(wexp.we_wordv[0])); - if (access(result, F_OK) != 0) { - mkdirs(result, 0755); - } - } - wordfree(&wexp); - return result; -} - -char *get_user_config_file(void) { - const char *filename = "spm.conf"; - char template[PATH_MAX]; - char *ucd = get_user_conf_dir(); - if (!ucd) { - return NULL; - } - // Initialize temporary path - template[0] = '\0'; - - sprintf(template, "%s%c%s", ucd, DIRSEP, filename); - if (access(template, F_OK) != 0) { - // No configuration exists, so fail - return NULL; - } - free(ucd); - // Allocate and return path to configuration file - return strdup(template); -} - -char *get_user_tmp_dir(void) { - char template[PATH_MAX]; - char *ucd = get_user_conf_dir(); - sprintf(template, "%s%ctmp", ucd, DIRSEP); - - if (access(template, F_OK) != 0) { - if (mkdirs(template, 0755) != 0) { - return NULL; - } - } - - free(ucd); - return strdup(template); -} - -char *get_user_package_dir(void) { - char template[PATH_MAX]; - char *ucd = get_user_conf_dir(); - sprintf(template, "%s%cpkgs", ucd, DIRSEP); - - if (access(template, F_OK) != 0) { - if (mkdirs(template, 0755) != 0) { - return NULL; - } - } - - free(ucd); - return strdup(template); -} - -/** - * Check whether SPM has access to external programs it needs - */ -void check_runtime_environment(void) { - int bad_rt = 0; - char *required[] = { - "patchelf", - "rsync", - "tar", - "bash", - "reloc", - NULL, - }; - for (int i = 0; required[i] != NULL; i++) { - char *result = find_executable(required[i]); - if (!result) { - fprintf(stderr, "Required program '%s' is not installed\n", required[i]); - bad_rt = 1; - } - free(result); - } - if (bad_rt) { - exit(1); - } -} - - - -void init_config_global(void) { - SPM_GLOBAL.user_config_basedir = NULL; - SPM_GLOBAL.user_config_file = NULL; - SPM_GLOBAL.package_dir = NULL; - SPM_GLOBAL.tmp_dir = NULL; - SPM_GLOBAL.config = NULL; - - if (uname(&SPM_GLOBAL.sysinfo) != 0) { - fprintf(SYSERROR); - exit(1); - } - - SPM_GLOBAL.user_config_basedir = get_user_conf_dir(); - SPM_GLOBAL.user_config_file = get_user_config_file(); - if (SPM_GLOBAL.user_config_file) { - SPM_GLOBAL.config = config_read(SPM_GLOBAL.user_config_file); - } - - ConfigItem *item = NULL; - - // Initialize temp directory - item = config_get(SPM_GLOBAL.config, "tmp_dir"); - if (item) { - SPM_GLOBAL.tmp_dir = item->value; - if (access(SPM_GLOBAL.tmp_dir, F_OK) != 0) { - if (mkdirs(SPM_GLOBAL.tmp_dir, 0755) != 0) { - fprintf(stderr, "Unable to create global temporary directory: %s\n", SPM_GLOBAL.tmp_dir); - fprintf(SYSERROR); - exit(1); - } - } - } - else { - SPM_GLOBAL.tmp_dir = get_user_tmp_dir(); - } - - // Initialize package directory - item = config_get(SPM_GLOBAL.config, "package_dir"); - if (item) { - SPM_GLOBAL.package_dir = item->value; - if (access(SPM_GLOBAL.package_dir, F_OK) != 0) { - if (mkdirs(SPM_GLOBAL.package_dir, 0755) != 0) { - fprintf(stderr, "Unable to create global package directory: %s\n", SPM_GLOBAL.package_dir); - fprintf(SYSERROR); - exit(1); - } - } - } - else { - SPM_GLOBAL.package_dir = get_user_package_dir(); - } -} - -void free_global_config(void) { - if (SPM_GLOBAL.package_dir) { - free(SPM_GLOBAL.package_dir); - } - if (SPM_GLOBAL.tmp_dir) { - free(SPM_GLOBAL.tmp_dir); - } - if (SPM_GLOBAL.user_config_basedir) { - free(SPM_GLOBAL.user_config_basedir); - } - if (SPM_GLOBAL.user_config_file) { - free(SPM_GLOBAL.user_config_file); - } - if (SPM_GLOBAL.config) { - config_free(SPM_GLOBAL.config); - } -} - -void show_global_config(void) { - printf("#---------------------------\n"); - printf("#---- SPM CONFIGURATION ----\n"); - printf("#---------------------------\n"); - printf("# base dir: %s\n", SPM_GLOBAL.user_config_basedir ? SPM_GLOBAL.user_config_basedir : "none (check write permission on home directory)"); - printf("# config file: %s\n", SPM_GLOBAL.user_config_file ? SPM_GLOBAL.user_config_file : "none"); - if (SPM_GLOBAL.user_config_file) { - printf("# config file contents:\n"); - for (int i = 0; SPM_GLOBAL.config[i] != NULL; i++) { - printf("# -> %s: %s\n", SPM_GLOBAL.config[i]->key, SPM_GLOBAL.config[i]->value); - } - } - printf("# package storage: %s\n", SPM_GLOBAL.package_dir); - printf("# temp storage: %s\n", SPM_GLOBAL.tmp_dir); - printf("\n"); -} diff --git a/deps.c b/deps.c deleted file mode 100644 index 99f6148..0000000 --- a/deps.c +++ /dev/null @@ -1,188 +0,0 @@ -// -// Created by jhunk on 12/16/19. -// -#include "spm.h" - -int exists(const char *filename) { - return access(filename, F_OK); -} - -int dep_seen(Dependencies **deps, const char *name) { - if (!deps) { - return -1; - } - for (int i = 0; i != (*deps)->records; i++) { - if (strstr((*deps)->list[i], name) != NULL) { - return 1; - } - } - return 0; -} - -int dep_init(Dependencies **deps) { - (*deps) = (Dependencies *)calloc(1, sizeof(Dependencies)); - (*deps)->__size = 2; - (*deps)->records = 0; - (*deps)->list = (char **)calloc((*deps)->__size, sizeof(char *)); - if (!(*deps)->list) { - return -1; - } - return 0; -} - -void dep_free(Dependencies **deps) { - if ((*deps) != NULL) { - return; - } - for (int i = 0; i < (*deps)->__size; i++) { - if ((*deps)->list[i] != NULL) { - free((*deps)->list[i]); - } - } - free((*deps)); -} - -int dep_append(Dependencies **deps, char *_name) { - char *name = NULL; - char *bname = NULL; - - if (!(*deps)) { - return -1; - } - - name = find_package(_name); - if (!name) { - perror(_name); - fprintf(SYSERROR); - return -1; - } - - bname = basename(name); - if (!bname) { - perror(name); - fprintf(SYSERROR); - return -1; - } - - (*deps)->__size++; - (*deps)->list = (char **)realloc((*deps)->list, sizeof(char *) * (*deps)->__size); - if (!(*deps)->list) { - free(name); - return -1; - } - - (*deps)->list[(*deps)->records] = (char *)calloc(strlen(bname) + 1, sizeof(char)); - if (!(*deps)->list[(*deps)->records]) { - free(name); - return -1; - } - - strcpy((*deps)->list[(*deps)->records], bname); - (*deps)->records++; - - free(name); - return 0; -} - -int dep_solve(Dependencies **deps, const char *filename) { - if (!(*deps)) { - return -1; - } - if (exists(filename) != 0) { - return -1; - } - FILE *fp = fopen(filename, "r"); - if (!fp) { - perror(filename); - return -1; - } - - char data[BUFSIZ]; - memset(data, '\0', sizeof(data)); - - char *line = data; - int line_count = 0; - while (fgets(line, BUFSIZ, fp) != NULL) { - size_t line_length = strlen(line); - if (line_length > 1 && strstr(line, "\r\n")) { - line[line_length - 2] = '\0'; - } - if (line_length > 1 && line[line_length - 1] == '\n') { - line[line_length - 1] = '\0'; - } - if (strcmp(line, "") == 0) { - continue; - } - line_count++; - if (dep_seen(deps, line) > 0) { - // Already seen this dependency. Skip it. - continue; - } - else { - // Have not seen this dependency before - if (dep_append(deps, line) == 0) { - dep_solve(deps, line); - } - } - } - fclose(fp); - return line_count; -} - -int dep_all(Dependencies **deps, const char *_package) { - static int next = 0; - char *package = NULL; - char depfile[PATH_MAX]; - char template[PATH_MAX]; - char suffix[PATH_MAX] = "spm_depends_all_XXXXXX"; - - // Verify the requested package pattern exists - package = find_package(_package); - if (!package) { - perror(_package); - fprintf(SYSERROR); - return -1; - } - - // Create a new temporary directory and extract the requested package into it - sprintf(template, "%s%c%s", TMP_DIR, DIRSEP, suffix); - char *tmpdir = mkdtemp(template); - if (!tmpdir) { - perror(template); - fprintf(SYSERROR); - return -1; - } - if (tar_extract_file(package, ".SPM_DEPENDS", tmpdir) < 0) { - perror(package); - fprintf(SYSERROR); - return -1; - } - - // Scan depencency tree - sprintf(depfile, "%s%c%s", tmpdir, DIRSEP, ".SPM_DEPENDS"); - int resolved = dep_solve(deps, depfile); - - // NOTE: - // 1. `resolved` is the number of dependencies for the package we're scanning - // 2. `next` permits us to converge on `resolved`, otherwise `i` would reset to `0` each time `dep_all` is called - for (int i = next; i < resolved; i++) { - next++; - if (dep_seen(deps, (*deps)->list[i])) { - dep_all(deps, (*deps)->list[i]); - } - } - - // Remove temporary data - unlink(depfile); - unlink(tmpdir); - return 0; -} - -void dep_show(Dependencies **deps) { - if ((*deps) == NULL) { - return; - } - for (int i = 0; i < (*deps)->records; i++) { - printf("%d: %s\n", i, (*deps)->list[i]); - } -} diff --git a/find.c b/find.c deleted file mode 100644 index 84ded16..0000000 --- a/find.c +++ /dev/null @@ -1,154 +0,0 @@ -#include "spm.h" - -/** - * glob callback function - * @param epath path to file that generated the error condition - * @param eerrno the error condition - * @return the error condition - */ -int errglob(const char *epath, int eerrno) { - fprintf(stderr, "glob matching error: %s (%d)", epath, eerrno); - return eerrno; -} - -/** - * Scan a directory for a file by name, or by wildcard - * - * @param root directory path to scan - * @param filename file to find (wildcards accepted) - * @return success=path to file, failure=NULL - */ -char *find_file(const char *root, const char *filename) { - glob_t results; - int glob_flags = 0; - int match = 0; - char *rootpath = NULL; - char *path = NULL; - - // GUARD - if (!root || !filename || strstr(filename, "..") || strstr(filename, "./")) { - return NULL; - } - - if (!(path = (char *)calloc(PATH_MAX + 1, sizeof(char)))) { - fprintf(SYSERROR); - exit(errno); - } - - if (!(rootpath = realpath(root, NULL))) { - return NULL; - } - - strcat(path, rootpath); - strcat(path, "/"); - strcat(path, filename); - - // Save a little time if the file exists - if (access(path, F_OK) != -1) { - return path; - } - - // Inject wildcard - strcat(path, "*"); - // Search for the file - match = glob(path, glob_flags, errglob, &results); - - if (match != 0) { - // report critical errors except GLOB_NOMATCH - if (match == GLOB_NOSPACE || match == GLOB_ABORTED) { - fprintf(SYSERROR); - } - return NULL; - } - - // Resize path to the length of the first match - char *want = results.gl_pathv[0]; - if (!(path = (char *)realloc(path, sizeof(char) * (strlen(want) + 1)))) { - fprintf(SYSERROR); - exit(errno); - } - - // Replace path string with wanted path string - strncpy(path, want, strlen(want)); - - free(rootpath); - globfree(&results); - return path; -} - -/** - * Scan the package directory for a package by name - * @param filename file to find - * @return success=path to file, failure=NULL - */ -char *find_package(const char *filename) { - return find_file(PKG_DIR, filename); -} - -/** - * Determine whether `pattern` is present within a file - * @param filename - * @param pattern - * @return 0=found, 1=not found, -1=OS error - */ -int find_in_file(const char *filename, const char *pattern) { - int result = 1; // default "not found" - - FILE *fp = fopen(filename, "rb"); - if (!fp) { - return -1; - } - - long int file_len = get_file_size(filename); - if (file_len < 0) { - return -1; - } - char *buffer = (char *)calloc((size_t) file_len, sizeof(char)); - if (!buffer) { - return -1; - } - size_t pattern_len = strlen(pattern); - - fread(buffer, (size_t) file_len, sizeof(char), fp); - fclose(fp); - - for (size_t i = 0; i < file_len; i++) { - if (!memcmp(&buffer[i], pattern, pattern_len)) { - result = 0; // found - break; - } - } - free(buffer); - return result; -} - -/** - * Get the full path of a shell command - * @param program - * @return success=absolute path to program, failure=NULL - */ -char *find_executable(const char *program) { - int found = 0; - char *result = NULL; - char *env_path = NULL; - env_path = getenv("PATH"); - if (!env_path) { - return NULL; - } - char **search_paths = split(env_path, ":"); - - char buf[PATH_MAX]; - for (int i = 0; search_paths[i] != NULL; i++) { - sprintf(buf, "%s%c%s", search_paths[i], DIRSEP, program); - if (access(buf, F_OK | X_OK) == 0) { - found = 1; - break; - } - memset(buf, '\0', sizeof(buf)); - } - if (found) { - result = strdup(buf); - } - split_free(search_paths); - return result; -} diff --git a/fs.c b/fs.c deleted file mode 100644 index 27bdf2f..0000000 --- a/fs.c +++ /dev/null @@ -1,264 +0,0 @@ -#include "spm.h" - -FSTree *fstree(const char *_path) { - FTS *parent = NULL; - FTSENT *node = NULL; - FSTree *fsdata = NULL; - char *path = realpath(_path, NULL); - char *root[2] = { path, NULL }; - - size_t dirs_size = 2; - size_t dirs_records = 0; - size_t files_size = 2; - size_t files_records = 0; - - fsdata = (FSTree *)calloc(1, sizeof(FSTree)); - fsdata->root= (char *)calloc(strlen(path) + 1, sizeof(char)); - fsdata->dirs = (char **)calloc(dirs_size, sizeof(char *)); - fsdata->files= (char **)calloc(files_size, sizeof(char *)); - - strncpy(fsdata->root, path, strlen(path)); - parent = fts_open(root, FTS_PHYSICAL | FTS_NOCHDIR, &_fstree_compare); - - if (parent != NULL) { - while ((node = fts_read(parent)) != NULL) { - switch (node->fts_info) { - case FTS_D: - if (strcmp(node->fts_path, "..") == 0 || strcmp(node->fts_path, ".") == 0) { - continue; - } - fsdata->dirs = (char **)realloc(fsdata->dirs, sizeof(char*) * dirs_size); - fsdata->dirs[dirs_size - 1] = NULL; - fsdata->dirs[dirs_records] = (char *)calloc(strlen(node->fts_path) + 1, sizeof(char)); - strncpy(fsdata->dirs[dirs_records], node->fts_path, strlen(node->fts_path)); - dirs_size++; - dirs_records++; - break; - case FTS_F: - case FTS_SL: - fsdata->files = (char **)realloc(fsdata->files, sizeof(char*) * files_size); - fsdata->files[files_size - 1] = NULL; - fsdata->files[files_records] = (char *)calloc(strlen(node->fts_path) + 1, sizeof(char)); - strncpy(fsdata->files[files_records], node->fts_path, strlen(node->fts_path)); - files_size++; - files_records++; - break; - default: - break; - } - } - fts_close(parent); - } - fsdata->dirs_length = dirs_records; - fsdata->files_length = files_records; - free(path); - return fsdata; -} - -int _fstree_compare(const FTSENT **one, const FTSENT **two) { - return (strcmp((*one)->fts_name, (*two)->fts_name)); -} - -int rmdirs(const char *_path) { - if (access(_path, F_OK) != 0) { - return -1; - } - - FSTree *data = fstree(_path); - if (data->files) { - for (int i = 0; data->files[i] != NULL; i++) { - remove(data->files[i]); - } - } - if (data->dirs) { - for (int i = data->dirs_length - 1; i != 0; i--) { - remove(data->dirs[i]); - } - } - remove(data->root); - - fstree_free(data); - return 0; -} - -void fstree_free(FSTree *fsdata) { - if (fsdata != NULL) { - if (fsdata->root != NULL) { - free(fsdata->root); - } - if (fsdata->files != NULL) { - for (int i = 0; fsdata->files[i] != NULL; i++) { - free(fsdata->files[i]); - } - } - if (fsdata->dirs != NULL) { - for (int i = 0; fsdata->dirs[i] != NULL; i++) { - free(fsdata->dirs[i]); - } - } - free(fsdata); - } -} - -/** - * Converts Win32 path to Unix path, and vice versa - * - On UNIX, Win32 paths will be converted UNIX - * - On Win32, UNIX paths will be converted to Win32 - * - * This function is platform dependent. - * - * @param path a system path - * @return string (caller is responsible for `free`ing memory) - */ -char *normpath(const char *path) { - char *result = strdup(path); - char *tmp = result; - - while (*tmp) { - if (*tmp == NOT_DIRSEP) { - *tmp = DIRSEP; - tmp++; - continue; - } - tmp++; - } - return result; -} - -/** - * Strip file name from directory - * Note: Caller is responsible for freeing memory - * - * @param _path - * @return success=path to directory, failure=NULL - */ -char *dirname(const char *_path) { - char *path = strdup(_path); - char *last = strrchr(path, DIRSEP); - if (!last) { - return NULL; - } - // Step backward, stopping on the first non-separator - // This ensures strings like "/usr//////" are converted to "/usr", but... - // it will do nothing to fix up a path like "/usr//////bin/bash - char *lookback = last; - while (*(lookback - 1) == DIRSEP) { - lookback--; - } - - *lookback = '\0'; - return path; -} - -/** - * Strip directory from file name - * Note: Caller is responsible for freeing memory - * - * @param _path - * @return success=file name, failure=NULL - */ -char *basename(char *path) { - char *result = NULL; - char *last = strrchr(path, DIRSEP); - if (!last) { - return NULL; - } - - // Perform a lookahead ensuring the string is valid beyond the last separator - if ((last + 1) != NULL) { - result = last + 1; - } - - return result; -} - -/** - * Basic rsync wrapper for copying files - * @param _args arguments to pass to rsync (set to `NULL` for default options) - * @param _source source file or directory - * @param _destination destination file or directory - * @return success=0, failure=-1 - */ -int rsync(const char *_args, const char *_source, const char *_destination) { - int returncode; - Process *proc = NULL; - char *args = NULL; - if (_args) { - args = strdup(_args); - } - char *source = strdup(_source); - char *destination = strdup(_destination); - char cmd[PATH_MAX]; - char args_combined[PATH_MAX]; - - memset(cmd, '\0', sizeof(cmd)); - memset(args_combined, '\0', sizeof(args_combined)); - strcpy(args_combined, "--archive --hard-links "); - if (args) { - strcat(args_combined, _args); - } - - sprintf(cmd, "rsync %s \"%s\" \"%s\"", args_combined, source, destination); - // sanitize command - strchrdel(cmd, "&;|"); - shell(&proc, SHELL_OUTPUT, cmd); - if (!proc) { - if (args) { - free(args); - } - free(source); - free(destination); - return -1; - } - - returncode = proc->returncode; - if (returncode != 0 && proc->output) { - fprintf(stderr, proc->output); - } - shell_free(proc); - - if (args) { - free(args); - } - free(source); - free(destination); - return returncode; -} - -long int get_file_size(const char *filename) { - long int result = 0; - FILE *fp = fopen(filename, "rb"); - if (!fp) { - return -1; - } - fseek(fp, 0, SEEK_END); - result = ftell(fp); - fclose(fp); - return result; -} - -/** - * Attempt to create a directory (or directories) - * @param _path A path to create - * @param mode UNIX permissions (octal) - * @return success=0, failure=-1 (+ errno will be set) - */ -int mkdirs(const char *_path, mode_t mode) { - int result = 0; - char *path = normpath(_path); - char tmp[PATH_MAX]; - tmp[0] = '\0'; - - char sep[2]; - sprintf(sep, "%c", DIRSEP); - char **parts = split(path, sep); - for (int i = 0; parts[i] != NULL; i++) { - strcat(tmp, parts[i]); - strcat(tmp, sep); - if (access(tmp, F_OK) != 0) { - result = mkdir(tmp, mode); - } - } - split_free(parts); - return result; -} diff --git a/include/spm.h b/include/spm.h new file mode 100644 index 0000000..9f3ae17 --- /dev/null +++ b/include/spm.h @@ -0,0 +1,181 @@ +#ifndef SPM_SPM_H +#define SPM_SPM_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(_WIN32) +#include +#endif + +#include "config.h" + +// spm.c +#define SYSERROR stderr, "%s:%s:%d: %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno) +#define DIRSEP_WIN32 '\\' +#define DIRSEP_UNIX '/' +#if defined(_WIN32) +#define DIRSEP DIRSEP_WIN32 +#define NOT_DIRSEP DIRSEP_UNIX +#else +#define DIRSEP DIRSEP_UNIX +#define NOT_DIRSEP DIRSEP_WIN32 +#endif + +#define PKG_DIR SPM_GLOBAL.package_dir +#define TMP_DIR SPM_GLOBAL.tmp_dir + +#define SHELL_DEFAULT 1 << 0 +#define SHELL_OUTPUT 1 << 1 +#define SHELL_BENCHMARK 1 << 2 + +typedef struct { + char *root; + char **dirs; + size_t dirs_length; + char **files; + size_t files_length; +} FSTree; + +typedef struct { + size_t __size; // Count of allocated records + size_t records; // Count of usable records + char **list; // Array of dependencies +} Dependencies; + +typedef struct { + char *key; + char *value; + size_t key_length; + size_t value_length; +} ConfigItem; + +typedef struct { + char *package_dir; + char *tmp_dir; + char *user_config_basedir; + char *user_config_file; + ConfigItem **config; + struct utsname sysinfo; +} spm_vars; + +typedef struct { + struct timespec start_time, stop_time; + double time_elapsed; + int returncode; + char *output; +} Process; + +typedef struct { + char *prefix; + char *path; +} RelocationEntry; + +// GLOBALS +spm_vars SPM_GLOBAL; + +// shell.c +void shell(Process **proc_info, u_int64_t option, const char *fmt, ...); +void shell_free(Process *proc_info); + +// archive.c +int tar_extract_archive(const char *_archive, const char *_destination); +int tar_extract_file(const char *archive, const char* filename, const char *destination); + +// relocation.c +int relocate(const char *filename, const char *_oldstr, const char *_newstr); +int replace_text(char *data, const char *_spattern, const char *_sreplacement); +int file_replace_text(char *filename, const char *spattern, const char *sreplacement); +RelocationEntry **prefixes_read(const char *filename); +void prefixes_free(RelocationEntry **entry); + +// strings.c +int num_chars(const char *sptr, int ch); +int startswith(const char *sptr, const char *pattern); +int endswith(const char *sptr, const char *pattern); +char *normpath(const char *path); +void strchrdel(char *sptr, const char *chars); +long int strchroff(const char *sptr, int ch); +void strdelsuffix(char *sptr, const char *suffix); +char** split(char *sptr, const char* delim); +void split_free(char **ptr); +char *substring_between(char *sptr, const char *delims); +static int _strsort_compare(const void *a, const void *b); +void strsort(char **arr); +int find_in_file(const char *filename, const char *pattern); + +// find.c +char *find_executable(const char *program); +char *find_file(const char *root, const char *filename); +char *find_package(const char *filename); +int errglob(const char *epath, int eerrno); + +// rpath.c +Process *patchelf(const char *_filename, const char *_args); +char *rpath_autodetect(const char *filename); +int has_rpath(const char *_filename); +char *rpath_get(const char *_filename); +char *rpath_generate(const char *_filename); +int rpath_set(const char *filename, char *_rpath); + +// fs.c +long int get_file_size(const char *filename); +int mkdirs(const char *_path, mode_t mode); +char *dirname(const char *_path); +char *basename(char *path); +int rsync(const char *_args, const char *_source, const char *_destination); + +// config_global.c +char *get_user_conf_dir(void); +char *get_user_config_file(void); +char *get_user_tmp_dir(void); +char *get_user_package_dir(void); + +void init_config_global(void); +void free_global_config(void); +void show_global_config(void); +void check_runtime_environment(void); + +// install.c +int install(const char *destroot, const char *_package); + +// config.c +#define CONFIG_BUFFER_SIZE 1024 + +char *lstrip(char *sptr); +char *strip(char *sptr); +int isempty(char *sptr); +int isquoted(char *sptr); +ConfigItem **config_read(const char *filename); +ConfigItem *config_get(ConfigItem **item, const char *key); +void config_free(ConfigItem **item); +void config_test(void); + +// deps.c +int exists(const char *filename); +int dep_seen(Dependencies **deps, const char *name); +int dep_init(Dependencies **deps); +void dep_free(Dependencies **deps); +int dep_append(Dependencies **deps, char *name); +int dep_solve(Dependencies **deps, const char *filename); +int dep_all(Dependencies **deps, const char *_package); +void dep_show(Dependencies **deps); + +// fstree.c +int _fstree_compare(const FTSENT **a, const FTSENT **b); +void fstree_free(FSTree *fsdata); +FSTree *fstree(const char *_path); +int rmdirs(const char *_path); + +#endif //SPM_SPM_H diff --git a/install.c b/install.c deleted file mode 100644 index e647d31..0000000 --- a/install.c +++ /dev/null @@ -1,63 +0,0 @@ -#include "spm.h" - -int install(const char *destroot, const char *_package) { - char *package = find_package(_package); - if (!package) { - fprintf(SYSERROR); - return -1; - } - - if (exists(destroot) != 0) { - if (mkdirs(destroot, 0755) != 0) { - fprintf(SYSERROR); - return -2; - } - } - - char cwd[PATH_MAX]; - char source[PATH_MAX]; - char template[PATH_MAX]; - char suffix[PATH_MAX] = "spm_destroot_XXXXXX"; - sprintf(template, "%s%c%s", TMP_DIR, DIRSEP, suffix); - - // Create a new temporary directory and extract the requested package into it - char *tmpdir = mkdtemp(template); - tar_extract_archive(package, tmpdir); - - getcwd(cwd, sizeof(cwd)); - - RelocationEntry **b_record = NULL; - RelocationEntry **t_record = NULL; - chdir(tmpdir); - { - // Rewrite binary prefixes - RelocationEntry **b_record = prefixes_read(".SPM_PREFIX_BIN"); - if (b_record) { - for (int i = 0; b_record[i] != NULL; i++) { - relocate(b_record[i]->path, b_record[i]->prefix, destroot); - } - } - - // Rewrite text prefixes - RelocationEntry **t_record = prefixes_read(".SPM_PREFIX_TEXT"); - if (t_record) { - for (int i = 0; t_record[i] != NULL; i++) { - file_replace_text(t_record[i]->path, t_record[i]->prefix, destroot); - } - } - - prefixes_free(b_record); - prefixes_free(t_record); - } - chdir(cwd); - - - // Append a trailing slash to tmpdir to direct rsync to copy files, not the directory, into destroot - sprintf(source, "%s%c", tmpdir, DIRSEP); - if (rsync(NULL, source, destroot) != 0) { - exit(1); - } - rmdirs(tmpdir); - - free(package); -} diff --git a/relocation.c b/relocation.c deleted file mode 100644 index 13ae799..0000000 --- a/relocation.c +++ /dev/null @@ -1,237 +0,0 @@ -#include "spm.h" - -int replace_text(char *data, const char *spattern, const char *sreplacement) { - char *tmp = data; - size_t data_len = strlen(data); - size_t spattern_len = strlen(spattern); - size_t sreplacement_len = strlen(sreplacement); - - if (sreplacement_len > spattern_len) { - fprintf(stderr, "replacement string too long\n"); - return -1; - } - - while (*tmp != '\0') { - if (strncmp(tmp, spattern, spattern_len) == 0) { - memmove(tmp, sreplacement, sreplacement_len); - memmove(tmp + sreplacement_len, tmp + spattern_len, data_len - spattern_len); - } - tmp++; - } - return 0; -} - -/** - * Replace all occurrences of `oldstr` in file `path` with `newstr` - * @param filename - * @param oldstr - * @param newstr - * @return success=0, failure=-1, or value of `ferror()` - */ -int file_replace_text(char *filename, const char *spattern, const char *sreplacement) { - char data[BUFSIZ]; - char tempfile[PATH_MAX]; - FILE *fp = NULL; - if ((fp = fopen(filename, "r")) == NULL) { - perror(filename); - return -1; - } - - sprintf(tempfile, "%s.spmfrt", filename); - FILE *tfp = NULL; - if ((tfp = fopen(tempfile, "w+")) == NULL) { - perror(tempfile); - return -1; - } - - // Zero the data buffer - memset(data, '\0', BUFSIZ); - while(fgets(data, BUFSIZ, fp) != NULL) { - replace_text(data, spattern, sreplacement); - fprintf(tfp, "%s", data); - } - fclose(fp); - rewind(tfp); - - // Truncate the original file - if ((fp = fopen(filename, "w+")) == NULL) { - perror(filename); - return -1; - } - // Zero the data buffer once more - memset(data, '\0', BUFSIZ); - // Dump the contents of the temporary file into the original file - while(fgets(data, BUFSIZ, tfp) != NULL) { - fprintf(fp, "%s", data); - } - fclose(fp); - fclose(tfp); - - // Remove temporary file - unlink(tempfile); - return 0; -} - -/** - * Free memory allocated by `prefixes_read` function - * @param entry array of RelocationEntry - */ -void prefixes_free(RelocationEntry **entry) { - if (!entry) { - return; - } - for (int i = 0; entry[i] != NULL; i++) { - if (entry[i]->prefix) free(entry[i]->prefix); - if (entry[i]->path) free(entry[i]->path); - if (entry[i]) free(entry[i]); - } - free(entry); -} - -/** - * Parse a prefix file - * - * The file format is as follows: - * - * ~~~ - * #prefix - * path - * #prefix - * path - * #...N - * ...N - * ~~~ - * @param filename - * @return success=array of RelocationEntry, failure=NULL - */ -RelocationEntry **prefixes_read(const char *filename) { - size_t i = 0; - int record_count = 0; - int parity = 0; - FILE *fp = fopen(filename, "r"); - if (!fp) { - fprintf(SYSERROR); - return NULL; - } - RelocationEntry **entry = NULL; - char line[BUFSIZ]; - memset(line, '\0', BUFSIZ); - - while (fgets(line, BUFSIZ, fp) != NULL) { - if (isempty(line)) { - continue; - } - record_count++; - } - rewind(fp); - - // Initialize the relocation entry array - if (record_count == 0) { - return NULL; - } - - parity = record_count % 2; - if (parity != 0) { - fprintf(stderr, "%s: records are not divisible by 2 (got: %d %% 2 = %d)\n", filename, record_count, parity); - return NULL; - } - record_count /= 2; - - entry = (RelocationEntry **)calloc(record_count + 1, sizeof(RelocationEntry *)); - if (!entry) { - return NULL; - } - for (int i = 0; i < record_count; i++) { - entry[i] = (RelocationEntry *) calloc(1, sizeof(RelocationEntry)); - if (!entry[i]) { - return NULL; - } - } - - int do_prefix = 0; - int do_path = 0; - while (fgets(line, BUFSIZ, fp) != NULL) { - char *wtf = line; - if (isempty(line)) { - continue; - } - if (startswith(line, "#") == 0) { - do_prefix = 1; - } - else { - do_path = 1; - } - - // Allocate a relocation record - if (!entry[i]) { - fclose(fp); - return NULL; - } - - - if (do_prefix) { - // Populate prefix data (a prefix starts with a #) - entry[i]->prefix = (char *) calloc(strlen(line) + 1, sizeof(char)); - if (!entry[i]->prefix) { - fclose(fp); - return NULL; - } - strncpy(entry[i]->prefix, line, strlen(line)); - // Remove prefix delimiter and whitespace - strchrdel(entry[i]->prefix, "#"); - entry[i]->prefix = strip(entry[i]->prefix); - do_prefix = 0; - continue; - } - - else if (do_path) { - // Populate path data - entry[i]->path = (char *) calloc(strlen(line) + 1, sizeof(char)); - if (!entry[i]->path) { - fclose(fp); - return NULL; - } - strncpy(entry[i]->path, line, strlen(line)); - entry[i]->path = strip(entry[i]->path); - do_path = 0; - } - i++; - } - fclose(fp); - return entry; -} - -int relocate(const char *_filename, const char *_oldstr, const char *_newstr) { - int returncode; - Process *proc = NULL; - char *oldstr = strdup(_oldstr); - char *newstr = strdup(_newstr); - char *filename = strdup(_filename); - char cmd[PATH_MAX]; - - memset(cmd, '\0', sizeof(cmd)); - sprintf(cmd, "reloc \"%s\" \"%s\" \"%s\" \"%s\"", oldstr, newstr, filename, filename); - - // sanitize command - strchrdel(cmd, "&;|"); - - shell(&proc, SHELL_OUTPUT, cmd); - if (!proc) { - free(oldstr); - free(newstr); - free(filename); - return -1; - } - - returncode = proc->returncode; - if (returncode != 0 && proc->output) { - fprintf(stderr, proc->output); - } - - shell_free(proc); - free(oldstr); - free(newstr); - free(filename); - return returncode; -} - diff --git a/rpath.c b/rpath.c deleted file mode 100644 index f499e98..0000000 --- a/rpath.c +++ /dev/null @@ -1,233 +0,0 @@ -#include "spm.h" - -/** - * Wrapper function to execute `patchelf` with arguments - * @param _filename Path of file to modify - * @param _args Arguments to pass to `patchelf` - * @return success=Process struct, failure=NULL - */ -Process *patchelf(const char *_filename, const char *_args) { - char *filename = strdup(_filename); - char *args = strdup(_args); - Process *proc_info = NULL; - char sh_cmd[PATH_MAX]; - sh_cmd[0] = '\0'; - - strchrdel(args, "&;|"); - strchrdel(filename, "&;|"); - sprintf(sh_cmd, "patchelf %s %s", args, filename); - - shell(&proc_info, SHELL_OUTPUT, sh_cmd); - - free(filename); - free(args); - return proc_info; -} - -/** - * Determine whether a RPATH or RUNPATH is present in file - * - * TODO: Replace with OS-native solution(s) - * - * @param _filename path to executable or library - * @return -1=OS error, 0=has rpath, 1=not found - */ -int has_rpath(const char *_filename) { - int result = 1; // default: not found - - char *filename = strdup(_filename); - if (!filename) { - return -1; - } - - Process *proc_info = NULL; - char *rpath = NULL; - - // sanitize input path - strchrdel(filename, "&;|"); - - Process *pe = patchelf(filename, "--print-rpath"); - strip(pe->output); - if (!isempty(pe->output)) { - result = 0; - } - else { - // something went wrong with patchelf other than - // what we're looking for - result = -1; - } - - free(filename); - shell_free(pe); - return result; -} - -/** - * Returns a RPATH or RUNPATH if one is defined in `_filename` - * - * TODO: Replace with OS-native solution(s) - * - * @param _filename path to executable or library - * @return RPATH string, NULL=error (caller is responsible for freeing memory) - */ -char *rpath_get(const char *_filename) { - if ((has_rpath(_filename)) != 0) { - return NULL; - } - char *filename = strdup(_filename); - if (!filename) { - return NULL; - } - char *path = strdup(filename); - if (!path) { - free(filename); - return NULL; - } - - Process *proc_info = NULL; - char *rpath = NULL; - - // sanitize input path - strchrdel(path, "&;|"); - - Process *pe = patchelf(filename, "--print-rpath"); - rpath = (char *)calloc(strlen(pe->output) + 1, sizeof(char)); - if (!rpath) { - free(filename); - free(path); - shell_free(pe); - return NULL; - } - strncpy(rpath, pe->output, strlen(pe->output)); - strip(rpath); - - free(filename); - free(path); - shell_free(pe); - return rpath; -} - -/** - * Generate a RPATH in the form of: - * - * `$ORIGIN/relative/path/to/lib/from/_filename/path` - * - * @param _filename - * @return - */ -char *rpath_generate(const char *_filename) { - const char *origin = "$ORIGIN/"; - char *filename = realpath(_filename, NULL); - if (!filename) { - return NULL; - } - char *nearest_lib = rpath_autodetect(filename); - if (!nearest_lib) { - return NULL; - } - char *result = (char *)calloc(strlen(origin) + strlen(nearest_lib) + 1, sizeof(char)); - if (!result) { - return NULL; - } - sprintf(result, "%s%s", origin, nearest_lib); - free(filename); - free(nearest_lib); - return result; -} - -int rpath_set(const char *filename, char *_rpath) { - int returncode; - - char *rpath_new = rpath_generate(filename); - if (!rpath_new) { - return -1; - } - - char *rpath_orig = rpath_get(filename); - if (!rpath_orig) { - return -1; - } - - // Are the original and new RPATH identical? - if (strcmp(rpath_orig, rpath_new) == 0) { - free(rpath_new); - free(rpath_orig); - return 0; - } - - Process *pe = patchelf(filename, "--set-rpath"); - if (pe) { - returncode = pe->returncode; - } - shell_free(pe); - free(rpath_new); - free(rpath_orig); - return pe->returncode; -} - -/** - * Using `filename` as a starting point, step backward through the filesystem looking for a lib directory - * @param filename path to file (or a directory) - * @return success=relative path from `filename` to nearest lib directory, failure=NULL - */ -char *rpath_autodetect(const char *filename) { - int has_real_libdir = 0; - char *rootdir = dirname(filename); - char *start = realpath(rootdir, NULL); - char *cwd = realpath(".", NULL); - char *result = NULL; - - // Change directory to the requested root - chdir(start); - - char visit[PATH_MAX]; // Current directory - char tmp[PATH_MAX]; // Current directory with lib directory appended - char relative[PATH_MAX]; // Generated relative path to lib directory - char sep[2]; // Holds the platform's directory separator - - // Initialize character arrays; - visit[0] = '\0'; - tmp[0] = '\0'; - relative[0] = '\0'; - sprintf(sep, "%c", DIRSEP); - - while(1) { - // Where are we in the file system? - getcwd(visit, sizeof(visit)); - // Using the current visit path, check if it contains a lib directory - sprintf(tmp, "%s%clib", visit, DIRSEP); - if (access(tmp, F_OK) == 0) { - strcat(relative, "lib"); - has_real_libdir = 1; // gate for memory allocation below - break; - } - // Reaching the top of the file system indicates our search for a lib directory failed - else if (strcmp(visit, "/") == 0) { - break; - } - - // Assemble relative path step for this location - strcat(relative, ".."); - strcat(relative, sep); - - // Step one directory level back - chdir(".."); - } - - // If we found a viable lib directory, allocate memory for it - if (has_real_libdir) { - result = (char *)calloc(strlen(relative) + 1, sizeof(char)); - if (!result) { - chdir(cwd); // return to calling directory - return NULL; - } - // Copy character array data to the result - strncpy(result, relative, strlen(relative)); - } - - chdir(cwd); // return to calling directory - free(rootdir); - free(cwd); - free(start); - return result; -} diff --git a/shell.c b/shell.c deleted file mode 100644 index 8905c60..0000000 --- a/shell.c +++ /dev/null @@ -1,112 +0,0 @@ -#include "spm.h" - -/** - * A wrapper for `popen()` that executes non-interactive programs and reports their exit value. - * To redirect stdout and stderr you must do so from within the `fmt` string - * - * ~~~{.c} - * int fd = 1; // stdout - * const char *log_file = "log.txt"; - * Process *proc_info; - * int status; - * - * // Send stderr to stdout - * shell(&proc_info, SHELL_OUTPUT, "foo 2>&1"); - * // Send stdout and stderr to /dev/null - * shell(&proc_info, SHELL_OUTPUT,"bar &>/dev/null"); - * // Send stdout from baz to log.txt - * shell(&proc_info, SHELL_OUTPUT, "baz %d>%s", fd, log_file); - * // Do not record or redirect output from any streams - * shell(&proc_info, SHELL_DEFAULT, "biff"); - * ~~~ - * - * @param Process uninitialized `Process` struct will be populated with process data - * @param options change behavior of the function - * @param fmt shell command to execute (accepts `printf` style formatters) - * @param ... variadic arguments (used by `fmt`) - */ -void shell(Process **proc_info, u_int64_t option, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - - size_t bytes_read = 0; - size_t i = 0; - size_t new_buf_size = 0; - clockid_t clkid = CLOCK_REALTIME; - FILE *proc = NULL; - - (*proc_info) = (Process *)calloc(1, sizeof(Process)); - if (!(*proc_info)) { - fprintf(SYSERROR); - exit(errno); - } - (*proc_info)->returncode = -1; - - // outbuf needs to be an integer type because fgetc returns EOF (> char) - int *outbuf = (int *)calloc(1, sizeof(int)); - if (!outbuf) { - fprintf(SYSERROR); - exit(errno); - } - char *cmd = (char *)calloc(PATH_MAX, sizeof(char)); - if (!cmd) { - fprintf(SYSERROR); - exit(errno); - } - - vsnprintf(cmd, PATH_MAX, fmt, args); - - if (option & SHELL_BENCHMARK) { - if (clock_gettime(clkid, &(*proc_info)->start_time) == -1) { - perror("clock_gettime"); - exit(errno); - } - } - - proc = popen(cmd, "r"); - if (!proc) { - return; - } - - if (option & SHELL_BENCHMARK) { - if (clock_gettime(clkid, &(*proc_info)->stop_time) == -1) { - perror("clock_gettime"); - exit(errno); - } - (*proc_info)->time_elapsed = ((*proc_info)->stop_time.tv_sec - (*proc_info)->start_time.tv_sec) - + ((*proc_info)->stop_time.tv_nsec - (*proc_info)->start_time.tv_nsec) / 1E9; - } - - if (option & SHELL_OUTPUT) { - (*proc_info)->output = (char *)calloc(BUFSIZ, sizeof(char)); - - while ((*outbuf = fgetc(proc)) != EOF) { - - if (i >= BUFSIZ) { - new_buf_size = BUFSIZ + (i + bytes_read); - (*proc_info)->output = (char *)realloc((*proc_info)->output, new_buf_size); - i = 0; - } - if (*outbuf) { - (*proc_info)->output[bytes_read] = (char)*outbuf; - } - bytes_read++; - i++; - } - } - (*proc_info)->returncode = pclose(proc); - va_end(args); - free(outbuf); - free(cmd); -} - -/** - * Free process resources allocated by `shell()` - * @param proc_info `Process` struct - */ -void shell_free(Process *proc_info) { - if (proc_info->output) { - free(proc_info->output); - } - free(proc_info); -} diff --git a/spm.c b/spm.c deleted file mode 100644 index 3607117..0000000 --- a/spm.c +++ /dev/null @@ -1,60 +0,0 @@ -/** - * SPM - Simple Package Manager - * @file spm.c - */ -#include "spm.h" - -int main(int argc, char *argv[]) { - // not much to see here yet - // at the moment this will all be random tests, for better or worse - // everything here is subject to change without notice - - // Initialize configuration data - init_config_global(); - show_global_config(); - - // Ensure external programs are available for use. - check_runtime_environment(); - - // Install a package to test things out - char *match; - char *package; - const char *root = "/tmp/root"; - if ((match = find_package("python")) == NULL) { - fprintf(SYSERROR); - exit(1); - } - if ((package = basename(match)) == NULL) { - fprintf(stderr, "Unable to derive package name from package path:\n\t-> %s\n", match); - exit(1); - } - - Dependencies *deps = NULL; - dep_init(&deps); - - if (dep_all(&deps, package) < 0) { - dep_free(&deps); - free_global_config(); - exit(1); - } - - printf("%s requires:\n", package); - dep_show(&deps); - - // Install dependencies first - for (int i = 0; i < deps->records; i++) { - if (install(root, deps->list[i]) < 0) { - fprintf(SYSERROR); - exit(errno); - } - } - // Install package - if (install(root, package) < 0) { - fprintf(SYSERROR); - exit(errno); - } - - dep_free(&deps); - free_global_config(); - return 0; -} diff --git a/spm.h b/spm.h deleted file mode 100644 index 9f3ae17..0000000 --- a/spm.h +++ /dev/null @@ -1,181 +0,0 @@ -#ifndef SPM_SPM_H -#define SPM_SPM_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if !defined(_WIN32) -#include -#endif - -#include "config.h" - -// spm.c -#define SYSERROR stderr, "%s:%s:%d: %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno) -#define DIRSEP_WIN32 '\\' -#define DIRSEP_UNIX '/' -#if defined(_WIN32) -#define DIRSEP DIRSEP_WIN32 -#define NOT_DIRSEP DIRSEP_UNIX -#else -#define DIRSEP DIRSEP_UNIX -#define NOT_DIRSEP DIRSEP_WIN32 -#endif - -#define PKG_DIR SPM_GLOBAL.package_dir -#define TMP_DIR SPM_GLOBAL.tmp_dir - -#define SHELL_DEFAULT 1 << 0 -#define SHELL_OUTPUT 1 << 1 -#define SHELL_BENCHMARK 1 << 2 - -typedef struct { - char *root; - char **dirs; - size_t dirs_length; - char **files; - size_t files_length; -} FSTree; - -typedef struct { - size_t __size; // Count of allocated records - size_t records; // Count of usable records - char **list; // Array of dependencies -} Dependencies; - -typedef struct { - char *key; - char *value; - size_t key_length; - size_t value_length; -} ConfigItem; - -typedef struct { - char *package_dir; - char *tmp_dir; - char *user_config_basedir; - char *user_config_file; - ConfigItem **config; - struct utsname sysinfo; -} spm_vars; - -typedef struct { - struct timespec start_time, stop_time; - double time_elapsed; - int returncode; - char *output; -} Process; - -typedef struct { - char *prefix; - char *path; -} RelocationEntry; - -// GLOBALS -spm_vars SPM_GLOBAL; - -// shell.c -void shell(Process **proc_info, u_int64_t option, const char *fmt, ...); -void shell_free(Process *proc_info); - -// archive.c -int tar_extract_archive(const char *_archive, const char *_destination); -int tar_extract_file(const char *archive, const char* filename, const char *destination); - -// relocation.c -int relocate(const char *filename, const char *_oldstr, const char *_newstr); -int replace_text(char *data, const char *_spattern, const char *_sreplacement); -int file_replace_text(char *filename, const char *spattern, const char *sreplacement); -RelocationEntry **prefixes_read(const char *filename); -void prefixes_free(RelocationEntry **entry); - -// strings.c -int num_chars(const char *sptr, int ch); -int startswith(const char *sptr, const char *pattern); -int endswith(const char *sptr, const char *pattern); -char *normpath(const char *path); -void strchrdel(char *sptr, const char *chars); -long int strchroff(const char *sptr, int ch); -void strdelsuffix(char *sptr, const char *suffix); -char** split(char *sptr, const char* delim); -void split_free(char **ptr); -char *substring_between(char *sptr, const char *delims); -static int _strsort_compare(const void *a, const void *b); -void strsort(char **arr); -int find_in_file(const char *filename, const char *pattern); - -// find.c -char *find_executable(const char *program); -char *find_file(const char *root, const char *filename); -char *find_package(const char *filename); -int errglob(const char *epath, int eerrno); - -// rpath.c -Process *patchelf(const char *_filename, const char *_args); -char *rpath_autodetect(const char *filename); -int has_rpath(const char *_filename); -char *rpath_get(const char *_filename); -char *rpath_generate(const char *_filename); -int rpath_set(const char *filename, char *_rpath); - -// fs.c -long int get_file_size(const char *filename); -int mkdirs(const char *_path, mode_t mode); -char *dirname(const char *_path); -char *basename(char *path); -int rsync(const char *_args, const char *_source, const char *_destination); - -// config_global.c -char *get_user_conf_dir(void); -char *get_user_config_file(void); -char *get_user_tmp_dir(void); -char *get_user_package_dir(void); - -void init_config_global(void); -void free_global_config(void); -void show_global_config(void); -void check_runtime_environment(void); - -// install.c -int install(const char *destroot, const char *_package); - -// config.c -#define CONFIG_BUFFER_SIZE 1024 - -char *lstrip(char *sptr); -char *strip(char *sptr); -int isempty(char *sptr); -int isquoted(char *sptr); -ConfigItem **config_read(const char *filename); -ConfigItem *config_get(ConfigItem **item, const char *key); -void config_free(ConfigItem **item); -void config_test(void); - -// deps.c -int exists(const char *filename); -int dep_seen(Dependencies **deps, const char *name); -int dep_init(Dependencies **deps); -void dep_free(Dependencies **deps); -int dep_append(Dependencies **deps, char *name); -int dep_solve(Dependencies **deps, const char *filename); -int dep_all(Dependencies **deps, const char *_package); -void dep_show(Dependencies **deps); - -// fstree.c -int _fstree_compare(const FTSENT **a, const FTSENT **b); -void fstree_free(FSTree *fsdata); -FSTree *fstree(const char *_path); -int rmdirs(const char *_path); - -#endif //SPM_SPM_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..009eb92 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,17 @@ +include_directories( + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include +) + +add_executable(spm spm.c config.c compat.c deps.c fs.c rpath.c find.c shell.c archive.c strings.c relocation.c install.c config_global.c) +target_link_libraries(spm rt) +install( + TARGETS spm + DESTINATION ${CMAKE_INSTALL_PREFIX}/bin +) +install( + FILES + ${CMAKE_BINARY_DIR}/include/config.h + ${CMAKE_SOURCE_DIR}/include/spm.h + DESTINATION "${CMAKE_INSTALL_PREFIX}/include/${PROJECT_NAME}" +) diff --git a/src/archive.c b/src/archive.c new file mode 100644 index 0000000..7ec9d04 --- /dev/null +++ b/src/archive.c @@ -0,0 +1,76 @@ +#include "spm.h" + +/** + * Extract a single file from a tar archive into a directory + * + * @param archive path to tar archive + * @param filename known path inside the archive to extract + * @param destination where to extract file to (must exist) + * @return + */ +int tar_extract_file(const char *archive, const char* filename, const char *destination) { + Process *proc = NULL; + int status; + char cmd[PATH_MAX]; + + sprintf(cmd, "tar xf %s -C %s %s 2>&1", archive, destination, filename); + if (exists(archive) != 0) { + fprintf(stderr, "%s :: ", archive); + fprintf(SYSERROR); + return -1; + } + + shell(&proc, SHELL_OUTPUT, cmd); + if (!proc) { + fprintf(SYSERROR); + return -1; + } + + status = proc->returncode; + shell_free(proc); + + return status; +} + +int tar_extract_archive(const char *_archive, const char *_destination) { + Process *proc = NULL; + int status; + char cmd[PATH_MAX]; + + if (exists(_archive) != 0) { + fprintf(SYSERROR); + return -1; + } + + char *archive = strdup(_archive); + if (!archive) { + fprintf(SYSERROR); + return -1; + } + char *destination = strdup(_destination); + if (!destination) { + fprintf(SYSERROR); + return -1; + } + + // sanitize archive + strchrdel(archive, "&;|"); + // sanitize destination + strchrdel(destination, "&;|"); + + sprintf(cmd, "tar xf %s -C %s 2>&1", archive, destination); + shell(&proc, SHELL_OUTPUT, cmd); + if (!proc) { + fprintf(SYSERROR); + free(archive); + free(destination); + return -1; + } + + status = proc->returncode; + shell_free(proc); + free(archive); + free(destination); + return status; +} + diff --git a/src/compat.c b/src/compat.c new file mode 100644 index 0000000..082a602 --- /dev/null +++ b/src/compat.c @@ -0,0 +1,16 @@ +#include +#include "config.h" + +#ifndef HAVE_STRSEP +// credit: Dan Cross via https://unixpapa.com/incnote/string.html +char *strsep(char **sp, char *sep) +{ + char *p, *s; + if (sp == NULL || *sp == NULL || **sp == '\0') return(NULL); + s = *sp; + p = s + strcspn(s, sep); + if (*p != '\0') *p++ = '\0'; + *sp = p; + return(s); +} +#endif \ No newline at end of file diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..93ff673 --- /dev/null +++ b/src/config.c @@ -0,0 +1,163 @@ +/** + * @file config.c + */ +#include "spm.h" + +/** + * Parse a basic configuration file + * + * NOTE: All values are stored as strings. You need to convert non-string values yourself. + * + * @param filename + * @return success=`ConfigItem` array, failure=NULL + */ +ConfigItem **config_read(const char *filename) { + const char sep = '='; + char *line = (char *)calloc(CONFIG_BUFFER_SIZE, sizeof(char)); + FILE *fp = fopen(filename, "r"); + if (!fp) { + // errno will be set, so die, and let the caller handle it + return NULL; + } + int record_initial = 2; + ConfigItem **config = (ConfigItem **) calloc(record_initial, sizeof(ConfigItem *)); + int record = 0; + + while (fgets(line, CONFIG_BUFFER_SIZE, fp) != NULL) { + char *lptr = line; + // Remove trailing space and newlines + lptr = strip(lptr); + + // Remove leading space and newlines + lptr = lstrip(lptr); + + // Skip empty lines + if (isempty(lptr)) { + continue; + } + // Skip comment-only lines + if (*lptr == '#' || *lptr == ';') { + continue; + } + + // Get a pointer to the key pair separator + char *sep_pos = strchr(lptr, sep); + if (!sep_pos) { + printf("invalid entry on line %d: missing '%c': '%s'\n", record, sep, lptr); + continue; + } + + // These values are approximations. The real length(s) are recorded using strlen below. + // At most we'll lose a few heap bytes to whitespace, but it's better than allocating PATH_MAX or BUFSIZ + // for a measly ten byte string. + size_t key_length = strcspn(lptr, &sep); + size_t value_length = strlen(sep_pos); + + // Allocate a ConfigItem record + config[record] = (ConfigItem *)calloc(1, sizeof(ConfigItem)); + config[record]->key = (char *)calloc(key_length + 1, sizeof(char)); + config[record]->value = (char *)calloc(value_length + 1, sizeof(char)); + + // Shortcut our array at this point. Things get pretty ugly otherwise. + char *key = config[record]->key; + char *value = config[record]->value; + + // Copy the array pointers (used to populate config->key/value_length + char *key_orig = key; + char *value_orig = value; + + // Populate the key and remove any trailing space + while (lptr != sep_pos) { + *key++ = *lptr++; + } + key = strip(key); + + // We're at the separator now, so skip over it + lptr++; + // and remove any leading space + lptr = lstrip(lptr); + + // Determine whether the string is surrounded by quotes, if so, get rid of them + if (isquoted(lptr)) { + // Move pointer beyond quote + lptr = strpbrk(lptr, "'\"") + 1; + // Terminate on closing quote + char *tmp = strpbrk(lptr, "'\""); + *tmp = '\0'; + } + + // Populate the value, and ignore any inline comments + while (*lptr) { + if (*lptr == '#' || *lptr == ';') { + // strip trailing whitespace where the comment is and stop processing + value = strip(value); + break; + } + *value++ = *lptr++; + } + + // Populate length data + config[record]->key_length = strlen(key_orig); + config[record]->value_length = strlen(value_orig); + + // Destroy contents of line buffer + memset(line, '\0', CONFIG_BUFFER_SIZE); + + // increment record count + record++; + // Expand config by another record + config = (ConfigItem **)reallocarray(config, record + record_initial + 1, sizeof(ConfigItem *)); + } + free(line); + return config; +} + +/** + * Free memory allocated by `config_read` + * @param item `ConfigItem` array + */ +void config_free(ConfigItem **item) { + for (int i = 0; item[i] != NULL; i++) { + free(item[i]); + } + free(item); +} + +/** + * If the configuration contains `key` return a pointer to that record + * @param item pointer to array of config records + * @param key search for key in config records + * @return success=pointer to record, failure=NULL + */ +ConfigItem *config_get(ConfigItem **item, const char *key) { + if (!item) { + return NULL; + } + for (int i = 0; item[i] != NULL; i++) { + if (!strcmp(item[i]->key, key)) { + return item[i]; + } + } + return NULL; +} + +void config_test(void) { + ConfigItem **config = config_read("program.conf"); + printf("Data Parsed:\n"); + for (int i = 0; config[i] != NULL; i++) { + printf("key: '%s', value: '%s'\n", config[i]->key, config[i]->value); + } + + printf("Testing config_get():\n"); + ConfigItem *cptr = NULL; + if ((cptr = config_get(config, "integer_value"))) { + printf("%s = %d\n", cptr->key, atoi(cptr->value)); + } + if ((cptr = config_get(config, "float_value"))) { + printf("%s = %.3f\n", cptr->key, atof(cptr->value)); + } + if ((cptr = config_get(config, "string_value"))) { + printf("%s = %s\n", cptr->key, cptr->value); + } + config_free(config); +} diff --git a/src/config_global.c b/src/config_global.c new file mode 100644 index 0000000..b1c6dc2 --- /dev/null +++ b/src/config_global.c @@ -0,0 +1,186 @@ +#include "spm.h" + +char *get_user_conf_dir(void) { + char *result = NULL; + wordexp_t wexp; + wordexp("~/.spm", &wexp, 0); + if (wexp.we_wordc != 0) { + result = (char *)calloc(strlen(wexp.we_wordv[0]) + 1, sizeof(char)); + if (!result) { + wordfree(&wexp); + return NULL; + } + strncpy(result, wexp.we_wordv[0], strlen(wexp.we_wordv[0])); + if (access(result, F_OK) != 0) { + mkdirs(result, 0755); + } + } + wordfree(&wexp); + return result; +} + +char *get_user_config_file(void) { + const char *filename = "spm.conf"; + char template[PATH_MAX]; + char *ucd = get_user_conf_dir(); + if (!ucd) { + return NULL; + } + // Initialize temporary path + template[0] = '\0'; + + sprintf(template, "%s%c%s", ucd, DIRSEP, filename); + if (access(template, F_OK) != 0) { + // No configuration exists, so fail + return NULL; + } + free(ucd); + // Allocate and return path to configuration file + return strdup(template); +} + +char *get_user_tmp_dir(void) { + char template[PATH_MAX]; + char *ucd = get_user_conf_dir(); + sprintf(template, "%s%ctmp", ucd, DIRSEP); + + if (access(template, F_OK) != 0) { + if (mkdirs(template, 0755) != 0) { + return NULL; + } + } + + free(ucd); + return strdup(template); +} + +char *get_user_package_dir(void) { + char template[PATH_MAX]; + char *ucd = get_user_conf_dir(); + sprintf(template, "%s%cpkgs", ucd, DIRSEP); + + if (access(template, F_OK) != 0) { + if (mkdirs(template, 0755) != 0) { + return NULL; + } + } + + free(ucd); + return strdup(template); +} + +/** + * Check whether SPM has access to external programs it needs + */ +void check_runtime_environment(void) { + int bad_rt = 0; + char *required[] = { + "patchelf", + "rsync", + "tar", + "bash", + "reloc", + NULL, + }; + for (int i = 0; required[i] != NULL; i++) { + char *result = find_executable(required[i]); + if (!result) { + fprintf(stderr, "Required program '%s' is not installed\n", required[i]); + bad_rt = 1; + } + free(result); + } + if (bad_rt) { + exit(1); + } +} + + + +void init_config_global(void) { + SPM_GLOBAL.user_config_basedir = NULL; + SPM_GLOBAL.user_config_file = NULL; + SPM_GLOBAL.package_dir = NULL; + SPM_GLOBAL.tmp_dir = NULL; + SPM_GLOBAL.config = NULL; + + if (uname(&SPM_GLOBAL.sysinfo) != 0) { + fprintf(SYSERROR); + exit(1); + } + + SPM_GLOBAL.user_config_basedir = get_user_conf_dir(); + SPM_GLOBAL.user_config_file = get_user_config_file(); + if (SPM_GLOBAL.user_config_file) { + SPM_GLOBAL.config = config_read(SPM_GLOBAL.user_config_file); + } + + ConfigItem *item = NULL; + + // Initialize temp directory + item = config_get(SPM_GLOBAL.config, "tmp_dir"); + if (item) { + SPM_GLOBAL.tmp_dir = item->value; + if (access(SPM_GLOBAL.tmp_dir, F_OK) != 0) { + if (mkdirs(SPM_GLOBAL.tmp_dir, 0755) != 0) { + fprintf(stderr, "Unable to create global temporary directory: %s\n", SPM_GLOBAL.tmp_dir); + fprintf(SYSERROR); + exit(1); + } + } + } + else { + SPM_GLOBAL.tmp_dir = get_user_tmp_dir(); + } + + // Initialize package directory + item = config_get(SPM_GLOBAL.config, "package_dir"); + if (item) { + SPM_GLOBAL.package_dir = item->value; + if (access(SPM_GLOBAL.package_dir, F_OK) != 0) { + if (mkdirs(SPM_GLOBAL.package_dir, 0755) != 0) { + fprintf(stderr, "Unable to create global package directory: %s\n", SPM_GLOBAL.package_dir); + fprintf(SYSERROR); + exit(1); + } + } + } + else { + SPM_GLOBAL.package_dir = get_user_package_dir(); + } +} + +void free_global_config(void) { + if (SPM_GLOBAL.package_dir) { + free(SPM_GLOBAL.package_dir); + } + if (SPM_GLOBAL.tmp_dir) { + free(SPM_GLOBAL.tmp_dir); + } + if (SPM_GLOBAL.user_config_basedir) { + free(SPM_GLOBAL.user_config_basedir); + } + if (SPM_GLOBAL.user_config_file) { + free(SPM_GLOBAL.user_config_file); + } + if (SPM_GLOBAL.config) { + config_free(SPM_GLOBAL.config); + } +} + +void show_global_config(void) { + printf("#---------------------------\n"); + printf("#---- SPM CONFIGURATION ----\n"); + printf("#---------------------------\n"); + printf("# base dir: %s\n", SPM_GLOBAL.user_config_basedir ? SPM_GLOBAL.user_config_basedir : "none (check write permission on home directory)"); + printf("# config file: %s\n", SPM_GLOBAL.user_config_file ? SPM_GLOBAL.user_config_file : "none"); + if (SPM_GLOBAL.user_config_file) { + printf("# config file contents:\n"); + for (int i = 0; SPM_GLOBAL.config[i] != NULL; i++) { + printf("# -> %s: %s\n", SPM_GLOBAL.config[i]->key, SPM_GLOBAL.config[i]->value); + } + } + printf("# package storage: %s\n", SPM_GLOBAL.package_dir); + printf("# temp storage: %s\n", SPM_GLOBAL.tmp_dir); + printf("\n"); +} diff --git a/src/deps.c b/src/deps.c new file mode 100644 index 0000000..99f6148 --- /dev/null +++ b/src/deps.c @@ -0,0 +1,188 @@ +// +// Created by jhunk on 12/16/19. +// +#include "spm.h" + +int exists(const char *filename) { + return access(filename, F_OK); +} + +int dep_seen(Dependencies **deps, const char *name) { + if (!deps) { + return -1; + } + for (int i = 0; i != (*deps)->records; i++) { + if (strstr((*deps)->list[i], name) != NULL) { + return 1; + } + } + return 0; +} + +int dep_init(Dependencies **deps) { + (*deps) = (Dependencies *)calloc(1, sizeof(Dependencies)); + (*deps)->__size = 2; + (*deps)->records = 0; + (*deps)->list = (char **)calloc((*deps)->__size, sizeof(char *)); + if (!(*deps)->list) { + return -1; + } + return 0; +} + +void dep_free(Dependencies **deps) { + if ((*deps) != NULL) { + return; + } + for (int i = 0; i < (*deps)->__size; i++) { + if ((*deps)->list[i] != NULL) { + free((*deps)->list[i]); + } + } + free((*deps)); +} + +int dep_append(Dependencies **deps, char *_name) { + char *name = NULL; + char *bname = NULL; + + if (!(*deps)) { + return -1; + } + + name = find_package(_name); + if (!name) { + perror(_name); + fprintf(SYSERROR); + return -1; + } + + bname = basename(name); + if (!bname) { + perror(name); + fprintf(SYSERROR); + return -1; + } + + (*deps)->__size++; + (*deps)->list = (char **)realloc((*deps)->list, sizeof(char *) * (*deps)->__size); + if (!(*deps)->list) { + free(name); + return -1; + } + + (*deps)->list[(*deps)->records] = (char *)calloc(strlen(bname) + 1, sizeof(char)); + if (!(*deps)->list[(*deps)->records]) { + free(name); + return -1; + } + + strcpy((*deps)->list[(*deps)->records], bname); + (*deps)->records++; + + free(name); + return 0; +} + +int dep_solve(Dependencies **deps, const char *filename) { + if (!(*deps)) { + return -1; + } + if (exists(filename) != 0) { + return -1; + } + FILE *fp = fopen(filename, "r"); + if (!fp) { + perror(filename); + return -1; + } + + char data[BUFSIZ]; + memset(data, '\0', sizeof(data)); + + char *line = data; + int line_count = 0; + while (fgets(line, BUFSIZ, fp) != NULL) { + size_t line_length = strlen(line); + if (line_length > 1 && strstr(line, "\r\n")) { + line[line_length - 2] = '\0'; + } + if (line_length > 1 && line[line_length - 1] == '\n') { + line[line_length - 1] = '\0'; + } + if (strcmp(line, "") == 0) { + continue; + } + line_count++; + if (dep_seen(deps, line) > 0) { + // Already seen this dependency. Skip it. + continue; + } + else { + // Have not seen this dependency before + if (dep_append(deps, line) == 0) { + dep_solve(deps, line); + } + } + } + fclose(fp); + return line_count; +} + +int dep_all(Dependencies **deps, const char *_package) { + static int next = 0; + char *package = NULL; + char depfile[PATH_MAX]; + char template[PATH_MAX]; + char suffix[PATH_MAX] = "spm_depends_all_XXXXXX"; + + // Verify the requested package pattern exists + package = find_package(_package); + if (!package) { + perror(_package); + fprintf(SYSERROR); + return -1; + } + + // Create a new temporary directory and extract the requested package into it + sprintf(template, "%s%c%s", TMP_DIR, DIRSEP, suffix); + char *tmpdir = mkdtemp(template); + if (!tmpdir) { + perror(template); + fprintf(SYSERROR); + return -1; + } + if (tar_extract_file(package, ".SPM_DEPENDS", tmpdir) < 0) { + perror(package); + fprintf(SYSERROR); + return -1; + } + + // Scan depencency tree + sprintf(depfile, "%s%c%s", tmpdir, DIRSEP, ".SPM_DEPENDS"); + int resolved = dep_solve(deps, depfile); + + // NOTE: + // 1. `resolved` is the number of dependencies for the package we're scanning + // 2. `next` permits us to converge on `resolved`, otherwise `i` would reset to `0` each time `dep_all` is called + for (int i = next; i < resolved; i++) { + next++; + if (dep_seen(deps, (*deps)->list[i])) { + dep_all(deps, (*deps)->list[i]); + } + } + + // Remove temporary data + unlink(depfile); + unlink(tmpdir); + return 0; +} + +void dep_show(Dependencies **deps) { + if ((*deps) == NULL) { + return; + } + for (int i = 0; i < (*deps)->records; i++) { + printf("%d: %s\n", i, (*deps)->list[i]); + } +} diff --git a/src/find.c b/src/find.c new file mode 100644 index 0000000..84ded16 --- /dev/null +++ b/src/find.c @@ -0,0 +1,154 @@ +#include "spm.h" + +/** + * glob callback function + * @param epath path to file that generated the error condition + * @param eerrno the error condition + * @return the error condition + */ +int errglob(const char *epath, int eerrno) { + fprintf(stderr, "glob matching error: %s (%d)", epath, eerrno); + return eerrno; +} + +/** + * Scan a directory for a file by name, or by wildcard + * + * @param root directory path to scan + * @param filename file to find (wildcards accepted) + * @return success=path to file, failure=NULL + */ +char *find_file(const char *root, const char *filename) { + glob_t results; + int glob_flags = 0; + int match = 0; + char *rootpath = NULL; + char *path = NULL; + + // GUARD + if (!root || !filename || strstr(filename, "..") || strstr(filename, "./")) { + return NULL; + } + + if (!(path = (char *)calloc(PATH_MAX + 1, sizeof(char)))) { + fprintf(SYSERROR); + exit(errno); + } + + if (!(rootpath = realpath(root, NULL))) { + return NULL; + } + + strcat(path, rootpath); + strcat(path, "/"); + strcat(path, filename); + + // Save a little time if the file exists + if (access(path, F_OK) != -1) { + return path; + } + + // Inject wildcard + strcat(path, "*"); + // Search for the file + match = glob(path, glob_flags, errglob, &results); + + if (match != 0) { + // report critical errors except GLOB_NOMATCH + if (match == GLOB_NOSPACE || match == GLOB_ABORTED) { + fprintf(SYSERROR); + } + return NULL; + } + + // Resize path to the length of the first match + char *want = results.gl_pathv[0]; + if (!(path = (char *)realloc(path, sizeof(char) * (strlen(want) + 1)))) { + fprintf(SYSERROR); + exit(errno); + } + + // Replace path string with wanted path string + strncpy(path, want, strlen(want)); + + free(rootpath); + globfree(&results); + return path; +} + +/** + * Scan the package directory for a package by name + * @param filename file to find + * @return success=path to file, failure=NULL + */ +char *find_package(const char *filename) { + return find_file(PKG_DIR, filename); +} + +/** + * Determine whether `pattern` is present within a file + * @param filename + * @param pattern + * @return 0=found, 1=not found, -1=OS error + */ +int find_in_file(const char *filename, const char *pattern) { + int result = 1; // default "not found" + + FILE *fp = fopen(filename, "rb"); + if (!fp) { + return -1; + } + + long int file_len = get_file_size(filename); + if (file_len < 0) { + return -1; + } + char *buffer = (char *)calloc((size_t) file_len, sizeof(char)); + if (!buffer) { + return -1; + } + size_t pattern_len = strlen(pattern); + + fread(buffer, (size_t) file_len, sizeof(char), fp); + fclose(fp); + + for (size_t i = 0; i < file_len; i++) { + if (!memcmp(&buffer[i], pattern, pattern_len)) { + result = 0; // found + break; + } + } + free(buffer); + return result; +} + +/** + * Get the full path of a shell command + * @param program + * @return success=absolute path to program, failure=NULL + */ +char *find_executable(const char *program) { + int found = 0; + char *result = NULL; + char *env_path = NULL; + env_path = getenv("PATH"); + if (!env_path) { + return NULL; + } + char **search_paths = split(env_path, ":"); + + char buf[PATH_MAX]; + for (int i = 0; search_paths[i] != NULL; i++) { + sprintf(buf, "%s%c%s", search_paths[i], DIRSEP, program); + if (access(buf, F_OK | X_OK) == 0) { + found = 1; + break; + } + memset(buf, '\0', sizeof(buf)); + } + if (found) { + result = strdup(buf); + } + split_free(search_paths); + return result; +} diff --git a/src/fs.c b/src/fs.c new file mode 100644 index 0000000..27bdf2f --- /dev/null +++ b/src/fs.c @@ -0,0 +1,264 @@ +#include "spm.h" + +FSTree *fstree(const char *_path) { + FTS *parent = NULL; + FTSENT *node = NULL; + FSTree *fsdata = NULL; + char *path = realpath(_path, NULL); + char *root[2] = { path, NULL }; + + size_t dirs_size = 2; + size_t dirs_records = 0; + size_t files_size = 2; + size_t files_records = 0; + + fsdata = (FSTree *)calloc(1, sizeof(FSTree)); + fsdata->root= (char *)calloc(strlen(path) + 1, sizeof(char)); + fsdata->dirs = (char **)calloc(dirs_size, sizeof(char *)); + fsdata->files= (char **)calloc(files_size, sizeof(char *)); + + strncpy(fsdata->root, path, strlen(path)); + parent = fts_open(root, FTS_PHYSICAL | FTS_NOCHDIR, &_fstree_compare); + + if (parent != NULL) { + while ((node = fts_read(parent)) != NULL) { + switch (node->fts_info) { + case FTS_D: + if (strcmp(node->fts_path, "..") == 0 || strcmp(node->fts_path, ".") == 0) { + continue; + } + fsdata->dirs = (char **)realloc(fsdata->dirs, sizeof(char*) * dirs_size); + fsdata->dirs[dirs_size - 1] = NULL; + fsdata->dirs[dirs_records] = (char *)calloc(strlen(node->fts_path) + 1, sizeof(char)); + strncpy(fsdata->dirs[dirs_records], node->fts_path, strlen(node->fts_path)); + dirs_size++; + dirs_records++; + break; + case FTS_F: + case FTS_SL: + fsdata->files = (char **)realloc(fsdata->files, sizeof(char*) * files_size); + fsdata->files[files_size - 1] = NULL; + fsdata->files[files_records] = (char *)calloc(strlen(node->fts_path) + 1, sizeof(char)); + strncpy(fsdata->files[files_records], node->fts_path, strlen(node->fts_path)); + files_size++; + files_records++; + break; + default: + break; + } + } + fts_close(parent); + } + fsdata->dirs_length = dirs_records; + fsdata->files_length = files_records; + free(path); + return fsdata; +} + +int _fstree_compare(const FTSENT **one, const FTSENT **two) { + return (strcmp((*one)->fts_name, (*two)->fts_name)); +} + +int rmdirs(const char *_path) { + if (access(_path, F_OK) != 0) { + return -1; + } + + FSTree *data = fstree(_path); + if (data->files) { + for (int i = 0; data->files[i] != NULL; i++) { + remove(data->files[i]); + } + } + if (data->dirs) { + for (int i = data->dirs_length - 1; i != 0; i--) { + remove(data->dirs[i]); + } + } + remove(data->root); + + fstree_free(data); + return 0; +} + +void fstree_free(FSTree *fsdata) { + if (fsdata != NULL) { + if (fsdata->root != NULL) { + free(fsdata->root); + } + if (fsdata->files != NULL) { + for (int i = 0; fsdata->files[i] != NULL; i++) { + free(fsdata->files[i]); + } + } + if (fsdata->dirs != NULL) { + for (int i = 0; fsdata->dirs[i] != NULL; i++) { + free(fsdata->dirs[i]); + } + } + free(fsdata); + } +} + +/** + * Converts Win32 path to Unix path, and vice versa + * - On UNIX, Win32 paths will be converted UNIX + * - On Win32, UNIX paths will be converted to Win32 + * + * This function is platform dependent. + * + * @param path a system path + * @return string (caller is responsible for `free`ing memory) + */ +char *normpath(const char *path) { + char *result = strdup(path); + char *tmp = result; + + while (*tmp) { + if (*tmp == NOT_DIRSEP) { + *tmp = DIRSEP; + tmp++; + continue; + } + tmp++; + } + return result; +} + +/** + * Strip file name from directory + * Note: Caller is responsible for freeing memory + * + * @param _path + * @return success=path to directory, failure=NULL + */ +char *dirname(const char *_path) { + char *path = strdup(_path); + char *last = strrchr(path, DIRSEP); + if (!last) { + return NULL; + } + // Step backward, stopping on the first non-separator + // This ensures strings like "/usr//////" are converted to "/usr", but... + // it will do nothing to fix up a path like "/usr//////bin/bash + char *lookback = last; + while (*(lookback - 1) == DIRSEP) { + lookback--; + } + + *lookback = '\0'; + return path; +} + +/** + * Strip directory from file name + * Note: Caller is responsible for freeing memory + * + * @param _path + * @return success=file name, failure=NULL + */ +char *basename(char *path) { + char *result = NULL; + char *last = strrchr(path, DIRSEP); + if (!last) { + return NULL; + } + + // Perform a lookahead ensuring the string is valid beyond the last separator + if ((last + 1) != NULL) { + result = last + 1; + } + + return result; +} + +/** + * Basic rsync wrapper for copying files + * @param _args arguments to pass to rsync (set to `NULL` for default options) + * @param _source source file or directory + * @param _destination destination file or directory + * @return success=0, failure=-1 + */ +int rsync(const char *_args, const char *_source, const char *_destination) { + int returncode; + Process *proc = NULL; + char *args = NULL; + if (_args) { + args = strdup(_args); + } + char *source = strdup(_source); + char *destination = strdup(_destination); + char cmd[PATH_MAX]; + char args_combined[PATH_MAX]; + + memset(cmd, '\0', sizeof(cmd)); + memset(args_combined, '\0', sizeof(args_combined)); + strcpy(args_combined, "--archive --hard-links "); + if (args) { + strcat(args_combined, _args); + } + + sprintf(cmd, "rsync %s \"%s\" \"%s\"", args_combined, source, destination); + // sanitize command + strchrdel(cmd, "&;|"); + shell(&proc, SHELL_OUTPUT, cmd); + if (!proc) { + if (args) { + free(args); + } + free(source); + free(destination); + return -1; + } + + returncode = proc->returncode; + if (returncode != 0 && proc->output) { + fprintf(stderr, proc->output); + } + shell_free(proc); + + if (args) { + free(args); + } + free(source); + free(destination); + return returncode; +} + +long int get_file_size(const char *filename) { + long int result = 0; + FILE *fp = fopen(filename, "rb"); + if (!fp) { + return -1; + } + fseek(fp, 0, SEEK_END); + result = ftell(fp); + fclose(fp); + return result; +} + +/** + * Attempt to create a directory (or directories) + * @param _path A path to create + * @param mode UNIX permissions (octal) + * @return success=0, failure=-1 (+ errno will be set) + */ +int mkdirs(const char *_path, mode_t mode) { + int result = 0; + char *path = normpath(_path); + char tmp[PATH_MAX]; + tmp[0] = '\0'; + + char sep[2]; + sprintf(sep, "%c", DIRSEP); + char **parts = split(path, sep); + for (int i = 0; parts[i] != NULL; i++) { + strcat(tmp, parts[i]); + strcat(tmp, sep); + if (access(tmp, F_OK) != 0) { + result = mkdir(tmp, mode); + } + } + split_free(parts); + return result; +} diff --git a/src/install.c b/src/install.c new file mode 100644 index 0000000..e647d31 --- /dev/null +++ b/src/install.c @@ -0,0 +1,63 @@ +#include "spm.h" + +int install(const char *destroot, const char *_package) { + char *package = find_package(_package); + if (!package) { + fprintf(SYSERROR); + return -1; + } + + if (exists(destroot) != 0) { + if (mkdirs(destroot, 0755) != 0) { + fprintf(SYSERROR); + return -2; + } + } + + char cwd[PATH_MAX]; + char source[PATH_MAX]; + char template[PATH_MAX]; + char suffix[PATH_MAX] = "spm_destroot_XXXXXX"; + sprintf(template, "%s%c%s", TMP_DIR, DIRSEP, suffix); + + // Create a new temporary directory and extract the requested package into it + char *tmpdir = mkdtemp(template); + tar_extract_archive(package, tmpdir); + + getcwd(cwd, sizeof(cwd)); + + RelocationEntry **b_record = NULL; + RelocationEntry **t_record = NULL; + chdir(tmpdir); + { + // Rewrite binary prefixes + RelocationEntry **b_record = prefixes_read(".SPM_PREFIX_BIN"); + if (b_record) { + for (int i = 0; b_record[i] != NULL; i++) { + relocate(b_record[i]->path, b_record[i]->prefix, destroot); + } + } + + // Rewrite text prefixes + RelocationEntry **t_record = prefixes_read(".SPM_PREFIX_TEXT"); + if (t_record) { + for (int i = 0; t_record[i] != NULL; i++) { + file_replace_text(t_record[i]->path, t_record[i]->prefix, destroot); + } + } + + prefixes_free(b_record); + prefixes_free(t_record); + } + chdir(cwd); + + + // Append a trailing slash to tmpdir to direct rsync to copy files, not the directory, into destroot + sprintf(source, "%s%c", tmpdir, DIRSEP); + if (rsync(NULL, source, destroot) != 0) { + exit(1); + } + rmdirs(tmpdir); + + free(package); +} diff --git a/src/relocation.c b/src/relocation.c new file mode 100644 index 0000000..13ae799 --- /dev/null +++ b/src/relocation.c @@ -0,0 +1,237 @@ +#include "spm.h" + +int replace_text(char *data, const char *spattern, const char *sreplacement) { + char *tmp = data; + size_t data_len = strlen(data); + size_t spattern_len = strlen(spattern); + size_t sreplacement_len = strlen(sreplacement); + + if (sreplacement_len > spattern_len) { + fprintf(stderr, "replacement string too long\n"); + return -1; + } + + while (*tmp != '\0') { + if (strncmp(tmp, spattern, spattern_len) == 0) { + memmove(tmp, sreplacement, sreplacement_len); + memmove(tmp + sreplacement_len, tmp + spattern_len, data_len - spattern_len); + } + tmp++; + } + return 0; +} + +/** + * Replace all occurrences of `oldstr` in file `path` with `newstr` + * @param filename + * @param oldstr + * @param newstr + * @return success=0, failure=-1, or value of `ferror()` + */ +int file_replace_text(char *filename, const char *spattern, const char *sreplacement) { + char data[BUFSIZ]; + char tempfile[PATH_MAX]; + FILE *fp = NULL; + if ((fp = fopen(filename, "r")) == NULL) { + perror(filename); + return -1; + } + + sprintf(tempfile, "%s.spmfrt", filename); + FILE *tfp = NULL; + if ((tfp = fopen(tempfile, "w+")) == NULL) { + perror(tempfile); + return -1; + } + + // Zero the data buffer + memset(data, '\0', BUFSIZ); + while(fgets(data, BUFSIZ, fp) != NULL) { + replace_text(data, spattern, sreplacement); + fprintf(tfp, "%s", data); + } + fclose(fp); + rewind(tfp); + + // Truncate the original file + if ((fp = fopen(filename, "w+")) == NULL) { + perror(filename); + return -1; + } + // Zero the data buffer once more + memset(data, '\0', BUFSIZ); + // Dump the contents of the temporary file into the original file + while(fgets(data, BUFSIZ, tfp) != NULL) { + fprintf(fp, "%s", data); + } + fclose(fp); + fclose(tfp); + + // Remove temporary file + unlink(tempfile); + return 0; +} + +/** + * Free memory allocated by `prefixes_read` function + * @param entry array of RelocationEntry + */ +void prefixes_free(RelocationEntry **entry) { + if (!entry) { + return; + } + for (int i = 0; entry[i] != NULL; i++) { + if (entry[i]->prefix) free(entry[i]->prefix); + if (entry[i]->path) free(entry[i]->path); + if (entry[i]) free(entry[i]); + } + free(entry); +} + +/** + * Parse a prefix file + * + * The file format is as follows: + * + * ~~~ + * #prefix + * path + * #prefix + * path + * #...N + * ...N + * ~~~ + * @param filename + * @return success=array of RelocationEntry, failure=NULL + */ +RelocationEntry **prefixes_read(const char *filename) { + size_t i = 0; + int record_count = 0; + int parity = 0; + FILE *fp = fopen(filename, "r"); + if (!fp) { + fprintf(SYSERROR); + return NULL; + } + RelocationEntry **entry = NULL; + char line[BUFSIZ]; + memset(line, '\0', BUFSIZ); + + while (fgets(line, BUFSIZ, fp) != NULL) { + if (isempty(line)) { + continue; + } + record_count++; + } + rewind(fp); + + // Initialize the relocation entry array + if (record_count == 0) { + return NULL; + } + + parity = record_count % 2; + if (parity != 0) { + fprintf(stderr, "%s: records are not divisible by 2 (got: %d %% 2 = %d)\n", filename, record_count, parity); + return NULL; + } + record_count /= 2; + + entry = (RelocationEntry **)calloc(record_count + 1, sizeof(RelocationEntry *)); + if (!entry) { + return NULL; + } + for (int i = 0; i < record_count; i++) { + entry[i] = (RelocationEntry *) calloc(1, sizeof(RelocationEntry)); + if (!entry[i]) { + return NULL; + } + } + + int do_prefix = 0; + int do_path = 0; + while (fgets(line, BUFSIZ, fp) != NULL) { + char *wtf = line; + if (isempty(line)) { + continue; + } + if (startswith(line, "#") == 0) { + do_prefix = 1; + } + else { + do_path = 1; + } + + // Allocate a relocation record + if (!entry[i]) { + fclose(fp); + return NULL; + } + + + if (do_prefix) { + // Populate prefix data (a prefix starts with a #) + entry[i]->prefix = (char *) calloc(strlen(line) + 1, sizeof(char)); + if (!entry[i]->prefix) { + fclose(fp); + return NULL; + } + strncpy(entry[i]->prefix, line, strlen(line)); + // Remove prefix delimiter and whitespace + strchrdel(entry[i]->prefix, "#"); + entry[i]->prefix = strip(entry[i]->prefix); + do_prefix = 0; + continue; + } + + else if (do_path) { + // Populate path data + entry[i]->path = (char *) calloc(strlen(line) + 1, sizeof(char)); + if (!entry[i]->path) { + fclose(fp); + return NULL; + } + strncpy(entry[i]->path, line, strlen(line)); + entry[i]->path = strip(entry[i]->path); + do_path = 0; + } + i++; + } + fclose(fp); + return entry; +} + +int relocate(const char *_filename, const char *_oldstr, const char *_newstr) { + int returncode; + Process *proc = NULL; + char *oldstr = strdup(_oldstr); + char *newstr = strdup(_newstr); + char *filename = strdup(_filename); + char cmd[PATH_MAX]; + + memset(cmd, '\0', sizeof(cmd)); + sprintf(cmd, "reloc \"%s\" \"%s\" \"%s\" \"%s\"", oldstr, newstr, filename, filename); + + // sanitize command + strchrdel(cmd, "&;|"); + + shell(&proc, SHELL_OUTPUT, cmd); + if (!proc) { + free(oldstr); + free(newstr); + free(filename); + return -1; + } + + returncode = proc->returncode; + if (returncode != 0 && proc->output) { + fprintf(stderr, proc->output); + } + + shell_free(proc); + free(oldstr); + free(newstr); + free(filename); + return returncode; +} + diff --git a/src/rpath.c b/src/rpath.c new file mode 100644 index 0000000..f499e98 --- /dev/null +++ b/src/rpath.c @@ -0,0 +1,233 @@ +#include "spm.h" + +/** + * Wrapper function to execute `patchelf` with arguments + * @param _filename Path of file to modify + * @param _args Arguments to pass to `patchelf` + * @return success=Process struct, failure=NULL + */ +Process *patchelf(const char *_filename, const char *_args) { + char *filename = strdup(_filename); + char *args = strdup(_args); + Process *proc_info = NULL; + char sh_cmd[PATH_MAX]; + sh_cmd[0] = '\0'; + + strchrdel(args, "&;|"); + strchrdel(filename, "&;|"); + sprintf(sh_cmd, "patchelf %s %s", args, filename); + + shell(&proc_info, SHELL_OUTPUT, sh_cmd); + + free(filename); + free(args); + return proc_info; +} + +/** + * Determine whether a RPATH or RUNPATH is present in file + * + * TODO: Replace with OS-native solution(s) + * + * @param _filename path to executable or library + * @return -1=OS error, 0=has rpath, 1=not found + */ +int has_rpath(const char *_filename) { + int result = 1; // default: not found + + char *filename = strdup(_filename); + if (!filename) { + return -1; + } + + Process *proc_info = NULL; + char *rpath = NULL; + + // sanitize input path + strchrdel(filename, "&;|"); + + Process *pe = patchelf(filename, "--print-rpath"); + strip(pe->output); + if (!isempty(pe->output)) { + result = 0; + } + else { + // something went wrong with patchelf other than + // what we're looking for + result = -1; + } + + free(filename); + shell_free(pe); + return result; +} + +/** + * Returns a RPATH or RUNPATH if one is defined in `_filename` + * + * TODO: Replace with OS-native solution(s) + * + * @param _filename path to executable or library + * @return RPATH string, NULL=error (caller is responsible for freeing memory) + */ +char *rpath_get(const char *_filename) { + if ((has_rpath(_filename)) != 0) { + return NULL; + } + char *filename = strdup(_filename); + if (!filename) { + return NULL; + } + char *path = strdup(filename); + if (!path) { + free(filename); + return NULL; + } + + Process *proc_info = NULL; + char *rpath = NULL; + + // sanitize input path + strchrdel(path, "&;|"); + + Process *pe = patchelf(filename, "--print-rpath"); + rpath = (char *)calloc(strlen(pe->output) + 1, sizeof(char)); + if (!rpath) { + free(filename); + free(path); + shell_free(pe); + return NULL; + } + strncpy(rpath, pe->output, strlen(pe->output)); + strip(rpath); + + free(filename); + free(path); + shell_free(pe); + return rpath; +} + +/** + * Generate a RPATH in the form of: + * + * `$ORIGIN/relative/path/to/lib/from/_filename/path` + * + * @param _filename + * @return + */ +char *rpath_generate(const char *_filename) { + const char *origin = "$ORIGIN/"; + char *filename = realpath(_filename, NULL); + if (!filename) { + return NULL; + } + char *nearest_lib = rpath_autodetect(filename); + if (!nearest_lib) { + return NULL; + } + char *result = (char *)calloc(strlen(origin) + strlen(nearest_lib) + 1, sizeof(char)); + if (!result) { + return NULL; + } + sprintf(result, "%s%s", origin, nearest_lib); + free(filename); + free(nearest_lib); + return result; +} + +int rpath_set(const char *filename, char *_rpath) { + int returncode; + + char *rpath_new = rpath_generate(filename); + if (!rpath_new) { + return -1; + } + + char *rpath_orig = rpath_get(filename); + if (!rpath_orig) { + return -1; + } + + // Are the original and new RPATH identical? + if (strcmp(rpath_orig, rpath_new) == 0) { + free(rpath_new); + free(rpath_orig); + return 0; + } + + Process *pe = patchelf(filename, "--set-rpath"); + if (pe) { + returncode = pe->returncode; + } + shell_free(pe); + free(rpath_new); + free(rpath_orig); + return pe->returncode; +} + +/** + * Using `filename` as a starting point, step backward through the filesystem looking for a lib directory + * @param filename path to file (or a directory) + * @return success=relative path from `filename` to nearest lib directory, failure=NULL + */ +char *rpath_autodetect(const char *filename) { + int has_real_libdir = 0; + char *rootdir = dirname(filename); + char *start = realpath(rootdir, NULL); + char *cwd = realpath(".", NULL); + char *result = NULL; + + // Change directory to the requested root + chdir(start); + + char visit[PATH_MAX]; // Current directory + char tmp[PATH_MAX]; // Current directory with lib directory appended + char relative[PATH_MAX]; // Generated relative path to lib directory + char sep[2]; // Holds the platform's directory separator + + // Initialize character arrays; + visit[0] = '\0'; + tmp[0] = '\0'; + relative[0] = '\0'; + sprintf(sep, "%c", DIRSEP); + + while(1) { + // Where are we in the file system? + getcwd(visit, sizeof(visit)); + // Using the current visit path, check if it contains a lib directory + sprintf(tmp, "%s%clib", visit, DIRSEP); + if (access(tmp, F_OK) == 0) { + strcat(relative, "lib"); + has_real_libdir = 1; // gate for memory allocation below + break; + } + // Reaching the top of the file system indicates our search for a lib directory failed + else if (strcmp(visit, "/") == 0) { + break; + } + + // Assemble relative path step for this location + strcat(relative, ".."); + strcat(relative, sep); + + // Step one directory level back + chdir(".."); + } + + // If we found a viable lib directory, allocate memory for it + if (has_real_libdir) { + result = (char *)calloc(strlen(relative) + 1, sizeof(char)); + if (!result) { + chdir(cwd); // return to calling directory + return NULL; + } + // Copy character array data to the result + strncpy(result, relative, strlen(relative)); + } + + chdir(cwd); // return to calling directory + free(rootdir); + free(cwd); + free(start); + return result; +} diff --git a/src/shell.c b/src/shell.c new file mode 100644 index 0000000..8905c60 --- /dev/null +++ b/src/shell.c @@ -0,0 +1,112 @@ +#include "spm.h" + +/** + * A wrapper for `popen()` that executes non-interactive programs and reports their exit value. + * To redirect stdout and stderr you must do so from within the `fmt` string + * + * ~~~{.c} + * int fd = 1; // stdout + * const char *log_file = "log.txt"; + * Process *proc_info; + * int status; + * + * // Send stderr to stdout + * shell(&proc_info, SHELL_OUTPUT, "foo 2>&1"); + * // Send stdout and stderr to /dev/null + * shell(&proc_info, SHELL_OUTPUT,"bar &>/dev/null"); + * // Send stdout from baz to log.txt + * shell(&proc_info, SHELL_OUTPUT, "baz %d>%s", fd, log_file); + * // Do not record or redirect output from any streams + * shell(&proc_info, SHELL_DEFAULT, "biff"); + * ~~~ + * + * @param Process uninitialized `Process` struct will be populated with process data + * @param options change behavior of the function + * @param fmt shell command to execute (accepts `printf` style formatters) + * @param ... variadic arguments (used by `fmt`) + */ +void shell(Process **proc_info, u_int64_t option, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + + size_t bytes_read = 0; + size_t i = 0; + size_t new_buf_size = 0; + clockid_t clkid = CLOCK_REALTIME; + FILE *proc = NULL; + + (*proc_info) = (Process *)calloc(1, sizeof(Process)); + if (!(*proc_info)) { + fprintf(SYSERROR); + exit(errno); + } + (*proc_info)->returncode = -1; + + // outbuf needs to be an integer type because fgetc returns EOF (> char) + int *outbuf = (int *)calloc(1, sizeof(int)); + if (!outbuf) { + fprintf(SYSERROR); + exit(errno); + } + char *cmd = (char *)calloc(PATH_MAX, sizeof(char)); + if (!cmd) { + fprintf(SYSERROR); + exit(errno); + } + + vsnprintf(cmd, PATH_MAX, fmt, args); + + if (option & SHELL_BENCHMARK) { + if (clock_gettime(clkid, &(*proc_info)->start_time) == -1) { + perror("clock_gettime"); + exit(errno); + } + } + + proc = popen(cmd, "r"); + if (!proc) { + return; + } + + if (option & SHELL_BENCHMARK) { + if (clock_gettime(clkid, &(*proc_info)->stop_time) == -1) { + perror("clock_gettime"); + exit(errno); + } + (*proc_info)->time_elapsed = ((*proc_info)->stop_time.tv_sec - (*proc_info)->start_time.tv_sec) + + ((*proc_info)->stop_time.tv_nsec - (*proc_info)->start_time.tv_nsec) / 1E9; + } + + if (option & SHELL_OUTPUT) { + (*proc_info)->output = (char *)calloc(BUFSIZ, sizeof(char)); + + while ((*outbuf = fgetc(proc)) != EOF) { + + if (i >= BUFSIZ) { + new_buf_size = BUFSIZ + (i + bytes_read); + (*proc_info)->output = (char *)realloc((*proc_info)->output, new_buf_size); + i = 0; + } + if (*outbuf) { + (*proc_info)->output[bytes_read] = (char)*outbuf; + } + bytes_read++; + i++; + } + } + (*proc_info)->returncode = pclose(proc); + va_end(args); + free(outbuf); + free(cmd); +} + +/** + * Free process resources allocated by `shell()` + * @param proc_info `Process` struct + */ +void shell_free(Process *proc_info) { + if (proc_info->output) { + free(proc_info->output); + } + free(proc_info); +} diff --git a/src/spm.c b/src/spm.c new file mode 100644 index 0000000..3607117 --- /dev/null +++ b/src/spm.c @@ -0,0 +1,60 @@ +/** + * SPM - Simple Package Manager + * @file spm.c + */ +#include "spm.h" + +int main(int argc, char *argv[]) { + // not much to see here yet + // at the moment this will all be random tests, for better or worse + // everything here is subject to change without notice + + // Initialize configuration data + init_config_global(); + show_global_config(); + + // Ensure external programs are available for use. + check_runtime_environment(); + + // Install a package to test things out + char *match; + char *package; + const char *root = "/tmp/root"; + if ((match = find_package("python")) == NULL) { + fprintf(SYSERROR); + exit(1); + } + if ((package = basename(match)) == NULL) { + fprintf(stderr, "Unable to derive package name from package path:\n\t-> %s\n", match); + exit(1); + } + + Dependencies *deps = NULL; + dep_init(&deps); + + if (dep_all(&deps, package) < 0) { + dep_free(&deps); + free_global_config(); + exit(1); + } + + printf("%s requires:\n", package); + dep_show(&deps); + + // Install dependencies first + for (int i = 0; i < deps->records; i++) { + if (install(root, deps->list[i]) < 0) { + fprintf(SYSERROR); + exit(errno); + } + } + // Install package + if (install(root, package) < 0) { + fprintf(SYSERROR); + exit(errno); + } + + dep_free(&deps); + free_global_config(); + return 0; +} diff --git a/src/strings.c b/src/strings.c new file mode 100644 index 0000000..ad784d1 --- /dev/null +++ b/src/strings.c @@ -0,0 +1,431 @@ +#include "spm.h" + +/** + * Determine how many times the character `ch` appears in `sptr` string + * @param sptr string to scan + * @param ch character to find + * @return count of characters found + */ +int num_chars(const char *sptr, int ch) { + int result = 0; + for (int i = 0; sptr[i] != '\0'; i++) { + if (sptr[i] == ch) { + result++; + } + } + return result; +} + +/** + * Scan for `pattern` string at the beginning of `sptr` + * + * @param sptr string to scan + * @param pattern string to search for + * @return 0 = success, -1 = failure + */ +int startswith(const char *sptr, const char *pattern) { + for (size_t i = 0; i < strlen(pattern); i++) { + if (sptr[i] != pattern[i]) { + return -1; + } + } + return 0; +} + +/** + * Scan for `pattern` string at the end of `sptr` + * + * @param sptr string to scan + * @param pattern string to search for + * @return 0 = success, -1 = failure + */ +int endswith(const char *sptr, const char *pattern) { + size_t sptr_size = strlen(sptr); + size_t pattern_size = strlen(pattern); + for (size_t s = sptr_size - pattern_size, p = 0 ; s < sptr_size; s++, p++) { + if (sptr[s] != pattern[p]) { + return -1; + } + } + return 0; +} + +/** + * Deletes any characters matching `chars` from `sptr` string + * + * @param sptr string to be modified in-place + * @param chars a string containing characters (e.g. " \n" would delete whitespace and line feeds) + */ +void strchrdel(char *sptr, const char *chars) { + while (*sptr != '\0') { + for (int i = 0; chars[i] != '\0'; i++) { + if (*sptr == chars[i]) { + memmove(sptr, sptr + 1, strlen(sptr)); + } + } + sptr++; + } +} + +/** + * Find the integer offset of the first occurrence of `ch` in `sptr` + * + * ~~~{.c} + * char buffer[255]; + * char string[] = "abc=123"; + * long int separator_offset = strchroff(string, '='); + * for (long int i = 0; i < separator_offset); i++) { + * buffer[i] = string[i]; + * } + * ~~~ + * + * @param sptr string to scan + * @param ch character to find + * @return offset to character in string, or 0 on failure + */ +long int strchroff(const char *sptr, int ch) { + char *orig = strdup(sptr); + char *tmp = orig; + long int result = 0; + while (*tmp != '\0') { + if (*tmp == ch) { + break; + } + tmp++; + } + result = tmp - orig; + free(orig); + + return result; +} + +/** + * This function scans `sptr` from right to left removing any matches to `suffix` + * from the string. + * + * @param sptr string to be modified + * @param suffix string to be removed from `sptr` + */ +void strdelsuffix(char *sptr, const char *suffix) { + if (!sptr || !suffix) { + return; + } + size_t sptr_len = strlen(sptr); + size_t suffix_len = strlen(suffix); + intptr_t target_offset = sptr_len - suffix_len; + + // Prevent access to memory below input string + if (target_offset < 0) { + return; + } + + // Create a pointer to + char *target = sptr + target_offset; + if (!strcmp(target, suffix)) { + // Purge the suffix + memset(target, '\0', suffix_len); + // Recursive call continues removing suffix until it is gone + strip(sptr); + } +} + +/** + * Split a string by every delimiter in `delim` string. + * + * Callee must free memory using `split_free()` + * + * @param sptr string to split + * @param delim characters to split on + * @return success=parts of string, failure=NULL + */ +char** split(char *_sptr, const char* delim) +{ + size_t split_alloc = 0; + // Duplicate the input string and save a copy of the pointer to be freed later + char *orig = strdup(_sptr); + char *sptr = orig; + if (!sptr) { + return NULL; + } + + // Determine how many delimiters are present + for (size_t i = 0; i < strlen(delim); i++) { + split_alloc += num_chars(sptr, delim[i]); + } + // Preallocate enough records based on the number of delimiters + char **result = (char **)calloc(split_alloc + 2, sizeof(char *)); + if (!result) { + free(sptr); + return NULL; + } + + // Separate the string into individual parts and store them in the result array + int i = 0; + char *token = NULL; + while((token = strsep(&sptr, delim)) != NULL) { + result[i] = (char *)calloc(1, sizeof(char) * strlen(token) + 1); + if (!result[i]) { + free(sptr); + return NULL; + } + strncpy(result[i], token, strlen(token)); // copy the string contents into the record + i++; // next record + } + free(orig); + return result; +} + +/** + * Frees memory allocated by `split()` + * @param ptr pointer to array + */ +void split_free(char **ptr) { + for (int i = 0; ptr[i] != NULL; i++) { + free(ptr[i]); + } + free(ptr); +} + +/** + * Extract the string encapsulated by characters listed in `delims` + * + * ~~~{.c} + * char *str = "this is [some data] in a string"; + * char *data = substring_between(string, "[]"); + * // data = "some data"; + * ~~~ + * + * @param sptr string to parse + * @param delims two characters surrounding a string + * @return success=text between delimiters, failure=NULL + */ +char *substring_between(char *sptr, const char *delims) { + // Ensure we have enough delimiters to continue + size_t delim_count = strlen(delims); + if (delim_count != 2) { + return NULL; + } + + // Create pointers to the delimiters + char *start = strpbrk(sptr, &delims[0]); + char *end = strpbrk(sptr, &delims[1]); + + // Ensure the string has both delimiters + if (!start || !end) { + return NULL; + } + + start++; // ignore leading delimiter + + // Get length of the substring + size_t length = end - start; + + char *result = (char *)calloc(length + 1, sizeof(char)); + if (!result) { + return NULL; + } + + // Copy the contents of the substring to the result + char *tmp = result; + while (start != end) { + *tmp = *start; + tmp++; + start++; + } + + return result; +} + +/* + * Helper function for `strsort` + */ +static int _strsort_compare(const void *a, const void *b) { + const char *aa = *(const char**)a; + const char *bb = *(const char**)b; + int result = strcmp(aa, bb); + return result; +} + +/** + * Sort an array of strings alphabetically + * @param arr + */ +void strsort(char **arr) { + size_t arr_size = 0; + + // Determine size of array + for (size_t i = 0; arr[i] != NULL; i++) { + arr_size = i; + } + qsort(arr, arr_size, sizeof(char *), _strsort_compare); +} + +/* + * Helper function for `strsortlen` + */ +static int _strsortlen_asc_compare(const void *a, const void *b) { + const char *aa = *(const char**)a; + const char *bb = *(const char**)b; + size_t len_a = strlen(aa); + size_t len_b = strlen(bb); + return len_a > len_b; +} + +/* + * Helper function for `strsortlen` + */ +static int _strsortlen_dsc_compare(const void *a, const void *b) { + const char *aa = *(const char**)a; + const char *bb = *(const char**)b; + size_t len_a = strlen(aa); + size_t len_b = strlen(bb); + return len_a < len_b; +} +/** + * Sort an array of strings by length + * @param arr + */ +void strsortlen(char **arr, unsigned int sort_mode) { + typedef int (*compar)(const void *, const void *); + + compar fn = _strsortlen_asc_compare; + if (sort_mode != 0) { + fn = _strsortlen_dsc_compare; + } + + size_t arr_size = 0; + + // Determine size of array + for (size_t i = 0; arr[i] != NULL; i++) { + arr_size = i; + } + qsort(arr, arr_size, sizeof(char *), fn); +} + +/** + * Search for string in an array of strings + * @param arr array of strings + * @param str string to search for + * @return yes=0, no=1, failure=-1 + */ +int strstr_array(char **arr, const char *str) { + if (!arr) { + return -1; + } + + for (int i = 0; arr[i] != NULL; i++) { + if (strstr(arr[i], str) != NULL) { + return 0; + } + } + return 1; +} + +/** + * Remove duplicate strings from an array of strings + * @param arr + * @return success=array of unique strings, failure=NULL + */ +char **strdeldup(char **arr) { + if (!arr) { + return NULL; + } + + int records; + // Determine the length of the array + for (records = 0; arr[records] != NULL; records++); + + // Allocate enough memory to store the original array contents + // (It might not have duplicate values, for example) + char **result = (char **)calloc(records + 1, sizeof(char *)); + if (!result) { + return NULL; + } + + int rec = 0; + int i = 0; + while(i < records) { + // Search for value in results + if (strstr_array(result, arr[i]) == 0) { + // value already exists in results so ignore it + i++; + continue; + } + + // Store unique value + result[rec] = (char *)calloc(strlen(arr[i]) + 1, sizeof(char)); + if (!result[rec]) { + free(result); + return NULL; + } + strncpy(result[rec], arr[i], strlen(arr[i])); + i++; + rec++; + } + return result; +} + +/** Remove leading whitespace from a string + * @param sptr pointer to string + * @return pointer to first non-whitespace character in string + */ +char *lstrip(char *sptr) { + char *tmp = sptr; + size_t bytes = 0; + while (isblank(*tmp)) { + bytes++; + tmp++; + } + if (tmp != sptr) { + memmove(sptr, sptr + bytes, strlen(sptr) - bytes); + memset((sptr + strlen(sptr)) - bytes, '\0', bytes); + } + return sptr; +} + +/** + * Remove trailing whitespace from a string + * @param sptr string + * @return truncated string + */ +char *strip(char *sptr) { + if (!strlen(sptr)) { + return sptr; + } + strchrdel(sptr, " \r\n"); + return sptr; +} + +/** + * Determine if a string is empty + * @param sptr pointer to string + * @return 0=not empty, 1=empty + */ +int isempty(char *sptr) { + char *tmp = sptr; + while (*tmp) { + if (!isblank(*tmp)) { + return 0; + } + tmp++; + } + return 1; +} + +/** + * Determine if a string is encapsulated by quotes + * @param sptr pointer to string + * @return 0=not quoted, 1=quoted + */ +int isquoted(char *sptr) { + const char *quotes = "'\""; + char *quote_open = strpbrk(sptr, quotes); + if (!quote_open) { + return 0; + } + char *quote_close = strpbrk(quote_open + 1, quotes); + if (!quote_close) { + return 0; + } + return 1; +} diff --git a/strings.c b/strings.c deleted file mode 100644 index ad784d1..0000000 --- a/strings.c +++ /dev/null @@ -1,431 +0,0 @@ -#include "spm.h" - -/** - * Determine how many times the character `ch` appears in `sptr` string - * @param sptr string to scan - * @param ch character to find - * @return count of characters found - */ -int num_chars(const char *sptr, int ch) { - int result = 0; - for (int i = 0; sptr[i] != '\0'; i++) { - if (sptr[i] == ch) { - result++; - } - } - return result; -} - -/** - * Scan for `pattern` string at the beginning of `sptr` - * - * @param sptr string to scan - * @param pattern string to search for - * @return 0 = success, -1 = failure - */ -int startswith(const char *sptr, const char *pattern) { - for (size_t i = 0; i < strlen(pattern); i++) { - if (sptr[i] != pattern[i]) { - return -1; - } - } - return 0; -} - -/** - * Scan for `pattern` string at the end of `sptr` - * - * @param sptr string to scan - * @param pattern string to search for - * @return 0 = success, -1 = failure - */ -int endswith(const char *sptr, const char *pattern) { - size_t sptr_size = strlen(sptr); - size_t pattern_size = strlen(pattern); - for (size_t s = sptr_size - pattern_size, p = 0 ; s < sptr_size; s++, p++) { - if (sptr[s] != pattern[p]) { - return -1; - } - } - return 0; -} - -/** - * Deletes any characters matching `chars` from `sptr` string - * - * @param sptr string to be modified in-place - * @param chars a string containing characters (e.g. " \n" would delete whitespace and line feeds) - */ -void strchrdel(char *sptr, const char *chars) { - while (*sptr != '\0') { - for (int i = 0; chars[i] != '\0'; i++) { - if (*sptr == chars[i]) { - memmove(sptr, sptr + 1, strlen(sptr)); - } - } - sptr++; - } -} - -/** - * Find the integer offset of the first occurrence of `ch` in `sptr` - * - * ~~~{.c} - * char buffer[255]; - * char string[] = "abc=123"; - * long int separator_offset = strchroff(string, '='); - * for (long int i = 0; i < separator_offset); i++) { - * buffer[i] = string[i]; - * } - * ~~~ - * - * @param sptr string to scan - * @param ch character to find - * @return offset to character in string, or 0 on failure - */ -long int strchroff(const char *sptr, int ch) { - char *orig = strdup(sptr); - char *tmp = orig; - long int result = 0; - while (*tmp != '\0') { - if (*tmp == ch) { - break; - } - tmp++; - } - result = tmp - orig; - free(orig); - - return result; -} - -/** - * This function scans `sptr` from right to left removing any matches to `suffix` - * from the string. - * - * @param sptr string to be modified - * @param suffix string to be removed from `sptr` - */ -void strdelsuffix(char *sptr, const char *suffix) { - if (!sptr || !suffix) { - return; - } - size_t sptr_len = strlen(sptr); - size_t suffix_len = strlen(suffix); - intptr_t target_offset = sptr_len - suffix_len; - - // Prevent access to memory below input string - if (target_offset < 0) { - return; - } - - // Create a pointer to - char *target = sptr + target_offset; - if (!strcmp(target, suffix)) { - // Purge the suffix - memset(target, '\0', suffix_len); - // Recursive call continues removing suffix until it is gone - strip(sptr); - } -} - -/** - * Split a string by every delimiter in `delim` string. - * - * Callee must free memory using `split_free()` - * - * @param sptr string to split - * @param delim characters to split on - * @return success=parts of string, failure=NULL - */ -char** split(char *_sptr, const char* delim) -{ - size_t split_alloc = 0; - // Duplicate the input string and save a copy of the pointer to be freed later - char *orig = strdup(_sptr); - char *sptr = orig; - if (!sptr) { - return NULL; - } - - // Determine how many delimiters are present - for (size_t i = 0; i < strlen(delim); i++) { - split_alloc += num_chars(sptr, delim[i]); - } - // Preallocate enough records based on the number of delimiters - char **result = (char **)calloc(split_alloc + 2, sizeof(char *)); - if (!result) { - free(sptr); - return NULL; - } - - // Separate the string into individual parts and store them in the result array - int i = 0; - char *token = NULL; - while((token = strsep(&sptr, delim)) != NULL) { - result[i] = (char *)calloc(1, sizeof(char) * strlen(token) + 1); - if (!result[i]) { - free(sptr); - return NULL; - } - strncpy(result[i], token, strlen(token)); // copy the string contents into the record - i++; // next record - } - free(orig); - return result; -} - -/** - * Frees memory allocated by `split()` - * @param ptr pointer to array - */ -void split_free(char **ptr) { - for (int i = 0; ptr[i] != NULL; i++) { - free(ptr[i]); - } - free(ptr); -} - -/** - * Extract the string encapsulated by characters listed in `delims` - * - * ~~~{.c} - * char *str = "this is [some data] in a string"; - * char *data = substring_between(string, "[]"); - * // data = "some data"; - * ~~~ - * - * @param sptr string to parse - * @param delims two characters surrounding a string - * @return success=text between delimiters, failure=NULL - */ -char *substring_between(char *sptr, const char *delims) { - // Ensure we have enough delimiters to continue - size_t delim_count = strlen(delims); - if (delim_count != 2) { - return NULL; - } - - // Create pointers to the delimiters - char *start = strpbrk(sptr, &delims[0]); - char *end = strpbrk(sptr, &delims[1]); - - // Ensure the string has both delimiters - if (!start || !end) { - return NULL; - } - - start++; // ignore leading delimiter - - // Get length of the substring - size_t length = end - start; - - char *result = (char *)calloc(length + 1, sizeof(char)); - if (!result) { - return NULL; - } - - // Copy the contents of the substring to the result - char *tmp = result; - while (start != end) { - *tmp = *start; - tmp++; - start++; - } - - return result; -} - -/* - * Helper function for `strsort` - */ -static int _strsort_compare(const void *a, const void *b) { - const char *aa = *(const char**)a; - const char *bb = *(const char**)b; - int result = strcmp(aa, bb); - return result; -} - -/** - * Sort an array of strings alphabetically - * @param arr - */ -void strsort(char **arr) { - size_t arr_size = 0; - - // Determine size of array - for (size_t i = 0; arr[i] != NULL; i++) { - arr_size = i; - } - qsort(arr, arr_size, sizeof(char *), _strsort_compare); -} - -/* - * Helper function for `strsortlen` - */ -static int _strsortlen_asc_compare(const void *a, const void *b) { - const char *aa = *(const char**)a; - const char *bb = *(const char**)b; - size_t len_a = strlen(aa); - size_t len_b = strlen(bb); - return len_a > len_b; -} - -/* - * Helper function for `strsortlen` - */ -static int _strsortlen_dsc_compare(const void *a, const void *b) { - const char *aa = *(const char**)a; - const char *bb = *(const char**)b; - size_t len_a = strlen(aa); - size_t len_b = strlen(bb); - return len_a < len_b; -} -/** - * Sort an array of strings by length - * @param arr - */ -void strsortlen(char **arr, unsigned int sort_mode) { - typedef int (*compar)(const void *, const void *); - - compar fn = _strsortlen_asc_compare; - if (sort_mode != 0) { - fn = _strsortlen_dsc_compare; - } - - size_t arr_size = 0; - - // Determine size of array - for (size_t i = 0; arr[i] != NULL; i++) { - arr_size = i; - } - qsort(arr, arr_size, sizeof(char *), fn); -} - -/** - * Search for string in an array of strings - * @param arr array of strings - * @param str string to search for - * @return yes=0, no=1, failure=-1 - */ -int strstr_array(char **arr, const char *str) { - if (!arr) { - return -1; - } - - for (int i = 0; arr[i] != NULL; i++) { - if (strstr(arr[i], str) != NULL) { - return 0; - } - } - return 1; -} - -/** - * Remove duplicate strings from an array of strings - * @param arr - * @return success=array of unique strings, failure=NULL - */ -char **strdeldup(char **arr) { - if (!arr) { - return NULL; - } - - int records; - // Determine the length of the array - for (records = 0; arr[records] != NULL; records++); - - // Allocate enough memory to store the original array contents - // (It might not have duplicate values, for example) - char **result = (char **)calloc(records + 1, sizeof(char *)); - if (!result) { - return NULL; - } - - int rec = 0; - int i = 0; - while(i < records) { - // Search for value in results - if (strstr_array(result, arr[i]) == 0) { - // value already exists in results so ignore it - i++; - continue; - } - - // Store unique value - result[rec] = (char *)calloc(strlen(arr[i]) + 1, sizeof(char)); - if (!result[rec]) { - free(result); - return NULL; - } - strncpy(result[rec], arr[i], strlen(arr[i])); - i++; - rec++; - } - return result; -} - -/** Remove leading whitespace from a string - * @param sptr pointer to string - * @return pointer to first non-whitespace character in string - */ -char *lstrip(char *sptr) { - char *tmp = sptr; - size_t bytes = 0; - while (isblank(*tmp)) { - bytes++; - tmp++; - } - if (tmp != sptr) { - memmove(sptr, sptr + bytes, strlen(sptr) - bytes); - memset((sptr + strlen(sptr)) - bytes, '\0', bytes); - } - return sptr; -} - -/** - * Remove trailing whitespace from a string - * @param sptr string - * @return truncated string - */ -char *strip(char *sptr) { - if (!strlen(sptr)) { - return sptr; - } - strchrdel(sptr, " \r\n"); - return sptr; -} - -/** - * Determine if a string is empty - * @param sptr pointer to string - * @return 0=not empty, 1=empty - */ -int isempty(char *sptr) { - char *tmp = sptr; - while (*tmp) { - if (!isblank(*tmp)) { - return 0; - } - tmp++; - } - return 1; -} - -/** - * Determine if a string is encapsulated by quotes - * @param sptr pointer to string - * @return 0=not quoted, 1=quoted - */ -int isquoted(char *sptr) { - const char *quotes = "'\""; - char *quote_open = strpbrk(sptr, quotes); - if (!quote_open) { - return 0; - } - char *quote_close = strpbrk(quote_open + 1, quotes); - if (!quote_close) { - return 0; - } - return 1; -} -- cgit