diff options
author | Joseph Hunkeler <jhunkeler@gmail.com> | 2019-12-18 01:14:48 -0500 |
---|---|---|
committer | Joseph Hunkeler <jhunkeler@gmail.com> | 2019-12-18 01:14:48 -0500 |
commit | 2be3d5c5d905bd748b8ce511033065fa5a83a59c (patch) | |
tree | 740de985535e765c37deb61dd87b03e131c5d0bb | |
parent | fa992c8655f2fe27a97fe0e6768800a356de3744 (diff) | |
download | spmc-2be3d5c5d905bd748b8ce511033065fa5a83a59c.tar.gz |
Split up functions into different source files
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | archive.c | 65 | ||||
-rw-r--r-- | config.c | 80 | ||||
-rw-r--r-- | config_global.c | 186 | ||||
-rw-r--r-- | deps.c | 29 | ||||
-rw-r--r-- | find.c | 154 | ||||
-rw-r--r-- | fs.c | 264 | ||||
-rw-r--r-- | fstree.c | 101 | ||||
-rw-r--r-- | install.c | 63 | ||||
-rw-r--r-- | relocation.c | 237 | ||||
-rw-r--r-- | rpath.c | 233 | ||||
-rw-r--r-- | shell.c | 112 | ||||
-rw-r--r-- | spm.c | 1704 | ||||
-rw-r--r-- | spm.h | 40 | ||||
-rw-r--r-- | strings.c | 431 |
15 files changed, 1824 insertions, 1877 deletions
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; +} + @@ -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"); +} @@ -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; @@ -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; +} @@ -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; +} + @@ -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; +} @@ -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); +} @@ -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); @@ -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; +} |