diff options
author | Joseph Hunkeler <jhunkeler@users.noreply.github.com> | 2020-04-24 15:29:05 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-24 15:29:05 -0400 |
commit | 338abde356efcae6cf0a000b71b781d21c8733b6 (patch) | |
tree | b275d303c93662ea4879fdca4597aa21e1c9421c | |
parent | d78903961ee95376bccde7c1313be5b583911ab7 (diff) | |
parent | 24992f1f426111f0ce05df341087a55486ae48db (diff) | |
download | spmc-338abde356efcae6cf0a000b71b781d21c8733b6.tar.gz |
Merge pull request #30 from jhunkeler/shlib-macos
Shlib macos
-rw-r--r-- | include/error_handler.h | 1 | ||||
-rw-r--r-- | include/shlib.h | 8 | ||||
-rw-r--r-- | include/spm.h | 22 | ||||
-rw-r--r-- | lib/config_global.c | 6 | ||||
-rw-r--r-- | lib/error_handler.c | 1 | ||||
-rw-r--r-- | lib/extern/url.c | 2 | ||||
-rw-r--r-- | lib/internal_cmd.c | 4 | ||||
-rw-r--r-- | lib/mime.c | 2 | ||||
-rw-r--r-- | lib/shlib.c | 84 | ||||
-rw-r--r-- | tests/framework.h | 1 | ||||
-rw-r--r-- | tests/test_error_handler_spm_strerror.c | 4 | ||||
-rw-r--r-- | tests/test_error_handler_spmerrno_cause.c | 4 | ||||
-rw-r--r-- | tests/test_shlib_spm_shlib_deps.c | 73 |
13 files changed, 183 insertions, 29 deletions
diff --git a/include/error_handler.h b/include/error_handler.h index 92705fe..e58698e 100644 --- a/include/error_handler.h +++ b/include/error_handler.h @@ -16,6 +16,7 @@ #define SPM_ERR_PKG_FETCH _SPM_ERR(6) // failed to download package #define SPM_ERR_MANIFEST_INVALID _SPM_ERR(7) // manifest file is invalid (no header) #define SPM_ERR_MANIFEST_EMPTY _SPM_ERR(8) // manifest file has no data +#define SPM_ERR_PARSE _SPM_ERR(9) // general parsing error extern int spmerrno; extern const char *SPM_ERR_STRING[]; diff --git a/include/shlib.h b/include/shlib.h index e0ccb5b..68b8487 100644 --- a/include/shlib.h +++ b/include/shlib.h @@ -4,13 +4,13 @@ #ifndef SPM_SHLIB_H #define SPM_SHLIB_H -#if defined(_MSC_VER) +#if OS_WINDOWS && defined(_MSC_VER) #define SPM_SHLIB_EXEC "dumpbin" #define SPM_SHLIB_EXEC_ARGS "/dependents" #define SPM_SHLIB_EXTENSION ".dll" -#elif defined(__APPLE__) && defined(__MACH__) -#define SPM_SHLIB_EXEC "/usr/bin/otool" -#define SPM_SHLIB_EXEC_ARGS "-l" +#elif OS_APPLE +#define SPM_SHLIB_EXEC "/usr/bin/objdump" +#define SPM_SHLIB_EXEC_ARGS "-macho -p" #define SPM_SHLIB_EXTENSION ".dylib" #else // linux (hopefully) #define SPM_SHLIB_EXEC "/usr/bin/objdump" diff --git a/include/spm.h b/include/spm.h index f9b2201..5382378 100644 --- a/include/spm.h +++ b/include/spm.h @@ -4,6 +4,22 @@ #ifndef SPM_SPM_H #define SPM_SPM_H +// Define some platform detection shortcuts +#define OS_DARWIN 0 +#define OS_WINDOWS 0 +#define OS_LINUX 0 + +#if defined(__APPLE__) && defined(__MACH__) +#undef OS_DARWIN +#define OS_DARWIN 1 +#elif defined(_WIN32) +#undef OS_WINDOWS +#define OS_WINDOWS 1 +#elif defined(__linux) || defined(__linux__) +#undef OS_LINUX +#define OS_LINUX 1 +#endif + #include <ctype.h> #include <dirent.h> #include <errno.h> @@ -19,7 +35,7 @@ #include <openssl/md5.h> #include <openssl/sha.h> -#if !defined(_WIN32) +#if !OS_WINDOWS #include <fts.h> #include <glob.h> #include <unistd.h> @@ -65,7 +81,7 @@ extern spm_vars SPM_GLOBAL; #define DIRSEPS_UNIX "/" #define PATHSEP_UNIX ';' #define PATHSEPS_UNIX ";" -#if defined(_WIN32) +#if OS_WINDOWS #define DIRSEP DIRSEP_WIN32 #define DIRSEPS DIRSEPS_WIN32 #define NOT_DIRSEP DIRSEP_UNIX @@ -98,7 +114,7 @@ _1________________________________________________" // GLOBALS -#ifdef __APPLE__ +#if OS_DARWIN extern char **environ; #define __environ environ #endif diff --git a/lib/config_global.c b/lib/config_global.c index 52589d4..95302c3 100644 --- a/lib/config_global.c +++ b/lib/config_global.c @@ -129,11 +129,11 @@ void check_runtime_environment(void) { int bad_rt = 0; char *required[] = { "file", -#if defined(__linux) || defined(__linux__) +#if OS_LINUX "patchelf", -#elif defined(__APPLE__) && defined(__MACH__) +#elif OS_DARWIN "install_name_tool", -#elif defined(__WIN32__) +#elif OS_WINDOWS // TODO: Does windows provide some kind of equivalent? #endif "objdump", diff --git a/lib/error_handler.c b/lib/error_handler.c index cb2ef51..aa48274 100644 --- a/lib/error_handler.c +++ b/lib/error_handler.c @@ -13,6 +13,7 @@ const char *SPM_ERR_STRING[] = { "Failed to fetch package", "Manifest has no header", "Manifest has no data", + "Parsing error", NULL, }; diff --git a/lib/extern/url.c b/lib/extern/url.c index fe54db2..c39b314 100644 --- a/lib/extern/url.c +++ b/lib/extern/url.c @@ -137,7 +137,7 @@ static int fill_buffer(URL_FILE *file, size_t want) { curl_multi_fdset() doc. */ if (maxfd == -1) { -#ifdef _WIN32 +#ifdef _WIN32 // OS_WINDOWS not available at this level Sleep(100); rc = 0; #else diff --git a/lib/internal_cmd.c b/lib/internal_cmd.c index e5f875f..6f6b1c6 100644 --- a/lib/internal_cmd.c +++ b/lib/internal_cmd.c @@ -237,9 +237,9 @@ int mkruntime_interface(int argc, char **argv) { } runtime_set(rt, "CFLAGS", "-I$SPM_INCLUDE $CFLAGS"); -#if defined(__APPLE__) && defined (__MACH__) +#if OS_DARWIN runtime_set(rt, "LDFLAGS", "-rpath $SPM_LIB:$SPM_LIB64 -L$SPM_LIB -L$SPM_LIB64 $LDFLAGS"); -#elif defined(__linux) || defined (__linux__) +#elif OS_LINUX runtime_set(rt, "LDFLAGS", "-Wl,-rpath=$SPM_LIB:$SPM_LIB64 -L$SPM_LIB -L$SPM_LIB64 $LDFLAGS"); #else // TODO: Windows? @@ -14,7 +14,7 @@ Process *file_command(const char *_filename) { Process *proc_info = NULL; char sh_cmd[PATH_MAX]; sh_cmd[0] = '\0'; -#ifdef __APPLE__ +#if OS_DARWIN const char *fmt_cmd = "file -I \"%s\" 2>&1"; #else // GNU const char *fmt_cmd = "file -i \"%s\" 2>&1"; diff --git a/lib/shlib.c b/lib/shlib.c index a8222af..04dbb49 100644 --- a/lib/shlib.c +++ b/lib/shlib.c @@ -9,13 +9,19 @@ char *shlib_deps_objdump(const char *_filename) { char cmd[PATH_MAX]; memset(cmd, '\0', sizeof(cmd)); + if (_filename == NULL) { + spmerrno = EINVAL; + spmerrno_cause("_filename was NULL"); + return NULL; + } + if ((filename = strdup(_filename)) == NULL) { fprintf(SYSERROR); return NULL; } strchrdel(filename, SHELL_INVALID); - snprintf(cmd, sizeof(cmd), "%s %s '%s'", SPM_SHLIB_EXEC, "-p", filename); + snprintf(cmd, sizeof(cmd), "%s %s '%s'", SPM_SHLIB_EXEC, SPM_SHLIB_EXEC_ARGS, filename); shell(&proc, SHELL_OUTPUT, cmd); if (proc->returncode != 0) { @@ -32,41 +38,97 @@ char *shlib_deps_objdump(const char *_filename) { StrList *shlib_deps(const char *filename) { char **data = NULL; - char *output = NULL; + char *raw_data = NULL; StrList *result = NULL; + if (filename == NULL) { + spmerrno = EINVAL; + spmerrno_cause("filename was NULL"); + return NULL; + } + // Get output from objdump // TODO: use preprocessor or another function to select the correct shlib_deps_*() in the future - if ((output = shlib_deps_objdump(filename)) == NULL) { + if ((raw_data = shlib_deps_objdump(filename)) == NULL) { return NULL; } // Initialize list array if ((result = strlist_init()) == NULL) { - free(output); + free(raw_data); return NULL; } // Split output into individual lines - if ((data = split(output, "\n")) == NULL) { - free(output); + if ((data = split(raw_data, "\n")) == NULL) { + free(raw_data); strlist_free(result); return NULL; } - // Parse output: - // Collapse whitespace and extract the NEEDED libraries (second field) - // AFAIK when "NEEDED" is present, a string containing the library name is guaranteed to be there + // Collapse all whitespace in each line + // i.e. " stuff things" -> "stuff things" for (size_t i = 0; data[i] != NULL; i++) { data[i] = normalize_space(data[i]); + } + + for (size_t i = 0; data[i] != NULL; i++) { + char **field = NULL; + char reason[255] = {0,}; + +#if OS_LINUX + // Extract the NEEDED libraries (second field) + // AFAIK when "NEEDED" is present, a string containing the library name is guaranteed to be there if (startswith(data[i], "NEEDED")) { - char **field = split(data[i], " "); + if ((field = split(data[i], " ")) == NULL) { + strlist_free(result); + result = NULL; + break; + } + + // record library path + strlist_append(result, field[1]); + split_free(field); + } +#elif OS_DARWIN + size_t offset_name = i + 2; // how many lines to look ahead after reaching LC_LOAD_DYLIB + size_t numLines; + for (numLines = 0; data[numLines] != NULL; numLines++); // get line count + + // Find APPLE's equivalent to NEEDED on Linux + if (startswith(data[i], "cmd LC_LOAD_DYLIB")) { + // Don't overrun the data buffer + if (offset_name > numLines || data[offset_name] == NULL) { + break; + } + + // split on: "name /library/path" + if ((field = split(data[offset_name], " ")) == NULL) { + sprintf(reason, "'%s' produced unreadable output", SPM_SHLIB_EXEC, i, offset_name); + spmerrno = SPM_ERR_PARSE; + spmerrno_cause(reason); + + strlist_free(result); + result = NULL; + break; + } + + // verify it was actually "name ..." + if (strcmp(field[0], "name") != 0) { + sprintf(reason, "'%s' produced unexpected LC_LOAD_DYLIB format between lines %zu:%zu", SPM_SHLIB_EXEC, i, offset_name); + spmerrno = SPM_ERR_PARSE; + spmerrno_cause(reason); + break; + } + + // record library path strlist_append(result, field[1]); split_free(field); } +#endif } - free(output); + free(raw_data); split_free(data); return result; } diff --git a/tests/framework.h b/tests/framework.h index 2a7b268..312e917 100644 --- a/tests/framework.h +++ b/tests/framework.h @@ -2,6 +2,7 @@ #define SPM_FRAMEWORK_H #include <limits.h> #include <fcntl.h> +#pragma GCC diagnostic ignored "-Wunused-parameter" union TestValue { const char *sptr; diff --git a/tests/test_error_handler_spm_strerror.c b/tests/test_error_handler_spm_strerror.c index 2164f93..4a40571 100644 --- a/tests/test_error_handler_spm_strerror.c +++ b/tests/test_error_handler_spm_strerror.c @@ -3,10 +3,10 @@ const char *testFmt = "translated error code '%d': returned '%s', expected '%s'\n"; struct TestCase testCase[] = { -#if defined(__APPLE__) && defined(__MACH__) +#if OS_DARWIN {.caseValue.signed_integer = 0, .truthValue.sptr = "Undefined error: 0", .arg[0].signed_integer = 0}, {.caseValue.signed_integer = -1, .truthValue.sptr = "Unknown error: -1", .arg[0].signed_integer = 0}, -#elif defined(__linux) || defined(__linux__) +#elif OS_LINUX {.caseValue.signed_integer = 0, .truthValue.sptr = "Success", .arg[0].signed_integer = 0}, {.caseValue.signed_integer = -1, .truthValue.sptr = "Unknown error -1", .arg[0].signed_integer = 0}, #endif diff --git a/tests/test_error_handler_spmerrno_cause.c b/tests/test_error_handler_spmerrno_cause.c index c24b996..a8b47a0 100644 --- a/tests/test_error_handler_spmerrno_cause.c +++ b/tests/test_error_handler_spmerrno_cause.c @@ -3,10 +3,10 @@ const char *testFmt = "translated error code '%d': returned '%s', expected '%s'\n"; struct TestCase testCase[] = { -#if defined(__APPLE__) && defined(__MACH__) +#if OS_DARWIN {.caseValue.signed_integer = 0, .truthValue.sptr = "Undefined error: 0 (winning)", .arg[0].signed_integer = 0, .arg[1].sptr = "winning"}, {.caseValue.signed_integer = -1, .truthValue.sptr = "Unknown error: -1 (not winning)", .arg[0].signed_integer = 0, .arg[1].sptr = "not winning"}, -#elif defined(__linux) || defined(__linux__) +#elif OS_LINUX {.caseValue.signed_integer = 0, .truthValue.sptr = "Success (winning)", .arg[0].signed_integer = 0, .arg[1].sptr = "winning"}, {.caseValue.signed_integer = -1, .truthValue.sptr = "Unknown error -1 (not winning)", .arg[0].signed_integer = 0, .arg[1].sptr = "not winning"}, #endif diff --git a/tests/test_shlib_spm_shlib_deps.c b/tests/test_shlib_spm_shlib_deps.c new file mode 100644 index 0000000..4f0bfd4 --- /dev/null +++ b/tests/test_shlib_spm_shlib_deps.c @@ -0,0 +1,73 @@ +#include "spm.h" +#include "framework.h" + +static char *LIBRARY_SEARCH_PATH[] = { +#if OS_DARWIN + "/usr/lib", + "/usr/local/lib", +#elif OS_LINUX + "/lib", + "/usr/lib", + "/usr/local/lib", + "/lib64", + "/usr/lib64", + "/usr/local/lib64", +#endif + NULL, +}; + +/** + * Find a library based on known library search paths. This cannot handle macos-style `@string` paths, and + * will not follow RPATHs. + * @param name + * @return path to library (or NULL) + */ +static char *find_library(const char *name) { + char *path = NULL; + + if (strstr(name, DIRSEPS) != NULL) { + return strdup(name); + } + + for (size_t i = 0; LIBRARY_SEARCH_PATH[i] != NULL; i++) { + path = join_ex(DIRSEPS, LIBRARY_SEARCH_PATH[i], name, NULL); + if (path != NULL && exists(path) == 0) { + break; + } + free(path); + path = NULL; + } + return path; +} + +struct TestCase testCase[] = { + {.caseValue.sptr = "/bin/sh", .truthValue.signed_integer = 0}, + {.caseValue.sptr = "/usr/bin/tar", .truthValue.signed_integer = 0}, + {.caseValue.sptr = "/dev/null", .truthValue.signed_integer = -1}, // not an object + {.caseValue.sptr = NULL, .truthValue.signed_integer = -1}, // invalid call +}; +size_t numCases = sizeof(testCase) / sizeof(struct TestCase); + + +int main(int argc, char *argv[]) { + for (size_t i = 0; i < numCases; i++) { + StrList *result = shlib_deps(testCase[i].caseValue.sptr); + if (result == NULL && testCase[i].truthValue.signed_integer < 0) { + // expected failure + fprintf(stderr, "case %zu: trapped expected failure (ignore any stderr text)\n", i); + continue; + } + + myassert(spmerrno == 0, "case %zu: raised unhandled exception %d: %s\n", i, spmerrno, spm_strerror(spmerrno)); + myassert(result != NULL, "case %zu: unexpected NULL", i); + for (size_t j = 0; j < strlist_count(result); j++) { + char *item = strlist_item(result, j); + char *libpath = find_library(item); + myassert(libpath != NULL, + "library record found, but does not exist: '%s' (your OS is broken)\n", item); + free(libpath); + } + strlist_free(result); + } + return 0; +}
\ No newline at end of file |