Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -15,37 +15,27 @@ TCLCONFIG_SH_PATH = $(shell echo 'puts [::tcl::pkgconfig get libdir,install]' | tclsh)/tclConfig.sh endif TCL_CFLAGS = $(shell . $(TCLCONFIG_SH_PATH); echo "$${TCL_INCLUDE_SPEC}") TCL_LIBS = $(shell . $(TCLCONFIG_SH_PATH); echo "$${TCL_LIB_SPEC}") -all: appfs - -appfs: appfs.o - $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfs appfs.o $(LIBS) - -appfs-test: appfs-test.o - $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfs-test appfs-test.o $(LIBS) - -appfs.o: appfs.c appfs.tcl.h - $(CC) $(CPPFLAGS) $(CFLAGS) -o appfs.o -c appfs.c - -appfs-test.o: appfs.c appfs.tcl.h - $(CC) $(CPPFLAGS) $(CFLAGS) -DAPPFS_TEST_DRIVER=1 -o appfs-test.o -c appfs.c - -appfs.tcl.h: appfs.tcl stringify.tcl - ./stringify.tcl appfs.tcl > appfs.tcl.h.new - mv appfs.tcl.h.new appfs.tcl.h - -install: appfs - cp appfs $(bindir) - -test: appfs-test - ./appfs-test +all: appfsd + +appfsd: appfsd.o + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfsd appfsd.o $(LIBS) + +appfsd.o: appfsd.c appfsd.tcl.h + $(CC) $(CPPFLAGS) $(CFLAGS) -o appfsd.o -c appfsd.c + +appfsd.tcl.h: appfsd.tcl stringify.tcl + ./stringify.tcl appfsd.tcl > appfsd.tcl.h.new + mv appfsd.tcl.h.new appfsd.tcl.h + +install: appfsd + cp appfsd $(bindir) clean: - rm -f appfs appfs.o - rm -f appfs-test appfs-test.o - rm -f appfs.tcl.h + rm -f appfsd appfsd.o + rm -f appfsd.tcl.h distclean: clean .PHONY: all test clean distclean install DELETED appfs.c Index: appfs.c ================================================================== --- appfs.c +++ appfs.c @@ -1,1037 +0,0 @@ -#define FUSE_USE_VERSION 26 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define APPFS_CACHEDIR "/tmp/appfs-cache" - -#define APPFS_DEBUG(x...) { fprintf(stderr, "[debug] %s:%i:%s: ", __FILE__, __LINE__, __func__); fprintf(stderr, x); fprintf(stderr, "\n"); } - -static pthread_key_t interpKey; - -struct appfs_thread_data { - sqlite3 *db; - const char *cachedir; - time_t boottime; -}; - -struct appfs_thread_data globalThread; - -typedef enum { - APPFS_OS_UNKNOWN, - APPFS_OS_ALL, - APPFS_OS_LINUX, - APPFS_OS_MACOSX, - APPFS_OS_FREEBSD, - APPFS_OS_OPENBSD, - APPFS_OS_SOLARIS -} appfs_os_t; - -typedef enum { - APPFS_CPU_UNKNOWN, - APPFS_CPU_ALL, - APPFS_CPU_AMD64, - APPFS_CPU_I386, - APPFS_CPU_ARM -} appfs_cpuArch_t; - -typedef enum { - APPFS_PATHTYPE_INVALID, - APPFS_PATHTYPE_FILE, - APPFS_PATHTYPE_DIRECTORY, - APPFS_PATHTYPE_SYMLINK -} appfs_pathtype_t; - -struct appfs_package { - struct appfs_package *_next; - int counter; - - char name[256]; - char version[64]; - char sha1[41]; - char os_str[64]; - char cpuArch_str[64]; - appfs_os_t os; - appfs_cpuArch_t cpuArch; - int isLatest; -}; - -struct appfs_site { - struct appfs_site *_next; - int counter; - - char name[256]; -}; - -struct appfs_children { - struct appfs_children *_next; - int counter; - - char name[256]; -}; - -struct appfs_pathinfo { - appfs_pathtype_t type; - time_t time; - char hostname[256]; - union { - struct { - int childcount; - } dir; - struct { - int executable; - off_t size; - char sha1[41]; - } file; - } typeinfo; -}; - -struct appfs_sqlite3_query_cb_handle { - struct appfs_children *head; - int argc; - const char *fmt; -}; - -static appfs_os_t appfs_convert_os_fromString(const char *os) { - if (strcasecmp(os, "Linux") == 0) { - return(APPFS_OS_LINUX); - } - - if (strcasecmp(os, "Darwin") == 0 || strcasecmp(os, "Mac OS") == 0 || strcasecmp(os, "Mac OS X") == 0) { - return(APPFS_OS_MACOSX); - } - - if (strcasecmp(os, "noarch") == 0) { - return(APPFS_OS_ALL); - } - - return(APPFS_OS_UNKNOWN); -} - -static const char *appfs_convert_os_toString(appfs_os_t os) { - switch (os) { - case APPFS_OS_ALL: - return("noarch"); - case APPFS_OS_LINUX: - return("linux"); - case APPFS_OS_MACOSX: - return("macosx"); - case APPFS_OS_FREEBSD: - return("freebsd"); - case APPFS_OS_OPENBSD: - return("openbsd"); - case APPFS_OS_SOLARIS: - return("freebsd"); - case APPFS_CPU_UNKNOWN: - return("unknown"); - } - - return("unknown"); -} - -static appfs_cpuArch_t appfs_convert_cpuArch_fromString(const char *cpu) { - if (strcasecmp(cpu, "amd64") == 0 || strcasecmp(cpu, "x86_64") == 0) { - return(APPFS_CPU_AMD64); - } - - if (strcasecmp(cpu, "i386") == 0 || \ - strcasecmp(cpu, "i486") == 0 || \ - strcasecmp(cpu, "i586") == 0 || \ - strcasecmp(cpu, "i686") == 0 || \ - strcasecmp(cpu, "ix86") == 0) { - return(APPFS_CPU_I386); - } - - if (strcasecmp(cpu, "arm") == 0) { - return(APPFS_CPU_ARM); - } - - if (strcasecmp(cpu, "noarch") == 0) { - return(APPFS_CPU_ALL); - } - - return(APPFS_CPU_UNKNOWN); -} - -static const char *appfs_convert_cpuArch_toString(appfs_cpuArch_t cpu) { - switch (cpu) { - case APPFS_CPU_ALL: - return("noarch"); - case APPFS_CPU_AMD64: - return("amd64"); - case APPFS_CPU_I386: - return("ix86"); - case APPFS_CPU_ARM: - return("arm"); - case APPFS_CPU_UNKNOWN: - return("unknown"); - } - - return("unknown"); -} - -static Tcl_Interp *appfs_create_TclInterp(const char *cachedir) { - Tcl_Interp *interp; - int tcl_ret; - - interp = Tcl_CreateInterp(); - if (interp == NULL) { - fprintf(stderr, "Unable to create Tcl Interpreter. Aborting.\n"); - - return(NULL); - } - - tcl_ret = Tcl_Init(interp); - if (tcl_ret != TCL_OK) { - fprintf(stderr, "Unable to initialize Tcl. Aborting.\n"); - - return(NULL); - } - - tcl_ret = Tcl_Eval(interp, "" -#include "appfs.tcl.h" - ""); - if (tcl_ret != TCL_OK) { - fprintf(stderr, "Unable to initialize Tcl AppFS script. Aborting.\n"); - fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp)); - - return(NULL); - } - - if (Tcl_SetVar(interp, "::appfs::cachedir", cachedir, TCL_GLOBAL_ONLY) == NULL) { - fprintf(stderr, "Unable to set cache directory. This should never fail.\n"); - - return(NULL); - } - - tcl_ret = Tcl_Eval(interp, "::appfs::init"); - if (tcl_ret != TCL_OK) { - fprintf(stderr, "Unable to initialize Tcl AppFS script (::appfs::init). Aborting.\n"); - fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp)); - - return(NULL); - } - - return(interp); -} - -static int appfs_Tcl_Eval(Tcl_Interp *interp, int objc, const char *cmd, ...) { - Tcl_Obj **objv; - const char *arg; - va_list argp; - int retval; - int i; - - objv = (void *) ckalloc(sizeof(*objv) * objc); - objv[0] = Tcl_NewStringObj(cmd, -1); - Tcl_IncrRefCount(objv[0]); - - va_start(argp, cmd); - for (i = 1; i < objc; i++) { - arg = va_arg(argp, const char *); - objv[i] = Tcl_NewStringObj(arg, -1); - Tcl_IncrRefCount(objv[i]); - } - va_end(argp); - - retval = Tcl_EvalObjv(interp, objc, objv, 0); - - for (i = 0; i < objc; i++) { - Tcl_DecrRefCount(objv[i]); - } - - ckfree((void *) objv); - - if (retval != TCL_OK) { - APPFS_DEBUG("Tcl command failed, ::errorInfo contains: %s\n", Tcl_GetVar(interp, "::errorInfo", 0)); - } - - return(retval); -} - -static void appfs_update_index(const char *hostname) { - Tcl_Interp *interp; - int tcl_ret; - - APPFS_DEBUG("Enter: hostname = %s", hostname); - - interp = pthread_getspecific(interpKey); - if (interp == NULL) { - interp = appfs_create_TclInterp(globalThread.cachedir); - - pthread_setspecific(interpKey, interp); - } - - tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::getindex", hostname); - if (tcl_ret != TCL_OK) { - APPFS_DEBUG("Call to ::appfs::getindex failed: %s", Tcl_GetStringResult(interp)); - - return; - } - - return; -} - -static const char *appfs_getfile(const char *hostname, const char *sha1) { - Tcl_Interp *interp; - char *retval; - int tcl_ret; - - interp = pthread_getspecific(interpKey); - if (interp == NULL) { - interp = appfs_create_TclInterp(globalThread.cachedir); - - pthread_setspecific(interpKey, interp); - } - - tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::download", hostname, sha1); - if (tcl_ret != TCL_OK) { - APPFS_DEBUG("Call to ::appfs::download failed: %s", Tcl_GetStringResult(interp)); - - return(NULL); - } - - retval = strdup(Tcl_GetStringResult(interp)); - - return(retval); -} - -static void appfs_update_manifest(const char *hostname, const char *sha1) { - Tcl_Interp *interp; - int tcl_ret; - - interp = pthread_getspecific(interpKey); - if (interp == NULL) { - interp = appfs_create_TclInterp(globalThread.cachedir); - - pthread_setspecific(interpKey, interp); - } - - tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::getpkgmanifest", hostname, sha1); - if (tcl_ret != TCL_OK) { - APPFS_DEBUG("Call to ::appfs::getpkgmanifest failed: %s", Tcl_GetStringResult(interp)); - - return; - } - - return; -} - - -#define appfs_free_list_type(id, type) static void appfs_free_list_ ## id(type *head) { \ - type *obj, *next; \ - for (obj = head; obj; obj = next) { \ - next = obj->_next; \ - ckfree((void *) obj); \ - } \ -} - -appfs_free_list_type(children, struct appfs_children) - -static int appfs_getchildren_cb(void *_head, int columns, char **values, char **names) { - struct appfs_children **head_p, *obj; - - head_p = _head; - - obj = (void *) ckalloc(sizeof(*obj)); - - snprintf(obj->name, sizeof(obj->name), "%s", values[0]); - - if (*head_p == NULL) { - obj->counter = 0; - } else { - obj->counter = (*head_p)->counter + 1; - } - - obj->_next = *head_p; - *head_p = obj; - - return(0); - -} - -static struct appfs_children *appfs_getchildren(const char *hostname, const char *package_hash, const char *path, int *children_count_p) { - struct appfs_children *head = NULL; - char *sql; - int sqlite_ret; - - if (children_count_p == NULL) { - return(NULL); - } - - appfs_update_index(hostname); - appfs_update_manifest(hostname, package_hash); - - sql = sqlite3_mprintf("SELECT file_name FROM files WHERE package_sha1 = %Q AND file_directory = %Q;", package_hash, path); - if (sql == NULL) { - APPFS_DEBUG("Call to sqlite3_mprintf failed."); - - return(NULL); - } - - APPFS_DEBUG("SQL: %s", sql); - sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_getchildren_cb, &head, NULL); - sqlite3_free(sql); - - if (sqlite_ret != SQLITE_OK) { - APPFS_DEBUG("Call to sqlite3_exec failed."); - - return(NULL); - } - - if (head != NULL) { - *children_count_p = head->counter + 1; - } - - return(head); -} - -static int appfs_sqlite3_query_cb(void *_cb_handle, int columns, char **values, char **names) { - struct appfs_sqlite3_query_cb_handle *cb_handle; - struct appfs_children *obj; - - cb_handle = _cb_handle; - - obj = (void *) ckalloc(sizeof(*obj)); - - switch (cb_handle->argc) { - case 1: - snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0]); - break; - case 2: - snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0], values[1]); - break; - case 3: - snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0], values[1], values[2]); - break; - case 4: - snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0], values[1], values[2], values[3]); - break; - } - - if (cb_handle->head == NULL) { - obj->counter = 0; - } else { - obj->counter = cb_handle->head->counter + 1; - } - - obj->_next = cb_handle->head; - cb_handle->head = obj; - - return(0); -} - -static struct appfs_children *appfs_sqlite3_query(char *sql, int argc, const char *fmt, int *results_count_p) { - struct appfs_sqlite3_query_cb_handle cb_handle; - int sqlite_ret; - - if (results_count_p == NULL) { - return(NULL); - } - - if (sql == NULL) { - APPFS_DEBUG("Call to sqlite3_mprintf probably failed."); - - return(NULL); - } - - if (fmt == NULL) { - fmt = "%s"; - } - - cb_handle.head = NULL; - cb_handle.argc = argc; - cb_handle.fmt = fmt; - - APPFS_DEBUG("SQL: %s", sql); - sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_sqlite3_query_cb, &cb_handle, NULL); - sqlite3_free(sql); - - if (sqlite_ret != SQLITE_OK) { - APPFS_DEBUG("Call to sqlite3_exec failed."); - - return(NULL); - } - - if (cb_handle.head != NULL) { - *results_count_p = cb_handle.head->counter + 1; - } - - return(cb_handle.head); -} - -static int appfs_lookup_package_hash_cb(void *_retval, int columns, char **values, char **names) { - char **retval = _retval; - - *retval = strdup(values[0]); - - return(0); -} - -static char *appfs_lookup_package_hash(const char *hostname, const char *package, const char *os, const char *cpuArch, const char *version) { - char *sql; - char *retval = NULL; - int sqlite_ret; - - appfs_update_index(hostname); - - sql = sqlite3_mprintf("SELECT sha1 FROM packages WHERE hostname = %Q AND package = %Q AND os = %Q AND cpuArch = %Q AND version = %Q;", - hostname, - package, - os, - cpuArch, - version - ); - if (sql == NULL) { - APPFS_DEBUG("Call to sqlite3_mprintf failed."); - - return(NULL); - } - - APPFS_DEBUG("SQL: %s", sql); - sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_lookup_package_hash_cb, &retval, NULL); - sqlite3_free(sql); - - if (sqlite_ret != SQLITE_OK) { - APPFS_DEBUG("Call to sqlite3_exec failed."); - - return(NULL); - } - - return(retval); -} - -static int appfs_getfileinfo_cb(void *_pathinfo, int columns, char **values, char **names) { - struct appfs_pathinfo *pathinfo = _pathinfo; - const char *type, *time, *source, *size, *perms, *sha1; - - type = values[0]; - time = values[1]; - source = values[2]; - size = values[3]; - perms = values[4]; - sha1 = values[5]; - - pathinfo->time = strtoull(time, NULL, 10); - - if (strcmp(type, "file") == 0) { - pathinfo->type = APPFS_PATHTYPE_FILE; - - if (!size) { - size = "0"; - } - - if (!perms) { - perms = ""; - } - - if (!sha1) { - sha1 = ""; - } - - pathinfo->typeinfo.file.size = strtoull(size, NULL, 10); - snprintf(pathinfo->typeinfo.file.sha1, sizeof(pathinfo->typeinfo.file.sha1), "%s", sha1); - - if (strcmp(perms, "x") == 0) { - pathinfo->typeinfo.file.executable = 1; - } else { - pathinfo->typeinfo.file.executable = 0; - } - - return(0); - } - - if (strcmp(type, "directory") == 0) { - pathinfo->type = APPFS_PATHTYPE_DIRECTORY; - pathinfo->typeinfo.dir.childcount = 0; - - return(0); - } - - return(0); - - /* Until this is used, prevent the compiler from complaining */ - source = source; -} - -static int appfs_getfileinfo(const char *hostname, const char *package_hash, const char *_path, struct appfs_pathinfo *pathinfo) { - char *directory, *file, *path; - char *sql; - int sqlite_ret; - - if (pathinfo == NULL) { - return(-EIO); - } - - appfs_update_index(hostname); - appfs_update_manifest(hostname, package_hash); - - path = strdup(_path); - directory = path; - file = strrchr(path, '/'); - if (file == NULL) { - file = path; - directory = ""; - } else { - *file = '\0'; - file++; - } - - sql = sqlite3_mprintf("SELECT type, time, source, size, perms, file_sha1 FROM files WHERE package_sha1 = %Q AND file_directory = %Q AND file_name = %Q;", package_hash, directory, file); - if (sql == NULL) { - APPFS_DEBUG("Call to sqlite3_mprintf failed."); - - free(path); - - return(-EIO); - } - - free(path); - - pathinfo->type = APPFS_PATHTYPE_INVALID; - - APPFS_DEBUG("SQL: %s", sql); - sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_getfileinfo_cb, pathinfo, NULL); - sqlite3_free(sql); - - if (sqlite_ret != SQLITE_OK) { - APPFS_DEBUG("Call to sqlite3_exec failed."); - - return(-EIO); - } - - if (pathinfo->type == APPFS_PATHTYPE_INVALID) { - return(-ENOENT); - } - - return(0); -} - -static int appfs_get_path_info_sql(char *sql, int argc, const char *fmt, struct appfs_pathinfo *pathinfo, struct appfs_children **children) { - struct appfs_children *node, *dir_children, *dir_child; - int dir_children_count = 0; - - dir_children = appfs_sqlite3_query(sql, argc, fmt, &dir_children_count); - - if (dir_children == NULL || dir_children_count == 0) { - return(-ENOENT); - } - - /* Request for a single hostname */ - pathinfo->type = APPFS_PATHTYPE_DIRECTORY; - pathinfo->typeinfo.dir.childcount = dir_children_count; - pathinfo->time = globalThread.boottime; - - if (children) { - for (dir_child = dir_children; dir_child; dir_child = dir_child->_next) { - node = (void *) ckalloc(sizeof(*node)); - node->_next = *children; - strcpy(node->name, dir_child->name); - *children = node; - } - } - - appfs_free_list_children(dir_children); - - return(0); -} -/* Get information about a path, and optionally list children */ -static int appfs_get_path_info(const char *_path, struct appfs_pathinfo *pathinfo, struct appfs_children **children) { - struct appfs_children *dir_children; - appfs_os_t os_val; - appfs_cpuArch_t cpuArch_val; - char *hostname, *packagename, *os_cpuArch, *os, *cpuArch, *version; - char *path, *path_s; - char *package_hash; - char *sql; - int files_count; - int fileinfo_ret, retval; - - if (children) { - *children = NULL; - } - - if (_path == NULL) { - return(-ENOENT); - } - - if (_path[0] != '/') { - return(-ENOENT); - } - - if (_path[1] == '\0') { - /* Request for the root directory */ - pathinfo->hostname[0] = '\0'; - - sql = sqlite3_mprintf("SELECT DISTINCT hostname FROM packages;"); - - retval = appfs_get_path_info_sql(sql, 1, NULL, pathinfo, children); - - /* The root directory always exists, even if it has no subordinates */ - if (retval != 0) { - pathinfo->type = APPFS_PATHTYPE_DIRECTORY; - pathinfo->typeinfo.dir.childcount = 0; - pathinfo->time = globalThread.boottime; - - retval = 0; - } - - return(retval); - } - - path = strdup(_path); - path_s = path; - - hostname = path + 1; - packagename = strchr(hostname, '/'); - - if (packagename != NULL) { - *packagename = '\0'; - packagename++; - } - - snprintf(pathinfo->hostname, sizeof(pathinfo->hostname), "%s", hostname); - - if (packagename == NULL) { - appfs_update_index(hostname); - - sql = sqlite3_mprintf("SELECT DISTINCT package FROM packages WHERE hostname = %Q;", hostname); - - free(path_s); - - return(appfs_get_path_info_sql(sql, 1, NULL, pathinfo, children)); - } - - os_cpuArch = strchr(packagename, '/'); - - if (os_cpuArch != NULL) { - *os_cpuArch = '\0'; - os_cpuArch++; - } - - if (os_cpuArch == NULL) { - appfs_update_index(hostname); - - sql = sqlite3_mprintf("SELECT DISTINCT os, cpuArch FROM packages WHERE hostname = %Q AND package = %Q;", hostname, packagename); - - free(path_s); - - return(appfs_get_path_info_sql(sql, 2, "%s-%s", pathinfo, children)); - } - - version = strchr(os_cpuArch, '/'); - - if (version != NULL) { - *version = '\0'; - version++; - } - - os = os_cpuArch; - cpuArch = strchr(os_cpuArch, '-'); - if (cpuArch) { - *cpuArch = '\0'; - cpuArch++; - - cpuArch_val = appfs_convert_cpuArch_fromString(cpuArch); - } else { - cpuArch_val = APPFS_CPU_UNKNOWN; - } - - os_val = appfs_convert_os_fromString(os); - - if (version == NULL) { - /* Request for version list for a package on an OS/CPU */ - appfs_update_index(hostname); - - sql = sqlite3_mprintf("SELECT DISTINCT version FROM packages WHERE hostname = %Q AND package = %Q AND os = %Q and cpuArch = %Q;", hostname, packagename, os, cpuArch); - - free(path_s); - - return(appfs_get_path_info_sql(sql, 1, NULL, pathinfo, children)); - } - - path = strchr(version, '/'); - if (path == NULL) { - path = ""; - } else { - *path = '\0'; - path++; - } - - /* Request for a file in a specific package */ - APPFS_DEBUG("Requesting information for hostname = %s, package = %s, os = %s, cpuArch = %s, version = %s, path = %s", - hostname, packagename, os, cpuArch, version, path - ); - - package_hash = appfs_lookup_package_hash(hostname, packagename, os, cpuArch, version); - if (package_hash == NULL) { - free(path_s); - - return(-ENOENT); - } - - APPFS_DEBUG(" ... which hash a hash of %s", package_hash); - - appfs_update_manifest(hostname, package_hash); - - if (strcmp(path, "") == 0) { - pathinfo->type = APPFS_PATHTYPE_DIRECTORY; - pathinfo->time = globalThread.boottime; - } else { - fileinfo_ret = appfs_getfileinfo(hostname, package_hash, path, pathinfo); - if (fileinfo_ret != 0) { - free(path_s); - - return(fileinfo_ret); - } - } - - if (pathinfo->type == APPFS_PATHTYPE_DIRECTORY) { - dir_children = appfs_getchildren(hostname, package_hash, path, &files_count); - - if (dir_children != NULL) { - pathinfo->typeinfo.dir.childcount = files_count; - } - - if (children) { - *children = dir_children; - } - } - - free(path_s); - - return(0); -} - -static int appfs_fuse_getattr(const char *path, struct stat *stbuf) { - struct appfs_pathinfo pathinfo; - int res = 0; - - APPFS_DEBUG("Enter (path = %s, ...)", path); - - res = appfs_get_path_info(path, &pathinfo, NULL); - if (res != 0) { - return(res); - } - - memset(stbuf, 0, sizeof(struct stat)); - - stbuf->st_mtime = pathinfo.time; - stbuf->st_ctime = pathinfo.time; - stbuf->st_atime = pathinfo.time; - - if (pathinfo.type == APPFS_PATHTYPE_DIRECTORY) { - stbuf->st_mode = S_IFDIR | 0555; - stbuf->st_nlink = 2 + pathinfo.typeinfo.dir.childcount; - } else { - if (pathinfo.typeinfo.file.executable) { - stbuf->st_mode = S_IFREG | 0555; - } else { - stbuf->st_mode = S_IFREG | 0444; - } - - stbuf->st_nlink = 1; - stbuf->st_size = pathinfo.typeinfo.file.size; - } - - return res; -} - -static int appfs_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { - struct appfs_pathinfo pathinfo; - struct appfs_children *children, *child; - int retval; - - APPFS_DEBUG("Enter (path = %s, ...)", path); - - retval = appfs_get_path_info(path, &pathinfo, &children); - if (retval != 0) { - return(retval); - } - - filler(buf, ".", NULL, 0); - filler(buf, "..", NULL, 0); - - for (child = children; child; child = child->_next) { - filler(buf, child->name, NULL, 0); - } - - appfs_free_list_children(children); - - return(0); -} - -static int appfs_fuse_open(const char *path, struct fuse_file_info *fi) { - struct appfs_pathinfo pathinfo; - const char *real_path; - int fh; - int gpi_ret; - - APPFS_DEBUG("Enter (path = %s, ...)", path); - - if ((fi->flags & 3) != O_RDONLY) { - return(-EACCES); - } - - gpi_ret = appfs_get_path_info(path, &pathinfo, NULL); - if (gpi_ret != 0) { - return(gpi_ret); - } - - if (pathinfo.type == APPFS_PATHTYPE_DIRECTORY) { - return(-EISDIR); - } - - real_path = appfs_getfile(pathinfo.hostname, pathinfo.typeinfo.file.sha1); - if (real_path == NULL) { - return(-EIO); - } - - fh = open(real_path, O_RDONLY); - free((void *) real_path); - if (fh < 0) { - return(-EIO); - } - - fi->fh = fh; - - return(0); -} - -static int appfs_fuse_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { - off_t lseek_ret; - ssize_t read_ret; - - APPFS_DEBUG("Enter (path = %s, ...)", path); - - lseek_ret = lseek(fi->fh, offset, SEEK_SET); - if (lseek_ret != offset) { - return(-EIO); - } - - read_ret = read(fi->fh, buf, size); - - return(read_ret); -} - -#ifdef APPFS_TEST_DRIVER -static int appfs_test_driver(void) { - struct appfs_site *sites, *site; - struct appfs_package *packages, *package; - struct appfs_children *files, *file; - char *sha1 = NULL; - int packages_count = 0, sites_count = 0, files_count; - - sites = appfs_getsites(&sites_count); - printf("Sites:\n"); - for (site = sites; site; site = site->_next) { - printf("\tname = %s\n", site->name); - } - - appfs_free_list_site(sites); - - packages = appfs_getindex("rkeene.org", &packages_count); - if (packages == NULL || packages_count == 0) { - fprintf(stderr, "Unable to fetch package index from rkeene.org.\n"); - - return(1); - } - - for (package = packages; package; package = package->_next) { - sha1 = package->sha1; - - printf("Package:\n\tname = %s\n\tversion = %s\n\tsha1 = %s\n\tos = %s\n\tcpuArch = %s\n", - package->name, - package->version, - package->sha1, - appfs_convert_os_toString(package->os), - appfs_convert_cpuArch_toString(package->cpuArch) - ); - } - - files = appfs_getchildren("rkeene.org", sha1, "", &files_count); - if (files == NULL) { - fprintf(stderr, "Unable to list files in the last package.\n"); - - return(1); - } - - printf("Files:\n"); - for (file = files; file; file = file->_next) { - printf("\t%s\n", file->name); - } - - appfs_free_list_children(files); - - files = appfs_getchildren("rkeene.org", sha1, "tcl", &files_count); - if (files == NULL) { - fprintf(stderr, "Unable to list files in the last package.\n"); - - return(1); - } - - printf("Files:\n"); - for (file = files; file; file = file->_next) { - printf("\ttcl/%s\n", file->name); - } - - appfs_free_list_children(files); - appfs_free_list_package(packages); - - return(0); -} -#else -static struct fuse_operations appfs_oper = { - .getattr = appfs_fuse_getattr, - .readdir = appfs_fuse_readdir, - .open = appfs_fuse_open, - .read = appfs_fuse_read -}; -#endif - -int main(int argc, char **argv) { - const char *cachedir = APPFS_CACHEDIR; - char dbfilename[1024]; - int pthread_ret, snprintf_ret, sqlite_ret; - - globalThread.cachedir = cachedir; - globalThread.boottime = time(NULL); - - pthread_ret = pthread_key_create(&interpKey, NULL); - if (pthread_ret != 0) { - fprintf(stderr, "Unable to create TSD key for Tcl. Aborting.\n"); - - return(1); - } - - snprintf_ret = snprintf(dbfilename, sizeof(dbfilename), "%s/%s", cachedir, "cache.db"); - if (snprintf_ret >= sizeof(dbfilename)) { - fprintf(stderr, "Unable to set database filename. Aborting.\n"); - - return(1); - } - - sqlite_ret = sqlite3_open(dbfilename, &globalThread.db); - if (sqlite_ret != SQLITE_OK) { - fprintf(stderr, "Unable to open database: %s\n", dbfilename); - - return(1); - } - -#ifdef APPFS_TEST_DRIVER - return(appfs_test_driver()); -#else - return(fuse_main(argc, argv, &appfs_oper, NULL)); -#endif -} - DELETED appfs.tcl Index: appfs.tcl ================================================================== --- appfs.tcl +++ appfs.tcl @@ -1,202 +0,0 @@ -#! /usr/bin/env tclsh - -package require http 2.7 -package require sqlite3 - -namespace eval ::appfs { - variable cachedir "/tmp/appfs-cache" - - proc _hash_sep {hash {seps 4}} { - for {set idx 0} {$idx < $seps} {incr idx} { - append retval "[string range $hash [expr {$idx * 2}] [expr {($idx * 2) + 1}]]/" - } - append retval "[string range $hash [expr {$idx * 2}] end]" - - return $retval - } - - proc _cachefile {url key {keyIsHash 1}} { - if {$keyIsHash} { - set key [_hash_sep $key] - } - - set file [file join $::appfs::cachedir $key] - - file mkdir [file dirname $file] - - if {![file exists $file]} { - set tmpfile "${file}.new" - - set fd [open $tmpfile "w"] - fconfigure $fd -translation binary - - set token [::http::geturl $url -channel $fd -binary true] - set ncode [::http::ncode $token] - ::http::reset $token - close $fd - - if {$ncode == "200"} { - file rename -force -- $tmpfile $file - } else { - file delete -force -- $tmpfile - } - } - - return $file - } - - proc _db {args} { - return [uplevel 1 [list ::appfs::db {*}$args]] - } - - proc init {} { - if {[info exists ::appfs::init_called]} { - return - } - - set ::appfs::init_called 1 - - if {![info exists ::appfs::db]} { - file mkdir $::appfs::cachedir - - sqlite3 ::appfs::db [file join $::appfs::cachedir cache.db] - } - - _db eval {CREATE TABLE IF NOT EXISTS packages(hostname, sha1, package, version, os, cpuArch, isLatest, haveManifest);} - _db eval {CREATE TABLE IF NOT EXISTS files(package_sha1, type, time, source, size, perms, file_sha1, file_name, file_directory);} - } - - proc download {hostname hash {method sha1}} { - set url "http://$hostname/appfs/$method/$hash" - set file [_cachefile $url $hash] - - if {![file exists $file]} { - return -code error "Unable to fetch" - } - - return $file - } - - proc getindex {hostname} { - if {[string match "*\[/~\]*" $hostname]} { - return -code error "Invalid hostname" - } - - set url "http://$hostname/appfs/index" - - set indexcachefile [_cachefile $url "SERVERS/[string tolower $hostname]" 0] - - if {![file exists $indexcachefile]} { - return -code error "Unable to fetch $url" - } - - set fd [open $indexcachefile] - gets $fd indexhash_data - set indexhash [lindex [split $indexhash_data ","] 0] - close $fd - - set file [download $hostname $indexhash] - set fd [open $file] - set data [read $fd] - close $fd - - array set packages [list] - foreach line [split $data "\n"] { - set line [string trim $line] - - if {[string match "*/*" $line]} { - continue - } - - if {$line == ""} { - continue - } - - set work [split $line ","] - - unset -nocomplain pkgInfo - set pkgInfo(package) [lindex $work 0] - set pkgInfo(version) [lindex $work 1] - set pkgInfo(os) [lindex $work 2] - set pkgInfo(cpuArch) [lindex $work 3] - set pkgInfo(hash) [string tolower [lindex $work 4]] - set pkgInfo(hash_type) "sha1" - set pkgInfo(isLatest) [expr {!![lindex $work 5]}] - - if {[string length $pkgInfo(hash)] != 40} { - continue - } - - if {![regexp {^[0-9a-f]*$} $pkgInfo(hash)]} { - continue - } - - set packages($pkgInfo(package)) [array get pkgInfo] - - # Do not do any additional work if we already have this package - set existing_packages [_db eval {SELECT package FROM packages WHERE hostname = $hostname AND sha1 = $pkgInfo(hash);}] - if {[lsearch -exact $existing_packages $pkgInfo(package)] != -1} { - continue - } - - if {$pkgInfo(isLatest)} { - _db eval {UPDATE packages SET isLatest = 0 WHERE hostname = $hostname AND package = $pkgInfo($package) AND os = $pkgInfo($package) AND cpuArch = $pkgInfo(cpuArch);} - } - - _db eval {INSERT INTO packages (hostname, sha1, package, version, os, cpuArch, isLatest, haveManifest) VALUES ($hostname, $pkgInfo(hash), $pkgInfo(package), $pkgInfo(version), $pkgInfo(os), $pkgInfo(cpuArch), $pkgInfo(isLatest), 0);} - - } - - return COMPLETE - } - - proc getpkgmanifest {hostname package_sha1} { - set haveManifests [_db eval {SELECT haveManifest FROM packages WHERE sha1 = $package_sha1 LIMIT 1;}] - set haveManifest [lindex $haveManifests 0] - - if {$haveManifest} { - return COMPLETE - } - - set file [download $hostname $package_sha1] - set fd [open $file] - set pkgdata [read $fd] - close $fd - - foreach line [split $pkgdata "\n"] { - set line [string trim $line] - - if {$line == ""} { - continue - } - - set work [split $line ","] - - unset -nocomplain fileInfo - set fileInfo(type) [lindex $work 0] - set fileInfo(time) [lindex $work 1] - set fileInfo(name) [lindex $work end] - - set fileInfo(name) [split [string trim $fileInfo(name) "/"] "/"] - set fileInfo(directory) [join [lrange $fileInfo(name) 0 end-1] "/"] - set fileInfo(name) [lindex $fileInfo(name) end] - - set work [lrange $work 2 end-1] - switch -- $fileInfo(type) { - "file" { - set fileInfo(size) [lindex $work 0] - set fileInfo(perms) [lindex $work 1] - set fileInfo(sha1) [lindex $work 2] - } - "symlink" { - set fileInfo(source) [lindex $work 0] - } - } - - _db eval {INSERT INTO files (package_sha1, type, time, source, size, perms, file_sha1, file_name, file_directory) VALUES ($package_sha1, $fileInfo(type), $fileInfo(time), $fileInfo(source), $fileInfo(size), $fileInfo(perms), $fileInfo(sha1), $fileInfo(name), $fileInfo(directory) );} - _db eval {UPDATE packages SET haveManifest = 1 WHERE sha1 = $package_sha1;} - } - - return COMPLETE - } -} ADDED appfsd.c Index: appfsd.c ================================================================== --- appfsd.c +++ appfsd.c @@ -0,0 +1,965 @@ +#define FUSE_USE_VERSION 26 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define APPFS_CACHEDIR "/tmp/appfs-cache" + +#define APPFS_DEBUG(x...) { fprintf(stderr, "[debug] %s:%i:%s: ", __FILE__, __LINE__, __func__); fprintf(stderr, x); fprintf(stderr, "\n"); } + +static pthread_key_t interpKey; + +struct appfs_thread_data { + sqlite3 *db; + const char *cachedir; + time_t boottime; +}; + +struct appfs_thread_data globalThread; + +typedef enum { + APPFS_OS_UNKNOWN, + APPFS_OS_ALL, + APPFS_OS_LINUX, + APPFS_OS_MACOSX, + APPFS_OS_FREEBSD, + APPFS_OS_OPENBSD, + APPFS_OS_SOLARIS +} appfs_os_t; + +typedef enum { + APPFS_CPU_UNKNOWN, + APPFS_CPU_ALL, + APPFS_CPU_AMD64, + APPFS_CPU_I386, + APPFS_CPU_ARM +} appfs_cpuArch_t; + +typedef enum { + APPFS_PATHTYPE_INVALID, + APPFS_PATHTYPE_FILE, + APPFS_PATHTYPE_DIRECTORY, + APPFS_PATHTYPE_SYMLINK +} appfs_pathtype_t; + +struct appfs_package { + struct appfs_package *_next; + int counter; + + char name[256]; + char version[64]; + char sha1[41]; + char os_str[64]; + char cpuArch_str[64]; + appfs_os_t os; + appfs_cpuArch_t cpuArch; + int isLatest; +}; + +struct appfs_site { + struct appfs_site *_next; + int counter; + + char name[256]; +}; + +struct appfs_children { + struct appfs_children *_next; + int counter; + + char name[256]; +}; + +struct appfs_pathinfo { + appfs_pathtype_t type; + time_t time; + char hostname[256]; + union { + struct { + int childcount; + } dir; + struct { + int executable; + off_t size; + char sha1[41]; + } file; + } typeinfo; +}; + +struct appfs_sqlite3_query_cb_handle { + struct appfs_children *head; + int argc; + const char *fmt; +}; + +static appfs_os_t appfs_convert_os_fromString(const char *os) { + if (strcasecmp(os, "Linux") == 0) { + return(APPFS_OS_LINUX); + } + + if (strcasecmp(os, "Darwin") == 0 || strcasecmp(os, "Mac OS") == 0 || strcasecmp(os, "Mac OS X") == 0) { + return(APPFS_OS_MACOSX); + } + + if (strcasecmp(os, "noarch") == 0) { + return(APPFS_OS_ALL); + } + + return(APPFS_OS_UNKNOWN); +} + +static const char *appfs_convert_os_toString(appfs_os_t os) { + switch (os) { + case APPFS_OS_ALL: + return("noarch"); + case APPFS_OS_LINUX: + return("linux"); + case APPFS_OS_MACOSX: + return("macosx"); + case APPFS_OS_FREEBSD: + return("freebsd"); + case APPFS_OS_OPENBSD: + return("openbsd"); + case APPFS_OS_SOLARIS: + return("freebsd"); + case APPFS_CPU_UNKNOWN: + return("unknown"); + } + + return("unknown"); +} + +static appfs_cpuArch_t appfs_convert_cpuArch_fromString(const char *cpu) { + if (strcasecmp(cpu, "amd64") == 0 || strcasecmp(cpu, "x86_64") == 0) { + return(APPFS_CPU_AMD64); + } + + if (strcasecmp(cpu, "i386") == 0 || \ + strcasecmp(cpu, "i486") == 0 || \ + strcasecmp(cpu, "i586") == 0 || \ + strcasecmp(cpu, "i686") == 0 || \ + strcasecmp(cpu, "ix86") == 0) { + return(APPFS_CPU_I386); + } + + if (strcasecmp(cpu, "arm") == 0) { + return(APPFS_CPU_ARM); + } + + if (strcasecmp(cpu, "noarch") == 0) { + return(APPFS_CPU_ALL); + } + + return(APPFS_CPU_UNKNOWN); +} + +static const char *appfs_convert_cpuArch_toString(appfs_cpuArch_t cpu) { + switch (cpu) { + case APPFS_CPU_ALL: + return("noarch"); + case APPFS_CPU_AMD64: + return("amd64"); + case APPFS_CPU_I386: + return("ix86"); + case APPFS_CPU_ARM: + return("arm"); + case APPFS_CPU_UNKNOWN: + return("unknown"); + } + + return("unknown"); +} + +static Tcl_Interp *appfs_create_TclInterp(const char *cachedir) { + Tcl_Interp *interp; + int tcl_ret; + + interp = Tcl_CreateInterp(); + if (interp == NULL) { + fprintf(stderr, "Unable to create Tcl Interpreter. Aborting.\n"); + + return(NULL); + } + + tcl_ret = Tcl_Init(interp); + if (tcl_ret != TCL_OK) { + fprintf(stderr, "Unable to initialize Tcl. Aborting.\n"); + + return(NULL); + } + + tcl_ret = Tcl_Eval(interp, "" +#include "appfsd.tcl.h" + ""); + if (tcl_ret != TCL_OK) { + fprintf(stderr, "Unable to initialize Tcl AppFS script. Aborting.\n"); + fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp)); + + return(NULL); + } + + if (Tcl_SetVar(interp, "::appfs::cachedir", cachedir, TCL_GLOBAL_ONLY) == NULL) { + fprintf(stderr, "Unable to set cache directory. This should never fail.\n"); + + return(NULL); + } + + tcl_ret = Tcl_Eval(interp, "::appfs::init"); + if (tcl_ret != TCL_OK) { + fprintf(stderr, "Unable to initialize Tcl AppFS script (::appfs::init). Aborting.\n"); + fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp)); + + return(NULL); + } + + return(interp); +} + +static int appfs_Tcl_Eval(Tcl_Interp *interp, int objc, const char *cmd, ...) { + Tcl_Obj **objv; + const char *arg; + va_list argp; + int retval; + int i; + + objv = (void *) ckalloc(sizeof(*objv) * objc); + objv[0] = Tcl_NewStringObj(cmd, -1); + Tcl_IncrRefCount(objv[0]); + + va_start(argp, cmd); + for (i = 1; i < objc; i++) { + arg = va_arg(argp, const char *); + objv[i] = Tcl_NewStringObj(arg, -1); + Tcl_IncrRefCount(objv[i]); + } + va_end(argp); + + retval = Tcl_EvalObjv(interp, objc, objv, 0); + + for (i = 0; i < objc; i++) { + Tcl_DecrRefCount(objv[i]); + } + + ckfree((void *) objv); + + if (retval != TCL_OK) { + APPFS_DEBUG("Tcl command failed, ::errorInfo contains: %s\n", Tcl_GetVar(interp, "::errorInfo", 0)); + } + + return(retval); +} + +static void appfs_update_index(const char *hostname) { + Tcl_Interp *interp; + int tcl_ret; + + APPFS_DEBUG("Enter: hostname = %s", hostname); + + interp = pthread_getspecific(interpKey); + if (interp == NULL) { + interp = appfs_create_TclInterp(globalThread.cachedir); + + pthread_setspecific(interpKey, interp); + } + + tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::getindex", hostname); + if (tcl_ret != TCL_OK) { + APPFS_DEBUG("Call to ::appfs::getindex failed: %s", Tcl_GetStringResult(interp)); + + return; + } + + return; +} + +static const char *appfs_getfile(const char *hostname, const char *sha1) { + Tcl_Interp *interp; + char *retval; + int tcl_ret; + + interp = pthread_getspecific(interpKey); + if (interp == NULL) { + interp = appfs_create_TclInterp(globalThread.cachedir); + + pthread_setspecific(interpKey, interp); + } + + tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::download", hostname, sha1); + if (tcl_ret != TCL_OK) { + APPFS_DEBUG("Call to ::appfs::download failed: %s", Tcl_GetStringResult(interp)); + + return(NULL); + } + + retval = strdup(Tcl_GetStringResult(interp)); + + return(retval); +} + +static void appfs_update_manifest(const char *hostname, const char *sha1) { + Tcl_Interp *interp; + int tcl_ret; + + interp = pthread_getspecific(interpKey); + if (interp == NULL) { + interp = appfs_create_TclInterp(globalThread.cachedir); + + pthread_setspecific(interpKey, interp); + } + + tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::getpkgmanifest", hostname, sha1); + if (tcl_ret != TCL_OK) { + APPFS_DEBUG("Call to ::appfs::getpkgmanifest failed: %s", Tcl_GetStringResult(interp)); + + return; + } + + return; +} + + +#define appfs_free_list_type(id, type) static void appfs_free_list_ ## id(type *head) { \ + type *obj, *next; \ + for (obj = head; obj; obj = next) { \ + next = obj->_next; \ + ckfree((void *) obj); \ + } \ +} + +appfs_free_list_type(children, struct appfs_children) + +static int appfs_getchildren_cb(void *_head, int columns, char **values, char **names) { + struct appfs_children **head_p, *obj; + + head_p = _head; + + obj = (void *) ckalloc(sizeof(*obj)); + + snprintf(obj->name, sizeof(obj->name), "%s", values[0]); + + if (*head_p == NULL) { + obj->counter = 0; + } else { + obj->counter = (*head_p)->counter + 1; + } + + obj->_next = *head_p; + *head_p = obj; + + return(0); + +} + +static struct appfs_children *appfs_getchildren(const char *hostname, const char *package_hash, const char *path, int *children_count_p) { + struct appfs_children *head = NULL; + char *sql; + int sqlite_ret; + + if (children_count_p == NULL) { + return(NULL); + } + + appfs_update_index(hostname); + appfs_update_manifest(hostname, package_hash); + + sql = sqlite3_mprintf("SELECT file_name FROM files WHERE package_sha1 = %Q AND file_directory = %Q;", package_hash, path); + if (sql == NULL) { + APPFS_DEBUG("Call to sqlite3_mprintf failed."); + + return(NULL); + } + + APPFS_DEBUG("SQL: %s", sql); + sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_getchildren_cb, &head, NULL); + sqlite3_free(sql); + + if (sqlite_ret != SQLITE_OK) { + APPFS_DEBUG("Call to sqlite3_exec failed."); + + return(NULL); + } + + if (head != NULL) { + *children_count_p = head->counter + 1; + } + + return(head); +} + +static int appfs_sqlite3_query_cb(void *_cb_handle, int columns, char **values, char **names) { + struct appfs_sqlite3_query_cb_handle *cb_handle; + struct appfs_children *obj; + + cb_handle = _cb_handle; + + obj = (void *) ckalloc(sizeof(*obj)); + + switch (cb_handle->argc) { + case 1: + snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0]); + break; + case 2: + snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0], values[1]); + break; + case 3: + snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0], values[1], values[2]); + break; + case 4: + snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0], values[1], values[2], values[3]); + break; + } + + if (cb_handle->head == NULL) { + obj->counter = 0; + } else { + obj->counter = cb_handle->head->counter + 1; + } + + obj->_next = cb_handle->head; + cb_handle->head = obj; + + return(0); +} + +static struct appfs_children *appfs_sqlite3_query(char *sql, int argc, const char *fmt, int *results_count_p) { + struct appfs_sqlite3_query_cb_handle cb_handle; + int sqlite_ret; + + if (results_count_p == NULL) { + return(NULL); + } + + if (sql == NULL) { + APPFS_DEBUG("Call to sqlite3_mprintf probably failed."); + + return(NULL); + } + + if (fmt == NULL) { + fmt = "%s"; + } + + cb_handle.head = NULL; + cb_handle.argc = argc; + cb_handle.fmt = fmt; + + APPFS_DEBUG("SQL: %s", sql); + sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_sqlite3_query_cb, &cb_handle, NULL); + sqlite3_free(sql); + + if (sqlite_ret != SQLITE_OK) { + APPFS_DEBUG("Call to sqlite3_exec failed."); + + return(NULL); + } + + if (cb_handle.head != NULL) { + *results_count_p = cb_handle.head->counter + 1; + } + + return(cb_handle.head); +} + +static int appfs_lookup_package_hash_cb(void *_retval, int columns, char **values, char **names) { + char **retval = _retval; + + *retval = strdup(values[0]); + + return(0); +} + +static char *appfs_lookup_package_hash(const char *hostname, const char *package, const char *os, const char *cpuArch, const char *version) { + char *sql; + char *retval = NULL; + int sqlite_ret; + + appfs_update_index(hostname); + + sql = sqlite3_mprintf("SELECT sha1 FROM packages WHERE hostname = %Q AND package = %Q AND os = %Q AND cpuArch = %Q AND version = %Q;", + hostname, + package, + os, + cpuArch, + version + ); + if (sql == NULL) { + APPFS_DEBUG("Call to sqlite3_mprintf failed."); + + return(NULL); + } + + APPFS_DEBUG("SQL: %s", sql); + sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_lookup_package_hash_cb, &retval, NULL); + sqlite3_free(sql); + + if (sqlite_ret != SQLITE_OK) { + APPFS_DEBUG("Call to sqlite3_exec failed."); + + return(NULL); + } + + return(retval); +} + +static int appfs_getfileinfo_cb(void *_pathinfo, int columns, char **values, char **names) { + struct appfs_pathinfo *pathinfo = _pathinfo; + const char *type, *time, *source, *size, *perms, *sha1; + + type = values[0]; + time = values[1]; + source = values[2]; + size = values[3]; + perms = values[4]; + sha1 = values[5]; + + pathinfo->time = strtoull(time, NULL, 10); + + if (strcmp(type, "file") == 0) { + pathinfo->type = APPFS_PATHTYPE_FILE; + + if (!size) { + size = "0"; + } + + if (!perms) { + perms = ""; + } + + if (!sha1) { + sha1 = ""; + } + + pathinfo->typeinfo.file.size = strtoull(size, NULL, 10); + snprintf(pathinfo->typeinfo.file.sha1, sizeof(pathinfo->typeinfo.file.sha1), "%s", sha1); + + if (strcmp(perms, "x") == 0) { + pathinfo->typeinfo.file.executable = 1; + } else { + pathinfo->typeinfo.file.executable = 0; + } + + return(0); + } + + if (strcmp(type, "directory") == 0) { + pathinfo->type = APPFS_PATHTYPE_DIRECTORY; + pathinfo->typeinfo.dir.childcount = 0; + + return(0); + } + + return(0); + + /* Until this is used, prevent the compiler from complaining */ + source = source; +} + +static int appfs_getfileinfo(const char *hostname, const char *package_hash, const char *_path, struct appfs_pathinfo *pathinfo) { + char *directory, *file, *path; + char *sql; + int sqlite_ret; + + if (pathinfo == NULL) { + return(-EIO); + } + + appfs_update_index(hostname); + appfs_update_manifest(hostname, package_hash); + + path = strdup(_path); + directory = path; + file = strrchr(path, '/'); + if (file == NULL) { + file = path; + directory = ""; + } else { + *file = '\0'; + file++; + } + + sql = sqlite3_mprintf("SELECT type, time, source, size, perms, file_sha1 FROM files WHERE package_sha1 = %Q AND file_directory = %Q AND file_name = %Q;", package_hash, directory, file); + if (sql == NULL) { + APPFS_DEBUG("Call to sqlite3_mprintf failed."); + + free(path); + + return(-EIO); + } + + free(path); + + pathinfo->type = APPFS_PATHTYPE_INVALID; + + APPFS_DEBUG("SQL: %s", sql); + sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_getfileinfo_cb, pathinfo, NULL); + sqlite3_free(sql); + + if (sqlite_ret != SQLITE_OK) { + APPFS_DEBUG("Call to sqlite3_exec failed."); + + return(-EIO); + } + + if (pathinfo->type == APPFS_PATHTYPE_INVALID) { + return(-ENOENT); + } + + return(0); +} + +static int appfs_get_path_info_sql(char *sql, int argc, const char *fmt, struct appfs_pathinfo *pathinfo, struct appfs_children **children) { + struct appfs_children *node, *dir_children, *dir_child; + int dir_children_count = 0; + + dir_children = appfs_sqlite3_query(sql, argc, fmt, &dir_children_count); + + if (dir_children == NULL || dir_children_count == 0) { + return(-ENOENT); + } + + /* Request for a single hostname */ + pathinfo->type = APPFS_PATHTYPE_DIRECTORY; + pathinfo->typeinfo.dir.childcount = dir_children_count; + pathinfo->time = globalThread.boottime; + + if (children) { + for (dir_child = dir_children; dir_child; dir_child = dir_child->_next) { + node = (void *) ckalloc(sizeof(*node)); + node->_next = *children; + strcpy(node->name, dir_child->name); + *children = node; + } + } + + appfs_free_list_children(dir_children); + + return(0); +} +/* Get information about a path, and optionally list children */ +static int appfs_get_path_info(const char *_path, struct appfs_pathinfo *pathinfo, struct appfs_children **children) { + struct appfs_children *dir_children; + appfs_os_t os_val; + appfs_cpuArch_t cpuArch_val; + char *hostname, *packagename, *os_cpuArch, *os, *cpuArch, *version; + char *path, *path_s; + char *package_hash; + char *sql; + int files_count; + int fileinfo_ret, retval; + + if (children) { + *children = NULL; + } + + if (_path == NULL) { + return(-ENOENT); + } + + if (_path[0] != '/') { + return(-ENOENT); + } + + if (_path[1] == '\0') { + /* Request for the root directory */ + pathinfo->hostname[0] = '\0'; + + sql = sqlite3_mprintf("SELECT DISTINCT hostname FROM packages;"); + + retval = appfs_get_path_info_sql(sql, 1, NULL, pathinfo, children); + + /* The root directory always exists, even if it has no subordinates */ + if (retval != 0) { + pathinfo->type = APPFS_PATHTYPE_DIRECTORY; + pathinfo->typeinfo.dir.childcount = 0; + pathinfo->time = globalThread.boottime; + + retval = 0; + } + + return(retval); + } + + path = strdup(_path); + path_s = path; + + hostname = path + 1; + packagename = strchr(hostname, '/'); + + if (packagename != NULL) { + *packagename = '\0'; + packagename++; + } + + snprintf(pathinfo->hostname, sizeof(pathinfo->hostname), "%s", hostname); + + if (packagename == NULL) { + appfs_update_index(hostname); + + sql = sqlite3_mprintf("SELECT DISTINCT package FROM packages WHERE hostname = %Q;", hostname); + + free(path_s); + + return(appfs_get_path_info_sql(sql, 1, NULL, pathinfo, children)); + } + + os_cpuArch = strchr(packagename, '/'); + + if (os_cpuArch != NULL) { + *os_cpuArch = '\0'; + os_cpuArch++; + } + + if (os_cpuArch == NULL) { + appfs_update_index(hostname); + + sql = sqlite3_mprintf("SELECT DISTINCT os, cpuArch FROM packages WHERE hostname = %Q AND package = %Q;", hostname, packagename); + + free(path_s); + + return(appfs_get_path_info_sql(sql, 2, "%s-%s", pathinfo, children)); + } + + version = strchr(os_cpuArch, '/'); + + if (version != NULL) { + *version = '\0'; + version++; + } + + os = os_cpuArch; + cpuArch = strchr(os_cpuArch, '-'); + if (cpuArch) { + *cpuArch = '\0'; + cpuArch++; + + cpuArch_val = appfs_convert_cpuArch_fromString(cpuArch); + } else { + cpuArch_val = APPFS_CPU_UNKNOWN; + } + + os_val = appfs_convert_os_fromString(os); + + if (version == NULL) { + /* Request for version list for a package on an OS/CPU */ + appfs_update_index(hostname); + + sql = sqlite3_mprintf("SELECT DISTINCT version FROM packages WHERE hostname = %Q AND package = %Q AND os = %Q and cpuArch = %Q;", hostname, packagename, os, cpuArch); + + free(path_s); + + return(appfs_get_path_info_sql(sql, 1, NULL, pathinfo, children)); + } + + path = strchr(version, '/'); + if (path == NULL) { + path = ""; + } else { + *path = '\0'; + path++; + } + + /* Request for a file in a specific package */ + APPFS_DEBUG("Requesting information for hostname = %s, package = %s, os = %s, cpuArch = %s, version = %s, path = %s", + hostname, packagename, os, cpuArch, version, path + ); + + package_hash = appfs_lookup_package_hash(hostname, packagename, os, cpuArch, version); + if (package_hash == NULL) { + free(path_s); + + return(-ENOENT); + } + + APPFS_DEBUG(" ... which hash a hash of %s", package_hash); + + appfs_update_manifest(hostname, package_hash); + + if (strcmp(path, "") == 0) { + pathinfo->type = APPFS_PATHTYPE_DIRECTORY; + pathinfo->time = globalThread.boottime; + } else { + fileinfo_ret = appfs_getfileinfo(hostname, package_hash, path, pathinfo); + if (fileinfo_ret != 0) { + free(path_s); + + return(fileinfo_ret); + } + } + + if (pathinfo->type == APPFS_PATHTYPE_DIRECTORY) { + dir_children = appfs_getchildren(hostname, package_hash, path, &files_count); + + if (dir_children != NULL) { + pathinfo->typeinfo.dir.childcount = files_count; + } + + if (children) { + *children = dir_children; + } + } + + free(path_s); + + return(0); +} + +static int appfs_fuse_getattr(const char *path, struct stat *stbuf) { + struct appfs_pathinfo pathinfo; + int res = 0; + + APPFS_DEBUG("Enter (path = %s, ...)", path); + + res = appfs_get_path_info(path, &pathinfo, NULL); + if (res != 0) { + return(res); + } + + memset(stbuf, 0, sizeof(struct stat)); + + stbuf->st_mtime = pathinfo.time; + stbuf->st_ctime = pathinfo.time; + stbuf->st_atime = pathinfo.time; + + if (pathinfo.type == APPFS_PATHTYPE_DIRECTORY) { + stbuf->st_mode = S_IFDIR | 0555; + stbuf->st_nlink = 2 + pathinfo.typeinfo.dir.childcount; + } else { + if (pathinfo.typeinfo.file.executable) { + stbuf->st_mode = S_IFREG | 0555; + } else { + stbuf->st_mode = S_IFREG | 0444; + } + + stbuf->st_nlink = 1; + stbuf->st_size = pathinfo.typeinfo.file.size; + } + + return res; +} + +static int appfs_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { + struct appfs_pathinfo pathinfo; + struct appfs_children *children, *child; + int retval; + + APPFS_DEBUG("Enter (path = %s, ...)", path); + + retval = appfs_get_path_info(path, &pathinfo, &children); + if (retval != 0) { + return(retval); + } + + filler(buf, ".", NULL, 0); + filler(buf, "..", NULL, 0); + + for (child = children; child; child = child->_next) { + filler(buf, child->name, NULL, 0); + } + + appfs_free_list_children(children); + + return(0); +} + +static int appfs_fuse_open(const char *path, struct fuse_file_info *fi) { + struct appfs_pathinfo pathinfo; + const char *real_path; + int fh; + int gpi_ret; + + APPFS_DEBUG("Enter (path = %s, ...)", path); + + if ((fi->flags & 3) != O_RDONLY) { + return(-EACCES); + } + + gpi_ret = appfs_get_path_info(path, &pathinfo, NULL); + if (gpi_ret != 0) { + return(gpi_ret); + } + + if (pathinfo.type == APPFS_PATHTYPE_DIRECTORY) { + return(-EISDIR); + } + + real_path = appfs_getfile(pathinfo.hostname, pathinfo.typeinfo.file.sha1); + if (real_path == NULL) { + return(-EIO); + } + + fh = open(real_path, O_RDONLY); + free((void *) real_path); + if (fh < 0) { + return(-EIO); + } + + fi->fh = fh; + + return(0); +} + +static int appfs_fuse_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { + off_t lseek_ret; + ssize_t read_ret; + + APPFS_DEBUG("Enter (path = %s, ...)", path); + + lseek_ret = lseek(fi->fh, offset, SEEK_SET); + if (lseek_ret != offset) { + return(-EIO); + } + + read_ret = read(fi->fh, buf, size); + + return(read_ret); +} + +static struct fuse_operations appfs_oper = { + .getattr = appfs_fuse_getattr, + .readdir = appfs_fuse_readdir, + .open = appfs_fuse_open, + .read = appfs_fuse_read +}; + +int main(int argc, char **argv) { + const char *cachedir = APPFS_CACHEDIR; + char dbfilename[1024]; + int pthread_ret, snprintf_ret, sqlite_ret; + + globalThread.cachedir = cachedir; + globalThread.boottime = time(NULL); + + pthread_ret = pthread_key_create(&interpKey, NULL); + if (pthread_ret != 0) { + fprintf(stderr, "Unable to create TSD key for Tcl. Aborting.\n"); + + return(1); + } + + snprintf_ret = snprintf(dbfilename, sizeof(dbfilename), "%s/%s", cachedir, "cache.db"); + if (snprintf_ret >= sizeof(dbfilename)) { + fprintf(stderr, "Unable to set database filename. Aborting.\n"); + + return(1); + } + + sqlite_ret = sqlite3_open(dbfilename, &globalThread.db); + if (sqlite_ret != SQLITE_OK) { + fprintf(stderr, "Unable to open database: %s\n", dbfilename); + + return(1); + } + + return(fuse_main(argc, argv, &appfs_oper, NULL)); +} + ADDED appfsd.tcl Index: appfsd.tcl ================================================================== --- appfsd.tcl +++ appfsd.tcl @@ -0,0 +1,202 @@ +#! /usr/bin/env tclsh + +package require http 2.7 +package require sqlite3 + +namespace eval ::appfs { + variable cachedir "/tmp/appfs-cache" + + proc _hash_sep {hash {seps 4}} { + for {set idx 0} {$idx < $seps} {incr idx} { + append retval "[string range $hash [expr {$idx * 2}] [expr {($idx * 2) + 1}]]/" + } + append retval "[string range $hash [expr {$idx * 2}] end]" + + return $retval + } + + proc _cachefile {url key {keyIsHash 1}} { + if {$keyIsHash} { + set key [_hash_sep $key] + } + + set file [file join $::appfs::cachedir $key] + + file mkdir [file dirname $file] + + if {![file exists $file]} { + set tmpfile "${file}.new" + + set fd [open $tmpfile "w"] + fconfigure $fd -translation binary + + set token [::http::geturl $url -channel $fd -binary true] + set ncode [::http::ncode $token] + ::http::reset $token + close $fd + + if {$ncode == "200"} { + file rename -force -- $tmpfile $file + } else { + file delete -force -- $tmpfile + } + } + + return $file + } + + proc _db {args} { + return [uplevel 1 [list ::appfs::db {*}$args]] + } + + proc init {} { + if {[info exists ::appfs::init_called]} { + return + } + + set ::appfs::init_called 1 + + if {![info exists ::appfs::db]} { + file mkdir $::appfs::cachedir + + sqlite3 ::appfs::db [file join $::appfs::cachedir cache.db] + } + + _db eval {CREATE TABLE IF NOT EXISTS packages(hostname, sha1, package, version, os, cpuArch, isLatest, haveManifest);} + _db eval {CREATE TABLE IF NOT EXISTS files(package_sha1, type, time, source, size, perms, file_sha1, file_name, file_directory);} + } + + proc download {hostname hash {method sha1}} { + set url "http://$hostname/appfs/$method/$hash" + set file [_cachefile $url $hash] + + if {![file exists $file]} { + return -code error "Unable to fetch" + } + + return $file + } + + proc getindex {hostname} { + if {[string match "*\[/~\]*" $hostname]} { + return -code error "Invalid hostname" + } + + set url "http://$hostname/appfs/index" + + set indexcachefile [_cachefile $url "SERVERS/[string tolower $hostname]" 0] + + if {![file exists $indexcachefile]} { + return -code error "Unable to fetch $url" + } + + set fd [open $indexcachefile] + gets $fd indexhash_data + set indexhash [lindex [split $indexhash_data ","] 0] + close $fd + + set file [download $hostname $indexhash] + set fd [open $file] + set data [read $fd] + close $fd + + array set packages [list] + foreach line [split $data "\n"] { + set line [string trim $line] + + if {[string match "*/*" $line]} { + continue + } + + if {$line == ""} { + continue + } + + set work [split $line ","] + + unset -nocomplain pkgInfo + set pkgInfo(package) [lindex $work 0] + set pkgInfo(version) [lindex $work 1] + set pkgInfo(os) [lindex $work 2] + set pkgInfo(cpuArch) [lindex $work 3] + set pkgInfo(hash) [string tolower [lindex $work 4]] + set pkgInfo(hash_type) "sha1" + set pkgInfo(isLatest) [expr {!![lindex $work 5]}] + + if {[string length $pkgInfo(hash)] != 40} { + continue + } + + if {![regexp {^[0-9a-f]*$} $pkgInfo(hash)]} { + continue + } + + set packages($pkgInfo(package)) [array get pkgInfo] + + # Do not do any additional work if we already have this package + set existing_packages [_db eval {SELECT package FROM packages WHERE hostname = $hostname AND sha1 = $pkgInfo(hash);}] + if {[lsearch -exact $existing_packages $pkgInfo(package)] != -1} { + continue + } + + if {$pkgInfo(isLatest)} { + _db eval {UPDATE packages SET isLatest = 0 WHERE hostname = $hostname AND package = $pkgInfo($package) AND os = $pkgInfo($package) AND cpuArch = $pkgInfo(cpuArch);} + } + + _db eval {INSERT INTO packages (hostname, sha1, package, version, os, cpuArch, isLatest, haveManifest) VALUES ($hostname, $pkgInfo(hash), $pkgInfo(package), $pkgInfo(version), $pkgInfo(os), $pkgInfo(cpuArch), $pkgInfo(isLatest), 0);} + + } + + return COMPLETE + } + + proc getpkgmanifest {hostname package_sha1} { + set haveManifests [_db eval {SELECT haveManifest FROM packages WHERE sha1 = $package_sha1 LIMIT 1;}] + set haveManifest [lindex $haveManifests 0] + + if {$haveManifest} { + return COMPLETE + } + + set file [download $hostname $package_sha1] + set fd [open $file] + set pkgdata [read $fd] + close $fd + + foreach line [split $pkgdata "\n"] { + set line [string trim $line] + + if {$line == ""} { + continue + } + + set work [split $line ","] + + unset -nocomplain fileInfo + set fileInfo(type) [lindex $work 0] + set fileInfo(time) [lindex $work 1] + set fileInfo(name) [lindex $work end] + + set fileInfo(name) [split [string trim $fileInfo(name) "/"] "/"] + set fileInfo(directory) [join [lrange $fileInfo(name) 0 end-1] "/"] + set fileInfo(name) [lindex $fileInfo(name) end] + + set work [lrange $work 2 end-1] + switch -- $fileInfo(type) { + "file" { + set fileInfo(size) [lindex $work 0] + set fileInfo(perms) [lindex $work 1] + set fileInfo(sha1) [lindex $work 2] + } + "symlink" { + set fileInfo(source) [lindex $work 0] + } + } + + _db eval {INSERT INTO files (package_sha1, type, time, source, size, perms, file_sha1, file_name, file_directory) VALUES ($package_sha1, $fileInfo(type), $fileInfo(time), $fileInfo(source), $fileInfo(size), $fileInfo(perms), $fileInfo(sha1), $fileInfo(name), $fileInfo(directory) );} + _db eval {UPDATE packages SET haveManifest = 1 WHERE sha1 = $package_sha1;} + } + + return COMPLETE + } +}