Index: .fossil-settings/ignore-glob
==================================================================
--- .fossil-settings/ignore-glob
+++ .fossil-settings/ignore-glob
@@ -1,3 +1,7 @@
 appfsd
 appfsd.o
 appfsd.tcl.h
+sha1.o
+sha1.tcl.h
+pki.tcl.h
+pki.tcl.new

Index: Makefile
==================================================================
--- Makefile
+++ Makefile
@@ -1,14 +1,12 @@
 CC = gcc
 PKG_CONFIG = pkg-config
 FUSE_CFLAGS = $(shell $(PKG_CONFIG) --cflags fuse)
-SQLITE3_CFLAGS = $(shell $(PKG_CONFIG) --cflags sqlite3)
-CFLAGS = -Wall $(FUSE_CFLAGS) $(SQLITE3_CFLAGS) $(TCL_CFLAGS) -DDEBUG=1
+CFLAGS = -Wall $(FUSE_CFLAGS) $(TCL_CFLAGS) -DDEBUG=1
 LDFLAGS = $(TCL_LDFLAGS)
 FUSE_LIBS = $(shell $(PKG_CONFIG) --libs fuse)
-SQLITE3_LIBS = $(shell $(PKG_CONFIG) --libs sqlite3)
-LIBS = $(FUSE_LIBS) $(SQLITE3_LIBS) $(TCL_LIBS)
+LIBS = $(FUSE_LIBS) $(TCL_LIBS)
 PREFIX = /usr/local
 prefix = $(PREFIX)
 bindir = $(prefix)/bin
 sbindir = $(prefix)/sbin
 
@@ -22,26 +20,50 @@
 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
+appfsd.o: appfsd.c appfsd.tcl.h pki.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
+
+pki.tcl:
+	rm -f pki.tcl.new
+	curl http://core.tcl.tk/tcllib/raw/modules/asn/asn.tcl?name=aea6802a16e69c9f2d4f5eca20fdc23174609731 > pki.tcl.new
+	curl http://core.tcl.tk/tcllib/raw/modules/aes/aes.tcl?name=94452b42b4ca98298ab1465c40fd87d11a40cf5e >> pki.tcl.new
+	curl http://core.tcl.tk/tcllib/raw/modules/des/tcldes.tcl?name=ffea6ca6eb4468c0edef7a745b1dadc632ff5aeb >> pki.tcl.new
+	curl http://core.tcl.tk/tcllib/raw/modules/des/des.tcl?name=5d8f3a7c1a6ea88ee988652643db8f06038aff49 >> pki.tcl.new
+	curl http://core.tcl.tk/tcllib/raw/modules/math/bignum.tcl?name=3bc84d9b1f18c2e7360573381317c4dc9af731f9 >> pki.tcl.new
+	curl http://core.tcl.tk/tcllib/raw/modules/md5/md5x.tcl?name=3cddfa803d680a79ab7dfac90edfd751f3d4fadd >> pki.tcl.new
+	curl http://core.tcl.tk/tcllib/raw/modules/sha1/sha256.tcl?name=1fd001eb65e88c823b980456726079deae3512df >> pki.tcl.new
+	curl 'http://core.tcl.tk/tcllib/raw/modules/pki/pki.tcl?name=8318fd31981dcc00bfadd6c427518f9d71a12b34' >> pki.tcl.new
+	openssl sha1 pki.tcl.new | grep '918ddd77f485a58192b2e86230777092d790191a' >/dev/null
+	mv pki.tcl.new pki.tcl
+
+%.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
+	rm -f pki.tcl.new pki.tcl.h
 
 distclean: clean
 
-.PHONY: all test clean distclean install
+mrproper: distclean
+	rm -f pki.tcl
+
+.PHONY: all install clean distclean mrproper

ADDED   appfs-cache
Index: appfs-cache
==================================================================
--- /dev/null
+++ 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 <package>|remove-site <site>}" >&2
+
+		exit 1
+		;;
+esac
+
+exit 0

Index: appfsd.c
==================================================================
--- appfsd.c
+++ appfsd.c
@@ -1,9 +1,9 @@
 #define FUSE_USE_VERSION 26
 
+#include <sys/fsuid.h>
 #include <sys/types.h>
