aboutsummaryrefslogtreecommitdiff
path: root/lib/rpath.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/rpath.c
parent3731bb4679ee8716d4f579d5744c36a2d1b4a257 (diff)
downloadspmc-ccaeb7092b5ad40b1b3833c987ba3ec4d47f0bb8.tar.gz
Refactor project: build/install libspm[_static.a].so to make unit testing possible
Diffstat (limited to 'lib/rpath.c')
-rw-r--r--lib/rpath.c303
1 files changed, 303 insertions, 0 deletions
diff --git a/lib/rpath.c b/lib/rpath.c
new file mode 100644
index 0000000..4d4d801
--- /dev/null
+++ b/lib/rpath.c
@@ -0,0 +1,303 @@
+/**
+ * @file rpath.c
+ */
+#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, SHELL_INVALID);
+ strchrdel(filename, SHELL_INVALID);
+ sprintf(sh_cmd, "patchelf %s %s 2>&1", args, filename);
+
+ if (SPM_GLOBAL.verbose > 1) {
+ printf(" EXEC : %s\n", sh_cmd);
+ }
+
+ 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;
+ }
+
+ // sanitize input path
+ strchrdel(filename, SHELL_INVALID);
+
+ 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 strdup("");
+ }
+ char *filename = strdup(_filename);
+ if (!filename) {
+ return NULL;
+ }
+ char *path = strdup(filename);
+ if (!path) {
+ free(filename);
+ return NULL;
+ }
+
+ char *rpath = NULL;
+
+ // sanitize input path
+ strchrdel(path, SHELL_INVALID);
+
+ Process *pe = patchelf(filename, "--print-rpath");
+ if (pe->returncode != 0) {
+ fprintf(stderr, "patchelf error: %s %s\n", path, strip(pe->output));
+ return NULL;
+ }
+
+ 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, FSTree *tree) {
+ char *filename = realpath(_filename, NULL);
+ if (!filename) {
+ return NULL;
+ }
+
+ char *result = rpath_autodetect(filename, tree);
+ if (!result) {
+ free(filename);
+ return NULL;
+ }
+
+ free(filename);
+ return result;
+}
+
+/**
+ * Set the RPATH of an executable
+ * @param filename
+ * @param rpath
+ * @return
+ */
+int rpath_set(const char *filename, const char *rpath) {
+ int returncode = 0;
+ char args[PATH_MAX];
+
+ memset(args, '\0', PATH_MAX);
+ sprintf(args, "--set-rpath '%s'", rpath); // note: rpath requires single-quotes
+ Process *pe = patchelf(filename, args);
+ if (pe != NULL) {
+ returncode = pe->returncode;
+ }
+ shell_free(pe);
+ return returncode;
+}
+
+/**
+ * Automatically detect the nearest lib directory and set the RPATH of an executable
+ * @param filename
+ * @param _rpath
+ * @return
+ */
+int rpath_autoset(const char *filename, FSTree *tree) {
+ int returncode = 0;
+
+ char *rpath_new = rpath_generate(filename, tree);
+ if (!rpath_new) {
+ return -1;
+ }
+
+ returncode = rpath_set(filename, rpath_new);
+ free(rpath_new);
+
+ return returncode;
+}
+
+/**
+ * Find shared libraries in a directory tree
+ *
+ * @param root directory
+ * @return `FSTree`
+ */
+FSTree *rpath_libraries_available(const char *root) {
+ FSTree *tree = fstree(root, (char *[]) {SPM_SHLIB_EXTENSION, NULL}, SPM_FSTREE_FLT_CONTAINS | SPM_FSTREE_FLT_RELATIVE);
+ if (tree == NULL) {
+ perror(root);
+ fprintf(SYSERROR);
+ return NULL;
+ }
+ return tree;
+}
+
+/**
+ * Compute a RPATH based on the location `filename` relative to the shared libraries it requires
+ *
+ * @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, FSTree *tree) {
+ const char *origin = "$ORIGIN";
+ char *rootdir = dirname(filename);
+ char *start = realpath(rootdir, NULL);
+ char *cwd = realpath(".", NULL);
+ char *result = NULL;
+
+ char *visit = NULL; // Current directory
+ char _relative[PATH_MAX] = {0,}; // Generated relative path to lib directory
+ char *relative = _relative; // Pointer to relative path
+ size_t depth_to_root = 0;
+
+ // BUG: Perl dumps its shared library in a strange place.
+ // This function returns `$ORIGIN/../../../CORE` which is not what we want to see.
+ // TODO: We WANT to see this: `$ORIGIN/../lib/perl5/xx.xx.xx/<arch>/CORE` not just the basename()
+
+ // Change directory to the requested root
+ chdir(start);
+
+ // Count the relative path distance between the location of the binary, and the top of the root
+ visit = strdup(start);
+ for (depth_to_root = 0; strcmp(tree->root, visit) != 0; depth_to_root++) {
+ // Copy the current visit pointer
+ char *prev = visit;
+ // Walk back another directory level
+ visit = dirname(visit);
+ // Free previous visit pointer
+ if (prev) free(prev);
+ }
+ free(visit);
+
+ // return to calling directory
+ chdir(cwd);
+
+ StrList *libs = strlist_init();
+ if (libs == NULL) {
+ fprintf(stderr, "failed to initialize library StrList\n");
+ fprintf(SYSERROR);
+ return NULL;
+ }
+
+ StrList *libs_wanted = shlib_deps(filename);
+ if (libs_wanted == NULL) {
+ fprintf(stderr, "failed to retrieve list of share libraries from: %s\n", filename);
+ fprintf(SYSERROR);
+ return NULL;
+ }
+
+ for (size_t i = 0; i < strlist_count(libs_wanted); i++) {
+ // zero out relative path string
+ memset(_relative, '\0', sizeof(_relative));
+ // Get the shared library name we are going to look for in the tree
+ char *shared_library = strlist_item(libs_wanted, i);
+
+ // Is the the shared library in the tree?
+ char *match = NULL;
+ if ((match = dirname(strstr_array(tree->files, shared_library))) != NULL) {
+ // Begin generating the relative path string
+ strcat(relative, origin);
+ strcat(relative, DIRSEPS);
+
+ // Append the number of relative levels to the relative path string
+ if (depth_to_root) {
+ for (size_t d = 0; d < depth_to_root; d++) {
+ strcat(relative, "..");
+ strcat(relative, DIRSEPS);
+ }
+ } else {
+ strcat(relative, "..");
+ strcat(relative, DIRSEPS);
+ }
+ // Append the match to the relative path string
+ strcat(relative, basename(match));
+
+ // Append relative path to array of libraries (if it isn't already in there)
+ if (strstr_array(libs->data, relative) == NULL) {
+ strlist_append(libs, relative);
+ }
+ }
+ }
+
+ // Some programs do not require local libraries provided by SPM (i.e. libc)
+ // Inject "likely" defaults here
+ if (strlist_count(libs) == 0) {
+ strlist_append(libs, "$ORIGIN/../lib");
+ strlist_append(libs, "$ORIGIN/../lib64");
+ }
+
+ // Populate result string
+ result = join(libs->data, ":");
+
+ // Clean up
+ strlist_free(libs);
+ strlist_free(libs_wanted);
+ free(rootdir);
+ free(cwd);
+ free(start);
+ return result;
+}