aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2019-12-18 01:14:48 -0500
committerJoseph Hunkeler <jhunkeler@gmail.com>2019-12-18 01:14:48 -0500
commit2be3d5c5d905bd748b8ce511033065fa5a83a59c (patch)
tree740de985535e765c37deb61dd87b03e131c5d0bb
parentfa992c8655f2fe27a97fe0e6768800a356de3744 (diff)
downloadspmc-2be3d5c5d905bd748b8ce511033065fa5a83a59c.tar.gz
Split up functions into different source files
-rw-r--r--CMakeLists.txt2
-rw-r--r--archive.c65
-rw-r--r--config.c80
-rw-r--r--config_global.c186
-rw-r--r--deps.c29
-rw-r--r--find.c154
-rw-r--r--fs.c264
-rw-r--r--fstree.c101
-rw-r--r--install.c63
-rw-r--r--relocation.c237
-rw-r--r--rpath.c233
-rw-r--r--shell.c112
-rw-r--r--spm.c1704
-rw-r--r--spm.h40
-rw-r--r--strings.c431
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;
+}
+
diff --git a/config.c b/config.c
index 0295be8..93ff673 100644
--- a/config.c
+++ b/config.c
@@ -3,64 +3,14 @@
*/
#include "spm.h"
-/// Remove leading whitespace from a string
-/// \param sptr pointer to string
-/// \return pointer to first non-whitespace character in string
-char *lstrip(char *sptr) {
- char *tmp = sptr;
- size_t bytes = 0;
- while (isblank(*tmp)) {
- bytes++;
- tmp++;
- }
- if (tmp != sptr) {
- memmove(sptr, sptr + bytes, strlen(sptr) - bytes);
- memset((sptr + strlen(sptr)) - bytes, '\0', bytes);
- }
- return sptr;
-}
-
-/// Remove trailing whitespace from a string
-/// \param sptr pointer to string
-/// \return truncated string
-char *strip(char *sptr) {
- if (!strlen(sptr)) {
- return sptr;
- }
- strchrdel(sptr, " \r\n");
- return sptr;
-}
-
-/// Determine if a string is empty
-/// \param sptr pointer to string
-/// \return 0=not empty, 1=empty
-int isempty(char *sptr) {
- char *tmp = sptr;
- while (*tmp) {
- if (!isblank(*tmp)) {
- return 0;
- }
- tmp++;
- }
- return 1;
-}
-
-/// Determine if a string is encapsulated by quotes
-/// \param sptr pointer to string
-/// \return 0=not quoted, 1=quoted
-int isquoted(char *sptr) {
- const char *quotes = "'\"";
- char *quote_open = strpbrk(sptr, quotes);
- if (!quote_open) {
- return 0;
- }
- char *quote_close = strpbrk(quote_open + 1, quotes);
- if (!quote_close) {
- return 0;
- }
- return 1;
-}
-
+/**
+ * Parse a basic configuration file
+ *
+ * NOTE: All values are stored as strings. You need to convert non-string values yourself.
+ *
+ * @param filename
+ * @return success=`ConfigItem` array, failure=NULL
+ */
ConfigItem **config_read(const char *filename) {
const char sep = '=';
char *line = (char *)calloc(CONFIG_BUFFER_SIZE, sizeof(char));
@@ -162,6 +112,10 @@ ConfigItem **config_read(const char *filename) {
return config;
}
+/**
+ * Free memory allocated by `config_read`
+ * @param item `ConfigItem` array
+ */
void config_free(ConfigItem **item) {
for (int i = 0; item[i] != NULL; i++) {
free(item[i]);
@@ -169,10 +123,12 @@ void config_free(ConfigItem **item) {
free(item);
}
-/// If the configuration contains `key` return a pointer to that record
-/// \param item pointer to array of config records
-/// \param key search for key in config records
-/// \return success=pointer to record, failure=NULL
+/**
+ * If the configuration contains `key` return a pointer to that record
+ * @param item pointer to array of config records
+ * @param key search for key in config records
+ * @return success=pointer to record, failure=NULL
+ */
ConfigItem *config_get(ConfigItem **item, const char *key) {
if (!item) {
return NULL;
diff --git a/config_global.c b/config_global.c
new file mode 100644
index 0000000..b1c6dc2
--- /dev/null
+++ b/config_global.c
@@ -0,0 +1,186 @@
+#include "spm.h"
+
+char *get_user_conf_dir(void) {
+ char *result = NULL;
+ wordexp_t wexp;
+ wordexp("~/.spm", &wexp, 0);
+ if (wexp.we_wordc != 0) {
+ result = (char *)calloc(strlen(wexp.we_wordv[0]) + 1, sizeof(char));
+ if (!result) {
+ wordfree(&wexp);
+ return NULL;
+ }
+ strncpy(result, wexp.we_wordv[0], strlen(wexp.we_wordv[0]));
+ if (access(result, F_OK) != 0) {
+ mkdirs(result, 0755);
+ }
+ }
+ wordfree(&wexp);
+ return result;
+}
+
+char *get_user_config_file(void) {
+ const char *filename = "spm.conf";
+ char template[PATH_MAX];
+ char *ucd = get_user_conf_dir();
+ if (!ucd) {
+ return NULL;
+ }
+ // Initialize temporary path
+ template[0] = '\0';
+
+ sprintf(template, "%s%c%s", ucd, DIRSEP, filename);
+ if (access(template, F_OK) != 0) {
+ // No configuration exists, so fail
+ return NULL;
+ }
+ free(ucd);
+ // Allocate and return path to configuration file
+ return strdup(template);
+}
+
+char *get_user_tmp_dir(void) {
+ char template[PATH_MAX];
+ char *ucd = get_user_conf_dir();
+ sprintf(template, "%s%ctmp", ucd, DIRSEP);
+
+ if (access(template, F_OK) != 0) {
+ if (mkdirs(template, 0755) != 0) {
+ return NULL;
+ }
+ }
+
+ free(ucd);
+ return strdup(template);
+}
+
+char *get_user_package_dir(void) {
+ char template[PATH_MAX];
+ char *ucd = get_user_conf_dir();
+ sprintf(template, "%s%cpkgs", ucd, DIRSEP);
+
+ if (access(template, F_OK) != 0) {
+ if (mkdirs(template, 0755) != 0) {
+ return NULL;
+ }
+ }
+
+ free(ucd);
+ return strdup(template);
+}
+
+/**
+ * Check whether SPM has access to external programs it needs
+ */
+void check_runtime_environment(void) {
+ int bad_rt = 0;
+ char *required[] = {
+ "patchelf",
+ "rsync",
+ "tar",
+ "bash",
+ "reloc",
+ NULL,
+ };
+ for (int i = 0; required[i] != NULL; i++) {
+ char *result = find_executable(required[i]);
+ if (!result) {
+ fprintf(stderr, "Required program '%s' is not installed\n", required[i]);
+ bad_rt = 1;
+ }
+ free(result);
+ }
+ if (bad_rt) {
+ exit(1);
+ }
+}
+
+
+
+void init_config_global(void) {
+ SPM_GLOBAL.user_config_basedir = NULL;
+ SPM_GLOBAL.user_config_file = NULL;
+ SPM_GLOBAL.package_dir = NULL;
+ SPM_GLOBAL.tmp_dir = NULL;
+ SPM_GLOBAL.config = NULL;
+
+ if (uname(&SPM_GLOBAL.sysinfo) != 0) {
+ fprintf(SYSERROR);
+ exit(1);
+ }
+
+ SPM_GLOBAL.user_config_basedir = get_user_conf_dir();
+ SPM_GLOBAL.user_config_file = get_user_config_file();
+ if (SPM_GLOBAL.user_config_file) {
+ SPM_GLOBAL.config = config_read(SPM_GLOBAL.user_config_file);
+ }
+
+ ConfigItem *item = NULL;
+
+ // Initialize temp directory
+ item = config_get(SPM_GLOBAL.config, "tmp_dir");
+ if (item) {
+ SPM_GLOBAL.tmp_dir = item->value;
+ if (access(SPM_GLOBAL.tmp_dir, F_OK) != 0) {
+ if (mkdirs(SPM_GLOBAL.tmp_dir, 0755) != 0) {
+ fprintf(stderr, "Unable to create global temporary directory: %s\n", SPM_GLOBAL.tmp_dir);
+ fprintf(SYSERROR);
+ exit(1);
+ }
+ }
+ }
+ else {
+ SPM_GLOBAL.tmp_dir = get_user_tmp_dir();
+ }
+
+ // Initialize package directory
+ item = config_get(SPM_GLOBAL.config, "package_dir");
+ if (item) {
+ SPM_GLOBAL.package_dir = item->value;
+ if (access(SPM_GLOBAL.package_dir, F_OK) != 0) {
+ if (mkdirs(SPM_GLOBAL.package_dir, 0755) != 0) {
+ fprintf(stderr, "Unable to create global package directory: %s\n", SPM_GLOBAL.package_dir);
+ fprintf(SYSERROR);
+ exit(1);
+ }
+ }
+ }
+ else {
+ SPM_GLOBAL.package_dir = get_user_package_dir();
+ }
+}
+
+void free_global_config(void) {
+ if (SPM_GLOBAL.package_dir) {
+ free(SPM_GLOBAL.package_dir);
+ }
+ if (SPM_GLOBAL.tmp_dir) {
+ free(SPM_GLOBAL.tmp_dir);
+ }
+ if (SPM_GLOBAL.user_config_basedir) {
+ free(SPM_GLOBAL.user_config_basedir);
+ }
+ if (SPM_GLOBAL.user_config_file) {
+ free(SPM_GLOBAL.user_config_file);
+ }
+ if (SPM_GLOBAL.config) {
+ config_free(SPM_GLOBAL.config);
+ }
+}
+
+void show_global_config(void) {
+ printf("#---------------------------\n");
+ printf("#---- SPM CONFIGURATION ----\n");
+ printf("#---------------------------\n");
+ printf("# base dir: %s\n", SPM_GLOBAL.user_config_basedir ? SPM_GLOBAL.user_config_basedir : "none (check write permission on home directory)");
+ printf("# config file: %s\n", SPM_GLOBAL.user_config_file ? SPM_GLOBAL.user_config_file : "none");
+ if (SPM_GLOBAL.user_config_file) {
+ printf("# config file contents:\n");
+ for (int i = 0; SPM_GLOBAL.config[i] != NULL; i++) {
+ printf("# -> %s: %s\n", SPM_GLOBAL.config[i]->key, SPM_GLOBAL.config[i]->value);
+ }
+ }
+ printf("# package storage: %s\n", SPM_GLOBAL.package_dir);
+ printf("# temp storage: %s\n", SPM_GLOBAL.tmp_dir);
+ printf("\n");
+}
diff --git a/deps.c b/deps.c
index 7eb587f..452e551 100644
--- a/deps.c
+++ b/deps.c
@@ -105,6 +105,35 @@ int dep_solve(Dependencies **deps, const char *filename) {
return line_count;
}
+void dep_all(Dependencies **deps, const char *_package) {
+ static int next = 0;
+ char *package = find_package(_package);
+ char depfile[PATH_MAX];
+ char template[PATH_MAX];
+ char suffix[PATH_MAX] = "spm_depends_all_XXXXXX";
+ sprintf(template, "%s%c%s", TMP_DIR, DIRSEP, suffix);
+
+ // Create a new temporary directory and extract the requested package into it
+ char *tmpdir = mkdtemp(template);
+ if (!tmpdir) {
+ perror(tmpdir);
+ exit(errno);
+ }
+ tar_extract_file(package, ".SPM_DEPENDS", tmpdir);
+ sprintf(depfile, "%s%c%s", tmpdir, DIRSEP, ".SPM_DEPENDS");
+
+ int resolved = dep_solve(deps, depfile);
+ for (int i = next; i < resolved; i++) {
+ next++;
+ if (dep_seen(deps, (*deps)->list[i])) {
+ dep_all(deps, (*deps)->list[i]);
+ }
+ }
+
+ unlink(depfile);
+ unlink(tmpdir);
+}
+
void dep_show(Dependencies **deps) {
if ((*deps) == NULL) {
return;
diff --git a/find.c b/find.c
new file mode 100644
index 0000000..84ded16
--- /dev/null
+++ b/find.c
@@ -0,0 +1,154 @@
+#include "spm.h"
+
+/**
+ * glob callback function
+ * @param epath path to file that generated the error condition
+ * @param eerrno the error condition
+ * @return the error condition
+ */
+int errglob(const char *epath, int eerrno) {
+ fprintf(stderr, "glob matching error: %s (%d)", epath, eerrno);
+ return eerrno;
+}
+
+/**
+ * Scan a directory for a file by name, or by wildcard
+ *
+ * @param root directory path to scan
+ * @param filename file to find (wildcards accepted)
+ * @return success=path to file, failure=NULL
+ */
+char *find_file(const char *root, const char *filename) {
+ glob_t results;
+ int glob_flags = 0;
+ int match = 0;
+ char *rootpath = NULL;
+ char *path = NULL;
+
+ // GUARD
+ if (!root || !filename || strstr(filename, "..") || strstr(filename, "./")) {
+ return NULL;
+ }
+
+ if (!(path = (char *)calloc(PATH_MAX + 1, sizeof(char)))) {
+ fprintf(SYSERROR);
+ exit(errno);
+ }
+
+ if (!(rootpath = realpath(root, NULL))) {
+ return NULL;
+ }
+
+ strcat(path, rootpath);
+ strcat(path, "/");
+ strcat(path, filename);
+
+ // Save a little time if the file exists
+ if (access(path, F_OK) != -1) {
+ return path;
+ }
+
+ // Inject wildcard
+ strcat(path, "*");
+ // Search for the file
+ match = glob(path, glob_flags, errglob, &results);
+
+ if (match != 0) {
+ // report critical errors except GLOB_NOMATCH
+ if (match == GLOB_NOSPACE || match == GLOB_ABORTED) {
+ fprintf(SYSERROR);
+ }
+ return NULL;
+ }
+
+ // Resize path to the length of the first match
+ char *want = results.gl_pathv[0];
+ if (!(path = (char *)realloc(path, sizeof(char) * (strlen(want) + 1)))) {
+ fprintf(SYSERROR);
+ exit(errno);
+ }
+
+ // Replace path string with wanted path string
+ strncpy(path, want, strlen(want));
+
+ free(rootpath);
+ globfree(&results);
+ return path;
+}
+
+/**
+ * Scan the package directory for a package by name
+ * @param filename file to find
+ * @return success=path to file, failure=NULL
+ */
+char *find_package(const char *filename) {
+ return find_file(PKG_DIR, filename);
+}
+
+/**
+ * Determine whether `pattern` is present within a file
+ * @param filename
+ * @param pattern
+ * @return 0=found, 1=not found, -1=OS error
+ */
+int find_in_file(const char *filename, const char *pattern) {
+ int result = 1; // default "not found"
+
+ FILE *fp = fopen(filename, "rb");
+ if (!fp) {
+ return -1;
+ }
+
+ long int file_len = get_file_size(filename);
+ if (file_len < 0) {
+ return -1;
+ }
+ char *buffer = (char *)calloc((size_t) file_len, sizeof(char));
+ if (!buffer) {
+ return -1;
+ }
+ size_t pattern_len = strlen(pattern);
+
+ fread(buffer, (size_t) file_len, sizeof(char), fp);
+ fclose(fp);
+
+ for (size_t i = 0; i < file_len; i++) {
+ if (!memcmp(&buffer[i], pattern, pattern_len)) {
+ result = 0; // found
+ break;
+ }
+ }
+ free(buffer);
+ return result;
+}
+
+/**
+ * Get the full path of a shell command
+ * @param program
+ * @return success=absolute path to program, failure=NULL
+ */
+char *find_executable(const char *program) {
+ int found = 0;
+ char *result = NULL;
+ char *env_path = NULL;
+ env_path = getenv("PATH");
+ if (!env_path) {
+ return NULL;
+ }
+ char **search_paths = split(env_path, ":");
+
+ char buf[PATH_MAX];
+ for (int i = 0; search_paths[i] != NULL; i++) {
+ sprintf(buf, "%s%c%s", search_paths[i], DIRSEP, program);
+ if (access(buf, F_OK | X_OK) == 0) {
+ found = 1;
+ break;
+ }
+ memset(buf, '\0', sizeof(buf));
+ }
+ if (found) {
+ result = strdup(buf);
+ }
+ split_free(search_paths);
+ return result;
+}
diff --git a/fs.c b/fs.c
new file mode 100644
index 0000000..27bdf2f
--- /dev/null
+++ b/fs.c
@@ -0,0 +1,264 @@
+#include "spm.h"
+
+FSTree *fstree(const char *_path) {
+ FTS *parent = NULL;
+ FTSENT *node = NULL;
+ FSTree *fsdata = NULL;
+ char *path = realpath(_path, NULL);
+ char *root[2] = { path, NULL };
+
+ size_t dirs_size = 2;
+ size_t dirs_records = 0;
+ size_t files_size = 2;
+ size_t files_records = 0;
+
+ fsdata = (FSTree *)calloc(1, sizeof(FSTree));
+ fsdata->root= (char *)calloc(strlen(path) + 1, sizeof(char));
+ fsdata->dirs = (char **)calloc(dirs_size, sizeof(char *));
+ fsdata->files= (char **)calloc(files_size, sizeof(char *));
+
+ strncpy(fsdata->root, path, strlen(path));
+ parent = fts_open(root, FTS_PHYSICAL | FTS_NOCHDIR, &_fstree_compare);
+
+ if (parent != NULL) {
+ while ((node = fts_read(parent)) != NULL) {
+ switch (node->fts_info) {
+ case FTS_D:
+ if (strcmp(node->fts_path, "..") == 0 || strcmp(node->fts_path, ".") == 0) {
+ continue;
+ }
+ fsdata->dirs = (char **)realloc(fsdata->dirs, sizeof(char*) * dirs_size);
+ fsdata->dirs[dirs_size - 1] = NULL;
+ fsdata->dirs[dirs_records] = (char *)calloc(strlen(node->fts_path) + 1, sizeof(char));
+ strncpy(fsdata->dirs[dirs_records], node->fts_path, strlen(node->fts_path));
+ dirs_size++;
+ dirs_records++;
+ break;
+ case FTS_F:
+ case FTS_SL:
+ fsdata->files = (char **)realloc(fsdata->files, sizeof(char*) * files_size);
+ fsdata->files[files_size - 1] = NULL;
+ fsdata->files[files_records] = (char *)calloc(strlen(node->fts_path) + 1, sizeof(char));
+ strncpy(fsdata->files[files_records], node->fts_path, strlen(node->fts_path));
+ files_size++;
+ files_records++;
+ break;
+ default:
+ break;
+ }
+ }
+ fts_close(parent);
+ }
+ fsdata->dirs_length = dirs_records;
+ fsdata->files_length = files_records;
+ free(path);
+ return fsdata;
+}
+
+int _fstree_compare(const FTSENT **one, const FTSENT **two) {
+ return (strcmp((*one)->fts_name, (*two)->fts_name));
+}
+
+int rmdirs(const char *_path) {
+ if (access(_path, F_OK) != 0) {
+ return -1;
+ }
+
+ FSTree *data = fstree(_path);
+ if (data->files) {
+ for (int i = 0; data->files[i] != NULL; i++) {
+ remove(data->files[i]);
+ }
+ }
+ if (data->dirs) {
+ for (int i = data->dirs_length - 1; i != 0; i--) {
+ remove(data->dirs[i]);
+ }
+ }
+ remove(data->root);
+
+ fstree_free(data);
+ return 0;
+}
+
+void fstree_free(FSTree *fsdata) {
+ if (fsdata != NULL) {
+ if (fsdata->root != NULL) {
+ free(fsdata->root);
+ }
+ if (fsdata->files != NULL) {
+ for (int i = 0; fsdata->files[i] != NULL; i++) {
+ free(fsdata->files[i]);
+ }
+ }
+ if (fsdata->dirs != NULL) {
+ for (int i = 0; fsdata->dirs[i] != NULL; i++) {
+ free(fsdata->dirs[i]);
+ }
+ }
+ free(fsdata);
+ }
+}
+
+/**
+ * Converts Win32 path to Unix path, and vice versa
+ * - On UNIX, Win32 paths will be converted UNIX
+ * - On Win32, UNIX paths will be converted to Win32
+ *
+ * This function is platform dependent.
+ *
+ * @param path a system path
+ * @return string (caller is responsible for `free`ing memory)
+ */
+char *normpath(const char *path) {
+ char *result = strdup(path);
+ char *tmp = result;
+
+ while (*tmp) {
+ if (*tmp == NOT_DIRSEP) {
+ *tmp = DIRSEP;
+ tmp++;
+ continue;
+ }
+ tmp++;
+ }
+ return result;
+}
+
+/**
+ * Strip file name from directory
+ * Note: Caller is responsible for freeing memory
+ *
+ * @param _path
+ * @return success=path to directory, failure=NULL
+ */
+char *dirname(const char *_path) {
+ char *path = strdup(_path);
+ char *last = strrchr(path, DIRSEP);
+ if (!last) {
+ return NULL;
+ }
+ // Step backward, stopping on the first non-separator
+ // This ensures strings like "/usr//////" are converted to "/usr", but...
+ // it will do nothing to fix up a path like "/usr//////bin/bash
+ char *lookback = last;
+ while (*(lookback - 1) == DIRSEP) {
+ lookback--;
+ }
+
+ *lookback = '\0';
+ return path;
+}
+
+/**
+ * Strip directory from file name
+ * Note: Caller is responsible for freeing memory
+ *
+ * @param _path
+ * @return success=file name, failure=NULL
+ */
+char *basename(char *path) {
+ char *result = NULL;
+ char *last = strrchr(path, DIRSEP);
+ if (!last) {
+ return NULL;
+ }
+
+ // Perform a lookahead ensuring the string is valid beyond the last separator
+ if ((last + 1) != NULL) {
+ result = last + 1;
+ }
+
+ return result;
+}
+
+/**
+ * Basic rsync wrapper for copying files
+ * @param _args arguments to pass to rsync (set to `NULL` for default options)
+ * @param _source source file or directory
+ * @param _destination destination file or directory
+ * @return success=0, failure=-1
+ */
+int rsync(const char *_args, const char *_source, const char *_destination) {
+ int returncode;
+ Process *proc = NULL;
+ char *args = NULL;
+ if (_args) {
+ args = strdup(_args);
+ }
+ char *source = strdup(_source);
+ char *destination = strdup(_destination);
+ char cmd[PATH_MAX];
+ char args_combined[PATH_MAX];
+
+ memset(cmd, '\0', sizeof(cmd));
+ memset(args_combined, '\0', sizeof(args_combined));
+ strcpy(args_combined, "--archive --hard-links ");
+ if (args) {
+ strcat(args_combined, _args);
+ }
+
+ sprintf(cmd, "rsync %s \"%s\" \"%s\"", args_combined, source, destination);
+ // sanitize command
+ strchrdel(cmd, "&;|");
+ shell(&proc, SHELL_OUTPUT, cmd);
+ if (!proc) {
+ if (args) {
+ free(args);
+ }
+ free(source);
+ free(destination);
+ return -1;
+ }
+
+ returncode = proc->returncode;
+ if (returncode != 0 && proc->output) {
+ fprintf(stderr, proc->output);
+ }
+ shell_free(proc);
+
+ if (args) {
+ free(args);
+ }
+ free(source);
+ free(destination);
+ return returncode;
+}
+
+long int get_file_size(const char *filename) {
+ long int result = 0;
+ FILE *fp = fopen(filename, "rb");
+ if (!fp) {
+ return -1;
+ }
+ fseek(fp, 0, SEEK_END);
+ result = ftell(fp);
+ fclose(fp);
+ return result;
+}
+
+/**
+ * Attempt to create a directory (or directories)
+ * @param _path A path to create
+ * @param mode UNIX permissions (octal)
+ * @return success=0, failure=-1 (+ errno will be set)
+ */
+int mkdirs(const char *_path, mode_t mode) {
+ int result = 0;
+ char *path = normpath(_path);
+ char tmp[PATH_MAX];
+ tmp[0] = '\0';
+
+ char sep[2];
+ sprintf(sep, "%c", DIRSEP);
+ char **parts = split(path, sep);
+ for (int i = 0; parts[i] != NULL; i++) {
+ strcat(tmp, parts[i]);
+ strcat(tmp, sep);
+ if (access(tmp, F_OK) != 0) {
+ result = mkdir(tmp, mode);
+ }
+ }
+ split_free(parts);
+ return result;
+}
diff --git a/fstree.c b/fstree.c
deleted file mode 100644
index 0f5e95a..0000000
--- a/fstree.c
+++ /dev/null
@@ -1,101 +0,0 @@
-#include "spm.h"
-
-FSTree *fstree(const char *_path) {
- FTS *parent = NULL;
- FTSENT *node = NULL;
- FSTree *fsdata = NULL;
- char *path = realpath(_path, NULL);
- char *root[2] = { path, NULL };
-
- size_t dirs_size = 2;
- size_t dirs_records = 0;
- size_t files_size = 2;
- size_t files_records = 0;
-
- fsdata = (FSTree *)calloc(1, sizeof(FSTree));
- fsdata->root= (char *)calloc(strlen(path) + 1, sizeof(char));
- fsdata->dirs = (char **)calloc(dirs_size, sizeof(char *));
- fsdata->files= (char **)calloc(files_size, sizeof(char *));
-
- strncpy(fsdata->root, path, strlen(path));
- parent = fts_open(root, FTS_PHYSICAL | FTS_NOCHDIR, &_fstree_compare);
-
- if (parent != NULL) {
- while ((node = fts_read(parent)) != NULL) {
- switch (node->fts_info) {
- case FTS_D:
- if (strcmp(node->fts_path, "..") == 0 || strcmp(node->fts_path, ".") == 0) {
- continue;
- }
- fsdata->dirs = (char **)realloc(fsdata->dirs, sizeof(char*) * dirs_size);
- fsdata->dirs[dirs_size - 1] = NULL;
- fsdata->dirs[dirs_records] = (char *)calloc(strlen(node->fts_path) + 1, sizeof(char));
- strncpy(fsdata->dirs[dirs_records], node->fts_path, strlen(node->fts_path));
- dirs_size++;
- dirs_records++;
- break;
- case FTS_F:
- case FTS_SL:
- fsdata->files = (char **)realloc(fsdata->files, sizeof(char*) * files_size);
- fsdata->files[files_size - 1] = NULL;
- fsdata->files[files_records] = (char *)calloc(strlen(node->fts_path) + 1, sizeof(char));
- strncpy(fsdata->files[files_records], node->fts_path, strlen(node->fts_path));
- files_size++;
- files_records++;
- break;
- default:
- break;
- }
- }
- fts_close(parent);
- }
- fsdata->dirs_length = dirs_records;
- fsdata->files_length = files_records;
- free(path);
- return fsdata;
-}
-
-int _fstree_compare(const FTSENT **one, const FTSENT **two) {
- return (strcmp((*one)->fts_name, (*two)->fts_name));
-}
-
-int rmdirs(const char *_path) {
- if (access(_path, F_OK) != 0) {
- return -1;
- }
-
- FSTree *data = fstree(_path);
- if (data->files) {
- for (int i = 0; data->files[i] != NULL; i++) {
- remove(data->files[i]);
- }
- }
- if (data->dirs) {
- for (int i = data->dirs_length - 1; i != 0; i--) {
- remove(data->dirs[i]);
- }
- }
- remove(data->root);
-
- fstree_free(data);
- return 0;
-}
-
-void fstree_free(FSTree *fsdata) {
- if (fsdata != NULL) {
- if (fsdata->root != NULL) {
- free(fsdata->root);
- }
- if (fsdata->files != NULL) {
- for (int i = 0; fsdata->files[i] != NULL; i++) {
- free(fsdata->files[i]);
- }
- }
- if (fsdata->dirs != NULL) {
- for (int i = 0; fsdata->dirs[i] != NULL; i++) {
- free(fsdata->dirs[i]);
- }
- }
- free(fsdata);
- }
-}
diff --git a/install.c b/install.c
new file mode 100644
index 0000000..95d4b4a
--- /dev/null
+++ b/install.c
@@ -0,0 +1,63 @@
+#include "spm.h"
+
+int install(const char *destroot, const char *_package) {
+ char *package = find_package(_package);
+ if (!package) {
+ fprintf(SYSERROR);
+ return -1;
+ }
+ printf("Installing: %s\n", package);
+ if (access(destroot, F_OK) != 0) {
+ if (mkdirs(destroot, 0755) != 0) {
+ fprintf(SYSERROR);
+ return -2;
+ }
+ }
+
+ char cwd[PATH_MAX];
+ char source[PATH_MAX];
+ char template[PATH_MAX];
+ char suffix[PATH_MAX] = "spm_destroot_XXXXXX";
+ sprintf(template, "%s%c%s", TMP_DIR, DIRSEP, suffix);
+
+ // Create a new temporary directory and extract the requested package into it
+ char *tmpdir = mkdtemp(template);
+ tar_extract_archive(package, tmpdir);
+
+ getcwd(cwd, sizeof(cwd));
+
+ RelocationEntry **b_record = NULL;
+ RelocationEntry **t_record = NULL;
+ chdir(tmpdir);
+ {
+ // Rewrite binary prefixes
+ RelocationEntry **b_record = prefixes_read(".SPM_PREFIX_BIN");
+ if (b_record) {
+ for (int i = 0; b_record[i] != NULL; i++) {
+ relocate(b_record[i]->path, b_record[i]->prefix, destroot);
+ }
+ }
+
+ // Rewrite text prefixes
+ RelocationEntry **t_record = prefixes_read(".SPM_PREFIX_TEXT");
+ if (t_record) {
+ for (int i = 0; t_record[i] != NULL; i++) {
+ file_replace_text(t_record[i]->path, t_record[i]->prefix, destroot);
+ }
+ }
+
+ prefixes_free(b_record);
+ prefixes_free(t_record);
+ }
+ chdir(cwd);
+
+
+ // Append a trailing slash to tmpdir to direct rsync to copy files, not the directory, into destroot
+ sprintf(source, "%s%c", tmpdir, DIRSEP);
+ if (rsync(NULL, source, destroot) != 0) {
+ exit(1);
+ }
+ rmdirs(tmpdir);
+
+ free(package);
+}
diff --git a/relocation.c b/relocation.c
new file mode 100644
index 0000000..13ae799
--- /dev/null
+++ b/relocation.c
@@ -0,0 +1,237 @@
+#include "spm.h"
+
+int replace_text(char *data, const char *spattern, const char *sreplacement) {
+ char *tmp = data;
+ size_t data_len = strlen(data);
+ size_t spattern_len = strlen(spattern);
+ size_t sreplacement_len = strlen(sreplacement);
+
+ if (sreplacement_len > spattern_len) {
+ fprintf(stderr, "replacement string too long\n");
+ return -1;
+ }
+
+ while (*tmp != '\0') {
+ if (strncmp(tmp, spattern, spattern_len) == 0) {
+ memmove(tmp, sreplacement, sreplacement_len);
+ memmove(tmp + sreplacement_len, tmp + spattern_len, data_len - spattern_len);
+ }
+ tmp++;
+ }
+ return 0;
+}
+
+/**
+ * Replace all occurrences of `oldstr` in file `path` with `newstr`
+ * @param filename
+ * @param oldstr
+ * @param newstr
+ * @return success=0, failure=-1, or value of `ferror()`
+ */
+int file_replace_text(char *filename, const char *spattern, const char *sreplacement) {
+ char data[BUFSIZ];
+ char tempfile[PATH_MAX];
+ FILE *fp = NULL;
+ if ((fp = fopen(filename, "r")) == NULL) {
+ perror(filename);
+ return -1;
+ }
+
+ sprintf(tempfile, "%s.spmfrt", filename);
+ FILE *tfp = NULL;
+ if ((tfp = fopen(tempfile, "w+")) == NULL) {
+ perror(tempfile);
+ return -1;
+ }
+
+ // Zero the data buffer
+ memset(data, '\0', BUFSIZ);
+ while(fgets(data, BUFSIZ, fp) != NULL) {
+ replace_text(data, spattern, sreplacement);
+ fprintf(tfp, "%s", data);
+ }
+ fclose(fp);
+ rewind(tfp);
+
+ // Truncate the original file
+ if ((fp = fopen(filename, "w+")) == NULL) {
+ perror(filename);
+ return -1;
+ }
+ // Zero the data buffer once more
+ memset(data, '\0', BUFSIZ);
+ // Dump the contents of the temporary file into the original file
+ while(fgets(data, BUFSIZ, tfp) != NULL) {
+ fprintf(fp, "%s", data);
+ }
+ fclose(fp);
+ fclose(tfp);
+
+ // Remove temporary file
+ unlink(tempfile);
+ return 0;
+}
+
+/**
+ * Free memory allocated by `prefixes_read` function
+ * @param entry array of RelocationEntry
+ */
+void prefixes_free(RelocationEntry **entry) {
+ if (!entry) {
+ return;
+ }
+ for (int i = 0; entry[i] != NULL; i++) {
+ if (entry[i]->prefix) free(entry[i]->prefix);
+ if (entry[i]->path) free(entry[i]->path);
+ if (entry[i]) free(entry[i]);
+ }
+ free(entry);
+}
+
+/**
+ * Parse a prefix file
+ *
+ * The file format is as follows:
+ *
+ * ~~~
+ * #prefix
+ * path
+ * #prefix
+ * path
+ * #...N
+ * ...N
+ * ~~~
+ * @param filename
+ * @return success=array of RelocationEntry, failure=NULL
+ */
+RelocationEntry **prefixes_read(const char *filename) {
+ size_t i = 0;
+ int record_count = 0;
+ int parity = 0;
+ FILE *fp = fopen(filename, "r");
+ if (!fp) {
+ fprintf(SYSERROR);
+ return NULL;
+ }
+ RelocationEntry **entry = NULL;
+ char line[BUFSIZ];
+ memset(line, '\0', BUFSIZ);
+
+ while (fgets(line, BUFSIZ, fp) != NULL) {
+ if (isempty(line)) {
+ continue;
+ }
+ record_count++;
+ }
+ rewind(fp);
+
+ // Initialize the relocation entry array
+ if (record_count == 0) {
+ return NULL;
+ }
+
+ parity = record_count % 2;
+ if (parity != 0) {
+ fprintf(stderr, "%s: records are not divisible by 2 (got: %d %% 2 = %d)\n", filename, record_count, parity);
+ return NULL;
+ }
+ record_count /= 2;
+
+ entry = (RelocationEntry **)calloc(record_count + 1, sizeof(RelocationEntry *));
+ if (!entry) {
+ return NULL;
+ }
+ for (int i = 0; i < record_count; i++) {
+ entry[i] = (RelocationEntry *) calloc(1, sizeof(RelocationEntry));
+ if (!entry[i]) {
+ return NULL;
+ }
+ }
+
+ int do_prefix = 0;
+ int do_path = 0;
+ while (fgets(line, BUFSIZ, fp) != NULL) {
+ char *wtf = line;
+ if (isempty(line)) {
+ continue;
+ }
+ if (startswith(line, "#") == 0) {
+ do_prefix = 1;
+ }
+ else {
+ do_path = 1;
+ }
+
+ // Allocate a relocation record
+ if (!entry[i]) {
+ fclose(fp);
+ return NULL;
+ }
+
+
+ if (do_prefix) {
+ // Populate prefix data (a prefix starts with a #)
+ entry[i]->prefix = (char *) calloc(strlen(line) + 1, sizeof(char));
+ if (!entry[i]->prefix) {
+ fclose(fp);
+ return NULL;
+ }
+ strncpy(entry[i]->prefix, line, strlen(line));
+ // Remove prefix delimiter and whitespace
+ strchrdel(entry[i]->prefix, "#");
+ entry[i]->prefix = strip(entry[i]->prefix);
+ do_prefix = 0;
+ continue;
+ }
+
+ else if (do_path) {
+ // Populate path data
+ entry[i]->path = (char *) calloc(strlen(line) + 1, sizeof(char));
+ if (!entry[i]->path) {
+ fclose(fp);
+ return NULL;
+ }
+ strncpy(entry[i]->path, line, strlen(line));
+ entry[i]->path = strip(entry[i]->path);
+ do_path = 0;
+ }
+ i++;
+ }
+ fclose(fp);
+ return entry;
+}
+
+int relocate(const char *_filename, const char *_oldstr, const char *_newstr) {
+ int returncode;
+ Process *proc = NULL;
+ char *oldstr = strdup(_oldstr);
+ char *newstr = strdup(_newstr);
+ char *filename = strdup(_filename);
+ char cmd[PATH_MAX];
+
+ memset(cmd, '\0', sizeof(cmd));
+ sprintf(cmd, "reloc \"%s\" \"%s\" \"%s\" \"%s\"", oldstr, newstr, filename, filename);
+
+ // sanitize command
+ strchrdel(cmd, "&;|");
+
+ shell(&proc, SHELL_OUTPUT, cmd);
+ if (!proc) {
+ free(oldstr);
+ free(newstr);
+ free(filename);
+ return -1;
+ }
+
+ returncode = proc->returncode;
+ if (returncode != 0 && proc->output) {
+ fprintf(stderr, proc->output);
+ }
+
+ shell_free(proc);
+ free(oldstr);
+ free(newstr);
+ free(filename);
+ return returncode;
+}
+
diff --git a/rpath.c b/rpath.c
new file mode 100644
index 0000000..f499e98
--- /dev/null
+++ b/rpath.c
@@ -0,0 +1,233 @@
+#include "spm.h"
+
+/**
+ * Wrapper function to execute `patchelf` with arguments
+ * @param _filename Path of file to modify
+ * @param _args Arguments to pass to `patchelf`
+ * @return success=Process struct, failure=NULL
+ */
+Process *patchelf(const char *_filename, const char *_args) {
+ char *filename = strdup(_filename);
+ char *args = strdup(_args);
+ Process *proc_info = NULL;
+ char sh_cmd[PATH_MAX];
+ sh_cmd[0] = '\0';
+
+ strchrdel(args, "&;|");
+ strchrdel(filename, "&;|");
+ sprintf(sh_cmd, "patchelf %s %s", args, filename);
+
+ shell(&proc_info, SHELL_OUTPUT, sh_cmd);
+
+ free(filename);
+ free(args);
+ return proc_info;
+}
+
+/**
+ * Determine whether a RPATH or RUNPATH is present in file
+ *
+ * TODO: Replace with OS-native solution(s)
+ *
+ * @param _filename path to executable or library
+ * @return -1=OS error, 0=has rpath, 1=not found
+ */
+int has_rpath(const char *_filename) {
+ int result = 1; // default: not found
+
+ char *filename = strdup(_filename);
+ if (!filename) {
+ return -1;
+ }
+
+ Process *proc_info = NULL;
+ char *rpath = NULL;
+
+ // sanitize input path
+ strchrdel(filename, "&;|");
+
+ Process *pe = patchelf(filename, "--print-rpath");
+ strip(pe->output);
+ if (!isempty(pe->output)) {
+ result = 0;
+ }
+ else {
+ // something went wrong with patchelf other than
+ // what we're looking for
+ result = -1;
+ }
+
+ free(filename);
+ shell_free(pe);
+ return result;
+}
+
+/**
+ * Returns a RPATH or RUNPATH if one is defined in `_filename`
+ *
+ * TODO: Replace with OS-native solution(s)
+ *
+ * @param _filename path to executable or library
+ * @return RPATH string, NULL=error (caller is responsible for freeing memory)
+ */
+char *rpath_get(const char *_filename) {
+ if ((has_rpath(_filename)) != 0) {
+ return NULL;
+ }
+ char *filename = strdup(_filename);
+ if (!filename) {
+ return NULL;
+ }
+ char *path = strdup(filename);
+ if (!path) {
+ free(filename);
+ return NULL;
+ }
+
+ Process *proc_info = NULL;
+ char *rpath = NULL;
+
+ // sanitize input path
+ strchrdel(path, "&;|");
+
+ Process *pe = patchelf(filename, "--print-rpath");
+ rpath = (char *)calloc(strlen(pe->output) + 1, sizeof(char));
+ if (!rpath) {
+ free(filename);
+ free(path);
+ shell_free(pe);
+ return NULL;
+ }
+ strncpy(rpath, pe->output, strlen(pe->output));
+ strip(rpath);
+
+ free(filename);
+ free(path);
+ shell_free(pe);
+ return rpath;
+}
+
+/**
+ * Generate a RPATH in the form of:
+ *
+ * `$ORIGIN/relative/path/to/lib/from/_filename/path`
+ *
+ * @param _filename
+ * @return
+ */
+char *rpath_generate(const char *_filename) {
+ const char *origin = "$ORIGIN/";
+ char *filename = realpath(_filename, NULL);
+ if (!filename) {
+ return NULL;
+ }
+ char *nearest_lib = rpath_autodetect(filename);
+ if (!nearest_lib) {
+ return NULL;
+ }
+ char *result = (char *)calloc(strlen(origin) + strlen(nearest_lib) + 1, sizeof(char));
+ if (!result) {
+ return NULL;
+ }
+ sprintf(result, "%s%s", origin, nearest_lib);
+ free(filename);
+ free(nearest_lib);
+ return result;
+}
+
+int rpath_set(const char *filename, char *_rpath) {
+ int returncode;
+
+ char *rpath_new = rpath_generate(filename);
+ if (!rpath_new) {
+ return -1;
+ }
+
+ char *rpath_orig = rpath_get(filename);
+ if (!rpath_orig) {
+ return -1;
+ }
+
+ // Are the original and new RPATH identical?
+ if (strcmp(rpath_orig, rpath_new) == 0) {
+ free(rpath_new);
+ free(rpath_orig);
+ return 0;
+ }
+
+ Process *pe = patchelf(filename, "--set-rpath");
+ if (pe) {
+ returncode = pe->returncode;
+ }
+ shell_free(pe);
+ free(rpath_new);
+ free(rpath_orig);
+ return pe->returncode;
+}
+
+/**
+ * Using `filename` as a starting point, step backward through the filesystem looking for a lib directory
+ * @param filename path to file (or a directory)
+ * @return success=relative path from `filename` to nearest lib directory, failure=NULL
+ */
+char *rpath_autodetect(const char *filename) {
+ int has_real_libdir = 0;
+ char *rootdir = dirname(filename);
+ char *start = realpath(rootdir, NULL);
+ char *cwd = realpath(".", NULL);
+ char *result = NULL;
+
+ // Change directory to the requested root
+ chdir(start);
+
+ char visit[PATH_MAX]; // Current directory
+ char tmp[PATH_MAX]; // Current directory with lib directory appended
+ char relative[PATH_MAX]; // Generated relative path to lib directory
+ char sep[2]; // Holds the platform's directory separator
+
+ // Initialize character arrays;
+ visit[0] = '\0';
+ tmp[0] = '\0';
+ relative[0] = '\0';
+ sprintf(sep, "%c", DIRSEP);
+
+ while(1) {
+ // Where are we in the file system?
+ getcwd(visit, sizeof(visit));
+ // Using the current visit path, check if it contains a lib directory
+ sprintf(tmp, "%s%clib", visit, DIRSEP);
+ if (access(tmp, F_OK) == 0) {
+ strcat(relative, "lib");
+ has_real_libdir = 1; // gate for memory allocation below
+ break;
+ }
+ // Reaching the top of the file system indicates our search for a lib directory failed
+ else if (strcmp(visit, "/") == 0) {
+ break;
+ }
+
+ // Assemble relative path step for this location
+ strcat(relative, "..");
+ strcat(relative, sep);
+
+ // Step one directory level back
+ chdir("..");
+ }
+
+ // If we found a viable lib directory, allocate memory for it
+ if (has_real_libdir) {
+ result = (char *)calloc(strlen(relative) + 1, sizeof(char));
+ if (!result) {
+ chdir(cwd); // return to calling directory
+ return NULL;
+ }
+ // Copy character array data to the result
+ strncpy(result, relative, strlen(relative));
+ }
+
+ chdir(cwd); // return to calling directory
+ free(rootdir);
+ free(cwd);
+ free(start);
+ return result;
+}
diff --git a/shell.c b/shell.c
new file mode 100644
index 0000000..8905c60
--- /dev/null
+++ b/shell.c
@@ -0,0 +1,112 @@
+#include "spm.h"
+
+/**
+ * A wrapper for `popen()` that executes non-interactive programs and reports their exit value.
+ * To redirect stdout and stderr you must do so from within the `fmt` string
+ *
+ * ~~~{.c}
+ * int fd = 1; // stdout
+ * const char *log_file = "log.txt";
+ * Process *proc_info;
+ * int status;
+ *
+ * // Send stderr to stdout
+ * shell(&proc_info, SHELL_OUTPUT, "foo 2>&1");
+ * // Send stdout and stderr to /dev/null
+ * shell(&proc_info, SHELL_OUTPUT,"bar &>/dev/null");
+ * // Send stdout from baz to log.txt
+ * shell(&proc_info, SHELL_OUTPUT, "baz %d>%s", fd, log_file);
+ * // Do not record or redirect output from any streams
+ * shell(&proc_info, SHELL_DEFAULT, "biff");
+ * ~~~
+ *
+ * @param Process uninitialized `Process` struct will be populated with process data
+ * @param options change behavior of the function
+ * @param fmt shell command to execute (accepts `printf` style formatters)
+ * @param ... variadic arguments (used by `fmt`)
+ */
+void shell(Process **proc_info, u_int64_t option, const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+
+ size_t bytes_read = 0;
+ size_t i = 0;
+ size_t new_buf_size = 0;
+ clockid_t clkid = CLOCK_REALTIME;
+ FILE *proc = NULL;
+
+ (*proc_info) = (Process *)calloc(1, sizeof(Process));
+ if (!(*proc_info)) {
+ fprintf(SYSERROR);
+ exit(errno);
+ }
+ (*proc_info)->returncode = -1;
+
+ // outbuf needs to be an integer type because fgetc returns EOF (> char)
+ int *outbuf = (int *)calloc(1, sizeof(int));
+ if (!outbuf) {
+ fprintf(SYSERROR);
+ exit(errno);
+ }
+ char *cmd = (char *)calloc(PATH_MAX, sizeof(char));
+ if (!cmd) {
+ fprintf(SYSERROR);
+ exit(errno);
+ }
+
+ vsnprintf(cmd, PATH_MAX, fmt, args);
+
+ if (option & SHELL_BENCHMARK) {
+ if (clock_gettime(clkid, &(*proc_info)->start_time) == -1) {
+ perror("clock_gettime");
+ exit(errno);
+ }
+ }
+
+ proc = popen(cmd, "r");
+ if (!proc) {
+ return;
+ }
+
+ if (option & SHELL_BENCHMARK) {
+ if (clock_gettime(clkid, &(*proc_info)->stop_time) == -1) {
+ perror("clock_gettime");
+ exit(errno);
+ }
+ (*proc_info)->time_elapsed = ((*proc_info)->stop_time.tv_sec - (*proc_info)->start_time.tv_sec)
+ + ((*proc_info)->stop_time.tv_nsec - (*proc_info)->start_time.tv_nsec) / 1E9;
+ }
+
+ if (option & SHELL_OUTPUT) {
+ (*proc_info)->output = (char *)calloc(BUFSIZ, sizeof(char));
+
+ while ((*outbuf = fgetc(proc)) != EOF) {
+
+ if (i >= BUFSIZ) {
+ new_buf_size = BUFSIZ + (i + bytes_read);
+ (*proc_info)->output = (char *)realloc((*proc_info)->output, new_buf_size);
+ i = 0;
+ }
+ if (*outbuf) {
+ (*proc_info)->output[bytes_read] = (char)*outbuf;
+ }
+ bytes_read++;
+ i++;
+ }
+ }
+ (*proc_info)->returncode = pclose(proc);
+ va_end(args);
+ free(outbuf);
+ free(cmd);
+}
+
+/**
+ * Free process resources allocated by `shell()`
+ * @param proc_info `Process` struct
+ */
+void shell_free(Process *proc_info) {
+ if (proc_info->output) {
+ free(proc_info->output);
+ }
+ free(proc_info);
+}
diff --git a/spm.c b/spm.c
index f1d38ac..ec8b7ba 100644
--- a/spm.c
+++ b/spm.c
@@ -2,1701 +2,7 @@
* SPM - Simple Package Manager
* @file spm.c
*/
-
#include "spm.h"
-static int VERBOSE_MODE = 0;
-
-char *get_user_conf_dir(void) {
- char *result = NULL;
- wordexp_t wexp;
- wordexp("~/.spm", &wexp, 0);
- if (wexp.we_wordc != 0) {
- result = (char *)calloc(strlen(wexp.we_wordv[0]) + 1, sizeof(char));
- if (!result) {
- wordfree(&wexp);
- return NULL;
- }
- strncpy(result, wexp.we_wordv[0], strlen(wexp.we_wordv[0]));
- if (access(result, F_OK) != 0) {
- mkdirs(result, 0755);
- }
- }
- wordfree(&wexp);
- return result;
-}
-
-char *get_user_config_file(void) {
- const char *filename = "spm.conf";
- char template[PATH_MAX];
- char *ucd = get_user_conf_dir();
- if (!ucd) {
- return NULL;
- }
- // Initialize temporary path
- template[0] = '\0';
-
- sprintf(template, "%s%c%s", ucd, DIRSEP, filename);
- if (access(template, F_OK) != 0) {
- // No configuration exists, so fail
- return NULL;
- }
- free(ucd);
- // Allocate and return path to configuration file
- return strdup(template);
-}
-
-char *get_user_tmp_dir(void) {
- char template[PATH_MAX];
- char *ucd = get_user_conf_dir();
- sprintf(template, "%s%ctmp", ucd, DIRSEP);
-
- if (access(template, F_OK) != 0) {
- if (mkdirs(template, 0755) != 0) {
- return NULL;
- }
- }
-
- free(ucd);
- return strdup(template);
-}
-
-char *get_user_package_dir(void) {
- char template[PATH_MAX];
- char *ucd = get_user_conf_dir();
- sprintf(template, "%s%cpkgs", ucd, DIRSEP);
-
- if (access(template, F_OK) != 0) {
- if (mkdirs(template, 0755) != 0) {
- return NULL;
- }
- }
-
- free(ucd);
- return strdup(template);
-}
-
-/**
- * A wrapper for `popen()` that executes non-interactive programs and reports their exit value.
- * To redirect stdout and stderr you must do so from within the `fmt` string
- *
- * ~~~{.c}
- * int fd = 1; // stdout
- * const char *log_file = "log.txt";
- * Process *proc_info;
- * int status;
- *
- * // Send stderr to stdout
- * shell(&proc_info, SHELL_OUTPUT, "foo 2>&1");
- * // Send stdout and stderr to /dev/null
- * shell(&proc_info, SHELL_OUTPUT,"bar &>/dev/null");
- * // Send stdout from baz to log.txt
- * shell(&proc_info, SHELL_OUTPUT, "baz %d>%s", fd, log_file);
- * // Do not record or redirect output from any streams
- * shell(&proc_info, SHELL_DEFAULT, "biff");
- * ~~~
- *
- * @param Process uninitialized `Process` struct will be populated with process data
- * @param options change behavior of the function
- * @param fmt shell command to execute (accepts `printf` style formatters)
- * @param ... variadic arguments (used by `fmt`)
- */
-void shell(Process **proc_info, u_int64_t option, const char *fmt, ...) {
- va_list args;
- va_start(args, fmt);
-
- size_t bytes_read = 0;
- size_t i = 0;
- size_t new_buf_size = 0;
- clockid_t clkid = CLOCK_REALTIME;
- FILE *proc = NULL;
-
- (*proc_info) = (Process *)calloc(1, sizeof(Process));
- if (!(*proc_info)) {
- fprintf(SYSERROR);
- exit(errno);
- }
- (*proc_info)->returncode = -1;
-
- // outbuf needs to be an integer type because fgetc returns EOF (> char)
- int *outbuf = (int *)calloc(1, sizeof(int));
- if (!outbuf) {
- fprintf(SYSERROR);
- exit(errno);
- }
- char *cmd = (char *)calloc(PATH_MAX, sizeof(char));
- if (!cmd) {
- fprintf(SYSERROR);
- exit(errno);
- }
-
- vsnprintf(cmd, PATH_MAX, fmt, args);
-
- if (option & SHELL_BENCHMARK) {
- if (clock_gettime(clkid, &(*proc_info)->start_time) == -1) {
- perror("clock_gettime");
- exit(errno);
- }
- }
-
- proc = popen(cmd, "r");
- if (!proc) {
- return;
- }
-
- if (option & SHELL_BENCHMARK) {
- if (clock_gettime(clkid, &(*proc_info)->stop_time) == -1) {
- perror("clock_gettime");
- exit(errno);
- }
- (*proc_info)->time_elapsed = ((*proc_info)->stop_time.tv_sec - (*proc_info)->start_time.tv_sec)
- + ((*proc_info)->stop_time.tv_nsec - (*proc_info)->start_time.tv_nsec) / 1E9;
- }
-
- if (option & SHELL_OUTPUT) {
- (*proc_info)->output = (char *)calloc(BUFSIZ, sizeof(char));
-
- while ((*outbuf = fgetc(proc)) != EOF) {
-
- if (i >= BUFSIZ) {
- new_buf_size = BUFSIZ + (i + bytes_read);
- (*proc_info)->output = (char *)realloc((*proc_info)->output, new_buf_size);
- i = 0;
- }
- if (*outbuf) {
- (*proc_info)->output[bytes_read] = (char)*outbuf;
- }
- bytes_read++;
- i++;
- }
- }
- (*proc_info)->returncode = pclose(proc);
- va_end(args);
- free(outbuf);
- free(cmd);
-}
-
-/**
- * Free process resources allocated by `shell()`
- * @param proc_info `Process` struct
- */
-void shell_free(Process *proc_info) {
- if (proc_info->output) {
- free(proc_info->output);
- }
- free(proc_info);
-}
-
-/**
- * Extract a single file from a tar archive into a directory
- *
- * @param archive path to tar archive
- * @param filename known path inside the archive to extract
- * @param destination where to extract file to (must exist)
- * @return
- */
-int tar_extract_file(const char *archive, const char* filename, const char *destination) {
- Process *proc = NULL;
- int status;
- char cmd[PATH_MAX];
-
- sprintf(cmd, "tar xf %s -C %s %s 2>&1", archive, destination, filename);
- shell(&proc, SHELL_OUTPUT, cmd);
- if (!proc) {
- fprintf(SYSERROR);
- return -1;
- }
-
- status = proc->returncode;
- shell_free(proc);
-
- return status;
-}
-
-int tar_extract_archive(const char *_archive, const char *_destination) {
- Process *proc = NULL;
- int status;
- char cmd[PATH_MAX];
-
- char *archive = strdup(_archive);
- if (!archive) {
- fprintf(SYSERROR);
- return -1;
- }
- char *destination = strdup(_destination);
- if (!destination) {
- fprintf(SYSERROR);
- return -1;
- }
-
- // sanitize archive
- strchrdel(archive, "&;|");
- // sanitize destination
- strchrdel(destination, "&;|");
-
- sprintf(cmd, "tar xf %s -C %s 2>&1", archive, destination);
- shell(&proc, SHELL_OUTPUT, cmd);
- if (!proc) {
- fprintf(SYSERROR);
- free(archive);
- free(destination);
- return -1;
- }
-
- status = proc->returncode;
- shell_free(proc);
- free(archive);
- free(destination);
- return status;
-}
-
-/**
- * glob callback function
- * @param epath path to file that generated the error condition
- * @param eerrno the error condition
- * @return the error condition
- */
-int errglob(const char *epath, int eerrno) {
- fprintf(stderr, "glob matching error: %s (%d)", epath, eerrno);
- return eerrno;
-}
-
-/**
- * Determine how many times the character `ch` appears in `sptr` string
- * @param sptr string to scan
- * @param ch character to find
- * @return count of characters found
- */
-int num_chars(const char *sptr, int ch) {
- int result = 0;
- for (int i = 0; sptr[i] != '\0'; i++) {
- if (sptr[i] == ch) {
- result++;
- }
- }
- return result;
-}
-
-/**
- * Scan for `pattern` string at the beginning of `sptr`
- *
- * @param sptr string to scan
- * @param pattern string to search for
- * @return 0 = success, -1 = failure
- */
-int startswith(const char *sptr, const char *pattern) {
- for (size_t i = 0; i < strlen(pattern); i++) {
- if (sptr[i] != pattern[i]) {
- return -1;
- }
- }
- return 0;
-}
-
-/**
- * Scan for `pattern` string at the end of `sptr`
- *
- * @param sptr string to scan
- * @param pattern string to search for
- * @return 0 = success, -1 = failure
- */
-int endswith(const char *sptr, const char *pattern) {
- size_t sptr_size = strlen(sptr);
- size_t pattern_size = strlen(pattern);
- for (size_t s = sptr_size - pattern_size, p = 0 ; s < sptr_size; s++, p++) {
- if (sptr[s] != pattern[p]) {
- return -1;
- }
- }
- return 0;
-}
-
-/**
- * Converts Win32 path to Unix path, and vice versa
- * - On UNIX, Win32 paths will be converted UNIX
- * - On Win32, UNIX paths will be converted to Win32
- *
- * This function is platform dependent.
- *
- * @param path a system path
- * @return string (caller is responsible for `free`ing memory)
- */
-char *normpath(const char *path) {
- char *result = strdup(path);
- char *tmp = result;
-
- while (*tmp) {
- if (*tmp == NOT_DIRSEP) {
- *tmp = DIRSEP;
- tmp++;
- continue;
- }
- tmp++;
- }
- return result;
-}
-
-/**
- * Deletes any characters matching `chars` from `sptr` string
- *
- * @param sptr string to be modified in-place
- * @param chars a string containing characters (e.g. " \n" would delete whitespace and line feeds)
- */
-void strchrdel(char *sptr, const char *chars) {
- while (*sptr != '\0') {
- for (int i = 0; chars[i] != '\0'; i++) {
- if (*sptr == chars[i]) {
- memmove(sptr, sptr + 1, strlen(sptr));
- }
- }
- sptr++;
- }
-}
-
-/**
- * Find the integer offset of the first occurrence of `ch` in `sptr`
- *
- * ~~~{.c}
- * char buffer[255];
- * char string[] = "abc=123";
- * long int separator_offset = strchroff(string, '=');
- * for (long int i = 0; i < separator_offset); i++) {
- * buffer[i] = string[i];
- * }
- * ~~~
- *
- * @param sptr string to scan
- * @param ch character to find
- * @return offset to character in string, or 0 on failure
- */
-long int strchroff(const char *sptr, int ch) {
- char *orig = strdup(sptr);
- char *tmp = orig;
- long int result = 0;
- while (*tmp != '\0') {
- if (*tmp == ch) {
- break;
- }
- tmp++;
- }
- result = tmp - orig;
- free(orig);
-
- return result;
-}
-
-/**
- * This function scans `sptr` from right to left removing any matches to `suffix`
- * from the string.
- *
- * @param sptr string to be modified
- * @param suffix string to be removed from `sptr`
- */
-void substrdel(char *sptr, const char *suffix) {
- if (!sptr || !suffix) {
- return;
- }
- size_t sptr_len = strlen(sptr);
- size_t suffix_len = strlen(suffix);
- intptr_t target_offset = sptr_len - suffix_len;
-
- // Prevent access to memory below input string
- if (target_offset < 0) {
- return;
- }
-
- // Create a pointer to
- char *target = sptr + target_offset;
- if (!strcmp(target, suffix)) {
- // Purge the suffix
- memset(target, '\0', suffix_len);
- // Recursive call continues removing suffix until it is gone
- strip(sptr);
- }
-}
-
-/**
- * Scan a directory for a file by name, or by wildcard
- *
- * @param root directory path to scan
- * @param filename file to find (wildcards accepted)
- * @return success=path to file, failure=NULL
- */
-char *find_file(const char *root, const char *filename) {
- glob_t results;
- int glob_flags = 0;
- int match = 0;
- char *rootpath = NULL;
- char *path = NULL;
-
- // GUARD
- if (!root || !filename || strstr(filename, "..") || strstr(filename, "./")) {
- return NULL;
- }
-
- if (!(path = (char *)calloc(PATH_MAX + 1, sizeof(char)))) {
- fprintf(SYSERROR);
- exit(errno);
- }
-
- if (!(rootpath = realpath(root, NULL))) {
- return NULL;
- }
-
- strcat(path, rootpath);
- strcat(path, "/");
- strcat(path, filename);
-
- // Save a little time if the file exists
- if (access(path, F_OK) != -1) {
- return path;
- }
-
- // Inject wildcard
- strcat(path, "*");
- // Search for the file
- match = glob(path, glob_flags, errglob, &results);
-
- if (match != 0) {
- // report critical errors except GLOB_NOMATCH
- if (match == GLOB_NOSPACE || match == GLOB_ABORTED) {
- fprintf(SYSERROR);
- }
- return NULL;
- }
-
- // Resize path to the length of the first match
- char *want = results.gl_pathv[0];
- if (!(path = (char *)realloc(path, strlen(want) + 1))) {
- fprintf(SYSERROR);
- exit(errno);
- }
-
- // Replace path string with wanted path string
- strncpy(path, want, strlen(want));
-
- free(rootpath);
- globfree(&results);
- return path;
-}
-
-/**
- * Scan the package directory for a package by name
- * @param filename file to find
- * @return success=path to file, failure=NULL
- */
-char *find_package(const char *filename) {
- return find_file(PKG_DIR, filename);
-}
-
-/**
- * Split a string by every delimiter in `delim` string.
- *
- * Callee must free memory using `split_free()`
- *
- * @param sptr string to split
- * @param delim characters to split on
- * @return success=parts of string, failure=NULL
- */
-char** split(char *_sptr, const char* delim)
-{
- size_t split_alloc = 0;
- // Duplicate the input string and save a copy of the pointer to be freed later
- char *orig = strdup(_sptr);
- char *sptr = orig;
- if (!sptr) {
- return NULL;
- }
-
- // Determine how many delimiters are present
- for (size_t i = 0; i < strlen(delim); i++) {
- split_alloc += num_chars(sptr, delim[i]);
- }
- // Preallocate enough records based on the number of delimiters
- char **result = (char **)calloc(split_alloc + 2, sizeof(char *));
- if (!result) {
- free(sptr);
- return NULL;
- }
-
- // Separate the string into individual parts and store them in the result array
- int i = 0;
- char *token = NULL;
- while((token = strsep(&sptr, delim)) != NULL) {
- result[i] = (char *)calloc(1, sizeof(char) * strlen(token) + 1);
- if (!result[i]) {
- free(sptr);
- return NULL;
- }
- strncpy(result[i], token, strlen(token)); // copy the string contents into the record
- i++; // next record
- }
- free(orig);
- return result;
-}
-
-/**
- * Frees memory allocated by `split()`
- * @param ptr pointer to array
- */
-void split_free(char **ptr) {
- for (int i = 0; ptr[i] != NULL; i++) {
- free(ptr[i]);
- }
- free(ptr);
-}
-
-/**
- * Extract the string encapsulated by characters listed in `delims`
- *
- * ~~~{.c}
- * char *str = "this is [some data] in a string";
- * char *data = substring_between(string, "[]");
- * // data = "some data";
- * ~~~
- *
- * @param sptr string to parse
- * @param delims two characters surrounding a string
- * @return success=text between delimiters, failure=NULL
- */
-char *substring_between(char *sptr, const char *delims) {
- // Ensure we have enough delimiters to continue
- size_t delim_count = strlen(delims);
- if (delim_count != 2) {
- return NULL;
- }
-
- // Create pointers to the delimiters
- char *start = strpbrk(sptr, &delims[0]);
- char *end = strpbrk(sptr, &delims[1]);
-
- // Ensure the string has both delimiters
- if (!start || !end) {
- return NULL;
- }
-
- start++; // ignore leading delimiter
-
- // Get length of the substring
- size_t length = end - start;
-
- char *result = (char *)calloc(length + 1, sizeof(char));
- if (!result) {
- return NULL;
- }
-
- // Copy the contents of the substring to the result
- char *tmp = result;
- while (start != end) {
- *tmp = *start;
- tmp++;
- start++;
- }
-
- return result;
-}
-
-/**
- * Determine whether a RPATH or RUNPATH is present in file
- *
- * TODO: Replace with OS-native solution(s)
- *
- * @param _filename path to executable or library
- * @return -1=OS error, 0=has rpath, 1=not found
- */
-int has_rpath(const char *_filename) {
- int result = 1; // default: not found
-
- char *filename = strdup(_filename);
- if (!filename) {
- return -1;
- }
-
- Process *proc_info = NULL;
- char *rpath = NULL;
-
- // sanitize input path
- strchrdel(filename, "&;|");
-
- Process *pe = patchelf(filename, "--print-rpath");
- strip(pe->output);
- if (!isempty(pe->output)) {
- result = 0;
- }
- else {
- // something went wrong with patchelf other than
- // what we're looking for
- result = -1;
- }
-
- free(filename);
- shell_free(pe);
- return result;
-}
-
-/**
- * Returns a RPATH or RUNPATH if one is defined in `_filename`
- *
- * TODO: Replace with OS-native solution(s)
- *
- * @param _filename path to executable or library
- * @return RPATH string, NULL=error (caller is responsible for freeing memory)
- */
-char *get_rpath(const char *_filename) {
- if ((has_rpath(_filename)) != 0) {
- return NULL;
- }
- char *filename = strdup(_filename);
- if (!filename) {
- return NULL;
- }
- char *path = strdup(filename);
- if (!path) {
- free(filename);
- return NULL;
- }
-
- Process *proc_info = NULL;
- char *rpath = NULL;
-
- // sanitize input path
- strchrdel(path, "&;|");
-
- Process *pe = patchelf(filename, "--print-rpath");
- rpath = (char *)calloc(strlen(pe->output) + 1, sizeof(char));
- if (!rpath) {
- free(filename);
- free(path);
- shell_free(pe);
- return NULL;
- }
- strncpy(rpath, pe->output, strlen(pe->output));
- strip(rpath);
-
- free(filename);
- free(path);
- shell_free(pe);
- return rpath;
-}
-
-/**
- * Generate a RPATH in the form of:
- *
- * `$ORIGIN/relative/path/to/lib/from/_filename/path`
- *
- * @param _filename
- * @return
- */
-char *gen_rpath(const char *_filename) {
- const char *origin = "$ORIGIN/";
- char *filename = realpath(_filename, NULL);
- if (!filename) {
- return NULL;
- }
- char *nearest_lib = libdir_nearest(filename);
- if (!nearest_lib) {
- return NULL;
- }
- char *result = (char *)calloc(strlen(origin) + strlen(nearest_lib) + 1, sizeof(char));
- if (!result) {
- return NULL;
- }
- sprintf(result, "%s%s", origin, nearest_lib);
- free(filename);
- free(nearest_lib);
- return result;
-}
-
-
-int set_rpath(const char *filename, char *_rpath) {
- int returncode;
-
- char *rpath_new = gen_rpath(filename);
- if (!rpath_new) {
- return -1;
- }
-
- char *rpath_orig = get_rpath(filename);
- if (!rpath_orig) {
- return -1;
- }
-
- // Are the original and new RPATH identical?
- if (strcmp(rpath_orig, rpath_new) == 0) {
- free(rpath_new);
- free(rpath_orig);
- return 0;
- }
-
- Process *pe = patchelf(filename, "--set-rpath");
- if (pe) {
- returncode = pe->returncode;
- }
- shell_free(pe);
- free(rpath_new);
- free(rpath_orig);
- return pe->returncode;
-}
-
-/**
- * Lists a directory (use `fstree` instead if you only want a basic recursive listing))
- * @param dirpath
- * @param result
- */
-void walkdir(char *dirpath, Dirwalk **result, unsigned int dirs) {
- static int i = 0;
- static int locked = 0;
- size_t dirpath_size = strlen(dirpath);
- const size_t initial_records = 2;
-
- DIR *dp = opendir(dirpath);
- if (!dp) {
- return;
- }
-
- struct dirent *entry;
- int record_count = 0;
-
- while ((entry = readdir(dp)) != NULL) {
- if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
- continue;
- }
- record_count++;
- }
- rewinddir(dp);
-
- if (!locked) {
- (*result) = (Dirwalk *)reallocarray((*result),1, sizeof(Dirwalk));
- (*result)->paths = (char **)calloc(record_count, sizeof(char *));
- i = 0;
- locked++;
- }
-
- //(*result)->paths = (char **) reallocarray((*result)->paths, record_count, sizeof(char *));
- while ((entry = readdir(dp)) != NULL) {
- printf("i=%d, dname=%s\n", i, entry->d_name);
- char *name = entry->d_name;
-
- char path[PATH_MAX];
- char sep = DIRSEP;
- int path_size = snprintf(path, PATH_MAX, "%s%c%s", dirpath, sep, entry->d_name);
-
- if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
- continue;
- }
-
- (*result)->paths[i] = (char *) calloc((size_t) (path_size + 1), sizeof(char));
- if (entry->d_type == DT_DIR) {
- if (dirs) {
- strncpy((*result)->paths[i], path, (size_t) path_size);
- //i++;
- }
- dirpath[dirpath_size] = DIRSEP;
- strcpy(dirpath + dirpath_size + 1, name);
- walkdir(dirpath, result, dirs);
- dirpath[dirpath_size] = '\0';
- }
- else {
- strncpy((*result)->paths[i], path, (size_t) path_size);
- }
- i++;
- }
- (*result)->count = i;
- (*result)->paths[i] = NULL;
- closedir(dp);
- if (!strcmp(dirpath, "..") || !strcmp(dirpath, ".")) {
- locked = 0;
- }
-}
-
-/*
- * Helper function for `strsort`
- */
-static int _strsort_compare(const void *a, const void *b) {
- const char *aa = *(const char**)a;
- const char *bb = *(const char**)b;
- int result = strcmp(aa, bb);
- return result;
-}
-
-/**
- * Sort an array of strings alphabetically
- * @param arr
- */
-void strsort(char **arr) {
- size_t arr_size = 0;
-
- // Determine size of array
- for (size_t i = 0; arr[i] != NULL; i++) {
- arr_size = i;
- }
- qsort(arr, arr_size, sizeof(char *), _strsort_compare);
-}
-
-/*
- * Helper function for `strsortlen`
- */
-static int _strsortlen_asc_compare(const void *a, const void *b) {
- const char *aa = *(const char**)a;
- const char *bb = *(const char**)b;
- size_t len_a = strlen(aa);
- size_t len_b = strlen(bb);
- return len_a > len_b;
-}
-
-/*
- * Helper function for `strsortlen`
- */
-static int _strsortlen_dsc_compare(const void *a, const void *b) {
- const char *aa = *(const char**)a;
- const char *bb = *(const char**)b;
- size_t len_a = strlen(aa);
- size_t len_b = strlen(bb);
- return len_a < len_b;
-}
-/**
- * Sort an array of strings by length
- * @param arr
- */
-void strsortlen(char **arr, unsigned int sort_mode) {
- typedef int (*compar)(const void *, const void *);
-
- compar fn = _strsortlen_asc_compare;
- if (sort_mode != 0) {
- fn = _strsortlen_dsc_compare;
- }
-
- size_t arr_size = 0;
-
- // Determine size of array
- for (size_t i = 0; arr[i] != NULL; i++) {
- arr_size = i;
- }
- qsort(arr, arr_size, sizeof(char *), fn);
-}
-
-long int get_file_size(const char *filename) {
- long int result = 0;
- FILE *fp = fopen(filename, "rb");
- if (!fp) {
- return -1;
- }
- fseek(fp, 0, SEEK_END);
- result = ftell(fp);
- fclose(fp);
- return result;
-}
-
-int fstrstr(const char *filename, const char *pattern) {
- int result = 1; // default "not found"
-
- FILE *fp = fopen(filename, "rb");
- if (!fp) {
- return -1;
- }
-
- long int file_len = get_file_size(filename);
- if (file_len < 0) {
- return -1;
- }
- char *buffer = (char *)calloc((size_t) file_len, sizeof(char));
- if (!buffer) {
- return -1;
- }
- size_t pattern_len = strlen(pattern);
-
- fread(buffer, (size_t) file_len, sizeof(char), fp);
- fclose(fp);
-
- for (size_t i = 0; i < file_len; i++) {
- if (!memcmp(&buffer[i], pattern, pattern_len)) {
- result = 0; // found
- break;
- }
- }
- free(buffer);
- return result;
-}
-
-/**
- * Attempt to create a directory (or directories)
- * @param _path A path to create
- * @param mode UNIX permissions (octal)
- * @return success=0, failure=-1 (+ errno will be set)
- */
-int mkdirs(const char *_path, mode_t mode) {
- int result = 0;
- char *path = normpath(_path);
- char tmp[PATH_MAX];
- tmp[0] = '\0';
-
- char sep[2];
- sprintf(sep, "%c", DIRSEP);
- char **parts = split(path, sep);
- for (int i = 0; parts[i] != NULL; i++) {
- strcat(tmp, parts[i]);
- strcat(tmp, sep);
- if (access(tmp, F_OK) != 0) {
- result = mkdir(tmp, mode);
- }
- }
- split_free(parts);
- return result;
-}
-
-/**
- * Get the full path of a shell command
- * @param program
- * @return success=absolute path to program, failure=NULL
- */
-char *find_executable(const char *program) {
- int found = 0;
- char *result = NULL;
- char *env_path = NULL;
- env_path = getenv("PATH");
- if (!env_path) {
- return NULL;
- }
- char **search_paths = split(env_path, ":");
-
- char buf[PATH_MAX];
- for (int i = 0; search_paths[i] != NULL; i++) {
- sprintf(buf, "%s%c%s", search_paths[i], DIRSEP, program);
- if (access(buf, F_OK | X_OK) == 0) {
- found = 1;
- break;
- }
- memset(buf, '\0', sizeof(buf));
- }
- if (found) {
- result = strdup(buf);
- }
- split_free(search_paths);
- return result;
-}
-
-/**
- * Check whether this program will run properly given the current runtime environment
- */
-void check_runtime_environment(void) {
- int bad_rt = 0;
- char *required[] = {
- "patchelf",
- "rsync",
- "tar",
- "bash",
- "reloc",
- NULL,
- };
- for (int i = 0; required[i] != NULL; i++) {
- char *result = find_executable(required[i]);
- if (!result) {
- fprintf(stderr, "Required program '%s' is not installed\n", required[i]);
- bad_rt = 1;
- }
- free(result);
- }
- if (bad_rt) {
- exit(1);
- }
-}
-
-/**
- * Strip file name from directory
- * Note: Caller is responsible for freeing memory
- *
- * @param _path
- * @return success=path to directory, failure=NULL
- */
-char *dirname(const char *_path) {
- char *path = strdup(_path);
- char *last = strrchr(path, DIRSEP);
- if (!last) {
- return NULL;
- }
- // Step backward, stopping on the first non-separator
- // This ensures strings like "/usr//////" are converted to "/usr", but...
- // it will do nothing to fix up a path like "/usr//////bin/bash
- char *lookback = last;
- while (*(lookback - 1) == DIRSEP) {
- lookback--;
- }
-
- *lookback = '\0';
- return path;
-}
-
-/**
- * Strip directory from file name
- * Note: Caller is responsible for freeing memory
- *
- * @param _path
- * @return success=file name, failure=NULL
- */
-char *basename(char *path) {
- char *result = NULL;
- char *last = strrchr(path, DIRSEP);
- if (!last) {
- return NULL;
- }
-
- // Perform a lookahead ensuring the string is valid beyond the last separator
- if ((last + 1) != NULL) {
- result = last + 1;
- }
-
- return result;
-}
-
-/**
- * Using `filename` as a starting point, step backward through the filesystem looking for a lib directory
- * @param filename path to file (or a directory)
- * @return success=relative path from `filename` to nearest lib directory, failure=NULL
- */
-char *libdir_nearest(const char *filename) {
- int has_real_libdir = 0;
- char *rootdir = dirname(filename);
- char *start = realpath(rootdir, NULL);
- char *cwd = realpath(".", NULL);
- char *result = NULL;
-
- // Change directory to the requested root
- chdir(start);
-
- char visit[PATH_MAX]; // Current directory
- char tmp[PATH_MAX]; // Current directory with lib directory appended
- char relative[PATH_MAX]; // Generated relative path to lib directory
- char sep[2]; // Holds the platform's directory separator
-
- // Initialize character arrays;
- visit[0] = '\0';
- tmp[0] = '\0';
- relative[0] = '\0';
- sprintf(sep, "%c", DIRSEP);
-
- while(1) {
- // Where are we in the file system?
- getcwd(visit, sizeof(visit));
- // Using the current visit path, check if it contains a lib directory
- sprintf(tmp, "%s%clib", visit, DIRSEP);
- if (access(tmp, F_OK) == 0) {
- strcat(relative, "lib");
- has_real_libdir = 1; // gate for memory allocation below
- break;
- }
- // Reaching the top of the file system indicates our search for a lib directory failed
- else if (strcmp(visit, "/") == 0) {
- break;
- }
-
- // Assemble relative path step for this location
- strcat(relative, "..");
- strcat(relative, sep);
-
- // Step one directory level back
- chdir("..");
- }
-
- // If we found a viable lib directory, allocate memory for it
- if (has_real_libdir) {
- result = (char *)calloc(strlen(relative) + 1, sizeof(char));
- if (!result) {
- chdir(cwd); // return to calling directory
- return NULL;
- }
- // Copy character array data to the result
- strncpy(result, relative, strlen(relative));
- }
-
- chdir(cwd); // return to calling directory
- free(rootdir);
- free(cwd);
- free(start);
- return result;
-}
-
-/**
- * Wrapper function to execute `patchelf` with arguments
- * @param _filename Path of file to modify
- * @param _args Arguments to pass to `patchelf`
- * @return success=Process struct, failure=NULL
- */
-Process *patchelf(const char *_filename, const char *_args) {
- char *filename = strdup(_filename);
- char *args = strdup(_args);
- Process *proc_info = NULL;
- char sh_cmd[PATH_MAX];
- sh_cmd[0] = '\0';
-
- strchrdel(args, "&;|");
- strchrdel(filename, "&;|");
- sprintf(sh_cmd, "patchelf %s %s", args, filename);
-
- shell(&proc_info, SHELL_OUTPUT, sh_cmd);
-
- free(filename);
- free(args);
- return proc_info;
-}
-
-/**
- * Replace all occurrences of `_oldstr` in `_oldbuf` with `_newstr`
- * @param _oldbuf
- * @param _oldstr
- * @param _newstr
- * @return
- */
-char *replace_text(char *_oldbuf, const char *_oldstr, const char *_newstr) {
- int occurrences = 0;
- int i = 0;
- size_t _oldstr_len = strlen(_oldstr);
- size_t _newstr_len = strlen(_newstr);
- size_t _oldbuf_len = strlen(_oldbuf);
-
- // Determine the number of times _oldstr occurs in _oldbuf.
- char *tmp = _oldbuf;
- while (*tmp) {
- if (strstr(tmp, _oldstr) == tmp) {
- occurrences++;
- // Move pointer past last occurrence
- i++;
- tmp += _oldstr_len;
- continue;
- }
- i++;
- tmp++;
- }
-
- char *result = (char *)calloc(((i + (occurrences * _newstr_len)) + 1), sizeof(char));
- if (!result) {
- fprintf(SYSERROR);
- return NULL;
- }
-
- // Continuously scan until _oldstr has been completely removed
- i = 0;
- while (strstr(_oldbuf, _oldstr) != NULL) {
- // Search for _oldstr in _oldbuf
- if (strstr(_oldbuf, _oldstr) == _oldbuf) {
- // Copy replacement string into result buffer
- strncpy(&result[i], _newstr, _newstr_len);
- i += _newstr_len;
- _oldbuf += _oldstr_len;
- }
- else {
- // Write non-matches to result buffer
- result[i++] = *_oldbuf++;
- }
- }
-
- return result;
-}
-
-/**
- * Replace all occurrences of `oldstr` in file `path` with `newstr`
- * @param filename
- * @param oldstr
- * @param newstr
- * @return success=0, failure=-1, or value of `ferror()`
- */
-int file_replace_text(const char *filename, const char *oldstr, const char *newstr) {
- int err = 0;
- long int file_size = get_file_size(filename);
- char *data_orig = (char *)calloc(file_size + 1, sizeof(char));
- FILE *fp = fopen(filename, "r+");
- if (!fp) {
- fprintf(SYSERROR);
- free(data_orig);
- return -1;
- }
-
- // Read the entire file into memory
- fread(data_orig, file_size, sizeof(char), fp);
- if ((err = ferror(fp)) < 0) {
- free(data_orig);
- fclose(fp);
- return err;
- }
- // Jump back to the beginning of the file
- rewind(fp);
-
- // Create a new buffer
- char *data_new = replace_text(data_orig, oldstr, newstr);
- if (!data_new) {
- fprintf(SYSERROR);
- free(data_orig);
- fclose(fp);
- return -1;
- }
- // Update expected file size
- file_size = strlen(data_new);
- // Write back changes
- fwrite(data_new, file_size, sizeof(char), fp);
- if ((err = ferror(fp)) < 0) {
- free(data_orig);
- free(data_new);
- fclose(fp);
- return err;
- }
-
- free(data_orig);
- free(data_new);
- fclose(fp);
- return 0;
-}
-
-/**
- * Search for string in an array of strings
- * @param arr array of strings
- * @param str string to search for
- * @return yes=0, no=1, failure=-1
- */
-int strstr_array(char **arr, const char *str) {
- if (!arr) {
- return -1;
- }
-
- for (int i = 0; arr[i] != NULL; i++) {
- if (strstr(arr[i], str) != NULL) {
- return 0;
- }
- }
- return 1;
-}
-
-/**
- * Remove duplicate strings from an array of strings
- * @param arr
- * @return success=array of unique strings, failure=NULL
- */
-char **strdeldup(char **arr) {
- if (!arr) {
- return NULL;
- }
-
- int records;
- // Determine the length of the array
- for (records = 0; arr[records] != NULL; records++);
-
- // Allocate enough memory to store the original array contents
- // (It might not have duplicate values, for example)
- char **result = (char **)calloc(records + 1, sizeof(char *));
- if (!result) {
- return NULL;
- }
-
- int rec = 0;
- int i = 0;
- while(i < records) {
- // Search for value in results
- if (strstr_array(result, arr[i]) == 0) {
- // value already exists in results so ignore it
- i++;
- continue;
- }
-
- // Store unique value
- result[rec] = (char *)calloc(strlen(arr[i]) + 1, sizeof(char));
- if (!result[rec]) {
- free(result);
- return NULL;
- }
- strncpy(result[rec], arr[i], strlen(arr[i]));
- i++;
- rec++;
- }
- return result;
-}
-
-void depends_all(Dependencies **deps, const char *_package) {
- static int next = 0;
- char *package = find_package(_package);
- char depfile[PATH_MAX];
- char template[PATH_MAX];
- char suffix[PATH_MAX] = "spm_depends_all_XXXXXX";
- sprintf(template, "%s%c%s", TMP_DIR, DIRSEP, suffix);
-
- // Create a new temporary directory and extract the requested package into it
- char *tmpdir = mkdtemp(template);
- tar_extract_file(package, ".SPM_DEPENDS", tmpdir);
- sprintf(depfile, "%s%c%s", tmpdir, DIRSEP, ".SPM_DEPENDS");
-
- int resolved = dep_solve(deps, depfile);
- for (int i = next; i < resolved; i++) {
- next++;
- if (dep_seen(deps, (*deps)->list[i])) {
- depends_all(deps, (*deps)->list[i]);
- }
- }
-
- unlink(depfile);
- unlink(tmpdir);
-}
-
-int install(const char *destroot, const char *_package) {
- char *package = find_package(_package);
- if (!package) {
- fprintf(SYSERROR);
- return -1;
- }
- printf("Installing: %s\n", package);
- if (access(destroot, F_OK) != 0) {
- if (mkdirs(destroot, 0755) != 0) {
- fprintf(SYSERROR);
- return -2;
- }
- }
-
- char cwd[PATH_MAX];
- char source[PATH_MAX];
- char template[PATH_MAX];
- char suffix[PATH_MAX] = "spm_destroot_XXXXXX";
- sprintf(template, "%s%c%s", TMP_DIR, DIRSEP, suffix);
-
- // Create a new temporary directory and extract the requested package into it
- char *tmpdir = mkdtemp(template);
- tar_extract_archive(package, tmpdir);
-
- getcwd(cwd, sizeof(cwd));
-
- RelocationEntry **b_record = NULL;
- RelocationEntry **t_record = NULL;
- chdir(tmpdir);
- {
- // Rewrite binary prefixes
- RelocationEntry **b_record = prefixes_read(".SPM_PREFIX_BIN");
- if (b_record) {
- for (int i = 0; b_record[i] != NULL; i++) {
- relocate(b_record[i]->path, b_record[i]->prefix, destroot);
- }
- }
-
- // Rewrite text prefixes
- RelocationEntry **t_record = prefixes_read(".SPM_PREFIX_TEXT");
- if (t_record) {
- for (int i = 0; t_record[i] != NULL; i++) {
- file_replace_text(t_record[i]->path, t_record[i]->prefix, destroot);
- }
- }
-
- prefixes_free(b_record);
- prefixes_free(t_record);
- }
- chdir(cwd);
-
-
- // Append a trailing slash to tmpdir to direct rsync to copy files, not the directory, into destroot
- sprintf(source, "%s%c", tmpdir, DIRSEP);
- if (rsync(NULL, source, destroot) != 0) {
- exit(1);
- }
- rmdirs(tmpdir);
-
- free(package);
-}
-
-/**
- * Free memory allocated by `prefixes_read` function
- * @param entry array of RelocationEntry
- */
-void prefixes_free(RelocationEntry **entry) {
- if (!entry) {
- return;
- }
- for (int i = 0; entry[i] != NULL; i++) {
- if (entry[i]->prefix) free(entry[i]->prefix);
- if (entry[i]->path) free(entry[i]->path);
- if (entry[i]) free(entry[i]);
- }
- free(entry);
-}
-
-/**
- * Parse a prefix file
- *
- * The file format is as follows:
- *
- * ~~~
- * #prefix
- * path
- * #prefix
- * path
- * #...N
- * ...N
- * ~~~
- * @param filename
- * @return success=array of RelocationEntry, failure=NULL
- */
-RelocationEntry **prefixes_read(const char *filename) {
- size_t i = 0;
- int record_count = 0;
- int parity = 0;
- FILE *fp = fopen(filename, "r");
- if (!fp) {
- fprintf(SYSERROR);
- return NULL;
- }
- RelocationEntry **entry = NULL;
- char line[BUFSIZ];
- memset(line, '\0', BUFSIZ);
-
- while (fgets(line, BUFSIZ, fp) != NULL) {
- if (isempty(line)) {
- continue;
- }
- record_count++;
- }
- rewind(fp);
-
- // Initialize the relocation entry array
- if (record_count == 0) {
- return NULL;
- }
-
- parity = record_count % 2;
- if (parity != 0) {
- fprintf(stderr, "%s: records are not divisible by 2 (got: %d %% 2 = %d)\n", filename, record_count, parity);
- return NULL;
- }
- record_count /= 2;
-
- entry = (RelocationEntry **)calloc(record_count + 1, sizeof(RelocationEntry *));
- if (!entry) {
- return NULL;
- }
- for (int i = 0; i < record_count; i++) {
- entry[i] = (RelocationEntry *) calloc(1, sizeof(RelocationEntry));
- if (!entry[i]) {
- return NULL;
- }
- }
-
- int do_prefix = 0;
- int do_path = 0;
- while (fgets(line, BUFSIZ, fp) != NULL) {
- char *wtf = line;
- if (isempty(line)) {
- continue;
- }
- if (startswith(line, "#") == 0) {
- do_prefix = 1;
- }
- else {
- do_path = 1;
- }
-
- // Allocate a relocation record
- if (!entry[i]) {
- fclose(fp);
- return NULL;
- }
-
-
- if (do_prefix) {
- // Populate prefix data (a prefix starts with a #)
- entry[i]->prefix = (char *) calloc(strlen(line) + 1, sizeof(char));
- if (!entry[i]->prefix) {
- fclose(fp);
- return NULL;
- }
- strncpy(entry[i]->prefix, line, strlen(line));
- // Remove prefix delimiter and whitespace
- strchrdel(entry[i]->prefix, "#");
- entry[i]->prefix = strip(entry[i]->prefix);
- do_prefix = 0;
- continue;
- }
-
- else if (do_path) {
- // Populate path data
- entry[i]->path = (char *) calloc(strlen(line) + 1, sizeof(char));
- if (!entry[i]->path) {
- fclose(fp);
- return NULL;
- }
- strncpy(entry[i]->path, line, strlen(line));
- entry[i]->path = strip(entry[i]->path);
- do_path = 0;
- }
- i++;
- }
- fclose(fp);
- return entry;
-}
-
-void init_config_global(void) {
- SPM_GLOBAL.user_config_basedir = NULL;
- SPM_GLOBAL.user_config_file = NULL;
- SPM_GLOBAL.package_dir = NULL;
- SPM_GLOBAL.tmp_dir = NULL;
- SPM_GLOBAL.config = NULL;
-
- if (uname(&SPM_GLOBAL.sysinfo) != 0) {
- fprintf(SYSERROR);
- exit(1);
- }
-
- SPM_GLOBAL.user_config_basedir = get_user_conf_dir();
- SPM_GLOBAL.user_config_file = get_user_config_file();
- if (SPM_GLOBAL.user_config_file) {
- SPM_GLOBAL.config = config_read(SPM_GLOBAL.user_config_file);
- }
-
- ConfigItem *item = NULL;
-
- // Initialize temp directory
- item = config_get(SPM_GLOBAL.config, "tmp_dir");
- if (item) {
- SPM_GLOBAL.tmp_dir = item->value;
- if (access(SPM_GLOBAL.tmp_dir, F_OK) != 0) {
- if (mkdirs(SPM_GLOBAL.tmp_dir, 0755) != 0) {
- fprintf(stderr, "Unable to create global temporary directory: %s\n", SPM_GLOBAL.tmp_dir);
- fprintf(SYSERROR);
- exit(1);
- }
- }
- }
- else {
- SPM_GLOBAL.tmp_dir = get_user_tmp_dir();
- }
-
- // Initialize package directory
- item = config_get(SPM_GLOBAL.config, "package_dir");
- if (item) {
- SPM_GLOBAL.package_dir = item->value;
- if (access(SPM_GLOBAL.package_dir, F_OK) != 0) {
- if (mkdirs(SPM_GLOBAL.package_dir, 0755) != 0) {
- fprintf(stderr, "Unable to create global package directory: %s\n", SPM_GLOBAL.package_dir);
- fprintf(SYSERROR);
- exit(1);
- }
- }
- }
- else {
- SPM_GLOBAL.package_dir = get_user_package_dir();
- }
-}
-
-void free_global_config(void) {
- if (SPM_GLOBAL.package_dir) {
- free(SPM_GLOBAL.package_dir);
- }
- if (SPM_GLOBAL.tmp_dir) {
- free(SPM_GLOBAL.tmp_dir);
- }
- if (SPM_GLOBAL.user_config_basedir) {
- free(SPM_GLOBAL.user_config_basedir);
- }
- if (SPM_GLOBAL.user_config_file) {
- free(SPM_GLOBAL.user_config_file);
- }
- if (SPM_GLOBAL.config) {
- config_free(SPM_GLOBAL.config);
- }
-}
-
-void show_global_config(void) {
- printf("#---------------------------\n");
- printf("#---- SPM CONFIGURATION ----\n");
- printf("#---------------------------\n");
- printf("# base dir: %s\n", SPM_GLOBAL.user_config_basedir ? SPM_GLOBAL.user_config_basedir : "none (check write permission on home directory)");
- printf("# config file: %s\n", SPM_GLOBAL.user_config_file ? SPM_GLOBAL.user_config_file : "none");
- if (SPM_GLOBAL.user_config_file) {
- printf("# config file contents:\n");
- for (int i = 0; SPM_GLOBAL.config[i] != NULL; i++) {
- printf("# -> %s: %s\n", SPM_GLOBAL.config[i]->key, SPM_GLOBAL.config[i]->value);
- }
- }
- printf("# package storage: %s\n", SPM_GLOBAL.package_dir);
- printf("# temp storage: %s\n", SPM_GLOBAL.tmp_dir);
- printf("\n");
-}
-
-/**
- * Basic rsync wrapper for copying files
- * @param _args arguments to pass to rsync (set to `NULL` for default options)
- * @param _source source file or directory
- * @param _destination destination file or directory
- * @return success=0, failure=-1
- */
-int rsync(const char *_args, const char *_source, const char *_destination) {
- int returncode;
- Process *proc = NULL;
- char *args = NULL;
- if (_args) {
- args = strdup(_args);
- }
- char *source = strdup(_source);
- char *destination = strdup(_destination);
- char cmd[PATH_MAX];
- char args_combined[PATH_MAX];
-
- memset(cmd, '\0', sizeof(cmd));
- memset(args_combined, '\0', sizeof(args_combined));
- strcpy(args_combined, "--archive --hard-links ");
- if (args) {
- strcat(args_combined, _args);
- }
-
- sprintf(cmd, "rsync %s \"%s\" \"%s\"", args_combined, source, destination);
- // sanitize command
- strchrdel(cmd, "&;|");
- shell(&proc, SHELL_OUTPUT, cmd);
- if (!proc) {
- if (args) {
- free(args);
- }
- free(source);
- free(destination);
- return -1;
- }
-
- returncode = proc->returncode;
- if (returncode != 0 && proc->output) {
- fprintf(stderr, proc->output);
- }
- shell_free(proc);
-
- if (args) {
- free(args);
- }
- free(source);
- free(destination);
- return returncode;
-}
-
-int relocate(const char *_filename, const char *_oldstr, const char *_newstr) {
- int returncode;
- Process *proc = NULL;
- char *oldstr = strdup(_oldstr);
- char *newstr = strdup(_newstr);
- char *filename = strdup(_filename);
- char cmd[PATH_MAX];
-
- memset(cmd, '\0', sizeof(cmd));
- sprintf(cmd, "reloc \"%s\" \"%s\" \"%s\" \"%s\"", oldstr, newstr, filename, filename);
-
- // sanitize command
- strchrdel(cmd, "&;|");
-
- shell(&proc, SHELL_OUTPUT, cmd);
- if (!proc) {
- free(oldstr);
- free(newstr);
- free(filename);
- return -1;
- }
-
- returncode = proc->returncode;
- if (returncode != 0 && proc->output) {
- fprintf(stderr, proc->output);
- }
-
- shell_free(proc);
- free(oldstr);
- free(newstr);
- free(filename);
- return returncode;
-}
int main(int argc, char *argv[]) {
// not much to see here yet
@@ -1712,15 +18,19 @@ int main(int argc, char *argv[]) {
// Install a package to test things out
const char *root = "/tmp/root";
- const char *package = "python-pip";
- Dependencies *deps;
+ const char *package = "python";
+
+ Dependencies *deps = NULL;
dep_init(&deps);
- depends_all(&deps, package);
+ dep_all(&deps, package);
+ printf("%s requires:\n", package);
dep_show(&deps);
+ // Install dependencies first
for (int i = 0; i < deps->records; i++) {
install(root, deps->list[i]);
}
+ // Install package
install(root, package);
dep_free(&deps);
diff --git a/spm.h b/spm.h
index f8eab43..52b791c 100644
--- a/spm.h
+++ b/spm.h
@@ -69,12 +69,6 @@ typedef struct {
ConfigItem **config;
struct utsname sysinfo;
} spm_vars;
-static spm_vars SPM_GLOBAL;
-
-typedef struct {
- int count;
- char **paths;
-} Dirwalk;
typedef struct {
struct timespec start_time, stop_time;
@@ -88,49 +82,61 @@ typedef struct {
char *path;
} RelocationEntry;
+// GLOBALS
+spm_vars SPM_GLOBAL;
+
+// shell.c
void shell(Process **proc_info, u_int64_t option, const char *fmt, ...);
void shell_free(Process *proc_info);
+
+// archive.c
int tar_extract_archive(const char *_archive, const char *_destination);
int tar_extract_file(const char *archive, const char* filename, const char *destination);
-int rsync(const char *_args, const char *_source, const char *_destination);
+
+// relocation.c
int relocate(const char *filename, const char *_oldstr, const char *_newstr);
-int file_replace_text(const char *filename, const char *oldstr, const char *newstr);
+int replace_text(char *data, const char *_spattern, const char *_sreplacement);
+int file_replace_text(char *filename, const char *spattern, const char *sreplacement);
RelocationEntry **prefixes_read(const char *filename);
void prefixes_free(RelocationEntry **entry);
-int file_replace_text(const char *filename, const char *oldstr, const char *newstr);
+// strings.c
int num_chars(const char *sptr, int ch);
int startswith(const char *sptr, const char *pattern);
int endswith(const char *sptr, const char *pattern);
char *normpath(const char *path);
void strchrdel(char *sptr, const char *chars);
long int strchroff(const char *sptr, int ch);
-void substrdel(char *sptr, const char *suffix);
+void strdelsuffix(char *sptr, const char *suffix);
char** split(char *sptr, const char* delim);
void split_free(char **ptr);
char *substring_between(char *sptr, const char *delims);
static int _strsort_compare(const void *a, const void *b);
void strsort(char **arr);
-int fstrstr(const char *filename, const char *pattern);
+int find_in_file(const char *filename, const char *pattern);
+// find.c
char *find_executable(const char *program);
char *find_file(const char *root, const char *filename);
char *find_package(const char *filename);
int errglob(const char *epath, int eerrno);
+// rpath.c
Process *patchelf(const char *_filename, const char *_args);
-char *libdir_nearest(const char *filename);
+char *rpath_autodetect(const char *filename);
int has_rpath(const char *_filename);
-char *get_rpath(const char *_filename);
-char *gen_rpath(const char *_filename);
-int set_rpath(const char *filename, char *_rpath);
+char *rpath_get(const char *_filename);
+char *rpath_generate(const char *_filename);
+int rpath_set(const char *filename, char *_rpath);
-void walkdir(char *dirpath, Dirwalk **result, unsigned int dirs);
+// fs.c
long int get_file_size(const char *filename);
int mkdirs(const char *_path, mode_t mode);
char *dirname(const char *_path);
char *basename(char *path);
+int rsync(const char *_args, const char *_source, const char *_destination);
+// config_global.c
char *get_user_conf_dir(void);
char *get_user_config_file(void);
char *get_user_tmp_dir(void);
@@ -141,6 +147,7 @@ void free_global_config(void);
void show_global_config(void);
void check_runtime_environment(void);
+// install.c
int install(const char *destroot, const char *_package);
// config.c
@@ -162,6 +169,7 @@ int dep_init(Dependencies **deps);
void dep_free(Dependencies **deps);
int dep_append(Dependencies **deps, char *name);
int dep_solve(Dependencies **deps, const char *filename);
+void dep_all(Dependencies **deps, const char *_package);
void dep_show(Dependencies **deps);
// fstree.c
diff --git a/strings.c b/strings.c
new file mode 100644
index 0000000..ad784d1
--- /dev/null
+++ b/strings.c
@@ -0,0 +1,431 @@
+#include "spm.h"
+
+/**
+ * Determine how many times the character `ch` appears in `sptr` string
+ * @param sptr string to scan
+ * @param ch character to find
+ * @return count of characters found
+ */
+int num_chars(const char *sptr, int ch) {
+ int result = 0;
+ for (int i = 0; sptr[i] != '\0'; i++) {
+ if (sptr[i] == ch) {
+ result++;
+ }
+ }
+ return result;
+}
+
+/**
+ * Scan for `pattern` string at the beginning of `sptr`
+ *
+ * @param sptr string to scan
+ * @param pattern string to search for
+ * @return 0 = success, -1 = failure
+ */
+int startswith(const char *sptr, const char *pattern) {
+ for (size_t i = 0; i < strlen(pattern); i++) {
+ if (sptr[i] != pattern[i]) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Scan for `pattern` string at the end of `sptr`
+ *
+ * @param sptr string to scan
+ * @param pattern string to search for
+ * @return 0 = success, -1 = failure
+ */
+int endswith(const char *sptr, const char *pattern) {
+ size_t sptr_size = strlen(sptr);
+ size_t pattern_size = strlen(pattern);
+ for (size_t s = sptr_size - pattern_size, p = 0 ; s < sptr_size; s++, p++) {
+ if (sptr[s] != pattern[p]) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Deletes any characters matching `chars` from `sptr` string
+ *
+ * @param sptr string to be modified in-place
+ * @param chars a string containing characters (e.g. " \n" would delete whitespace and line feeds)
+ */
+void strchrdel(char *sptr, const char *chars) {
+ while (*sptr != '\0') {
+ for (int i = 0; chars[i] != '\0'; i++) {
+ if (*sptr == chars[i]) {
+ memmove(sptr, sptr + 1, strlen(sptr));
+ }
+ }
+ sptr++;
+ }
+}
+
+/**
+ * Find the integer offset of the first occurrence of `ch` in `sptr`
+ *
+ * ~~~{.c}
+ * char buffer[255];
+ * char string[] = "abc=123";
+ * long int separator_offset = strchroff(string, '=');
+ * for (long int i = 0; i < separator_offset); i++) {
+ * buffer[i] = string[i];
+ * }
+ * ~~~
+ *
+ * @param sptr string to scan
+ * @param ch character to find
+ * @return offset to character in string, or 0 on failure
+ */
+long int strchroff(const char *sptr, int ch) {
+ char *orig = strdup(sptr);
+ char *tmp = orig;
+ long int result = 0;
+ while (*tmp != '\0') {
+ if (*tmp == ch) {
+ break;
+ }
+ tmp++;
+ }
+ result = tmp - orig;
+ free(orig);
+
+ return result;
+}
+
+/**
+ * This function scans `sptr` from right to left removing any matches to `suffix`
+ * from the string.
+ *
+ * @param sptr string to be modified
+ * @param suffix string to be removed from `sptr`
+ */
+void strdelsuffix(char *sptr, const char *suffix) {
+ if (!sptr || !suffix) {
+ return;
+ }
+ size_t sptr_len = strlen(sptr);
+ size_t suffix_len = strlen(suffix);
+ intptr_t target_offset = sptr_len - suffix_len;
+
+ // Prevent access to memory below input string
+ if (target_offset < 0) {
+ return;
+ }
+
+ // Create a pointer to
+ char *target = sptr + target_offset;
+ if (!strcmp(target, suffix)) {
+ // Purge the suffix
+ memset(target, '\0', suffix_len);
+ // Recursive call continues removing suffix until it is gone
+ strip(sptr);
+ }
+}
+
+/**
+ * Split a string by every delimiter in `delim` string.
+ *
+ * Callee must free memory using `split_free()`
+ *
+ * @param sptr string to split
+ * @param delim characters to split on
+ * @return success=parts of string, failure=NULL
+ */
+char** split(char *_sptr, const char* delim)
+{
+ size_t split_alloc = 0;
+ // Duplicate the input string and save a copy of the pointer to be freed later
+ char *orig = strdup(_sptr);
+ char *sptr = orig;
+ if (!sptr) {
+ return NULL;
+ }
+
+ // Determine how many delimiters are present
+ for (size_t i = 0; i < strlen(delim); i++) {
+ split_alloc += num_chars(sptr, delim[i]);
+ }
+ // Preallocate enough records based on the number of delimiters
+ char **result = (char **)calloc(split_alloc + 2, sizeof(char *));
+ if (!result) {
+ free(sptr);
+ return NULL;
+ }
+
+ // Separate the string into individual parts and store them in the result array
+ int i = 0;
+ char *token = NULL;
+ while((token = strsep(&sptr, delim)) != NULL) {
+ result[i] = (char *)calloc(1, sizeof(char) * strlen(token) + 1);
+ if (!result[i]) {
+ free(sptr);
+ return NULL;
+ }
+ strncpy(result[i], token, strlen(token)); // copy the string contents into the record
+ i++; // next record
+ }
+ free(orig);
+ return result;
+}
+
+/**
+ * Frees memory allocated by `split()`
+ * @param ptr pointer to array
+ */
+void split_free(char **ptr) {
+ for (int i = 0; ptr[i] != NULL; i++) {
+ free(ptr[i]);
+ }
+ free(ptr);
+}
+
+/**
+ * Extract the string encapsulated by characters listed in `delims`
+ *
+ * ~~~{.c}
+ * char *str = "this is [some data] in a string";
+ * char *data = substring_between(string, "[]");
+ * // data = "some data";
+ * ~~~
+ *
+ * @param sptr string to parse
+ * @param delims two characters surrounding a string
+ * @return success=text between delimiters, failure=NULL
+ */
+char *substring_between(char *sptr, const char *delims) {
+ // Ensure we have enough delimiters to continue
+ size_t delim_count = strlen(delims);
+ if (delim_count != 2) {
+ return NULL;
+ }
+
+ // Create pointers to the delimiters
+ char *start = strpbrk(sptr, &delims[0]);
+ char *end = strpbrk(sptr, &delims[1]);
+
+ // Ensure the string has both delimiters
+ if (!start || !end) {
+ return NULL;
+ }
+
+ start++; // ignore leading delimiter
+
+ // Get length of the substring
+ size_t length = end - start;
+
+ char *result = (char *)calloc(length + 1, sizeof(char));
+ if (!result) {
+ return NULL;
+ }
+
+ // Copy the contents of the substring to the result
+ char *tmp = result;
+ while (start != end) {
+ *tmp = *start;
+ tmp++;
+ start++;
+ }
+
+ return result;
+}
+
+/*
+ * Helper function for `strsort`
+ */
+static int _strsort_compare(const void *a, const void *b) {
+ const char *aa = *(const char**)a;
+ const char *bb = *(const char**)b;
+ int result = strcmp(aa, bb);
+ return result;
+}
+
+/**
+ * Sort an array of strings alphabetically
+ * @param arr
+ */
+void strsort(char **arr) {
+ size_t arr_size = 0;
+
+ // Determine size of array
+ for (size_t i = 0; arr[i] != NULL; i++) {
+ arr_size = i;
+ }
+ qsort(arr, arr_size, sizeof(char *), _strsort_compare);
+}
+
+/*
+ * Helper function for `strsortlen`
+ */
+static int _strsortlen_asc_compare(const void *a, const void *b) {
+ const char *aa = *(const char**)a;
+ const char *bb = *(const char**)b;
+ size_t len_a = strlen(aa);
+ size_t len_b = strlen(bb);
+ return len_a > len_b;
+}
+
+/*
+ * Helper function for `strsortlen`
+ */
+static int _strsortlen_dsc_compare(const void *a, const void *b) {
+ const char *aa = *(const char**)a;
+ const char *bb = *(const char**)b;
+ size_t len_a = strlen(aa);
+ size_t len_b = strlen(bb);
+ return len_a < len_b;
+}
+/**
+ * Sort an array of strings by length
+ * @param arr
+ */
+void strsortlen(char **arr, unsigned int sort_mode) {
+ typedef int (*compar)(const void *, const void *);
+
+ compar fn = _strsortlen_asc_compare;
+ if (sort_mode != 0) {
+ fn = _strsortlen_dsc_compare;
+ }
+
+ size_t arr_size = 0;
+
+ // Determine size of array
+ for (size_t i = 0; arr[i] != NULL; i++) {
+ arr_size = i;
+ }
+ qsort(arr, arr_size, sizeof(char *), fn);
+}
+
+/**
+ * Search for string in an array of strings
+ * @param arr array of strings
+ * @param str string to search for
+ * @return yes=0, no=1, failure=-1
+ */
+int strstr_array(char **arr, const char *str) {
+ if (!arr) {
+ return -1;
+ }
+
+ for (int i = 0; arr[i] != NULL; i++) {
+ if (strstr(arr[i], str) != NULL) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/**
+ * Remove duplicate strings from an array of strings
+ * @param arr
+ * @return success=array of unique strings, failure=NULL
+ */
+char **strdeldup(char **arr) {
+ if (!arr) {
+ return NULL;
+ }
+
+ int records;
+ // Determine the length of the array
+ for (records = 0; arr[records] != NULL; records++);
+
+ // Allocate enough memory to store the original array contents
+ // (It might not have duplicate values, for example)
+ char **result = (char **)calloc(records + 1, sizeof(char *));
+ if (!result) {
+ return NULL;
+ }
+
+ int rec = 0;
+ int i = 0;
+ while(i < records) {
+ // Search for value in results
+ if (strstr_array(result, arr[i]) == 0) {
+ // value already exists in results so ignore it
+ i++;
+ continue;
+ }
+
+ // Store unique value
+ result[rec] = (char *)calloc(strlen(arr[i]) + 1, sizeof(char));
+ if (!result[rec]) {
+ free(result);
+ return NULL;
+ }
+ strncpy(result[rec], arr[i], strlen(arr[i]));
+ i++;
+ rec++;
+ }
+ return result;
+}
+
+/** Remove leading whitespace from a string
+ * @param sptr pointer to string
+ * @return pointer to first non-whitespace character in string
+ */
+char *lstrip(char *sptr) {
+ char *tmp = sptr;
+ size_t bytes = 0;
+ while (isblank(*tmp)) {
+ bytes++;
+ tmp++;
+ }
+ if (tmp != sptr) {
+ memmove(sptr, sptr + bytes, strlen(sptr) - bytes);
+ memset((sptr + strlen(sptr)) - bytes, '\0', bytes);
+ }
+ return sptr;
+}
+
+/**
+ * Remove trailing whitespace from a string
+ * @param sptr string
+ * @return truncated string
+ */
+char *strip(char *sptr) {
+ if (!strlen(sptr)) {
+ return sptr;
+ }
+ strchrdel(sptr, " \r\n");
+ return sptr;
+}
+
+/**
+ * Determine if a string is empty
+ * @param sptr pointer to string
+ * @return 0=not empty, 1=empty
+ */
+int isempty(char *sptr) {
+ char *tmp = sptr;
+ while (*tmp) {
+ if (!isblank(*tmp)) {
+ return 0;
+ }
+ tmp++;
+ }
+ return 1;
+}
+
+/**
+ * Determine if a string is encapsulated by quotes
+ * @param sptr pointer to string
+ * @return 0=not quoted, 1=quoted
+ */
+int isquoted(char *sptr) {
+ const char *quotes = "'\"";
+ char *quote_open = strpbrk(sptr, quotes);
+ if (!quote_open) {
+ return 0;
+ }
+ char *quote_close = strpbrk(quote_open + 1, quotes);
+ if (!quote_close) {
+ return 0;
+ }
+ return 1;
+}