-#include <sqlite3.h>
 #include <pthread.h>
 #include <string.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -12,48 +12,59 @@
 #include <stdio.h>
 #include <fuse.h>
 #include <pwd.h>
 #include <tcl.h>
 
+/*
+ * 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_SYMLINK,
+	APPFS_PATHTYPE_SOCKET,
+	APPFS_PATHTYPE_FIFO,
 } 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,86 +74,196 @@
 			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(char **error_string) {
 	Tcl_Interp *interp;
 	int tcl_ret;
 
 	APPFS_DEBUG("Creating new Tcl interpreter for TID = 0x%llx", (unsigned long long) pthread_self());
 
 	interp = Tcl_CreateInterp();
 	if (interp == NULL) {
 		fprintf(stderr, "Unable to create Tcl Interpreter.  Aborting.\n");
+
+		if (error_string) {
+			*error_string = strdup("Unable to create Tcl interpreter.");
+		}
 
 		return(NULL);
 	}
 
 	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));
+
+		if (error_string) {
+			*error_string = strdup(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));
+
+		if (error_string) {
+			*error_string = strdup(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));
+
+		if (error_string) {
+			*error_string = strdup(Tcl_GetStringResult(interp));
+		}
+
+		Tcl_DeleteInterp(interp);
+
+		return(NULL);
+	}
+
+	/*
+	 * Load "pki.tcl" in the same way as appfsd.tcl (see below)
+	 */
+	tcl_ret = Tcl_Eval(interp, ""
+#include "pki.tcl.h"
+	"");
+	if (tcl_ret != TCL_OK) {
+		fprintf(stderr, "Unable to initialize Tcl PKI.  Aborting.\n");
+		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
+
+		if (error_string) {
+			*error_string = strdup(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");
 		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
+
+		if (error_string) {
+			*error_string = strdup(Tcl_GetStringResult(interp));
+		}
 
 		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");
+
+		if (error_string) {
+			*error_string = strdup(Tcl_GetStringResult(interp));
+		}
 
 		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));
+
+		if (error_string) {
+			*error_string = strdup(Tcl_GetStringResult(interp));
+		}
 
 		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(NULL);
+
+		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 +298,71 @@
 	}
 
 	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) {
+/*
+ * Determine the GID 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 gid_t appfs_get_fsgid(void) {
+	struct fuse_context *ctx;
+
+	if (!appfs_fuse_started) {
+		return(getgid());
+	}
+
+	ctx = fuse_get_context();
+	if (ctx == NULL) {
+		/* Unable to lookup user for some reason */
+		/* Return an unprivileged user ID */
+		return(1);
+	}
+
+	return(ctx->gid);
+}
+
+static void appfs_simulate_user_fs_enter(void) {
+	setfsuid(appfs_get_fsuid());
+	setfsgid(appfs_get_fsgid());
+}
+
+static void appfs_simulate_user_fs_leave(void) {
+	setfsuid(0);
+	setfsgid(0);
+}
+
+/*
+ * 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 +400,78 @@
 		);
 
 		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;
+	Tcl_Obj *homedir_obj;
+	uid_t fsuid;
+	static __thread Tcl_Obj *last_homedir_obj = NULL;
+	static __thread uid_t last_fsuid = -1;
+
+        if (objc != 1) {
+                Tcl_WrongNumArgs(interp, 1, objv, NULL);
+                return(TCL_ERROR);
+        }
+
+	fsuid = appfs_get_fsuid();
+
+	if (fsuid == last_fsuid && last_homedir_obj != NULL) {
+		homedir_obj = last_homedir_obj;
+	} else {
+		if (last_homedir_obj != NULL) {
+			Tcl_DecrRefCount(last_homedir_obj);
+		}
+
+		homedir = appfs_get_homedir(appfs_get_fsuid());
+
+		if (homedir == NULL) {
+			return(TCL_ERROR);
+		}
+
+		homedir_obj = Tcl_NewStringObj(homedir, -1);
+
+		free(homedir);
+
+		last_homedir_obj = homedir_obj;
+		last_fsuid = fsuid;
+
+		Tcl_IncrRefCount(last_homedir_obj);
+	}
+
+       	Tcl_SetObjResult(interp, homedir_obj);
+
+        return(TCL_OK);
+}
+
+static int tcl_appfs_simulate_user_fs_enter(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
+	appfs_simulate_user_fs_enter();
+
+	return(TCL_OK);
+}
+
+static int tcl_appfs_simulate_user_fs_leave(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
+	appfs_simulate_user_fs_leave();
+
+	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 +487,202 @@
 
 	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);
-
-	return(0);
+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;
+		case 'F': /* pipe/fifo */
+			pathinfo->type = APPFS_PATHTYPE_FIFO;
+			break;
+		case 'S': /* UNIX domain socket */
+			pathinfo->type = APPFS_PATHTYPE_SOCKET;
+			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 char *appfs_prepare_to_create(const char *path) {
+	Tcl_Interp *interp;
+	const char *real_path;
+	int tcl_ret;
+
+	interp = appfs_TclInterp();
+	if (interp == NULL) {
+		return(NULL);
+	}
+
+	tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::prepare_to_create", path);
+	if (tcl_ret != TCL_OK) {
+		APPFS_DEBUG("::appfs::prepare_to_create(%s) failed.", path);
+		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
+
+		return(NULL);
+	}
+
+	real_path = Tcl_GetStringResult(interp);
+	if (real_path == NULL) {
+		return(NULL);
+	}
+
+	return(strdup(real_path));
+}
+
+static char *appfs_localpath(const char *path) {
+	Tcl_Interp *interp;
+	const char *real_path;
+	int tcl_ret;
+
+	interp = appfs_TclInterp();
+	if (interp == NULL) {
+		return(NULL);
+	}
+
+	tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::localpath", path);
+	if (tcl_ret != TCL_OK) {
+		APPFS_DEBUG("::appfs::localpath(%s) failed.", path);
+		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
+
+		return(NULL);
+	}
+
+	real_path = Tcl_GetStringResult(interp);
+	if (real_path == NULL) {
+		return(NULL);
+	}
+
+	return(strdup(real_path));
 }
 
 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,28 +696,32 @@
 	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;
 	stbuf->st_ctime = pathinfo.time;
 	stbuf->st_atime = pathinfo.time;
 	stbuf->st_ino   = pathinfo.inode;
 	stbuf->st_mode  = 0;
