#define FUSE_USE_VERSION 26 #include <sys/types.h> #include <sqlite3.h> #include <pthread.h> #include <string.h> #include <stdarg.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <fuse.h> #include <tcl.h> #ifndef APPFS_CACHEDIR #define APPFS_CACHEDIR "/var/cache/appfs" #endif #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; const char *platform; }; struct appfs_thread_data globalThread; typedef enum { APPFS_PATHTYPE_INVALID, APPFS_PATHTYPE_FILE, APPFS_PATHTYPE_DIRECTORY, APPFS_PATHTYPE_SYMLINK } appfs_pathtype_t; 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]; unsigned long long inode; union { struct { int childcount; } dir; struct { int executable; off_t size; char sha1[41]; } file; struct { off_t size; char source[256]; } symlink; } typeinfo; }; struct appfs_sqlite3_query_cb_handle { struct appfs_children *head; int argc; const char *fmt; }; 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."); appfs_free_list_children(head); 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, *rowid; type = values[0]; time = values[1]; source = values[2]; size = values[3]; perms = values[4]; sha1 = values[5]; rowid = values[6]; pathinfo->time = strtoull(time, NULL, 10); /* Package file inodes start at 2^32, fake inodes are before then */ pathinfo->inode = strtoull(rowid, NULL, 10) + 4294967296ULL; 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); } if (strcmp(type, "symlink") == 0) { pathinfo->type = APPFS_PATHTYPE_SYMLINK; pathinfo->typeinfo.dir.childcount = 0; if (!source) { source = ".BADLINK"; } pathinfo->typeinfo.symlink.size = strlen(source); snprintf(pathinfo->typeinfo.symlink.source, sizeof(pathinfo->typeinfo.symlink.source), "%s", source); 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, rowid 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); } static int appfs_add_path_child(const char *name, struct appfs_pathinfo *pathinfo, struct appfs_children **children) { struct appfs_children *new_child; pathinfo->typeinfo.dir.childcount++; if (children) { new_child = (void *) ckalloc(sizeof(*new_child)); new_child->_next = *children; snprintf(new_child->name, sizeof(new_child->name), "%s", name); *children = new_child; } return(0); } /* Generate an inode for a given path */ static long long appfs_get_path_inode(const char *path) { long long retval; const char *p; retval = 10; for (p = path; *p; p++) { retval %= 4290960290ULL; retval += *p; retval <<= 7; } retval += 10; retval %= 4294967296ULL; return(retval); } /* 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; 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'; pathinfo->inode = 1; 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; pathinfo->inode = appfs_get_path_inode(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); retval = appfs_get_path_info_sql(sql, 2, "%s-%s", pathinfo, children); if (retval != 0) { return(retval); } appfs_add_path_child("platform", pathinfo, children); return(retval); } version = strchr(os_cpuArch, '/'); if (version != NULL) { *version = '\0'; version++; } os = os_cpuArch; cpuArch = strchr(os_cpuArch, '-'); if (cpuArch) { *cpuArch = '\0'; cpuArch++; } else { cpuArch = ""; } if (version == NULL) { if (strcmp(os, "platform") == 0 && strcmp(cpuArch, "") == 0) { pathinfo->type = APPFS_PATHTYPE_SYMLINK; pathinfo->time = globalThread.boottime; pathinfo->typeinfo.dir.childcount = 0; pathinfo->typeinfo.symlink.size = strlen(globalThread.platform); snprintf(pathinfo->typeinfo.symlink.source, sizeof(pathinfo->typeinfo.symlink.source), "%s", globalThread.platform); free(path_s); return(0); } /* 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; } else { appfs_free_list_children(dir_children); } } free(path_s); return(0); } static int appfs_fuse_readlink(const char *path, char *buf, size_t size) { struct appfs_pathinfo pathinfo; int res = 0; APPFS_DEBUG("Enter (path = %s, ...)", path); pathinfo.type = APPFS_PATHTYPE_INVALID; res = appfs_get_path_info(path, &pathinfo, NULL); if (res != 0) { return(res); } if (pathinfo.type != APPFS_PATHTYPE_SYMLINK) { return(-EINVAL); } if ((strlen(pathinfo.typeinfo.symlink.source) + 1) > size) { return(-ENAMETOOLONG); } memcpy(buf, pathinfo.typeinfo.symlink.source, strlen(pathinfo.typeinfo.symlink.source) + 1); 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); pathinfo.type = APPFS_PATHTYPE_INVALID; 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; stbuf->st_ino = pathinfo.inode; switch (pathinfo.type) { case APPFS_PATHTYPE_DIRECTORY: stbuf->st_mode = S_IFDIR | 0555; stbuf->st_nlink = 2 + pathinfo.typeinfo.dir.childcount; break; case APPFS_PATHTYPE_FILE: 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; break; case APPFS_PATHTYPE_SYMLINK: stbuf->st_mode = S_IFLNK | 0555; stbuf->st_nlink = 1; stbuf->st_size = pathinfo.typeinfo.symlink.size; break; case APPFS_PATHTYPE_INVALID: res = -EIO; break; } 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_close(const char *path, struct fuse_file_info *fi) { int close_ret; close_ret = close(fi->fh); if (close_ret != 0) { return(-EIO); } 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, .readlink = appfs_fuse_readlink, .open = appfs_fuse_open, .release = appfs_fuse_close, .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); globalThread.platform = "linux-x86_64"; 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)); }