Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 93 additions & 4 deletions ds4.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <float.h>
#include <inttypes.h>
#include <ctype.h>
#include <limits.h>
#include <math.h>
#include <pthread.h>
#include <stdbool.h>
Expand All @@ -36,6 +37,10 @@

#include "ds4.h"

#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif

#ifndef DS4_NO_GPU
#include "ds4_gpu.h"
#endif
Expand Down Expand Up @@ -483,6 +488,87 @@ static char *ds4_strdup(const char *s) {
return p;
}

static bool ds4_path_is_absolute(const char *path) {
return path && path[0] == '/';
}

static char *ds4_executable_path(void) {
#ifdef __APPLE__
uint32_t size = 0;
(void)_NSGetExecutablePath(NULL, &size);
if (size == 0) return NULL;
char *buf = xmalloc((size_t)size + 1);
if (_NSGetExecutablePath(buf, &size) != 0) {
free(buf);
return NULL;
}
buf[size] = '\0';
char *resolved = realpath(buf, NULL);
if (resolved) {
free(buf);
return resolved;
}
return buf;
#elif defined(__linux__)
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
char buf[PATH_MAX];
ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
if (n <= 0 || (size_t)n >= sizeof(buf) - 1) return NULL;
buf[n] = '\0';
return ds4_strdup(buf);
#else
return NULL;
#endif
}

static char *ds4_dirname_dup(const char *path) {
if (!path || !path[0]) return NULL;
const char *slash = strrchr(path, '/');
if (!slash) return ds4_strdup(".");
size_t n = slash == path ? 1u : (size_t)(slash - path);
char *dir = xmalloc(n + 1);
memcpy(dir, path, n);
dir[n] = '\0';
return dir;
}

static char *ds4_join_path(const char *dir, const char *rel) {
size_t dir_len = strlen(dir);
size_t rel_len = strlen(rel);
bool need_slash = dir_len > 0 && dir[dir_len - 1] != '/';
if (dir_len > SIZE_MAX - rel_len - (need_slash ? 2u : 1u)) {
ds4_die("path length overflow");
}
char *out = xmalloc(dir_len + rel_len + (need_slash ? 2u : 1u));
memcpy(out, dir, dir_len);
size_t pos = dir_len;
if (need_slash) out[pos++] = '/';
memcpy(out + pos, rel, rel_len + 1);
return out;
}

char *ds4_resolve_existing_path(const char *path) {
if (!path || !path[0]) return NULL;
if (ds4_path_is_absolute(path)) {
return access(path, F_OK) == 0 ? ds4_strdup(path) : NULL;
}
if (access(path, F_OK) == 0) return ds4_strdup(path);

char *exe = ds4_executable_path();
if (!exe) return NULL;
char *dir = ds4_dirname_dup(exe);
free(exe);
if (!dir) return NULL;

char *candidate = ds4_join_path(dir, path);
free(dir);
if (access(candidate, F_OK) == 0) return candidate;
free(candidate);
return NULL;
}

