diff options
Diffstat (limited to 'spm.c')
-rw-r--r-- | spm.c | 1704 |
1 files changed, 7 insertions, 1697 deletions
@@ -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); |