+	stbuf->st_uid   = appfs_get_fsuid();
+	stbuf->st_gid   = appfs_get_fsgid();
 
 	switch (pathinfo.type) {
 		case APPFS_PATHTYPE_DIRECTORY:
 			stbuf->st_mode = S_IFDIR | 0555;
 			stbuf->st_nlink = 2 + pathinfo.typeinfo.dir.childcount;
@@ -1032,78 +738,130 @@
 			break;
 		case APPFS_PATHTYPE_SYMLINK:
 			stbuf->st_mode = S_IFLNK | 0555;
 			stbuf->st_nlink = 1;
 			stbuf->st_size = pathinfo.typeinfo.symlink.size;
+			break;
+		case APPFS_PATHTYPE_SOCKET:
+			stbuf->st_mode = S_IFSOCK | 0555;
+			stbuf->st_nlink = 1;
+			stbuf->st_size = 0;
+			break;
+		case APPFS_PATHTYPE_FIFO:
+			stbuf->st_mode = S_IFIFO | 0555;
+			stbuf->st_nlink = 1;
+			stbuf->st_size = 0;
 			break;
 		case APPFS_PATHTYPE_INVALID:
-			res = -EIO;
+			retval = -ENOENT;
 
 			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);
 
-	gpi_ret = appfs_get_path_info(path, &pathinfo, NULL);
-	if (gpi_ret != 0) {
-		return(gpi_ret);
+	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 = "";
+
+		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 +894,436 @@
 	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);
