Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -1,3 +1,5 @@ appfsd appfsd.o appfsd.tcl.h +sha1.o +sha1.tcl.h Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -22,26 +22,33 @@ TCL_CFLAGS = $(shell . $(TCLCONFIG_SH_PATH); echo "$${TCL_INCLUDE_SPEC}") TCL_LIBS = $(shell . $(TCLCONFIG_SH_PATH); echo "$${TCL_LIB_SPEC}") all: appfsd -appfsd: appfsd.o - $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfsd appfsd.o $(LIBS) +appfsd: appfsd.o sha1.o + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfsd appfsd.o sha1.o $(LIBS) appfsd.o: appfsd.c appfsd.tcl.h $(CC) $(CPPFLAGS) $(CFLAGS) -o appfsd.o -c appfsd.c -appfsd.tcl.h: appfsd.tcl sha1.tcl - sed '/@@SHA1\.TCL@@/ r sha1.tcl' appfsd.tcl | sed '/@@SHA1\.TCL@@/ d' | sed 's@[\\"]@\\&@g;s@^@ "@;s@$$@\\n"@' > appfsd.tcl.h.new - mv appfsd.tcl.h.new appfsd.tcl.h +sha1.o: sha1.c sha1.tcl.h + $(CC) $(CPPFLAGS) $(CFLAGS) -o sha1.o -c sha1.c + +%.tcl.h: %.tcl + sed 's@[\\"]@\\&@g;s@^@ "@;s@$$@\\n"@' $^ > $@.new + mv $@.new $@ -install: appfsd +install: appfsd appfs-cache appfs-mkfs if [ ! -d '$(DESTDIR)$(sbindir)' ]; then mkdir -p '$(DESTDIR)$(sbindir)'; chmod 755 '$(DESTDIR)$(sbindir)'; fi + if [ ! -d '$(DESTDIR)$(bindir)' ]; then mkdir -p '$(DESTDIR)$(bindir)'; chmod 755 '$(DESTDIR)$(bindir)'; fi cp appfsd '$(DESTDIR)$(sbindir)/' + cp appfs-cache '$(DESTDIR)$(sbindir)/' + cp appfs-mkfs '$(DESTDIR)$(bindir)/' clean: rm -f appfsd appfsd.o rm -f appfsd.tcl.h + rm -f sha1.o sha1.tcl.h distclean: clean .PHONY: all test clean distclean install ADDED appfs-cache Index: appfs-cache ================================================================== --- appfs-cache +++ appfs-cache @@ -0,0 +1,93 @@ +#! /usr/bin/env bash + +appfsd_options=() +if [ "$1" == "--cachedir" ]; then + appfsd_options=("${appfsd_options[@]}" '--cachedir' "$2") + + shift; shift; +fi + +function call_appfsd() { + appfsd "${appfsd_options[@]}" "$@" +} + +function invalidate() { + call_appfsd --sqlite3 'UPDATE sites SET ttl = "0";' +} + +function remove_site() { + local site + + site="$1" + + call_appfsd --sqlite3 'DELETE FROM sites WHERE hostname = '"'$site'"'; DELETE FROM packages WHERE hostname = '"'$site'"';' || return 1 + + clean +} + +function clean() { + call_appfsd --tcl "$(cat <<\_EOF_ + unset -nocomplain row + ::appfs::db eval {SELECT sha1, hostname FROM packages;} row { + set hostname [::appfs::db onecolumn {SELECT hostname FROM sites WHERE hostname = $row(hostname) LIMIT 1;}] + if {$hostname == ""} { + continue + } + + set valid_sha1($row(sha1)) 1 + ::appfs::db eval {SELECT file_sha1 FROM files WHERE file_sha1 IS NOT NULL AND file_sha1 != '' AND package_sha1 = $row(sha1);} subrow { + set valid_sha1($subrow(file_sha1)) 1 + } + } + + foreach file [glob -nocomplain -tails -directory $::appfs::cachedir {[0-9a-f][0-9a-f]/*/*/*/*}] { + set sha1 [string map [list "/" "" "\\" ""] $file] + set file [file join $::appfs::cachedir $file] + + if {[info exists valid_sha1($sha1)]} { + continue + } + + puts "Cleaning $file" + file delete -force -- $file + } +_EOF_ + )" +} + +function clear() { + local package + + package="$1" + + if [ -n "${package}" ]; then + echo "not implemented" >&2 + + return 1 + fi + + call_appfsd --tcl 'file delete -force -- {*}[glob -directory $::appfs::cachedir {[0-9a-f][0-9a-f]}]' || return 1 + call_appfsd --sqlite3 'DELETE FROM sites; DELETE FROM packages; DELETE FROM files; VACUUM;' || return 1 +} + +case "$1" in + invalidate) + invalidate || exit 1 + ;; + remove-site) + remove_site "$2" || exit 1 + ;; + clean) + clean || exit 1 + ;; + clear) + clear "$2" || exit 1 + ;; + *) + echo "Usage: appfs-cache {invalidate|clean|clear|clear |remove-site }" >&2 + + exit 1 + ;; +esac + +exit 0 Index: appfsd.c ================================================================== --- appfsd.c +++ appfsd.c @@ -1,9 +1,8 @@ #define FUSE_USE_VERSION 26 #include -#include #include #include #include #include #include @@ -12,48 +11,57 @@ #include #include #include #include +/* + * Default cache directory + */ #ifndef APPFS_CACHEDIR #define APPFS_CACHEDIR "/var/cache/appfs" #endif +/* Debugging macros */ #ifdef DEBUG #define APPFS_DEBUG(x...) { fprintf(stderr, "[debug] %s:%i:%s: ", __FILE__, __LINE__, __func__); fprintf(stderr, x); fprintf(stderr, "\n"); } #else #define APPFS_DEBUG(x...) /**/ #endif +/* + * SHA1 Tcl Package initializer, from sha1.o + */ +int Sha1_Init(Tcl_Interp *interp); + +/* + * Thread Specific Data (TSD) for Tcl Interpreter for the current thread + */ static pthread_key_t interpKey; -struct appfs_thread_data { - sqlite3 *db; - const char *cachedir; - time_t boottime; - const char *platform; - struct { - int writable; - } options; -}; - -struct appfs_thread_data globalThread; - +/* + * 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 { 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]; -}; - +/* + * AppFS Path Information: + * Completely describes a specific path, how it should be returned to + * to the kernel + */ struct appfs_pathinfo { appfs_pathtype_t type; time_t time; char hostname[256]; int packaged; @@ -63,26 +71,22 @@ 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) { +/* + * Create a new Tcl interpreter and completely initialize it + */ +static Tcl_Interp *appfs_create_TclInterp(void) { Tcl_Interp *interp; int tcl_ret; APPFS_DEBUG("Creating new Tcl interpreter for TID = 0x%llx", (unsigned long long) pthread_self()); @@ -94,16 +98,42 @@ } tcl_ret = Tcl_Init(interp); if (tcl_ret != TCL_OK) { fprintf(stderr, "Unable to initialize Tcl. Aborting.\n"); + fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp)); + + Tcl_DeleteInterp(interp); + + return(NULL); + } + + tcl_ret = Tcl_Eval(interp, "package ifneeded sha1 1.0 [list load {} sha1]"); + if (tcl_ret != TCL_OK) { + fprintf(stderr, "Unable to initialize Tcl SHA1. Aborting.\n"); + fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp)); + + Tcl_DeleteInterp(interp); + + return(NULL); + } + + tcl_ret = Tcl_Eval(interp, "package ifneeded appfsd 1.0 [list load {} appfsd]"); + if (tcl_ret != TCL_OK) { + fprintf(stderr, "Unable to initialize Tcl AppFS Package. Aborting.\n"); + fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp)); Tcl_DeleteInterp(interp); return(NULL); } + /* + * Load the "appfsd.tcl" script, which is "compiled" into a C header + * so that it does not need to exist on the filesystem and can be + * directly evaluated. + */ tcl_ret = Tcl_Eval(interp, "" #include "appfsd.tcl.h" ""); if (tcl_ret != TCL_OK) { fprintf(stderr, "Unable to initialize Tcl AppFS script. Aborting.\n"); @@ -112,18 +142,25 @@ Tcl_DeleteInterp(interp); return(NULL); } - if (Tcl_SetVar(interp, "::appfs::cachedir", cachedir, TCL_GLOBAL_ONLY) == NULL) { + /* + * Set global variables from C to Tcl + */ + if (Tcl_SetVar(interp, "::appfs::cachedir", appfs_cachedir, TCL_GLOBAL_ONLY) == NULL) { fprintf(stderr, "Unable to set cache directory. This should never fail.\n"); Tcl_DeleteInterp(interp); return(NULL); } + /* + * Initialize the "appfsd.tcl" environment, which must be done after + * global variables are set. + */ 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)); @@ -130,19 +167,53 @@ Tcl_DeleteInterp(interp); return(NULL); } - Tcl_HideCommand(interp, "glob", "glob"); - Tcl_HideCommand(interp, "exec", "exec"); - Tcl_HideCommand(interp, "pid", "pid"); + /* + * Hide some Tcl commands that we do not care to use and which may + * slow down run-time operations. + */ Tcl_HideCommand(interp, "auto_load_index", "auto_load_index"); Tcl_HideCommand(interp, "unknown", "unknown"); + + /* + * Return the completely initialized interpreter + */ + return(interp); +} + +/* + * Return the thread-specific Tcl interpreter, creating it if needed + */ +static Tcl_Interp *appfs_TclInterp(void) { + Tcl_Interp *interp; + int pthread_ret; + + interp = pthread_getspecific(interpKey); + if (interp == NULL) { + interp = appfs_create_TclInterp(); + + if (interp == NULL) { + return(NULL); + } + + pthread_ret = pthread_setspecific(interpKey, interp); + if (pthread_ret != 0) { + Tcl_DeleteInterp(interp); + + return(NULL); + } + } return(interp); } +/* + * Evaluate a Tcl script constructed by concatenating a bunch of C strings + * together. + */ static int appfs_Tcl_Eval(Tcl_Interp *interp, int objc, const char *cmd, ...) { Tcl_Obj **objv; const char *arg; va_list argp; int retval; @@ -177,112 +248,39 @@ } 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); - - if (interp == NULL) { - return; - } - - 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); - - if (interp == NULL) { - return(NULL); - } - - 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); - - if (interp == NULL) { - return; - } - - 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) - +/* + * Determine the UID for the user making the current FUSE filesystem request. + * 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 */ return(1); } return(ctx->uid); } -static const char *appfs_get_homedir(uid_t fsuid) { +/* + * Look up the home directory for a given UID + * Returns a C string containing the user's home directory or NULL if + * the user's home directory does not exist or is not correctly + * configured + */ +static char *appfs_get_homedir(uid_t fsuid) { struct passwd entry, *result; struct stat stbuf; char buf[1024], *retval; int gpu_ret, stat_ret; @@ -320,444 +318,45 @@ ); return(NULL); } - retval = sqlite3_mprintf("%s", result->pw_dir); - - return(retval); -} - -static int appfs_getpackage_name_cb(void *_package_name, int columns, char **values, char **names) { - char **package_name; - - if (columns != 1) { - return(1); - } - - package_name = _package_name; - - *package_name = sqlite3_mprintf("%s", values[0]); - - return(0); -} - -static char *appfs_getpackage_name(const char *hostname, const char *package_hash) { - char *sql; - int sqlite_ret; - char *package_name = NULL; - - sql = sqlite3_mprintf("SELECT package FROM packages WHERE hostname = %Q AND sha1 = %Q LIMIT 1;", hostname, package_hash); - if (sql == NULL) { - APPFS_DEBUG("Call to sqlite3_mprintf failed."); - - return(sqlite3_mprintf("%s", "unknown-package-name")); - } - sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_getpackage_name_cb, &package_name, NULL); - sqlite3_free(sql); - - if (sqlite_ret != SQLITE_OK) { - APPFS_DEBUG("Call to sqlite3_exec failed."); - - return(sqlite3_mprintf("%s", "unknown-package-name")); - } - - return(package_name); -} - -static struct appfs_children *appfs_getchildren_fs(struct appfs_children *in_children, const char *fspath) { - APPFS_DEBUG("Searching %s", fspath); - - return(in_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, *filebuf, *homedir = NULL; - int sqlite_ret; - uid_t fsuid; - - 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 (globalThread.options.writable) { - /* Determine user of process accessing this file */ - fsuid = appfs_get_fsuid(); - - /* Check filesystem paths for updated files */ - /** Check the global directory (/etc) **/ - filebuf = sqlite3_mprintf("/etc/appfs/%z@%s/%s", appfs_getpackage_name(hostname, package_hash), hostname, path); - if (filebuf == NULL) { - APPFS_DEBUG("Call to sqlite3_mprintf failed."); - - return(NULL); - } - - head = appfs_getchildren_fs(head, filebuf); - - sqlite3_free(filebuf); - - /** Check the user's directory, if we are not root **/ - if (fsuid != 0) { - homedir = (char *) appfs_get_homedir(fsuid); - } - - if (homedir != NULL) { - filebuf = sqlite3_mprintf("%z/.appfs/%z@%s/%s", homedir, appfs_getpackage_name(hostname, package_hash), hostname, path); - - if (filebuf == NULL) { - APPFS_DEBUG("Call to sqlite3_mprintf failed."); - - return(NULL); - } - - head = appfs_getchildren_fs(head, filebuf); - - sqlite3_free(filebuf); - } - } - - if (head != NULL) { - *children_count_p = head->counter + 1; - } else { - *children_count_p = 0; - } - - 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 */ + retval = strdup(result->pw_dir); + + return(retval); +} + +/* + * Tcl interface to get the home directory for the user making the "current" + * FUSE I/O request + */ +static int tcl_appfs_get_homedir(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { + char *homedir; + + if (objc != 1) { + Tcl_WrongNumArgs(interp, 1, objv, NULL); + return(TCL_ERROR); + } + + homedir = appfs_get_homedir(appfs_get_fsuid()); + + if (homedir == NULL) { + return(TCL_ERROR); + } + + Tcl_SetObjResult(interp, Tcl_NewStringObj(homedir, -1)); + + free(homedir); + + return(TCL_OK); +} + +/* + * Generate an inode for a given path. The inode should be computed in such + * a way that it is unlikely to be duplicated and remains the same for a given + * file + */ static long long appfs_get_path_inode(const char *path) { long long retval; const char *p; retval = 10; @@ -773,214 +372,144 @@ 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; - - /* Initialize return */ - if (children) { - *children = NULL; - } - - /* Verify that this is a valid request */ - if (_path == NULL) { - return(-ENOENT); - } - - if (_path[0] != '/') { - return(-ENOENT); - } - - /* Note that this is not a "real" directory from a package */ - pathinfo->packaged = 0; - - 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 */ - pathinfo->packaged = 1; - 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); +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, *attr_key_packaged = NULL; + int tcl_ret; + + interp = appfs_TclInterp(); + if (interp == NULL) { + return(-EIO); + } + + 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(-ENOENT); + } + + 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); + attr_key_packaged = Tcl_NewStringObj("packaged", -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(-EIO); + } + + if (attr_value == NULL) { + return(-EIO); + } + + pathinfo->packaged = 0; + pathinfo->inode = appfs_get_path_inode(path); + + 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(-EIO); + } + + Tcl_DictObjGet(interp, attrs_dict, attr_key_packaged, &attr_value); + if (attr_value != NULL) { + pathinfo->packaged = 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); } @@ -994,19 +523,21 @@ 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_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); } memset(stbuf, 0, sizeof(struct stat)); stbuf->st_mtime = pathinfo.time; @@ -1034,76 +565,117 @@ 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) { - if (globalThread.options.writable) { - stbuf->st_mode |= 0222; - } + 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) { - struct appfs_pathinfo pathinfo; - struct appfs_children *children, *child; - int retval; + Tcl_Interp *interp; + Tcl_Obj **children; + int children_count, idx; + int tcl_ret; APPFS_DEBUG("Enter (path = %s, ...)", path); - retval = appfs_get_path_info(path, &pathinfo, &children); - if (retval != 0) { - return(retval); + interp = appfs_TclInterp(); + if (interp == NULL) { + return(0); } filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); - for (child = children; child; child = child->_next) { - filler(buf, child->name, 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)); + + return(0); + } + + tcl_ret = Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp), &children_count, &children); + if (tcl_ret != TCL_OK) { + APPFS_DEBUG("Parsing list of children on path %s failed.", path); + APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp)); + + return(0); } - appfs_free_list_children(children); + for (idx = 0; idx < children_count; idx++) { + filler(buf, Tcl_GetString(children[idx]), NULL, 0); + } return(0); } static int appfs_fuse_open(const char *path, struct fuse_file_info *fi) { + Tcl_Interp *interp; struct appfs_pathinfo pathinfo; - const char *real_path; + const char *real_path, *mode; + int gpi_ret, tcl_ret; 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); + + if ((fi->flags & (O_WRONLY|O_CREAT)) == (O_CREAT|O_WRONLY)) { + /* The file will be created if it does not exist */ + if (gpi_ret != 0 && gpi_ret != -ENOENT) { + return(gpi_ret); + } + + mode = "create"; + } else { + /* The file must already exist */ + if (gpi_ret != 0) { + return(gpi_ret); + } + + mode = ""; - gpi_ret = appfs_get_path_info(path, &pathinfo, NULL); - if (gpi_ret != 0) { - return(gpi_ret); + if ((fi->flags & O_WRONLY) == O_WRONLY) { + mode = "write"; + } } if (pathinfo.type == APPFS_PATHTYPE_DIRECTORY) { return(-EISDIR); } - real_path = appfs_getfile(pathinfo.hostname, pathinfo.typeinfo.file.sha1); + interp = appfs_TclInterp(); + if (interp == NULL) { + return(-EIO); + } + + tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::openpath", path, mode); + if (tcl_ret != TCL_OK) { + APPFS_DEBUG("::appfs::openpath(%s, %s) failed.", path, mode); + APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp)); + + return(-EIO); + } + + real_path = Tcl_GetStringResult(interp); if (real_path == NULL) { return(-EIO); } - fh = open(real_path, O_RDONLY); - free((void *) real_path); + APPFS_DEBUG("Translated request to open %s to opening %s (mode = \"%s\")", path, real_path, mode); + + fh = open(real_path, fi->flags, 0600); if (fh < 0) { return(-EIO); } fi->fh = fh; @@ -1136,48 +708,227 @@ read_ret = read(fi->fh, buf, size); return(read_ret); } -static struct fuse_operations appfs_oper = { +static int appfs_fuse_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { + off_t lseek_ret; + ssize_t write_ret; + + APPFS_DEBUG("Enter (path = %s, ...)", path); + + lseek_ret = lseek(fi->fh, offset, SEEK_SET); + if (lseek_ret != offset) { + return(-EIO); + } + + write_ret = write(fi->fh, buf, size); + + return(write_ret); +} + +/* + * SQLite3 mode: Execute raw SQL and return success or failure + */ +static int appfs_sqlite3(const char *sql) { + Tcl_Interp *interp; + const char *sql_ret; + int tcl_ret; + + interp = appfs_create_TclInterp(); + if (interp == NULL) { + fprintf(stderr, "Unable to create a Tcl interpreter. Aborting.\n"); + + return(1); + } + + tcl_ret = appfs_Tcl_Eval(interp, 5, "::appfs::db", "eval", sql, "row", "unset -nocomplain row(*); parray row; puts \"----\""); + sql_ret = Tcl_GetStringResult(interp); + + if (tcl_ret != TCL_OK) { + fprintf(stderr, "[error] %s\n", sql_ret); + + return(1); + } + + if (sql_ret && sql_ret[0] != '\0') { + printf("%s\n", sql_ret); + } + + return(0); +} + +/* + * Tcl mode: Execute raw Tcl and return success or failure + */ +static int appfs_tcl(const char *tcl) { + Tcl_Interp *interp; + const char *tcl_result; + int tcl_ret; + + interp = appfs_create_TclInterp(); + if (interp == NULL) { + fprintf(stderr, "Unable to create a Tcl interpreter. Aborting.\n"); + + return(1); + } + + tcl_ret = Tcl_Eval(interp, tcl); + tcl_result = Tcl_GetStringResult(interp); + + if (tcl_ret != TCL_OK) { + fprintf(stderr, "[error] %s\n", tcl_result); + + return(1); + } + + if (tcl_result && tcl_result[0] != '\0') { + printf("%s\n", tcl_result); + } + + return(0); +} + +/* + * AppFSd Package for Tcl: + * Bridge for I/O operations to request information about the current + * transaction + */ +static int Appfsd_Init(Tcl_Interp *interp) { +#ifdef USE_TCL_STUBS + if (Tcl_InitStubs(interp, TCL_VERSION, 0) == 0L) { + return(TCL_ERROR); + } +#endif + + Tcl_CreateObjCommand(interp, "appfsd::get_homedir", tcl_appfs_get_homedir, NULL, NULL); + + Tcl_PkgProvide(interp, "appfsd", "1.0"); + + return(TCL_OK); +} + +/* + * FUSE operations structure + */ +static struct fuse_operations appfs_operations = { .getattr = appfs_fuse_getattr, .readdir = appfs_fuse_readdir, .readlink = appfs_fuse_readlink, .open = appfs_fuse_open, .release = appfs_fuse_close, - .read = appfs_fuse_read + .read = appfs_fuse_read, + .write = appfs_fuse_write }; +/* + * FUSE option parsing callback + */ +static int appfs_fuse_opt_cb(void *data, const char *arg, int key, struct fuse_args *outargs) { + static int seen_cachedir = 0; + + if (key == FUSE_OPT_KEY_NONOPT && seen_cachedir == 0) { + seen_cachedir = 1; + + appfs_cachedir = strdup(arg); + + return(0); + } + + return(1); +} + +/* + * Entry point into this program. + */ 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"; - globalThread.options.writable = 1; - + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + int pthread_ret; + + /* + * Skip passed program name + */ + if (argc == 0 || argv == NULL) { + return(1); + } + argc--; + argv++; + + /* + * Set global variables, these should be configuration options. + */ + appfs_cachedir = APPFS_CACHEDIR; + + /* + * Set global variable for "boot time" to set a time on directories + * that we fake. + */ + appfs_boottime = time(NULL); + + /* + * Register "sha1" and "appfsd" package with libtcl so that any new + * interpreters created (which are done dynamically by FUSE) can have + * the appropriate configuration done automatically. + */ + Tcl_StaticPackage(NULL, "sha1", Sha1_Init, NULL); + Tcl_StaticPackage(NULL, "appfsd", Appfsd_Init, NULL); + + /* + * Create a thread-specific-data (TSD) key for each thread to refer + * to its own Tcl interpreter. Tcl interpreters must be unique per + * thread and new threads are dynamically created by FUSE. + */ 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)); + /* + * Manually specify cache directory, without FUSE callback + * This option only works when not using FUSE, since we + * do not process it with FUSEs option processing. + */ + if (argc >= 2) { + if (strcmp(argv[0], "--cachedir") == 0) { + appfs_cachedir = strdup(argv[1]); + + argc -= 2; + argv += 2; + } + } + + /* + * SQLite3 mode, for running raw SQL against the cache database + */ + if (argc == 2 && strcmp(argv[0], "--sqlite3") == 0) { + return(appfs_sqlite3(argv[1])); + } + + /* + * Tcl mode, for running raw Tcl in the same environment AppFSd would + * run code. + */ + if (argc == 2 && strcmp(argv[0], "--tcl") == 0) { + return(appfs_tcl(argv[1])); + } + + /* + * Add FUSE arguments which we always supply + */ + fuse_opt_parse(&args, NULL, NULL, appfs_fuse_opt_cb); + fuse_opt_add_arg(&args, "-odefault_permissions,fsname=appfs,subtype=appfsd,use_ino,kernel_cache,entry_timeout=60,attr_timeout=3600,intr,big_writes"); + + if (getuid() == 0) { + fuse_opt_parse(&args, NULL, NULL, NULL); + fuse_opt_add_arg(&args, "-oallow_other"); + } + + /* + * 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,21 +1,22 @@ #! /usr/bin/env tclsh package require http 2.7 package require sqlite3 - -if {[catch { - package require sha1 -}]} { - @@SHA1.TCL@@ - package require sha1 -} +package require sha1 +package require appfsd +package require platform namespace eval ::appfs { variable cachedir "/tmp/appfs-cache" variable ttl 3600 variable nttl 60 + + # User-replacable function to convert a hostname/hash/method to an URL + proc _construct_url {hostname hash method} { + return "http://$hostname/appfs/$method/$hash" + } 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}]]/" } @@ -36,11 +37,11 @@ if {[file exists $file]} { return $file } - set tmpfile "${file}.[expr {rand()}]" + set tmpfile "${file}.[expr {rand()}][clock clicks]" set fd [open $tmpfile "w"] fconfigure $fd -translation binary catch { @@ -84,14 +85,10 @@ } return true } - proc _db {args} { - return [uplevel 1 [list ::appfs::db {*}$args]] - } - proc _normalizeOS {os} { set os [string tolower [string trim $os]] switch -- $os { "linux" - "freebsd" - "openbsd" - "netbsd" { @@ -128,32 +125,43 @@ proc init {} { if {[info exists ::appfs::init_called]} { return } + + # Force [parray] to be loaded + catch { + parray does_not_exist + } set ::appfs::init_called 1 + + # Load configuration file + set config_file [file join $::appfs::cachedir config] + if {[file exists $config_file]} { + source $config_file + } if {![info exists ::appfs::db]} { file mkdir $::appfs::cachedir sqlite3 ::appfs::db [file join $::appfs::cachedir cache.db] } # Create tables - _db eval {CREATE TABLE IF NOT EXISTS sites(hostname PRIMARY KEY, lastUpdate, ttl);} - _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);} + db eval {CREATE TABLE IF NOT EXISTS sites(hostname PRIMARY KEY, lastUpdate, ttl);} + 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);} # Create indexes - _db eval {CREATE INDEX IF NOT EXISTS sites_index ON sites (hostname);} - _db eval {CREATE INDEX IF NOT EXISTS packages_index ON packages (hostname, package, version, os, cpuArch);} - _db eval {CREATE INDEX IF NOT EXISTS files_index ON files (package_sha1, file_name, file_directory);} + db eval {CREATE INDEX IF NOT EXISTS sites_index ON sites (hostname);} + db eval {CREATE INDEX IF NOT EXISTS packages_index ON packages (hostname, package, version, os, cpuArch);} + db eval {CREATE INDEX IF NOT EXISTS files_index ON files (package_sha1, file_name, file_directory);} } proc download {hostname hash {method sha1}} { - set url "http://$hostname/appfs/$method/$hash" + set url [_construct_url $hostname $hash $method] set file [_cachefile $url $hash] if {![file exists $file]} { return -code error "Unable to fetch (file does not exist: $file)" } @@ -162,11 +170,11 @@ } proc getindex {hostname} { set now [clock seconds] - set lastUpdates [_db eval {SELECT lastUpdate, ttl FROM sites WHERE hostname = $hostname LIMIT 1;}] + set lastUpdates [db eval {SELECT lastUpdate, ttl FROM sites WHERE hostname = $hostname LIMIT 1;}] if {[llength $lastUpdates] == 0} { set lastUpdate 0 set ttl 0 } else { set lastUpdate [lindex $lastUpdates 0] @@ -192,11 +200,11 @@ ::http::cleanup $token } if {![info exists indexhash_data]} { # Cache this result for 60 seconds - _db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate, ttl) VALUES ($hostname, $now, $::appfs::nttl);} + db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate, ttl) VALUES ($hostname, $now, $::appfs::nttl);} return -code error "Unable to fetch $url" } set indexhash [lindex [split $indexhash_data ","] 0] @@ -242,43 +250,43 @@ } lappend curr_packages $pkgInfo(hash) # 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);}] + 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 {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);} + 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);} } # Look for packages that have been deleted - set found_packages [_db eval {SELECT sha1 FROM packages WHERE hostname = $hostname;}] + set found_packages [db eval {SELECT sha1 FROM packages WHERE hostname = $hostname;}] foreach package $found_packages { set found_packages_arr($package) 1 } foreach package $curr_packages { unset -nocomplain found_packages_arr($package) } foreach package [array names found_packages_arr] { - _db eval {DELETE FROM packages WHERE hostname = $hostname AND sha1 = $package;} + db eval {DELETE FROM packages WHERE hostname = $hostname AND sha1 = $package;} } - _db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate, ttl) VALUES ($hostname, $now, $::appfs::ttl);} + db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate, ttl) VALUES ($hostname, $now, $::appfs::ttl);} return COMPLETE } proc getpkgmanifest {hostname package_sha1} { - set haveManifests [_db eval {SELECT haveManifest FROM packages WHERE sha1 = $package_sha1 LIMIT 1;}] + set haveManifests [db eval {SELECT haveManifest FROM packages WHERE sha1 = $package_sha1 LIMIT 1;}] set haveManifest [lindex $haveManifests 0] if {$haveManifest} { return COMPLETE } @@ -290,11 +298,11 @@ set file [download $hostname $package_sha1] set fd [open $file] set pkgdata [read $fd] close $fd - _db transaction { + db transaction { foreach line [split $pkgdata "\n"] { set line [string trim $line] if {$line == ""} { continue @@ -324,13 +332,299 @@ set fileInfo(name) [join $work ","] 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] - _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;} + 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 } + + proc _localpath {package hostname file} { + set homedir [::appfsd::get_homedir] + set dir [file join $homedir .appfs "./${package}@${hostname}" "./${file}"] + return $dir + } + + proc _parsepath {path} { + set path [string trim $path "/"] + set path [split $path "/"] + set pathlen [llength $path] + + 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) "" + } + } else { + set retval(package) $package + set retval(_children) os-cpu + 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) == ""} { + set retval(_children) dead + return [array get retval] + } + + if {$pathlen > 4} { + set retval(_type) files + set retval(file) [join [lrange $path 4 end] "/"] + } else { + set retval(file) "" + } + } + } + } + } + } + + return [array get retval] + } + + proc getchildren {dir} { + array set pathinfo [_parsepath $dir] + + switch -- $pathinfo(_children) { + "sites" { + return [::appfs::db eval {SELECT DISTINCT hostname FROM packages;}] + } + "packages" { + catch { + ::appfs::getindex $pathinfo(hostname) + } + + return [::appfs::db eval {SELECT DISTINCT package FROM packages WHERE hostname = $pathinfo(hostname);}] + } + "os-cpu" { + 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" { + 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) + } + + 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] + + catch { + ::appfs::getindex $pathinfo(hostname) + ::appfs::getpkgmanifest $pathinfo(hostname) $pathinfo(package_sha1) + } + + switch -- $pathinfo(_type) { + "toplevel" { + set retval(type) directory + set retval(childcount) 2; + } + "sites" { + set check [::appfs::db onecolumn {SELECT 1 FROM packages WHERE hostname = $pathinfo(hostname);}] + if {$check == "1"} { + set retval(type) directory + set retval(childcount) 2; + } + } + "packages" { + set check [::appfs::db onecolumn {SELECT 1 FROM packages WHERE hostname = $pathinfo(hostname) AND package = $pathinfo(package);}] + if {$check == "1"} { + set retval(type) directory + set retval(childcount) 2; + } + } + "os-cpu" { + if {$pathinfo(os) == "platform" && $pathinfo(cpu) == ""} { + set retval(type) symlink + set retval(source) [platform::generic] + } else { + set check [::appfs::db onecolumn { + SELECT 1 FROM packages WHERE hostname = $pathinfo(hostname) AND package = $pathinfo(package) AND os = $pathinfo(os) AND cpuArch = $pathinfo(cpu); + }] + if {$check == "1"} { + set retval(type) directory + set retval(childcount) 2; + } + } + } + "versions" { + if {$pathinfo(version) == "latest"} { + set retval(type) symlink + set retval(source) "1.0" + } else { + if {[info exists pathinfo(package_sha1)] && $pathinfo(package_sha1) != ""} { + set retval(type) directory + set retval(childcount) 2; + } + } + } + "files" { + + set retval(packaged) 1 + + 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} { + array set pathinfo [_parsepath $path] + + if {$pathinfo(_type) != "files"} { + return -code error "invalid type" + } + + set localpath [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)] + + if {[file exists $localpath]} { + return $localpath + } + + if {$mode == "create"} { + return $localpath + } + + set work [split $pathinfo(file) "/"] + set directory [join [lrange $work 0 end-1] "/"] + set file [lindex $work end] + ::appfs::db eval {SELECT file_sha1, perms FROM files WHERE package_sha1 = $pathinfo(package_sha1) AND file_name = $file AND file_directory = $directory;} pkgpathinfo {} + + if {$pkgpathinfo(file_sha1) == ""} { + return -code error "No such file or directory" + } + + set localcachefile [download $pathinfo(hostname) $pkgpathinfo(file_sha1)] + + if {$mode == "write"} { + set tmplocalpath "${localpath}.[expr rand()][clock clicks]" + + catch { + file copy -force -- $localcachefile $tmplocalpath + + if {$pkgpathinfo(perms) == "x"} { + file attributes $tmplocalpath -permissions +x + } + + file rename -force -- $tmplocalpath $localpath + } + catch { + file delete -force -- $tmplocalpath + } + + return $localpath + } + + return $localcachefile + } } ADDED sha1.c Index: sha1.c ================================================================== --- sha1.c +++ sha1.c @@ -0,0 +1,322 @@ +/* + SHA-1 in C + By Steve Reid + 100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define SHA1HANDSOFF 1 + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + uint8_t buffer[64]; +} SHA1_CTX; + +#ifndef __BIG_ENDIAN +#define __BIG_ENDIAN 4321 +#endif +#ifndef __LITTLE_ENDIAN +#define __LITTLE_ENDIAN 1234 +#endif + +#ifndef __BYTE_ORDER +#ifdef WORDS_BIGENDIAN +#define __BYTE_ORDER __BIG_ENDIAN +#else +#define __BYTE_ORDER __LITTLE_ENDIAN +#endif +#endif + +#if __BYTE_ORDER == __BIG_ENDIAN +#ifndef BIG_ENDIAN +#define BIG_ENDIAN 1 +#endif +#undef LITTLE_ENDIAN +#else +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN 1 +#endif +#undef BIG_ENDIAN +#endif + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifdef LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +static void SHA1Transform(uint32_t state[5], uint8_t buffer[64]) { + uint32_t a, b, c, d, e; + typedef union { + uint8_t c[64]; + uint32_t l[16]; + } CHAR64LONG16; + CHAR64LONG16* block; +#ifdef SHA1HANDSOFF + uint8_t workspace[sizeof(*block)]; + + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, sizeof(*block)); +#else + block = (CHAR64LONG16*)buffer; +#endif + + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1Init - Initialize new context */ +static void SHA1Init(SHA1_CTX* context) { + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = 0; + context->count[1] = 0; +} + + +/* Run your data through this. */ +static void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int len) { + unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) { + context->count[1]++; + } + + context->count[1] += (len >> 29); + + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } else { + i = 0; + } + + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ +static void SHA1Final(unsigned char digest[20], SHA1_CTX* context) { + unsigned long i; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + + SHA1Update(context, (unsigned char *) "\200", 1); + + while ((context->count[0] & 504) != 448) { + SHA1Update(context, (unsigned char *)"\0", 1); + } + + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + + /* Wipe variables */ + i = 0; + + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite it's own static vars */ + SHA1Transform(context->state, context->buffer); +#endif +} + +static Tcl_Obj* c_sha1__sha1_file(char* file) { + SHA1_CTX ctx; + unsigned char digest[20]; + unsigned char buf[4096]; + int fd; + ssize_t read_ret; + Tcl_Obj *ret; + + fd = open(file, O_RDONLY); + if (fd < 0) { + return(NULL); + } + + SHA1Init(&ctx); + + while (1) { + read_ret = read(fd, buf, sizeof(buf)); + + if (read_ret == 0) { + break; + } + + if (read_ret < 0) { + close(fd); + + return(NULL); + } + + SHA1Update(&ctx, buf, read_ret); + } + + close(fd); + + SHA1Final(digest, &ctx); + + ret = Tcl_NewByteArrayObj(digest, sizeof(digest)); + + return(ret); +} + +static int tcl_sha1__sha1_file(ClientData dummy, Tcl_Interp *ip, int objc, Tcl_Obj *CONST objv[]) { + char* _file; + Tcl_Obj* rv; + if (objc != 2) { + Tcl_WrongNumArgs(ip, 1, objv, "file"); + return TCL_ERROR; + } + _file = Tcl_GetString(objv[1]); + + rv = c_sha1__sha1_file(_file); + if (rv == NULL) { + return(TCL_ERROR); + } + Tcl_SetObjResult(ip, rv); + return TCL_OK; +} + +static Tcl_Obj* c_sha1__sha1_string(Tcl_Obj* str) { + SHA1_CTX ctx; + unsigned char digest[20]; + unsigned char *buf; + int buf_len; + Tcl_Obj *ret; + + SHA1Init(&ctx); + + buf = Tcl_GetByteArrayFromObj(str, &buf_len); + if (buf == NULL) { + return(NULL); + } + + SHA1Update(&ctx, buf, buf_len); + + SHA1Final(digest, &ctx); + + ret = Tcl_NewByteArrayObj(digest, sizeof(digest)); + + return(ret); +} + +static int tcl_sha1__sha1_string(ClientData dummy, Tcl_Interp *ip, int objc, Tcl_Obj *CONST objv[]) { + Tcl_Obj* _str; + Tcl_Obj* rv; + if (objc != 2) { + Tcl_WrongNumArgs(ip, 1, objv, "str"); + return TCL_ERROR; + } + _str = objv[1]; + + rv = c_sha1__sha1_string(_str); + if (rv == NULL) { + return(TCL_ERROR); + } + Tcl_SetObjResult(ip, rv); + return TCL_OK; +} + +int Sha1_Init(Tcl_Interp *interp) { +#ifdef USE_TCL_STUBS + if (Tcl_InitStubs(interp, TCL_VERSION, 0) == 0L) { + return TCL_ERROR; + } +#endif + Tcl_CreateObjCommand(interp, "sha1::_sha1_file", tcl_sha1__sha1_file, NULL, NULL); + Tcl_CreateObjCommand(interp, "sha1::_sha1_string", tcl_sha1__sha1_string, NULL, NULL); + Tcl_Eval(interp, +#include "sha1.tcl.h" + ); + Tcl_PkgProvide(interp, "sha1", "1.0"); + return(TCL_OK); +} Index: sha1.tcl ================================================================== --- sha1.tcl +++ sha1.tcl @@ -1,607 +1,44 @@ -# sha1.tcl - - -# @@ Meta Begin -# Package sha1 2.0.3 -# Meta platform tcl -# Meta rsk::build::date 2011-03-30 -# Meta description Part of the Tclib sha1 module -# Meta require {Tcl 8.2} -# @@ Meta End - -# -# Copyright (C) 2001 Don Libes -# Copyright (C) 2003 Pat Thoyts -# -# SHA1 defined by FIPS 180-1, "The SHA1 Message-Digest Algorithm" -# HMAC defined by RFC 2104, "Keyed-Hashing for Message Authentication" -# -# This is an implementation of SHA1 based upon the example code given in -# FIPS 180-1 and upon the tcllib MD4 implementation and taking some ideas -# and methods from the earlier tcllib sha1 version by Don Libes. -# -# This implementation permits incremental updating of the hash and -# provides support for external compiled implementations either using -# critcl (sha1c) or Trf. -# -# ref: http://www.itl.nist.gov/fipspubs/fip180-1.htm -# -# ------------------------------------------------------------------------- -# See the file "license.terms" for information on usage and redistribution -# of this file, and for a DISCLAIMER OF ALL WARRANTIES. -# ------------------------------------------------------------------------- -# -# $Id: sha1.tcl,v 1.22 2009/05/07 00:35:10 patthoyts Exp $ - -# @mdgen EXCLUDE: sha1c.tcl - -package require Tcl 8.2; # tcl minimum version - -namespace eval ::sha1 { - variable version 2.0.3 - namespace export sha1 hmac SHA1Init SHA1Update SHA1Final - variable uid - if {![info exists uid]} { - set uid 0 - } -} - -proc ::sha1::SHA1Init {} { - variable uid - set token [namespace current]::[incr uid] - upvar #0 $token state - - # FIPS 180-1: 7 - Initialize the hash state - array set state \ - [list \ - A [expr {int(0x67452301)}] \ - B [expr {int(0xEFCDAB89)}] \ - C [expr {int(0x98BADCFE)}] \ - D [expr {int(0x10325476)}] \ - E [expr {int(0xC3D2E1F0)}] \ - n 0 i "" ] - return $token -} - -# SHA1Update -- -# -# This is called to add more data into the hash. You may call this -# as many times as you require. Note that passing in "ABC" is equivalent -# to passing these letters in as separate calls -- hence this proc -# permits hashing of chunked data -# -# If we have a C-based implementation available, then we will use -# it here in preference to the pure-Tcl implementation. -# -proc ::sha1::SHA1Update {token data} { - upvar #0 $token state - - # Update the state values - incr state(n) [string length $data] - append state(i) $data - - # Calculate the hash for any complete blocks - set len [string length $state(i)] - for {set n 0} {($n + 64) <= $len} {} { - SHA1Transform $token [string range $state(i) $n [incr n 64]] - } - - # Adjust the state for the blocks completed. - set state(i) [string range $state(i) $n end] - return -} - -# SHA1Final -- -# -# This procedure is used to close the current hash and returns the -# hash data. Once this procedure has been called the hash context -# is freed and cannot be used again. -# -# Note that the output is 160 bits represented as binary data. -# -proc ::sha1::SHA1Final {token} { - upvar #0 $token state - - # Padding - # - set len [string length $state(i)] - set pad [expr {56 - ($len % 64)}] - if {$len % 64 > 56} { - incr pad 64 - } - if {$pad == 0} { - incr pad 64 - } - append state(i) [binary format a$pad \x80] - - # Append length in bits as big-endian wide int. - set dlen [expr {8 * $state(n)}] - append state(i) [binary format II 0 $dlen] - - # Calculate the hash for the remaining block. - set len [string length $state(i)] - for {set n 0} {($n + 64) <= $len} {} { - SHA1Transform $token [string range $state(i) $n [incr n 64]] - } - - # Output - set r [bytes $state(A)][bytes $state(B)][bytes $state(C)][bytes $state(D)][bytes $state(E)] - unset state - return $r -} - -# ------------------------------------------------------------------------- -# HMAC Hashed Message Authentication (RFC 2104) -# -# hmac = H(K xor opad, H(K xor ipad, text)) -# - -# HMACInit -- -# -# This is equivalent to the SHA1Init procedure except that a key is -# added into the algorithm -# -proc ::sha1::HMACInit {K} { - - # Key K is adjusted to be 64 bytes long. If K is larger, then use - # the SHA1 digest of K and pad this instead. - set len [string length $K] - if {$len > 64} { - set tok [SHA1Init] - SHA1Update $tok $K - set K [SHA1Final $tok] - set len [string length $K] - } - set pad [expr {64 - $len}] - append K [string repeat \0 $pad] - - # Cacluate the padding buffers. - set Ki {} - set Ko {} - binary scan $K i16 Ks - foreach k $Ks { - append Ki [binary format i [expr {$k ^ 0x36363636}]] - append Ko [binary format i [expr {$k ^ 0x5c5c5c5c}]] - } - - set tok [SHA1Init] - SHA1Update $tok $Ki; # initialize with the inner pad - - # preserve the Ko value for the final stage. - # FRINK: nocheck - set [subst $tok](Ko) $Ko - - return $tok -} - -# HMACUpdate -- -# -# Identical to calling SHA1Update -# -proc ::sha1::HMACUpdate {token data} { - SHA1Update $token $data - return -} - -# HMACFinal -- -# -# This is equivalent to the SHA1Final procedure. The hash context is -# closed and the binary representation of the hash result is returned. -# -proc ::sha1::HMACFinal {token} { - upvar #0 $token state - - set tok [SHA1Init]; # init the outer hashing function - SHA1Update $tok $state(Ko); # prepare with the outer pad. - SHA1Update $tok [SHA1Final $token]; # hash the inner result - return [SHA1Final $tok] -} - -# ------------------------------------------------------------------------- -# Description: -# This is the core SHA1 algorithm. It is a lot like the MD4 algorithm but -# includes an extra round and a set of constant modifiers throughout. -# -set ::sha1::SHA1Transform_body { - upvar #0 $token state - - # FIPS 180-1: 7a: Process Message in 16-Word Blocks - binary scan $msg I* blocks - set blockLen [llength $blocks] - for {set i 0} {$i < $blockLen} {incr i 16} { - set W [lrange $blocks $i [expr {$i+15}]] - - # FIPS 180-1: 7b: Expand the input into 80 words - # For t = 16 to 79 - # let Wt = (Wt-3 ^ Wt-8 ^ Wt-14 ^ Wt-16) <<< 1 - set t3 12 - set t8 7 - set t14 1 - set t16 -1 - for {set t 16} {$t < 80} {incr t} { - set x [expr {[lindex $W [incr t3]] ^ [lindex $W [incr t8]] ^ \ - [lindex $W [incr t14]] ^ [lindex $W [incr t16]]}] - lappend W [expr {int(($x << 1) | (($x >> 31) & 1))}] - } - - # FIPS 180-1: 7c: Copy hash state. - set A $state(A) - set B $state(B) - set C $state(C) - set D $state(D) - set E $state(E) - - # FIPS 180-1: 7d: Do permutation rounds - # For t = 0 to 79 do - # TEMP = (A<<<5) + ft(B,C,D) + E + Wt + Kt; - # E = D; D = C; C = S30(B); B = A; A = TEMP; - - # Round 1: ft(B,C,D) = (B & C) | (~B & D) ( 0 <= t <= 19) - for {set t 0} {$t < 20} {incr t} { - set TEMP [F1 $A $B $C $D $E [lindex $W $t]] - set E $D - set D $C - set C [rotl32 $B 30] - set B $A - set A $TEMP - } - - # Round 2: ft(B,C,D) = (B ^ C ^ D) ( 20 <= t <= 39) - for {} {$t < 40} {incr t} { - set TEMP [F2 $A $B $C $D $E [lindex $W $t]] - set E $D - set D $C - set C [rotl32 $B 30] - set B $A - set A $TEMP - } - - # Round 3: ft(B,C,D) = ((B & C) | (B & D) | (C & D)) ( 40 <= t <= 59) - for {} {$t < 60} {incr t} { - set TEMP [F3 $A $B $C $D $E [lindex $W $t]] - set E $D - set D $C - set C [rotl32 $B 30] - set B $A - set A $TEMP - } - - # Round 4: ft(B,C,D) = (B ^ C ^ D) ( 60 <= t <= 79) - for {} {$t < 80} {incr t} { - set TEMP [F4 $A $B $C $D $E [lindex $W $t]] - set E $D - set D $C - set C [rotl32 $B 30] - set B $A - set A $TEMP - } - - # Then perform the following additions. (That is, increment each - # of the four registers by the value it had before this block - # was started.) - incr state(A) $A - incr state(B) $B - incr state(C) $C - incr state(D) $D - incr state(E) $E - } - - return -} - -proc ::sha1::F1 {A B C D E W} { - expr {(((($A << 5) & 0xffffffff) | (($A >> 27) & 0x1f)) \ - + ($D ^ ($B & ($C ^ $D))) + $E + $W + 0x5a827999) & 0xffffffff} -} - -proc ::sha1::F2 {A B C D E W} { - expr {(((($A << 5) & 0xffffffff) | (($A >> 27) & 0x1f)) \ - + ($B ^ $C ^ $D) + $E + $W + 0x6ed9eba1) & 0xffffffff} -} - -proc ::sha1::F3 {A B C D E W} { - expr {(((($A << 5) & 0xffffffff)| (($A >> 27) & 0x1f)) \ - + (($B & $C) | ($D & ($B | $C))) + $E + $W + 0x8f1bbcdc) & 0xffffffff} -} - -proc ::sha1::F4 {A B C D E W} { - expr {(((($A << 5) & 0xffffffff)| (($A >> 27) & 0x1f)) \ - + ($B ^ $C ^ $D) + $E + $W + 0xca62c1d6) & 0xffffffff} -} - -proc ::sha1::rotl32 {v n} { - return [expr {((($v << $n) \ - | (($v >> (32 - $n)) \ - & (0x7FFFFFFF >> (31 - $n))))) \ - & 0xFFFFFFFF}] -} - - -# ------------------------------------------------------------------------- -# -# In order to get this code to go as fast as possible while leaving -# the main code readable we can substitute the above function bodies -# into the transform procedure. This inlines the code for us an avoids -# a procedure call overhead within the loops. -# -# We can do some minor tweaking to improve speed on Tcl < 8.5 where we -# know our arithmetic is limited to 64 bits. On > 8.5 we may have -# unconstrained integer arithmetic and must avoid letting it run away. -# - -regsub -all -line \ - {\[F1 \$A \$B \$C \$D \$E (\[.*?\])\]} \ - $::sha1::SHA1Transform_body \ - {[expr {(rotl32($A,5) + ($D ^ ($B \& ($C ^ $D))) + $E + \1 + 0x5a827999) \& 0xffffffff}]} \ - ::sha1::SHA1Transform_body_tmp - -regsub -all -line \ - {\[F2 \$A \$B \$C \$D \$E (\[.*?\])\]} \ - $::sha1::SHA1Transform_body_tmp \ - {[expr {(rotl32($A,5) + ($B ^ $C ^ $D) + $E + \1 + 0x6ed9eba1) \& 0xffffffff}]} \ - ::sha1::SHA1Transform_body_tmp - -regsub -all -line \ - {\[F3 \$A \$B \$C \$D \$E (\[.*?\])\]} \ - $::sha1::SHA1Transform_body_tmp \ - {[expr {(rotl32($A,5) + (($B \& $C) | ($D \& ($B | $C))) + $E + \1 + 0x8f1bbcdc) \& 0xffffffff}]} \ - ::sha1::SHA1Transform_body_tmp - -regsub -all -line \ - {\[F4 \$A \$B \$C \$D \$E (\[.*?\])\]} \ - $::sha1::SHA1Transform_body_tmp \ - {[expr {(rotl32($A,5) + ($B ^ $C ^ $D) + $E + \1 + 0xca62c1d6) \& 0xffffffff}]} \ - ::sha1::SHA1Transform_body_tmp - -regsub -all -line \ - {rotl32\(\$A,5\)} \ - $::sha1::SHA1Transform_body_tmp \ - {((($A << 5) \& 0xffffffff) | (($A >> 27) \& 0x1f))} \ - ::sha1::SHA1Transform_body_tmp - -regsub -all -line \ - {\[rotl32 \$B 30\]} \ - $::sha1::SHA1Transform_body_tmp \ - {[expr {int(($B << 30) | (($B >> 2) \& 0x3fffffff))}]} \ - ::sha1::SHA1Transform_body_tmp -# -# Version 2 avoids a few truncations to 32 bits in non-essential places. -# -regsub -all -line \ - {\[F1 \$A \$B \$C \$D \$E (\[.*?\])\]} \ - $::sha1::SHA1Transform_body \ - {[expr {rotl32($A,5) + ($D ^ ($B \& ($C ^ $D))) + $E + \1 + 0x5a827999}]} \ - ::sha1::SHA1Transform_body_tmp2 - -regsub -all -line \ - {\[F2 \$A \$B \$C \$D \$E (\[.*?\])\]} \ - $::sha1::SHA1Transform_body_tmp2 \ - {[expr {rotl32($A,5) + ($B ^ $C ^ $D) + $E + \1 + 0x6ed9eba1}]} \ - ::sha1::SHA1Transform_body_tmp2 - -regsub -all -line \ - {\[F3 \$A \$B \$C \$D \$E (\[.*?\])\]} \ - $::sha1::SHA1Transform_body_tmp2 \ - {[expr {rotl32($A,5) + (($B \& $C) | ($D \& ($B | $C))) + $E + \1 + 0x8f1bbcdc}]} \ - ::sha1::SHA1Transform_body_tmp2 - -regsub -all -line \ - {\[F4 \$A \$B \$C \$D \$E (\[.*?\])\]} \ - $::sha1::SHA1Transform_body_tmp2 \ - {[expr {rotl32($A,5) + ($B ^ $C ^ $D) + $E + \1 + 0xca62c1d6}]} \ - ::sha1::SHA1Transform_body_tmp2 - -regsub -all -line \ - {rotl32\(\$A,5\)} \ - $::sha1::SHA1Transform_body_tmp2 \ - {(($A << 5) | (($A >> 27) \& 0x1f))} \ - ::sha1::SHA1Transform_body_tmp2 - -regsub -all -line \ - {\[rotl32 \$B 30\]} \ - $::sha1::SHA1Transform_body_tmp2 \ - {[expr {($B << 30) | (($B >> 2) \& 0x3fffffff)}]} \ - ::sha1::SHA1Transform_body_tmp2 - -if {[package vsatisfies [package provide Tcl] 8.5]} { - proc ::sha1::SHA1Transform {token msg} $::sha1::SHA1Transform_body_tmp -} else { - proc ::sha1::SHA1Transform {token msg} $::sha1::SHA1Transform_body_tmp2 -} - -unset ::sha1::SHA1Transform_body -unset ::sha1::SHA1Transform_body_tmp -unset ::sha1::SHA1Transform_body_tmp2 - -# ------------------------------------------------------------------------- - -proc ::sha1::byte {n v} {expr {((0xFF << (8 * $n)) & $v) >> (8 * $n)}} -proc ::sha1::bytes {v} { - #format %c%c%c%c [byte 0 $v] [byte 1 $v] [byte 2 $v] [byte 3 $v] - format %c%c%c%c \ - [expr {((0xFF000000 & $v) >> 24) & 0xFF}] \ - [expr {(0xFF0000 & $v) >> 16}] \ - [expr {(0xFF00 & $v) >> 8}] \ - [expr {0xFF & $v}] -} - -# ------------------------------------------------------------------------- - -proc ::sha1::Hex {data} { - binary scan $data H* result - return $result -} - -# ------------------------------------------------------------------------- - -# Description: -# Pop the nth element off a list. Used in options processing. -# -proc ::sha1::Pop {varname {nth 0}} { - upvar $varname args - set r [lindex $args $nth] - set args [lreplace $args $nth $nth] - return $r -} - -# ------------------------------------------------------------------------- - -# fileevent handler for chunked file hashing. -# -proc ::sha1::Chunk {token channel {chunksize 4096}} { - upvar #0 $token state - - if {[eof $channel]} { - fileevent $channel readable {} - set state(reading) 0 - } - - SHA1Update $token [read $channel $chunksize] -} - -# ------------------------------------------------------------------------- - -proc ::sha1::sha1 {args} { - array set opts {-hex 0 -filename {} -channel {} -chunksize 4096} - if {[llength $args] == 1} { - set opts(-hex) 1 - } else { - while {[string match -* [set option [lindex $args 0]]]} { - switch -glob -- $option { - -hex { set opts(-hex) 1 } - -bin { set opts(-hex) 0 } - -file* { set opts(-filename) [Pop args 1] } - -channel { set opts(-channel) [Pop args 1] } - -chunksize { set opts(-chunksize) [Pop args 1] } - default { - if {[llength $args] == 1} { break } - if {[string compare $option "--"] == 0} { Pop args; break } - set err [join [lsort [concat -bin [array names opts]]] ", "] - return -code error "bad option $option:\ - must be one of $err" - } - } - Pop args - } - } - - if {$opts(-filename) != {}} { - set opts(-channel) [open $opts(-filename) r] - fconfigure $opts(-channel) -translation binary - } - - if {$opts(-channel) == {}} { - - if {[llength $args] != 1} { - return -code error "wrong # args:\ - should be \"sha1 ?-hex? -filename file | string\"" - } - set tok [SHA1Init] - SHA1Update $tok [lindex $args 0] - set r [SHA1Final $tok] - - } else { - - set tok [SHA1Init] - # FRINK: nocheck - set [subst $tok](reading) 1 - fileevent $opts(-channel) readable \ - [list [namespace origin Chunk] \ - $tok $opts(-channel) $opts(-chunksize)] - # FRINK: nocheck - vwait [subst $tok](reading) - set r [SHA1Final $tok] - - # If we opened the channel - we should close it too. - if {$opts(-filename) != {}} { - close $opts(-channel) - } - } - - if {$opts(-hex)} { - set r [Hex $r] - } - return $r -} - -# ------------------------------------------------------------------------- - -proc ::sha1::hmac {args} { - array set opts {-hex 1 -filename {} -channel {} -chunksize 4096} - if {[llength $args] != 2} { - while {[string match -* [set option [lindex $args 0]]]} { - switch -glob -- $option { - -key { set opts(-key) [Pop args 1] } - -hex { set opts(-hex) 1 } - -bin { set opts(-hex) 0 } - -file* { set opts(-filename) [Pop args 1] } - -channel { set opts(-channel) [Pop args 1] } - -chunksize { set opts(-chunksize) [Pop args 1] } - default { - if {[llength $args] == 1} { break } - if {[string compare $option "--"] == 0} { Pop args; break } - set err [join [lsort [array names opts]] ", "] - return -code error "bad option $option:\ - must be one of $err" - } - } - Pop args - } - } - - if {[llength $args] == 2} { - set opts(-key) [Pop args] - } - - if {![info exists opts(-key)]} { - return -code error "wrong # args:\ - should be \"hmac ?-hex? -key key -filename file | string\"" - } - - if {$opts(-filename) != {}} { - set opts(-channel) [open $opts(-filename) r] - fconfigure $opts(-channel) -translation binary - } - - if {$opts(-channel) == {}} { - - if {[llength $args] != 1} { - return -code error "wrong # args:\ - should be \"hmac ?-hex? -key key -filename file | string\"" - } - set tok [HMACInit $opts(-key)] - HMACUpdate $tok [lindex $args 0] - set r [HMACFinal $tok] - - } else { - - set tok [HMACInit $opts(-key)] - # FRINK: nocheck - set [subst $tok](reading) 1 - fileevent $opts(-channel) readable \ - [list [namespace origin Chunk] \ - $tok $opts(-channel) $opts(-chunksize)] - # FRINK: nocheck - vwait [subst $tok](reading) - set r [HMACFinal $tok] - - # If we opened the channel - we should close it too. - if {$opts(-filename) != {}} { - close $opts(-channel) - } - } - - if {$opts(-hex)} { - set r [Hex $r] - } - return $r -} - -# ------------------------------------------------------------------------- - -package provide sha1 $::sha1::version - -# ------------------------------------------------------------------------- -# Local Variables: -# mode: tcl -# indent-tabs-mode: nil -# End: +#! /usr/bin/env tclsh + +proc sha1::sha1 args { + set outputmode "hex" + + if {[lindex $args 0] == "-hex"} { + set outputmode "hex" + + set args [lrange $args 1 end] + } elseif {[lindex $args 0] == "-bin"} { + set outputmode "binary" + + set args [lrange $args 1 end] + } + + if {[llength $args] == 2} { + set mode [lindex $args 0] + } elseif {[llength $args] == 1} { + set mode "-string" + } else { + return -code error "wrong # args: sha1::sha1 ?-bin|-hex? ?-channel channel|-file file|string?" + } + + switch -- $mode { + "-channel" { + return -code error "Not implemented" + } + "-file" { + set output [_sha1_file [lindex $args end]] + } + "-string" { + set output [_sha1_string [lindex $args end]] + } + default { + return -code error "invalid mode: $mode, must be one of -channel or -file (or a plain string)" + } + } + + if {$outputmode == "hex"} { + binary scan $output H* output + } + + return $output +}