aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/error_handler.h1
-rw-r--r--include/shlib.h8
-rw-r--r--include/spm.h22
-rw-r--r--lib/config_global.c6
-rw-r--r--lib/error_handler.c1
-rw-r--r--lib/extern/url.c2
-rw-r--r--lib/internal_cmd.c4
-rw-r--r--lib/mime.c2
-rw-r--r--lib/shlib.c84
-rw-r--r--tests/framework.h1
-rw-r--r--tests/test_error_handler_spm_strerror.c4
-rw-r--r--tests/test_error_handler_spmerrno_cause.c4
-rw-r--r--tests/test_shlib_spm_shlib_deps.c73
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?
diff --git a/lib/mime.c b/lib/mime.c
index 9e4bdce..9d0205c 100644
--- a/lib/mime.c
+++ b/lib/mime.c
@@ -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