Index: appfsd.c ================================================================== --- appfsd.c +++ appfsd.c @@ -41,10 +41,11 @@ * Global variables, needed for all threads but only initialized before any * FUSE threads are created */ const char *appfs_cachedir; time_t appfs_boottime; +int appfs_fuse_started = 0; /* * AppFS Path Type: Describes the type of path a given file is */ typedef enum { @@ -52,18 +53,10 @@ APPFS_PATHTYPE_FILE, APPFS_PATHTYPE_DIRECTORY, APPFS_PATHTYPE_SYMLINK } appfs_pathtype_t; -/* - * AppFS Children Files linked-list - */ -struct appfs_children { - struct appfs_children *_next; - char name[256]; -}; - /* * AppFS Path Information: * Completely describes a specific path, how it should be returned to * to the kernel */ @@ -262,10 +255,14 @@ * This will be used to lookup the user's home directory so we can search for * locally modified files. */ static uid_t appfs_get_fsuid(void) { struct fuse_context *ctx; + + if (!appfs_fuse_started) { + return(getuid()); + } ctx = fuse_get_context(); if (ctx == NULL) { /* Unable to lookup user for some reason */ /* Return an unprivileged user ID */ @@ -375,24 +372,137 @@ 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) { +static int appfs_get_path_info(const char *path, struct appfs_pathinfo *pathinfo) { + Tcl_Interp *interp; + Tcl_Obj *attrs_dict, *attr_value; + const char *attr_value_str; + Tcl_WideInt attr_value_wide; + int attr_value_int; + static __thread Tcl_Obj *attr_key_type = NULL, *attr_key_perms = NULL, *attr_key_size = NULL, *attr_key_time = NULL, *attr_key_source = NULL, *attr_key_childcount = NULL; + int tcl_ret; + + interp = appfs_TclInterp(); + if (interp == NULL) { + return(1); + } + + tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::getattr", path); + if (tcl_ret != TCL_OK) { + APPFS_DEBUG("::appfs::getattr(%s) failed.", path); + APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp)); + + return(1); + } + + if (attr_key_type == NULL) { + attr_key_type = Tcl_NewStringObj("type", -1); + attr_key_perms = Tcl_NewStringObj("perms", -1); + attr_key_size = Tcl_NewStringObj("size", -1); + attr_key_time = Tcl_NewStringObj("time", -1); + attr_key_source = Tcl_NewStringObj("source", -1); + attr_key_childcount = Tcl_NewStringObj("childcount", -1); + } + + attrs_dict = Tcl_GetObjResult(interp); + tcl_ret = Tcl_DictObjGet(interp, attrs_dict, attr_key_type, &attr_value); + if (tcl_ret != TCL_OK) { + APPFS_DEBUG("[dict get \"type\"] failed"); + APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp)); + + return(1); + } + + if (attr_value == NULL) { + return(1); + } + + pathinfo->packaged = 0; + + attr_value_str = Tcl_GetString(attr_value); + switch (attr_value_str[0]) { + case 'd': /* directory */ + pathinfo->type = APPFS_PATHTYPE_DIRECTORY; + pathinfo->typeinfo.dir.childcount = 0; + + Tcl_DictObjGet(interp, attrs_dict, attr_key_childcount, &attr_value); + if (attr_value != NULL) { + tcl_ret = Tcl_GetWideIntFromObj(NULL, attr_value, &attr_value_wide); + if (tcl_ret == TCL_OK) { + pathinfo->typeinfo.dir.childcount = attr_value_wide; + } + } + + break; + case 'f': /* file */ + pathinfo->type = APPFS_PATHTYPE_FILE; + pathinfo->typeinfo.file.size = 0; + pathinfo->typeinfo.file.executable = 0; + + Tcl_DictObjGet(interp, attrs_dict, attr_key_size, &attr_value); + if (attr_value != NULL) { + tcl_ret = Tcl_GetWideIntFromObj(NULL, attr_value, &attr_value_wide); + if (tcl_ret == TCL_OK) { + pathinfo->typeinfo.file.size = attr_value_wide; + } + } + + Tcl_DictObjGet(interp, attrs_dict, attr_key_perms, &attr_value); + if (attr_value != NULL) { + attr_value_str = Tcl_GetString(attr_value); + if (attr_value_str[0] == 'x') { + pathinfo->typeinfo.file.executable = 1; + } + } + break; + case 's': /* symlink */ + pathinfo->type = APPFS_PATHTYPE_SYMLINK; + pathinfo->typeinfo.symlink.size = 0; + pathinfo->typeinfo.symlink.source[0] = '\0'; + + Tcl_DictObjGet(interp, attrs_dict, attr_key_source, &attr_value); + if (attr_value != NULL) { + attr_value_str = Tcl_GetStringFromObj(attr_value, &attr_value_int); + + if ((attr_value_int + 1) <= sizeof(pathinfo->typeinfo.symlink.source)) { + pathinfo->typeinfo.symlink.size = attr_value_int; + pathinfo->typeinfo.symlink.source[attr_value_int] = '\0'; + + memcpy(pathinfo->typeinfo.symlink.source, attr_value_str, attr_value_int); + } + } + break; + default: + return(1); + } + + Tcl_DictObjGet(interp, attrs_dict, attr_key_time, &attr_value); + if (attr_value != NULL) { + tcl_ret = Tcl_GetWideIntFromObj(NULL, attr_value, &attr_value_wide); + if (tcl_ret == TCL_OK) { + pathinfo->time = attr_value_wide; + } + } else { + pathinfo->time = 0; + } + + return(0); } static int appfs_fuse_readlink(const char *path, char *buf, size_t size) { struct appfs_pathinfo pathinfo; - int res = 0; + int retval = 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); + retval = appfs_get_path_info(path, &pathinfo); + if (retval != 0) { + return(retval); } if (pathinfo.type != APPFS_PATHTYPE_SYMLINK) { return(-EINVAL); } @@ -406,16 +516,22 @@ return(0); } static int appfs_fuse_getattr(const char *path, struct stat *stbuf) { struct appfs_pathinfo pathinfo; - int res = 0; + int retval; + + retval = 0; APPFS_DEBUG("Enter (path = %s, ...)", path); - pathinfo.type = APPFS_PATHTYPE_DIRECTORY; - pathinfo.typeinfo.dir.childcount = 0; + pathinfo.type = APPFS_PATHTYPE_INVALID; + + retval = appfs_get_path_info(path, &pathinfo); + if (retval != 0) { + return(retval); + } memset(stbuf, 0, sizeof(struct stat)); stbuf->st_mtime = pathinfo.time; stbuf->st_ctime = pathinfo.time; @@ -442,11 +558,11 @@ stbuf->st_mode = S_IFLNK | 0555; stbuf->st_nlink = 1; stbuf->st_size = pathinfo.typeinfo.symlink.size; break; case APPFS_PATHTYPE_INVALID: - res = -EIO; + retval = -EIO; break; } if (pathinfo.packaged) { @@ -453,11 +569,11 @@ if (0) { stbuf->st_mode |= 0222; } } - return res; + return(retval); } static int appfs_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { Tcl_Interp *interp; Tcl_Obj **children; @@ -472,11 +588,10 @@ return(0); } filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); - tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::getchildren", path); if (tcl_ret != TCL_OK) { APPFS_DEBUG("::appfs::getchildren(%s) failed.", path); APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp)); @@ -765,8 +880,9 @@ /* * Enter the FUSE main loop -- this will process any arguments * and start servicing requests. */ + appfs_fuse_started = 1; return(fuse_main(args.argc, args.argv, &appfs_operations, NULL)); } Index: appfsd.tcl ================================================================== --- appfsd.tcl +++ appfsd.tcl @@ -1,10 +1,11 @@ #! /usr/bin/env tclsh package require http 2.7 package require sqlite3 package require sha1 +package require appfsd namespace eval ::appfs { variable cachedir "/tmp/appfs-cache" variable ttl 3600 variable nttl 60 @@ -337,61 +338,76 @@ } } return COMPLETE } + + proc _localpath {package hostname file} { + set homedir [::appfsd::get_homedir] + set dir [file join $homedir .appfs "./${package}@${hostname}" "./${file}"] + } proc _parsepath {path} { set path [string trim $path "/"] set path [split $path "/"] set pathlen [llength $path] - array set retval [list _children sites] + array set retval [list _children sites _type toplevel] if {$pathlen > 0} { set retval(hostname) [lindex $path 0] set retval(_children) packages + set retval(_type) sites if {$pathlen > 1} { set package [lindex $path 1] if {[string length $package] == "40" && [regexp {^[a-fA-F0-9]*$} $package]} { set retval(package_sha1) $package set retval(_children) files + set retval(_type) files + + ::appfs::db eval {SELECT package, os, cpuArch, version FROM packages WHERE sha1 = $retval(package_sha1);} pkginfo {} + set retval(package) $pkginfo(package) + set retval(os) $pkginfo(os) + set retval(cpu) $pkginfo(cpuArch) + set retval(version) $pkginfo(version) if {$pathlen > 2} { set retval(file) [join [lrange $path 2 end] "/"] } else { set retval(file) "" } - - return [array get retval] } else { set retval(package) $package set retval(_children) os-cpu - } - - if {$pathlen > 2} { - set os_cpu [lindex $path 2] - set os_cpu [split $os_cpu "-"] - - set retval(os) [lindex $os_cpu 0] - set retval(cpu) [lindex $os_cpu 1] - set retval(_children) versions - - if {$pathlen > 3} { - set retval(version) [lindex $path 3] - set retval(_children) files - - set retval(package_sha1) [::appfs::db onecolumn {SELECT sha1 FROM packages WHERE hostname = $retval(hostname) AND os = $retval(os) AND cpuArch = $retval(cpu) AND version = $retval(version);}] - if {$retval(package_sha1) == ""} { - return [list] - } - - if {$pathlen > 4} { - set retval(file) [join [lrange $path 4 end] "/"] - } else { - set retval(file) "" + set retval(_type) packages + + if {$pathlen > 2} { + set os_cpu [lindex $path 2] + set os_cpu [split $os_cpu "-"] + + set retval(os) [lindex $os_cpu 0] + set retval(cpu) [lindex $os_cpu 1] + set retval(_children) versions + set retval(_type) os-cpu + + if {$pathlen > 3} { + set retval(version) [lindex $path 3] + set retval(_children) files + set retval(_type) versions + + set retval(package_sha1) [::appfs::db onecolumn {SELECT sha1 FROM packages WHERE hostname = $retval(hostname) AND os = $retval(os) AND cpuArch = $retval(cpu) AND version = $retval(version);}] + if {$retval(package_sha1) == ""} { + return [list] + } + + if {$pathlen > 4} { + set retval(_type) files + set retval(file) [join [lrange $path 4 end] "/"] + } else { + set retval(file) "" + } } } } } } @@ -412,30 +428,106 @@ } return [::appfs::db eval {SELECT DISTINCT package FROM packages WHERE hostname = $pathinfo(hostname);}] } "os-cpu" { - return [::appfs::db eval {SELECT DISTINCT os || "-" || cpuArch FROM packages WHERE hostname = $pathinfo(hostname) AND package = $pathinfo(package);}] + set retval [::appfs::db eval {SELECT DISTINCT os || "-" || cpuArch FROM packages WHERE hostname = $pathinfo(hostname) AND package = $pathinfo(package);}] + + lappend retval "platform" + + return $retval } "versions" { - return [::appfs::db eval { + set retval [::appfs::db eval { SELECT DISTINCT version FROM packages WHERE hostname = $pathinfo(hostname) AND package = $pathinfo(package) AND os = $pathinfo(os) AND cpuArch = $pathinfo(cpu); }] + + lappend retval "latest" + + return $retval } "files" { catch { ::appfs::getpkgmanifest $pathinfo(hostname) $pathinfo(package_sha1) } - return [::appfs::db eval {SELECT DISTINCT file_name FROM files WHERE package_sha1 = $pathinfo(package_sha1) AND file_directory = $pathinfo(file);}] + set retval [::appfs::db eval {SELECT DISTINCT file_name FROM files WHERE package_sha1 = $pathinfo(package_sha1) AND file_directory = $pathinfo(file);}] + + if {[info exists pathinfo(package)] && [info exists pathinfo(hostname)] && [info exists pathinfo(file)]} { + set dir [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)] + foreach file [glob -nocomplain -tails -directory $dir -types {d f l} {{.,}*}] { + if {$file == "." || $file == ".."} { + continue + } + + if {[lsearch -exact $retval $file] != -1} { + continue + } + + lappend retval $file + } + } + + return $retval } } return -code error "Invalid or unacceptable path: $dir" } proc getattr {path} { + array set pathinfo [_parsepath $path] + array set retval [list] + + switch -- $pathinfo(_type) { + "toplevel" - "sites" - "packages" - "os-cpu" - "versions" { + set retval(type) directory + set retval(childcount) 2; + } + "files" { + set localpath [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)] + if {[file exists $localpath]} { + catch { + file lstat $localpath localpathinfo + set retval(time) $localpathinfo(mtime) + + switch -- $localpathinfo(type) { + "directory" { + set retval(type) "directory" + set retval(childcount) 2 + } + "file" { + set retval(type) "file" + set retval(size) $localpathinfo(size) + if {[file executable $localpath]} { + set retval(perms) "x" + } else { + set retval(perms) "" + } + } + "link" { + set retval(type) "symlink" + set retval(source) [file readlink $localpath] + } + } + } err + } else { + set work [split $pathinfo(file) "/"] + set directory [join [lrange $work 0 end-1] "/"] + set file [lindex $work end] + ::appfs::db eval {SELECT type, time, source, size, perms FROM files WHERE package_sha1 = $pathinfo(package_sha1) AND file_directory = $directory AND file_name = $file;} retval {} + unset -nocomplain retval(*) + } + + } + } + + if {![info exists retval(type)]} { + return -code error "No such file or directory" + } + + return [array get retval] } proc openpath {path mode} { } }