/* * Functions for handling UObject package search paths. * Copyright © 2009-2011 Nick Bowler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include "avl.h" /* Check if a character is a directory separator. */ #ifdef LT_DIRSEP_CHAR # define IS_DIRSEP(x) ((x) == '/' || (x) == LT_DIRSEP_CHAR) #else # define IS_DIRSEP(x) ((x) == '/') #endif /* Number of times the library has been initialized. */ static int initialized; /* Local package definitions. */ struct local_pkg { char *file, *name; }; static struct avl_table *local_tree; /* Global package search path. */ static char *search_path; static size_t search_path_sz; /* Package file extensions, in decreasing order of precedence. */ static const char u_pkg_exts[][5] = { ".u", ".utx", ".uax", ".umx", ".unr" }; static int localcmp(const void *_a, const void *_b, void *_data) { const struct local_pkg *a = _a, *b = _b; return strcasecmp(a->name, b->name); } static const char *pkgname_base(const char *file) { const char *base = NULL; int slash = 1; for (size_t i = 0; file[i]; i++) { if (IS_DIRSEP(file[i])) { slash = 1; } else if (slash == 1) { base = file+i; slash = 0; } } return base; } static size_t pkgname_len(const char *base) { size_t i; for (i = 0; base[i]; i++) { if (IS_DIRSEP(base[i]) || base[i] == '.') break; } return i; } const char *u_pkg_vfs_add_local(const char *name, const char *file) { size_t filelen = strlen(file)+1, namelen; struct local_pkg *spec; if (!name) name = pkgname_base(file); if (!name) return NULL; namelen = pkgname_len(name); /* For simplicity, stuff everything in a single allocation. */ spec = malloc(sizeof *spec + filelen + namelen + 1); if (!spec) { return NULL; } spec->file = (char *)spec + sizeof *spec; memcpy(spec->file, file, filelen); spec->name = (char *)spec + sizeof *spec + filelen; memcpy(spec->name, name, namelen); spec->name[namelen] = 0; if (avl_find(local_tree, spec)) { fprintf(stderr, "%s: attempted to add duplicate local package.\n", __func__); name = spec->name; free(spec); return name; /* "Success"-ish. */ } if (avl_probe(local_tree, spec) == NULL) { free(spec); return NULL; } return spec->name; } /* Enlarge the search path buffer so that it can store at least need bytes. */ static int expand_search_path(size_t need) { size_t want = search_path_sz; if (want == 0) want = 1; while (want < need) want *= 2; if (want > search_path_sz) { char *new = realloc(search_path, want); if (!new) { return -1; } search_path = new; search_path_sz = want; } return 0; } const char *u_pkg_vfs_get_search_path(void) { return search_path ? search_path : ""; } int u_pkg_vfs_set_search_path(const char *path) { if (expand_search_path(strlen(path)+1) != 0) return -1; strcpy(search_path, path); return 0; } int u_pkg_vfs_add_search_dir(const char *path) { size_t end = search_path ? strlen(search_path) : 0; if (end == 0) { return u_pkg_vfs_set_search_path(path); } if (expand_search_path(end + strlen(path) + 2) != 0) return -1; search_path[end] = LT_PATHSEP_CHAR; strcpy(search_path+end+1, path); return 0; } void u_pkg_vfs_del_local(const char *name) { struct local_pkg spec = { .name = (char *)name }, *item; item = avl_find(local_tree, &spec); free(item); } struct foreach_state { const char *name; struct upkg *f; size_t sz; char buf[]; }; static int foreachfile(const char *filename, void *_st) { struct foreach_state **st = _st, *tmp; size_t need, len; const char *base; /* Check if the filename matches the package name. */ base = pkgname_base(filename); if (!base || strcasecmp(base, (*st)->name) != 0) return 0; /* Enlarge the state buffer, if necessary. */ need = strlen(filename) + sizeof **u_pkg_exts; if ((*st)->sz < need) { tmp = realloc(*st, sizeof **st + need); if (!tmp) return -1; *st = tmp; (*st)->sz = need; } /* Try each file extension, in order. */ len = sprintf((*st)->buf, "%s", filename); for (unsigned i = 0; i < sizeof u_pkg_exts / sizeof *u_pkg_exts; i++) { strcpy((*st)->buf+len, u_pkg_exts[i]); (*st)->f = upkg_fopen((*st)->buf); if ((*st)->f != NULL) return 1; } return 0; } struct upkg *u_pkg_vfs_open_by_name(const char *name) { struct local_pkg spec = { .name = (char *)name }, *item; struct foreach_state *st; struct upkg *f = NULL; if (!initialized) return NULL; item = avl_find(local_tree, &spec); if (item) return upkg_fopen(item->file); st = malloc(sizeof *st + 256); if (!st) return NULL; *st = (struct foreach_state) { .sz = 256, .name = name }; if (lt_dlforeachfile(u_pkg_vfs_get_search_path(), foreachfile, &st) > 0) f = st->f; free(st); return f; } int u_pkg_vfs_init(void) { if (!initialized) { local_tree = avl_create(localcmp, NULL, NULL); if (!local_tree) { fprintf(stderr, "%s: failed to create local module tree.\n", __func__); return -1; } } initialized++; return 0; } static void local_destroy(void *item, void *data) { free(item); } void u_pkg_vfs_exit(void) { if (--initialized == 0) avl_destroy(local_tree, local_destroy); }