From 2be3d5c5d905bd748b8ce511033065fa5a83a59c Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Wed, 18 Dec 2019 01:14:48 -0500 Subject: Split up functions into different source files --- CMakeLists.txt | 2 +- archive.c | 65 +++ config.c | 80 +-- config_global.c | 186 ++++++ deps.c | 29 + find.c | 154 +++++ fs.c | 264 +++++++++ fstree.c | 101 ---- install.c | 63 ++ relocation.c | 237 ++++++++ rpath.c | 233 ++++++++ shell.c | 112 ++++ spm.c | 1704 +------------------------------------------------------ spm.h | 40 +- strings.c | 431 ++++++++++++++ 15 files changed, 1824 insertions(+), 1877 deletions(-) create mode 100644 archive.c create mode 100644 config_global.c create mode 100644 find.c create mode 100644 fs.c delete mode 100644 fstree.c create mode 100644 install.c create mode 100644 relocation.c create mode 100644 rpath.c create mode 100644 shell.c create mode 100644 strings.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f492572..e41eb88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,5 +7,5 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) check_symbol_exists(strsep string.h HAVE_STRSEP) configure_file(config.h.in config.h) -add_executable(spm spm.c config.c spm.h config.h.in compat.c deps.c fstree.c) +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) diff --git a/archive.c b/archive.c new file mode 100644 index 0000000..19269a8 --- /dev/null +++ b/archive.c @@ -0,0 +1,65 @@ +#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); + 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]; + + 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/config.c b/config.c index 0295be8..93ff673 100644 --- a/config.c +++ b/config.c @@ -3,64 +3,14 @@ */ #include "spm.h" -/// 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 pointer to 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; -} - +/** + * 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)); @@ -162,6 +112,10 @@ ConfigItem **config_read(const char *filename) { 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]); @@ -169,10 +123,12 @@ void config_free(ConfigItem **item) { 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 +/** + * 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; diff --git a/config_global.c b/config_global.c new file mode 100644 index 0000000..b1c6dc2 --- /dev/null +++ b/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/deps.c b/deps.c index 7eb587f..452e551 100644 --- a/deps.c +++ b/deps.c @@ -105,6 +105,35 @@ int dep_solve(Dependencies **deps, const char *filename) { return line_count; } +void dep_all(Dependencies **deps, const char *_package) { + static int next = 0; + char *package = find_package(_package); + char depfile[PATH_MAX]; + char template[PATH_MAX]; + char suffix[PATH_MAX] = "spm_depends_all_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); + if (!tmpdir) { + perror(tmpdir); + exit(errno); + } + tar_extract_file(package, ".SPM_DEPENDS", tmpdir); + sprintf(depfile, "%s%c%s", tmpdir, DIRSEP, ".SPM_DEPENDS"); + + int resolved = dep_solve(deps, depfile); + for (int i = next; i < resolved; i++) { + next++; + if (dep_seen(deps, (*deps)->list[i])) { + dep_all(deps, (*deps)->list[i]); + } + } + + unlink(depfile); + unlink(tmpdir); +} + void dep_show(Dependencies **deps) { if ((*deps) == NULL) { return; diff --git a/find.c b/find.c new file mode 100644 index 0000000..84ded16 --- /dev/null +++ b/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/fs.c b/fs.c new file mode 100644 index 0000000..27bdf2f --- /dev/null +++ b/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/fstree.c b/fstree.c deleted file mode 100644 index 0f5e95a..0000000 --- a/fstree.c +++ /dev/null @@ -1,101 +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); - } -} diff --git a/install.c b/install.c new file mode 100644 index 0000000..95d4b4a --- /dev/null +++ b/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; + } + printf("Installing: %s\n", package); + if (access(destroot, F_OK) != 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 new file mode 100644 index 0000000..13ae799 --- /dev/null +++ b/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/rpath.c b/rpath.c new file mode 100644 index 0000000..f499e98 --- /dev/null +++ b/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/shell.c b/shell.c new file mode 100644 index 0000000..8905c60 --- /dev/null +++ b/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/spm.c b/spm.c index f1d38ac..ec8b7ba 100644 --- a/spm.c +++ b/spm.c @@ -2,1701 +2,7 @@ * SPM - Simple Package Manager * @file spm.c */ - #include "spm.h" -static int VERBOSE_MODE = 0; - -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); -} - -/** - * 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); -} - -/** - * 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); - 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]; - - 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; -} - -/** - * 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; -} - -/** - * 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; -} - -/** - * 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; -} - -/** - * 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 substrdel(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); - } -} - -/** - * 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, 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); -} - -/** - * 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; -} - -/** - * 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 *get_rpath(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 *gen_rpath(const char *_filename) { - const char *origin = "$ORIGIN/"; - char *filename = realpath(_filename, NULL); - if (!filename) { - return NULL; - } - char *nearest_lib = libdir_nearest(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 set_rpath(const char *filename, char *_rpath) { - int returncode; - - char *rpath_new = gen_rpath(filename); - if (!rpath_new) { - return -1; - } - - char *rpath_orig = get_rpath(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; -} - -/** - * Lists a directory (use `fstree` instead if you only want a basic recursive listing)) - * @param dirpath - * @param result - */ -void walkdir(char *dirpath, Dirwalk **result, unsigned int dirs) { - static int i = 0; - static int locked = 0; - size_t dirpath_size = strlen(dirpath); - const size_t initial_records = 2; - - DIR *dp = opendir(dirpath); - if (!dp) { - return; - } - - struct dirent *entry; - int record_count = 0; - - while ((entry = readdir(dp)) != NULL) { - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { - continue; - } - record_count++; - } - rewinddir(dp); - - if (!locked) { - (*result) = (Dirwalk *)reallocarray((*result),1, sizeof(Dirwalk)); - (*result)->paths = (char **)calloc(record_count, sizeof(char *)); - i = 0; - locked++; - } - - //(*result)->paths = (char **) reallocarray((*result)->paths, record_count, sizeof(char *)); - while ((entry = readdir(dp)) != NULL) { - printf("i=%d, dname=%s\n", i, entry->d_name); - char *name = entry->d_name; - - char path[PATH_MAX]; - char sep = DIRSEP; - int path_size = snprintf(path, PATH_MAX, "%s%c%s", dirpath, sep, entry->d_name); - - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { - continue; - } - - (*result)->paths[i] = (char *) calloc((size_t) (path_size + 1), sizeof(char)); - if (entry->d_type == DT_DIR) { - if (dirs) { - strncpy((*result)->paths[i], path, (size_t) path_size); - //i++; - } - dirpath[dirpath_size] = DIRSEP; - strcpy(dirpath + dirpath_size + 1, name); - walkdir(dirpath, result, dirs); - dirpath[dirpath_size] = '\0'; - } - else { - strncpy((*result)->paths[i], path, (size_t) path_size); - } - i++; - } - (*result)->count = i; - (*result)->paths[i] = NULL; - closedir(dp); - if (!strcmp(dirpath, "..") || !strcmp(dirpath, ".")) { - locked = 0; - } -} - -/* - * 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); -} - -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; -} - -int fstrstr(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; -} - -/** - * 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; -} - -/** - * 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; -} - -/** - * Check whether this program will run properly given the current runtime environment - */ -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); - } -} - -/** - * 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; -} - -/** - * 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 *libdir_nearest(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; -} - -/** - * 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; -} - -/** - * Replace all occurrences of `_oldstr` in `_oldbuf` with `_newstr` - * @param _oldbuf - * @param _oldstr - * @param _newstr - * @return - */ -char *replace_text(char *_oldbuf, const char *_oldstr, const char *_newstr) { - int occurrences = 0; - int i = 0; - size_t _oldstr_len = strlen(_oldstr); - size_t _newstr_len = strlen(_newstr); - size_t _oldbuf_len = strlen(_oldbuf); - - // Determine the number of times _oldstr occurs in _oldbuf. - char *tmp = _oldbuf; - while (*tmp) { - if (strstr(tmp, _oldstr) == tmp) { - occurrences++; - // Move pointer past last occurrence - i++; - tmp += _oldstr_len; - continue; - } - i++; - tmp++; - } - - char *result = (char *)calloc(((i + (occurrences * _newstr_len)) + 1), sizeof(char)); - if (!result) { - fprintf(SYSERROR); - return NULL; - } - - // Continuously scan until _oldstr has been completely removed - i = 0; - while (strstr(_oldbuf, _oldstr) != NULL) { - // Search for _oldstr in _oldbuf - if (strstr(_oldbuf, _oldstr) == _oldbuf) { - // Copy replacement string into result buffer - strncpy(&result[i], _newstr, _newstr_len); - i += _newstr_len; - _oldbuf += _oldstr_len; - } - else { - // Write non-matches to result buffer - result[i++] = *_oldbuf++; - } - } - - return result; -} - -/** - * 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(const char *filename, const char *oldstr, const char *newstr) { - int err = 0; - long int file_size = get_file_size(filename); - char *data_orig = (char *)calloc(file_size + 1, sizeof(char)); - FILE *fp = fopen(filename, "r+"); - if (!fp) { - fprintf(SYSERROR); - free(data_orig); - return -1; - } - - // Read the entire file into memory - fread(data_orig, file_size, sizeof(char), fp); - if ((err = ferror(fp)) < 0) { - free(data_orig); - fclose(fp); - return err; - } - // Jump back to the beginning of the file - rewind(fp); - - // Create a new buffer - char *data_new = replace_text(data_orig, oldstr, newstr); - if (!data_new) { - fprintf(SYSERROR); - free(data_orig); - fclose(fp); - return -1; - } - // Update expected file size - file_size = strlen(data_new); - // Write back changes - fwrite(data_new, file_size, sizeof(char), fp); - if ((err = ferror(fp)) < 0) { - free(data_orig); - free(data_new); - fclose(fp); - return err; - } - - free(data_orig); - free(data_new); - fclose(fp); - return 0; -} - -/** - * 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; -} - -void depends_all(Dependencies **deps, const char *_package) { - static int next = 0; - char *package = find_package(_package); - char depfile[PATH_MAX]; - char template[PATH_MAX]; - char suffix[PATH_MAX] = "spm_depends_all_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_file(package, ".SPM_DEPENDS", tmpdir); - sprintf(depfile, "%s%c%s", tmpdir, DIRSEP, ".SPM_DEPENDS"); - - int resolved = dep_solve(deps, depfile); - for (int i = next; i < resolved; i++) { - next++; - if (dep_seen(deps, (*deps)->list[i])) { - depends_all(deps, (*deps)->list[i]); - } - } - - unlink(depfile); - unlink(tmpdir); -} - -int install(const char *destroot, const char *_package) { - char *package = find_package(_package); - if (!package) { - fprintf(SYSERROR); - return -1; - } - printf("Installing: %s\n", package); - if (access(destroot, F_OK) != 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); -} - -/** - * 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; -} - -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"); -} - -/** - * 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; -} - -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; -} int main(int argc, char *argv[]) { // not much to see here yet @@ -1712,15 +18,19 @@ int main(int argc, char *argv[]) { // Install a package to test things out const char *root = "/tmp/root"; - const char *package = "python-pip"; - Dependencies *deps; + const char *package = "python"; + + Dependencies *deps = NULL; dep_init(&deps); - depends_all(&deps, package); + dep_all(&deps, package); + printf("%s requires:\n", package); dep_show(&deps); + // Install dependencies first for (int i = 0; i < deps->records; i++) { install(root, deps->list[i]); } + // Install package install(root, package); dep_free(&deps); diff --git a/spm.h b/spm.h index f8eab43..52b791c 100644 --- a/spm.h +++ b/spm.h @@ -69,12 +69,6 @@ typedef struct { ConfigItem **config; struct utsname sysinfo; } spm_vars; -static spm_vars SPM_GLOBAL; - -typedef struct { - int count; - char **paths; -} Dirwalk; typedef struct { struct timespec start_time, stop_time; @@ -88,49 +82,61 @@ typedef struct { 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); -int rsync(const char *_args, const char *_source, const char *_destination); + +// relocation.c int relocate(const char *filename, const char *_oldstr, const char *_newstr); -int file_replace_text(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); -int file_replace_text(const char *filename, const char *oldstr, const char *newstr); +// 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 substrdel(char *sptr, const char *suffix); +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 fstrstr(const char *filename, const char *pattern); +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 *libdir_nearest(const char *filename); +char *rpath_autodetect(const char *filename); int has_rpath(const char *_filename); -char *get_rpath(const char *_filename); -char *gen_rpath(const char *_filename); -int set_rpath(const char *filename, char *_rpath); +char *rpath_get(const char *_filename); +char *rpath_generate(const char *_filename); +int rpath_set(const char *filename, char *_rpath); -void walkdir(char *dirpath, Dirwalk **result, unsigned int dirs); +// 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); @@ -141,6 +147,7 @@ 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 @@ -162,6 +169,7 @@ 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); +void dep_all(Dependencies **deps, const char *_package); void dep_show(Dependencies **deps); // fstree.c diff --git a/strings.c b/strings.c new file mode 100644 index 0000000..ad784d1 --- /dev/null +++ b/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; +} -- cgit