aboutsummaryrefslogtreecommitdiff
path: root/lib/relocation.c
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2020-03-18 22:25:27 -0400
committerJoseph Hunkeler <jhunkeler@gmail.com>2020-03-18 22:25:27 -0400
commitccaeb7092b5ad40b1b3833c987ba3ec4d47f0bb8 (patch)
treeae167772a9a2343aa77bf8944b56abe853f6a2ec /lib/relocation.c
parent3731bb4679ee8716d4f579d5744c36a2d1b4a257 (diff)
downloadspmc-ccaeb7092b5ad40b1b3833c987ba3ec4d47f0bb8.tar.gz
Refactor project: build/install libspm[_static.a].so to make unit testing possible
Diffstat (limited to 'lib/relocation.c')
-rw-r--r--lib/relocation.c440
1 files changed, 440 insertions, 0 deletions
diff --git a/lib/relocation.c b/lib/relocation.c
new file mode 100644
index 0000000..f22a25d
--- /dev/null
+++ b/lib/relocation.c
@@ -0,0 +1,440 @@
+/**
+ * @file relocation.c
+ */
+#include "spm.h"
+
+const char *METADATA_FILES[] = {
+ SPM_META_DEPENDS,
+ SPM_META_PREFIX_BIN,
+ SPM_META_PREFIX_TEXT,
+ SPM_META_DESCRIPTOR,
+ SPM_META_FILELIST,
+ NULL,
+};
+
+/**
+ * Replace all occurrences of `spattern` with `sreplacement` in `data`
+ *
+ * ~~~{.c}
+ * char *str = (char *)calloc(100, sizeof(char));
+ * strcpy(str, "This are a test.");
+ * replace_text(str, "are", "is");
+ * // str is: "This is a test."
+ * free(str);
+ * ~~~
+ *
+ * @param data string to modify
+ * @param spattern string value to replace
+ * @param sreplacement replacement string value
+ * @return success=0, error=-1
+ */
+int replace_text(char *data, const char *spattern, const char *sreplacement) {
+ if (data == NULL || spattern == NULL || sreplacement == NULL) {
+ return -1;
+ }
+
+ 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: %zu > %zu\n '%s'\n '%s'\n", sreplacement_len, spattern_len, sreplacement, spattern);
+ return -1;
+ }
+
+ while (*tmp != '\0') {
+ if (strncmp(tmp, spattern, spattern_len) == 0) {
+ if (sreplacement_len == 1) {
+ *tmp = *sreplacement;
+ } else {
+ memmove(tmp, sreplacement, sreplacement_len);
+ memmove(tmp + sreplacement_len, tmp + spattern_len, data_len - spattern_len);
+ memset(tmp + sreplacement_len + (data_len - spattern_len), '\0', 1);
+ }
+ }
+ tmp++;
+ }
+ return 0;
+}
+
+/**
+ * Replace all occurrences of `oldstr` in file `path` with `newstr`
+ * @param filename file to modify
+ * @param oldstr string to replace
+ * @param newstr replacement string
+ * @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) {
+ fclose(fp);
+ 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 path to prefix manifest
+ * @return success=array of RelocationEntry, failure=NULL
+ */
+RelocationEntry **prefixes_read(const char *filename) {
+ size_t record_count = 0;
+ size_t parity = 0;
+ FILE *fp = fopen(filename, "r");
+ if (!fp) {
+ fprintf(SYSERROR);
+ return NULL;
+ }
+ RelocationEntry **entry = NULL;
+ char line[BUFSIZ];
+ char *lptr = line;
+ memset(lptr, '\0', BUFSIZ);
+
+ while (fgets(lptr, BUFSIZ, fp) != NULL) {
+ if (isempty(lptr)) {
+ 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: %zu %% 2 = %zu)\n", filename, record_count, parity);
+ return NULL;
+ }
+ record_count /= 2;
+
+ entry = (RelocationEntry **)calloc(record_count + 1, sizeof(RelocationEntry *));
+ if (!entry) {
+ return NULL;
+ }
+ for (size_t 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;
+ size_t i = 0;
+ while (fgets(lptr, BUFSIZ, fp) != NULL) {
+ if (isempty(lptr)) {
+ continue;
+ }
+ if (startswith(lptr, "#")) {
+ 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(lptr) + 1, sizeof(char));
+ if (!entry[i]->prefix) {
+ fclose(fp);
+ return NULL;
+ }
+ strncpy(entry[i]->prefix, lptr, strlen(lptr));
+ // 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(lptr) + 1, sizeof(char));
+ if (!entry[i]->path) {
+ fclose(fp);
+ return NULL;
+ }
+ strncpy(entry[i]->path, lptr, strlen(lptr));
+ entry[i]->path = strip(entry[i]->path);
+ do_path = 0;
+ }
+ i++;
+ }
+ fclose(fp);
+ return entry;
+}
+
+/**
+ * Determine if `filename` is a SPM metadata file
+ *
+ * Example:
+ *
+ * ~~~{.c}
+ * #include "spm.h"
+ *
+ * int main() {
+ * if (file_is_metadata(".SPM_DEPENDS")) {
+ * // file is metadata
+ * } else {
+ * // file is not metadata
+ * }
+ * }
+ * ~~~
+ *
+ * @param filename
+ * @return 0=no, 1=yes
+ */
+int file_is_metadata(const char *filename) {
+ for (size_t i = 0; METADATA_FILES[i] != NULL; i++) {
+ if (strstr(filename, METADATA_FILES[i]) != NULL) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Scan `tree` for files containing `prefix`. Matches are recorded in `output_file` with the following format:
+ *
+ * ~~~
+ * #prefix
+ * path
+ * #prefix
+ * path
+ * #...N
+ * ...N
+ * ~~~
+ *
+ * Example:
+ * ~~~{.c}
+ * char **prefixes = {"/usr", "/var", NULL};
+ * prefixes_write("binary.manifest", PREFIX_WRITE_BIN, prefixes, "/usr/bin");
+ * prefixes_write("text.manifest", PREFIX_WRITE_TEXT, prefixes, "/etc");
+ * ~~~
+ *
+ * @param output_file file path to create
+ * @param mode `PREFIX_WRITE_BIN`, `PREFIX_WRITE_TEXT`
+ * @param prefix array of prefix strings
+ * @param tree directory to scan
+ * @return success=0, failure=1, error=-1
+ */
+int prefixes_write(const char *output_file, int mode, char **prefix, const char *tree) {
+ FILE *fp = fopen(output_file, "w+");
+ if (!fp) {
+ perror(output_file);
+ fprintf(SYSERROR);
+ return -1;
+ }
+
+ char *cwd = getcwd(NULL, PATH_MAX);
+ chdir(tree);
+ {
+ FSTree *fsdata = fstree(".", NULL, SPM_FSTREE_FLT_RELATIVE);
+ if (!fsdata) {
+ fclose(fp);
+ fprintf(SYSERROR);
+ return -1;
+ }
+ for (size_t i = 0; i < fsdata->files_length; i++) {
+ if (file_is_metadata(fsdata->files[i])) {
+ continue;
+ }
+ for (int p = 0; prefix[p] != NULL; p++) {
+ if (find_in_file(fsdata->files[i], prefix[p]) == 0) {
+ int proceed = 0;
+ if (mode == PREFIX_WRITE_BIN) {
+ proceed = file_is_binary(fsdata->files[i]);
+ } else if (mode == PREFIX_WRITE_TEXT) {
+ proceed = file_is_text(fsdata->files[i]);
+ }
+
+ // file_is_* functions return NULL when they encounter anything but a regular file
+ if (!proceed) {
+ continue;
+ }
+ // Record in file
+ fprintf(fp, "#%s\n%s\n", prefix[p], fsdata->files[i]);
+ }
+ }
+ }
+ } chdir(cwd);
+ free(cwd);
+ fclose(fp);
+ return 0;
+}
+
+/**
+ * Wrapper for `reloc` program. Replace text in binary data.
+ * @param _filename
+ * @param _oldstr
+ * @param _newstr
+ * @return
+ */
+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];
+
+ // sanitize command
+ strchrdel(oldstr, SHELL_INVALID);
+ strchrdel(newstr, SHELL_INVALID);
+ strchrdel(filename, SHELL_INVALID);
+
+ memset(cmd, '\0', sizeof(cmd));
+ sprintf(cmd, "reloc \"%s\" \"%s\" \"%s\" \"%s\" 2>&1", oldstr, newstr, filename, filename);
+
+ if (SPM_GLOBAL.verbose > 1) {
+ printf(" EXEC : %s\n", 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, "%s\n", proc->output);
+ }
+
+ shell_free(proc);
+ free(oldstr);
+ free(newstr);
+ free(filename);
+ return returncode;
+}
+
+/**
+ * Parse package metadata and set `baseroot` binaries/text to point to `destroot`.
+ * `baseroot` should be a temporary directory because its contents are modified
+ *
+ * @param destroot
+ * @param baseroot
+ */
+void relocate_root(const char *destroot, const char *baseroot) {
+ RelocationEntry **b_record = NULL;
+ RelocationEntry **t_record = NULL;
+ char cwd[PATH_MAX];
+
+ getcwd(cwd, sizeof(cwd));
+ chdir(baseroot);
+ {
+ FSTree *libs = rpath_libraries_available(".");
+ // Rewrite binary prefixes
+ b_record = prefixes_read(SPM_META_PREFIX_BIN);
+ if (b_record) {
+ for (int i = 0; b_record[i] != NULL; i++) {
+ if (file_is_binexec(b_record[i]->path)) {
+ if (SPM_GLOBAL.verbose) {
+ printf("Relocate RPATH: %s\n", b_record[i]->path);
+ }
+ rpath_autoset(b_record[i]->path, libs);
+ }
+ if (SPM_GLOBAL.verbose) {
+ printf("Relocate DATA : %s\n", b_record[i]->path);
+ }
+ relocate(b_record[i]->path, b_record[i]->prefix, destroot);
+ }
+ }
+
+ // Rewrite text prefixes
+ t_record = prefixes_read(SPM_META_PREFIX_TEXT);
+ if (t_record) {
+ for (int i = 0; t_record[i] != NULL; i++) {
+ if (SPM_GLOBAL.verbose) {
+ printf("Relocate TEXT : %s\n", t_record[i]->path);
+ }
+ if (SPM_GLOBAL.verbose > 1) {
+ printf(" EDIT : '%s' -> '%s'\n", t_record[i]->prefix, destroot);
+ }
+ file_replace_text(t_record[i]->path, t_record[i]->prefix, destroot);
+ }
+ }
+
+ prefixes_free(b_record);
+ prefixes_free(t_record);
+ }
+ chdir(cwd);
+}
+