static void *xrealloc(void *ptr, size_t size) {
ds4_alloc_guard_check("realloc", size);
void *p = realloc(ptr, size);
Expand Down Expand Up @@ -1198,11 +1284,13 @@ static void model_open(ds4_model *m, const char *path, bool metal_mapping,
memset(m, 0, sizeof(*m));
m->fd = -1;

int fd = open(path, O_RDONLY);
if (fd == -1) ds4_die_errno("cannot open model", path);
char *resolved_path = ds4_resolve_existing_path(path);
const char *open_path = resolved_path ? resolved_path : path;
int fd = open(open_path, O_RDONLY);
if (fd == -1) ds4_die_errno("cannot open model", open_path);

struct stat st;
if (fstat(fd, &st) == -1) ds4_die_errno("cannot stat model", path);
if (fstat(fd, &st) == -1) ds4_die_errno("cannot stat model", open_path);
if (st.st_size < 32) ds4_die("model file is too small to be GGUF");

/*
Expand All @@ -1219,7 +1307,8 @@ static void model_open(ds4_model *m, const char *path, bool metal_mapping,
*/
const int mmap_flags = metal_mapping ? MAP_SHARED : MAP_PRIVATE;
void *map = mmap(NULL, (size_t)st.st_size, PROT_READ, mmap_flags, fd, 0);
if (map == MAP_FAILED) ds4_die_errno("cannot mmap model", path);
if (map == MAP_FAILED) ds4_die_errno("cannot mmap model", open_path);
free(resolved_path);

m->fd = fd;
m->map = map;
Expand Down
3 changes: 3 additions & 0 deletions ds4.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ ds4_think_mode ds4_think_mode_for_context(ds4_think_mode mode, int ctx_size);
ds4_context_memory ds4_context_memory_estimate(ds4_backend backend, int ctx_size);
bool ds4_log_is_tty(FILE *fp);
void ds4_log(FILE *fp, ds4_log_type type, const char *fmt, ...);
/* Return a malloc-owned existing path for PATH. Relative paths are resolved
* against the current directory first, then against the executable directory. */
char *ds4_resolve_existing_path(const char *path);
int ds4_engine_generate_argmax(ds4_engine *e, const ds4_tokens *prompt,
int n_predict, int ctx_size,
ds4_token_emit_fn emit,
Expand Down
14 changes: 9 additions & 5 deletions ds4_metal.m
Original file line number Diff line number Diff line change
Expand Up @@ -1238,24 +1238,28 @@ void ds4_gpu_set_quality(bool quality) {
NSString *loaded = nil;
NSString *loaded_path = nil;
for (NSString *path in paths) {
if (![fm fileExistsAtPath:path]) continue;
char *resolved_path = ds4_resolve_existing_path([path UTF8String]);
if (!resolved_path) continue;
NSString *read_path = [NSString stringWithUTF8String:resolved_path];
free(resolved_path);
if (![fm fileExistsAtPath:read_path]) continue;

NSError *error = nil;
loaded = [NSString stringWithContentsOfFile:path
loaded = [NSString stringWithContentsOfFile:read_path
encoding:NSUTF8StringEncoding
error:&error];
if (!loaded) {
fprintf(stderr, "ds4: failed to read Metal source %s: %s\n",
[path UTF8String], [[error localizedDescription] UTF8String]);
[read_path UTF8String], [[error localizedDescription] UTF8String]);
return nil;
}
loaded_path = path;
loaded_path = read_path;
break;
}

if (!loaded) {
fprintf(stderr,
"ds4: Metal source %s not found (set %s to override)\n",
"ds4: Metal source %s not found relative to cwd or executable (set %s to override)\n",
[spec[1] UTF8String], [spec[0] UTF8String]);
return nil;
}
Expand Down
47 changes: 47 additions & 0 deletions tests/ds4_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,52 @@ static void test_server_unit_group(void) {
ds4_server_unit_tests_run();
}

static void test_binary_relative_path_resolution(void) {
char old_cwd[PATH_MAX] = {0};
TEST_ASSERT(getcwd(old_cwd, sizeof(old_cwd)) != NULL);
if (!old_cwd[0]) return;

char *repo_makefile = ds4_resolve_existing_path("Makefile");
TEST_ASSERT(repo_makefile != NULL);
TEST_ASSERT(repo_makefile && access(repo_makefile, F_OK) == 0);
free(repo_makefile);

char tmp_template[] = "/tmp/ds4-path-test-XXXXXX";
char *tmpdir = mkdtemp(tmp_template);
TEST_ASSERT(tmpdir != NULL);
if (!tmpdir) return;
int rc = chdir(tmpdir);
TEST_ASSERT(rc == 0);
if (rc != 0) {
(void)rmdir(tmpdir);
return;
}

const char *local_name = "ds4-local-path-test";
int fd = open(local_name, O_WRONLY | O_CREAT | O_TRUNC, 0600);
TEST_ASSERT(fd >= 0);
if (fd >= 0) close(fd);

char *local = ds4_resolve_existing_path(local_name);
TEST_ASSERT(local != NULL);
TEST_ASSERT(local && strcmp(local, local_name) == 0);
free(local);

char *fallback = ds4_resolve_existing_path("Makefile");
TEST_ASSERT(fallback != NULL);
TEST_ASSERT(fallback && fallback[0] == '/');
TEST_ASSERT(fallback && access(fallback, F_OK) == 0);
free(fallback);

char *missing = ds4_resolve_existing_path("ds4-missing-path-test");
TEST_ASSERT(missing == NULL);
free(missing);

TEST_ASSERT(unlink(local_name) == 0);
TEST_ASSERT(chdir(old_cwd) == 0);
TEST_ASSERT(rmdir(tmpdir) == 0);
}

typedef void (*test_fn)(void);

typedef struct {
Expand All @@ -581,6 +627,7 @@ static const ds4_test_entry test_entries[] = {
{"--metal-kernels", "metal-kernels", "isolated Metal kernel numeric regressions", test_metal_f16_matvec_fast_nr0_4},
#endif
{"--server", "server", "server parser/rendering/cache unit tests", test_server_unit_group},
{"--paths", "paths", "binary-relative path resolution unit tests", test_binary_relative_path_resolution},
};

static void test_print_help(const char *prog) {
Expand Down