+}
+
+static int appfs_fuse_mknod(const char *path, mode_t mode, dev_t device) {
+	char *real_path;
+	int mknod_ret;
+
+	APPFS_DEBUG("Enter (path = %s, ...)", path);
+
+	if ((mode & S_IFCHR) == S_IFCHR) {
+		return(-EPERM);
+	}
+
+	if ((mode & S_IFBLK) == S_IFBLK) {
+		return(-EPERM);
+	}
+
+	real_path = appfs_prepare_to_create(path);
+	if (real_path == NULL) {
+		return(-EIO);
+	}
+
+	appfs_simulate_user_fs_enter();
+
+	mknod_ret = mknod(real_path, mode, device);
+
+	appfs_simulate_user_fs_leave();
+
+	free(real_path);
+
+	if (mknod_ret != 0) {
+		return(errno * -1);
+	}
+
+	return(0);
+}
+
+static int appfs_fuse_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
+	char *real_path;
+	int fd;
+
+	APPFS_DEBUG("Enter (path = %s, ...)", path);
+
+	if ((mode & S_IFCHR) == S_IFCHR) {
+		return(-EPERM);
+	}
+
+	if ((mode & S_IFBLK) == S_IFBLK) {
+		return(-EPERM);
+	}
+
+	real_path = appfs_prepare_to_create(path);
+	if (real_path == NULL) {
+		return(-EIO);
+	}
+
+	appfs_simulate_user_fs_enter();
+
+	fd = creat(real_path, mode);
+
+	appfs_simulate_user_fs_leave();
+
+	free(real_path);
+
+	if (fd < 0) {
+		return(errno * -1);
+	}
+
+	fi->fh = fd;
+
+	return(0);
+}
+
+static int appfs_fuse_truncate(const char *path, off_t size) {
+	char *real_path;
+	int truncate_ret;
+
+	APPFS_DEBUG("Enter (path = %s, ...)", path);
+
+	real_path = appfs_localpath(path);
+	if (real_path == NULL) {
+		return(-EIO);
+	}
+
+	appfs_simulate_user_fs_enter();
+
+	truncate_ret = truncate(real_path, size);
+
+	appfs_simulate_user_fs_leave();
+
+	free(real_path);
+
+	if (truncate_ret != 0) {
+		return(errno * -1);
+	}
+
+	return(0);
+}
+
+static int appfs_fuse_unlink_rmdir(const char *path) {
+	Tcl_Interp *interp;
+	int tcl_ret;
+
+	APPFS_DEBUG("Enter (path = %s, ...)", path);
+
+	interp = appfs_TclInterp();
+	if (interp == NULL) {
+		return(-EIO);
+	}
+
+	tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::unlinkpath", path);
+	if (tcl_ret != TCL_OK) {
+		APPFS_DEBUG("::appfs::unlinkpath(%s) failed.", path);
+		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
+
+		return(-EIO);
+	}
+
+	return(0);
+}
+
+static int appfs_fuse_mkdir(const char *path, mode_t mode) {
+	char *real_path;
+	int mkdir_ret;
+
+	APPFS_DEBUG("Enter (path = %s, ...)", path);
+
+
+	real_path = appfs_prepare_to_create(path);
+	if (real_path == NULL) {
+		return(-EIO);
+	}
+
+	appfs_simulate_user_fs_enter();
+
+	mkdir_ret = mkdir(real_path, mode);
+
+	appfs_simulate_user_fs_leave();
+
+	free(real_path);
+
+	if (mkdir_ret != 0) {
+		if (errno != EEXIST) {
+			return(errno * -1);
+		}
+	}
+
+	return(0);
+}
+
+static int appfs_fuse_chmod(const char *path, mode_t mode) {
+	Tcl_Interp *interp;
+	const char *real_path;
+	int tcl_ret, chmod_ret;
+
+	APPFS_DEBUG("Enter (path = %s, ...)", path);
+
+	interp = appfs_TclInterp();
+	if (interp == NULL) {
+		return(-EIO);
+	}
+
+	tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::openpath", path, "write");
+	if (tcl_ret != TCL_OK) {
+		APPFS_DEBUG("::appfs::openpath(%s, %s) failed.", path, "write");
+		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
+
+		return(-EIO);
+	}
+
+	real_path = Tcl_GetStringResult(interp);
+	if (real_path == NULL) {
+		return(-EIO);
+	}
+
+	appfs_simulate_user_fs_enter();
+
+	chmod_ret = chmod(real_path, mode);
+
+	appfs_simulate_user_fs_leave();
+
+	return(chmod_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(NULL);
+	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(NULL);
+	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_CreateObjCommand(interp, "appfsd::simulate_user_fs_enter", tcl_appfs_simulate_user_fs_enter, NULL, NULL);
+	Tcl_CreateObjCommand(interp, "appfsd::simulate_user_fs_leave", tcl_appfs_simulate_user_fs_leave, 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,
+	.mknod     = appfs_fuse_mknod,
+	.create    = appfs_fuse_create,
+	.truncate  = appfs_fuse_truncate,
+	.unlink    = appfs_fuse_unlink_rmdir,
+	.rmdir     = appfs_fuse_unlink_rmdir,
+	.mkdir     = appfs_fuse_mkdir,
+	.chmod     = appfs_fuse_chmod,
 };
 
+/*
+ * 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;
-
+	Tcl_Interp *test_interp;
+	char *test_interp_error;
+	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");
+	/*
+	 * 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]));
+	}
 
-		return(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]));
 	}
 
-	sqlite_ret = sqlite3_open(dbfilename, &globalThread.db);
-	if (sqlite_ret != SQLITE_OK) {
-		fprintf(stderr, "Unable to open database: %s\n", dbfilename);
+	/*
+	 * Create a Tcl interpreter just to verify that things are in working 
+	 * order before we become a daemon.
+	 */
+	test_interp = appfs_create_TclInterp(&test_interp_error);
+	if (test_interp == NULL) {
+		if (test_interp_error == NULL) {
+			test_interp_error = "Unknown error";
+		}
+
+		fprintf(stderr, "Unable to initialize Tcl interpreter for AppFSd:\n");
+		fprintf(stderr, "%s\n", test_interp_error);
 
 		return(1);
 	}
+	Tcl_DeleteInterp(test_interp);
+
+	/*
+	 * 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");
+	}
 
-	return(fuse_main(argc, argv, &appfs_oper, NULL));
+	/*
+	 * 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,23 @@
 #! /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
+package require pki
 
 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 +38,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 +86,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" {
@@ -123,37 +121,58 @@
 			}
 		}
 
 		return -code error "Unable to normalize CPU: $cpu"
 	}
+
+	proc _as_user {code} {
+		::appfsd::simulate_user_fs_enter
+
+		set retcode [catch [list uplevel $code] retstr]
+
+		::appfsd::simulate_user_fs_leave
+
+		return -code $retcode $retstr
+	}
 
 	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 +181,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 +211,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 +261,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 +309,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 +343,454 @@
 				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 _whiteoutpath {package hostname file} {
+		set homedir [::appfsd::get_homedir]
+		set dir [file join $homedir .appfs "./${package}@${hostname}" ".APPFS.WHITEOUT" "./${file}.APPFS.WHITEOUT"]
+		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(_type) files
+								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)]} {
+					_as_user {
+						set dir [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)]
+						set whiteoutdir [string range [_whiteoutpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)] 0 end-15]
+
+						foreach file [glob -nocomplain -tails -directory $whiteoutdir {{.,}*.APPFS.WHITEOUT}] {
+							set remove [string range $file 0 end-15]
+							set idx [lsearch -exact $retval $remove]
+							if {$idx != -1} {
+								set retval [lreplace $retval $idx $idx]
+							}
+						}
+
+						foreach file [glob -nocomplain -tails -directory $dir -types {d f l p s} {{.,}*}] {
+							if {$file == "." || $file == ".."} {
+								continue
+							}
+
+							if {$file == ".APPFS.WHITEOUT"} {
+								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) [llength [getchildren $path]]
+			}
+			"sites" {
+				set check [::appfs::db onecolumn {SELECT 1 FROM packages WHERE hostname = $pathinfo(hostname);}]
+				if {$check == "1"} {
+					set retval(type) directory
+					set retval(childcount) [llength [getchildren $path]]
+				}
+			}
+			"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) [llength [getchildren $path]]
+				}
+			}
+			"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) [llength [getchildren $path]]
+					}
+				}
+			}
+			"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) [llength [getchildren $path]]
+					}
+				}
+			}
+			"files" {
+				set retval(packaged) 1
+
+				set localpath [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)]
+				set whiteoutpath  [_whiteoutpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)]
+
+				set retval(localpath) $localpath
+				set retval(whiteoutpath) $whiteoutpath
+
+				if {[file exists $localpath]} {
+					set retval(is_localfile) 1
+					catch {
+						_as_user {
+							file lstat $localpath localpathinfo
+						}
+						set retval(time) $localpathinfo(mtime)
+
+						switch -- $localpathinfo(type) {
+							"directory" {
+								set retval(type) "directory"
+								set retval(childcount) [llength [getchildren $path]]
+							}
+							"file" {
+								set retval(type) "file"
+								set retval(size) $localpathinfo(size)
+								_as_user {
+									if {[file executable $localpath]} {
+										set retval(perms) "x"
+									} else {
+										set retval(perms) ""
+									}
+								}
+							}
+							"link" {
+								set retval(type) "symlink"
+
+								_as_user {
+									set retval(source) [file readlink $localpath]
+								}
+							}
+							"fifo" {
+								# Capitalized so that the first char is unique
+								set retval(type) "Fifo"
+							}
+							"socket" {
+								# Capitalized so that the first char is unique
+								set retval(type) "Socket"
+							}
+						}
+					} err
+				} else {
+					if {![file exists $whiteoutpath]} {
+						set retval(is_remotefile) 1
+
+						set work [split $pathinfo(file) "/"]
+						set directory [join [lrange $work 0 end-1] "/"]
+						set file [lindex $work end]
+
+						if {$directory == "" && $file == ""} {
+							array set retval [list type directory]
+						}
+
+						::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 {}
+
+						if {$retval(type) == "directory"} {
+							set retval(childcount) [llength [getchildren $path]]
+						}
+
+						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 {$mode == "create"} {
+			return $localpath
+		}
+
+		if {[file exists $localpath]} {
+			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"} {
+			_as_user {
+				set tmplocalpath "${localpath}.[expr rand()][clock clicks]"
+
+				set failed 0
+				if {[catch {
+					file mkdir [file dirname $localpath]
+					file copy -force -- $localcachefile $tmplocalpath
+
+					if {$pkgpathinfo(perms) == "x"} {
+						file attributes $tmplocalpath -permissions +x
+					}
+
+					file rename -force -- $tmplocalpath $localpath
+				} err]} {
+					set failed 1
+				}
+				catch {
+					file delete -force -- $tmplocalpath
+				}
+			}
+
+			if {$failed} {
+				return -code error $err
+			}
+
+			return $localpath
+		}
+
+		return $localcachefile
+	}
+
+	proc localpath {path} {
+		array set pathinfo [_parsepath $path]
+
+		if {$pathinfo(_type) != "files"} {
+			return -code error "invalid type"
+		}
+
+		set localpath [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)]
+
+		return $localpath
+	}
+
+	proc exists {path} {
+		catch {
+			set info [getattr $path]
+		} err
+
+		if {![info exists info]} {
+			if {$err == "No such file or directory"} {
+				return [list]
+			} else {
+				return -code error $err
+			}
+		}
+
+		return $info
+	}
+
+	proc prepare_to_create {path {must_not_exist 1}} {
+		if {$must_not_exist} {
+			if {[exists $path] != ""} {
+				return -code error "File already exists"
+			}
+		}
+
+		set filename [localpath $path]
+
+		set dirname [file dirname $filename]
+
+		_as_user {
+			file mkdir $dirname
+		}
+
+		return $filename
+	}
+
+	proc unlinkpath {path} {
+		array set pathattrs [exists $path]
+
+		if {![info exists pathattrs(packaged)]} {
+			return -code error "invalid type"
+		}
+
+		set localpath $pathattrs(localpath)
+
+		if {[info exists pathattrs(is_localfile)]} {
+			if {[file isdirectory $localpath]} {
+				set children [getchildren $path]
+
+				if {[llength $children] != 0} {
+					return -code error "Asked to delete non-empty directory"
+				}
+			}
+
+			_as_user {
+				file delete -force -- $localpath
+			}
+		} elseif {[info exists pathattrs(is_remotefile)]} {
+			if {$pathattrs(type) == "directory"} {
+				set children [getchildren $path]
+
+				if {[llength $children] != 0} {
+					return -code error "Asked to delete non-empty directory"
+				}
+			}
+		} else {
+			return -code error "Unknown if file is remote or local !?"
+		}
+
+		set whiteoutfile $pathattrs(whiteoutpath)
+		set whiteoutdir [file dirname $whiteoutfile]
+
+		_as_user {
+			file mkdir $whiteoutdir
+			close [open $whiteoutfile w]
+		}
+	}
 }

ADDED   sha1.c
Index: sha1.c
==================================================================
--- /dev/null
+++ sha1.c
@@ -0,0 +1,322 @@
+/*
+	SHA-1 in C
+	By Steve Reid <steve@edmweb.com>
+	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 <tcl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+#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 <libes@nist.gov>
-# Copyright (C) 2003 Pat Thoyts <patthoyts@users.sourceforge.net>
-#
-# 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
+}