Check-in [0effed3239]
Overview
Comment:Moved to tcl-ops branch
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:0effed32391cf4c09cec9724e648cd6c62def9ed
User & Date: rkeene on 2014-11-10 05:57:32
Other Links: manifest | tags
Context
2014-11-10
06:19
Updated to deal with having no home directory gracefully and fixed bug introduced with childcount patch check-in: c0f54be8fb user: rkeene tags: trunk
05:57
Moved to tcl-ops branch check-in: 0effed3239 user: rkeene tags: trunk
05:43
Additional handling of determing child count Closed-Leaf check-in: 5f4e904dfd user: rkeene tags: tcl-ops
2014-11-06
03:05
Updated to use package name instead of hash for looking up extra files check-in: 98449bcf3e user: rkeene tags: trunk
Changes

Modified .fossil-settings/ignore-glob from [5155de6731] to [efc67b31c0].

     1      1   appfsd
     2      2   appfsd.o
     3      3   appfsd.tcl.h
            4  +sha1.o
            5  +sha1.tcl.h
            6  +pki.tcl.h
            7  +pki.tcl.new

Modified Makefile from [bee1193267] to [d5b77089dc].

     1      1   CC = gcc
     2      2   PKG_CONFIG = pkg-config
     3      3   FUSE_CFLAGS = $(shell $(PKG_CONFIG) --cflags fuse)
     4         -SQLITE3_CFLAGS = $(shell $(PKG_CONFIG) --cflags sqlite3)
     5         -CFLAGS = -Wall $(FUSE_CFLAGS) $(SQLITE3_CFLAGS) $(TCL_CFLAGS) -DDEBUG=1
            4  +CFLAGS = -Wall $(FUSE_CFLAGS) $(TCL_CFLAGS) -DDEBUG=1
     6      5   LDFLAGS = $(TCL_LDFLAGS)
     7      6   FUSE_LIBS = $(shell $(PKG_CONFIG) --libs fuse)
     8         -SQLITE3_LIBS = $(shell $(PKG_CONFIG) --libs sqlite3)
     9         -LIBS = $(FUSE_LIBS) $(SQLITE3_LIBS) $(TCL_LIBS)
            7  +LIBS = $(FUSE_LIBS) $(TCL_LIBS)
    10      8   PREFIX = /usr/local
    11      9   prefix = $(PREFIX)
    12     10   bindir = $(prefix)/bin
    13     11   sbindir = $(prefix)/sbin
    14     12   
    15     13   ifneq ($(TCLKIT_SDK_DIR),)
    16     14   TCLCONFIG_SH_PATH = $(TCLKIT_SDK_DIR)/lib/tclConfig.sh
................................................................................
    20     18   TCLCONFIG_SH_PATH = $(shell echo 'puts [::tcl::pkgconfig get libdir,install]' | tclsh)/tclConfig.sh
    21     19   endif
    22     20   TCL_CFLAGS = $(shell . $(TCLCONFIG_SH_PATH); echo "$${TCL_INCLUDE_SPEC}")
    23     21   TCL_LIBS = $(shell . $(TCLCONFIG_SH_PATH); echo "$${TCL_LIB_SPEC}")
    24     22   
    25     23   all: appfsd
    26     24   
    27         -appfsd: appfsd.o
    28         -	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfsd appfsd.o $(LIBS)
           25  +appfsd: appfsd.o sha1.o
           26  +	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfsd appfsd.o sha1.o $(LIBS)
    29     27   
    30         -appfsd.o: appfsd.c appfsd.tcl.h
           28  +appfsd.o: appfsd.c appfsd.tcl.h pki.tcl.h
    31     29   	$(CC) $(CPPFLAGS) $(CFLAGS) -o appfsd.o -c appfsd.c
    32     30   
    33         -appfsd.tcl.h: appfsd.tcl sha1.tcl
    34         -	sed '/@@SHA1\.TCL@@/ r sha1.tcl' appfsd.tcl | sed '/@@SHA1\.TCL@@/ d' | sed 's@[\\"]@\\&@g;s@^@   "@;s@$$@\\n"@' > appfsd.tcl.h.new
    35         -	mv appfsd.tcl.h.new appfsd.tcl.h
           31  +sha1.o: sha1.c sha1.tcl.h
           32  +	$(CC) $(CPPFLAGS) $(CFLAGS) -o sha1.o -c sha1.c
           33  +
           34  +pki.tcl:
           35  +	rm -f pki.tcl.new
           36  +	curl http://core.tcl.tk/tcllib/raw/modules/asn/asn.tcl?name=aea6802a16e69c9f2d4f5eca20fdc23174609731 > pki.tcl.new
           37  +	curl http://core.tcl.tk/tcllib/raw/modules/aes/aes.tcl?name=94452b42b4ca98298ab1465c40fd87d11a40cf5e >> pki.tcl.new
           38  +	curl http://core.tcl.tk/tcllib/raw/modules/des/tcldes.tcl?name=ffea6ca6eb4468c0edef7a745b1dadc632ff5aeb >> pki.tcl.new
           39  +	curl http://core.tcl.tk/tcllib/raw/modules/des/des.tcl?name=5d8f3a7c1a6ea88ee988652643db8f06038aff49 >> pki.tcl.new
           40  +	curl http://core.tcl.tk/tcllib/raw/modules/math/bignum.tcl?name=3bc84d9b1f18c2e7360573381317c4dc9af731f9 >> pki.tcl.new
           41  +	curl http://core.tcl.tk/tcllib/raw/modules/md5/md5x.tcl?name=3cddfa803d680a79ab7dfac90edfd751f3d4fadd >> pki.tcl.new
           42  +	curl http://core.tcl.tk/tcllib/raw/modules/sha1/sha256.tcl?name=1fd001eb65e88c823b980456726079deae3512df >> pki.tcl.new
           43  +	curl 'http://core.tcl.tk/tcllib/raw/modules/pki/pki.tcl?name=8318fd31981dcc00bfadd6c427518f9d71a12b34' >> pki.tcl.new
           44  +	openssl sha1 pki.tcl.new | grep '918ddd77f485a58192b2e86230777092d790191a' >/dev/null
           45  +	mv pki.tcl.new pki.tcl
           46  +
           47  +%.tcl.h: %.tcl
           48  +	sed 's@[\\"]@\\&@g;s@^@   "@;s@$$@\\n"@' $^ > $@.new
           49  +	mv $@.new $@
    36     50   
    37         -install: appfsd
           51  +install: appfsd appfs-cache appfs-mkfs
    38     52   	if [ ! -d '$(DESTDIR)$(sbindir)' ]; then mkdir -p '$(DESTDIR)$(sbindir)'; chmod 755 '$(DESTDIR)$(sbindir)'; fi
           53  +	if [ ! -d '$(DESTDIR)$(bindir)' ]; then mkdir -p '$(DESTDIR)$(bindir)'; chmod 755 '$(DESTDIR)$(bindir)'; fi
    39     54   	cp appfsd '$(DESTDIR)$(sbindir)/'
           55  +	cp appfs-cache '$(DESTDIR)$(sbindir)/'
           56  +	cp appfs-mkfs '$(DESTDIR)$(bindir)/'
    40     57   
    41     58   clean:
    42     59   	rm -f appfsd appfsd.o
    43     60   	rm -f appfsd.tcl.h
           61  +	rm -f sha1.o sha1.tcl.h
           62  +	rm -f pki.tcl.new pki.tcl.h
    44     63   
    45     64   distclean: clean
    46     65   
    47         -.PHONY: all test clean distclean install
           66  +mrproper: distclean
           67  +	rm -f pki.tcl
           68  +
           69  +.PHONY: all install clean distclean mrproper

Added appfs-cache version [f56f48d37a].

            1  +#! /usr/bin/env bash
            2  +
            3  +appfsd_options=()
            4  +if [ "$1" == "--cachedir" ]; then
            5  +	appfsd_options=("${appfsd_options[@]}" '--cachedir' "$2")
            6  +
            7  +	shift; shift;
            8  +fi
            9  +
           10  +function call_appfsd() {
           11  +	appfsd "${appfsd_options[@]}" "$@"
           12  +}
           13  +
           14  +function invalidate() {
           15  +	call_appfsd --sqlite3 'UPDATE sites SET ttl = "0";'
           16  +}
           17  +
           18  +function remove_site() {
           19  +	local site
           20  +
           21  +	site="$1"
           22  +
           23  +	call_appfsd --sqlite3 'DELETE FROM sites WHERE hostname = '"'$site'"'; DELETE FROM packages WHERE hostname = '"'$site'"';' || return 1
           24  +
           25  +	clean
           26  +}
           27  +
           28  +function clean() {
           29  +	call_appfsd --tcl "$(cat <<\_EOF_
           30  +		unset -nocomplain row
           31  +		::appfs::db eval {SELECT sha1, hostname FROM packages;} row {
           32  +			set hostname [::appfs::db onecolumn {SELECT hostname FROM sites WHERE hostname = $row(hostname) LIMIT 1;}]
           33  +			if {$hostname == ""} {
           34  +				continue
           35  +			}
           36  +
           37  +			set valid_sha1($row(sha1)) 1
           38  +			::appfs::db eval {SELECT file_sha1 FROM files WHERE file_sha1 IS NOT NULL AND file_sha1 != '' AND package_sha1 = $row(sha1);} subrow {
           39  +				set valid_sha1($subrow(file_sha1)) 1
           40  +			}
           41  +		}
           42  +
           43  +		foreach file [glob -nocomplain -tails -directory $::appfs::cachedir {[0-9a-f][0-9a-f]/*/*/*/*}] {
           44  +			set sha1 [string map [list "/" "" "\\" ""] $file]
           45  +			set file [file join $::appfs::cachedir $file]
           46  +
           47  +			if {[info exists valid_sha1($sha1)]} {
           48  +				continue
           49  +			}
           50  +
           51  +			puts "Cleaning $file"
           52  +			file delete -force -- $file
           53  +		}
           54  +_EOF_
           55  +	)"
           56  +}
           57  +
           58  +function clear() {
           59  +	local package
           60  +
           61  +	package="$1"
           62  +
           63  +	if [ -n "${package}" ]; then
           64  +		echo "not implemented" >&2
           65  +
           66  +		return 1
           67  +	fi
           68  +
           69  +	call_appfsd --tcl 'file delete -force -- {*}[glob -directory $::appfs::cachedir {[0-9a-f][0-9a-f]}]' || return 1
           70  +	call_appfsd --sqlite3 'DELETE FROM sites; DELETE FROM packages; DELETE FROM files; VACUUM;' || return 1
           71  +}
           72  +
           73  +case "$1" in
           74  +	invalidate)
           75  +		invalidate || exit 1
           76  +		;;
           77  +	remove-site)
           78  +		remove_site "$2" || exit 1
           79  +		;;
           80  +	clean)
           81  +		clean || exit 1
           82  +		;;
           83  +	clear)
           84  +		clear "$2" || exit 1
           85  +		;;
           86  +	*)
           87  +		echo "Usage: appfs-cache {invalidate|clean|clear|clear <package>|remove-site <site>}" >&2
           88  +
           89  +		exit 1
           90  +		;;
           91  +esac
           92  +
           93  +exit 0

Modified appfsd.c from [251c8531b4] to [1dcdd9bb28].

     1      1   #define FUSE_USE_VERSION 26
     2      2   
            3  +#include <sys/fsuid.h>
     3      4   #include <sys/types.h>
     4         -#include <sqlite3.h>
     5      5   #include <pthread.h>
     6      6   #include <string.h>
     7      7   #include <stdarg.h>
     8      8   #include <stdlib.h>
     9      9   #include <unistd.h>
    10     10   #include <errno.h>
    11     11   #include <fcntl.h>
    12     12   #include <stdio.h>
    13     13   #include <fuse.h>
    14     14   #include <pwd.h>
    15     15   #include <tcl.h>
    16     16   
           17  +/*
           18  + * Default cache directory
           19  + */
    17     20   #ifndef APPFS_CACHEDIR
    18     21   #define APPFS_CACHEDIR "/var/cache/appfs"
    19     22   #endif
    20     23   
           24  +/* Debugging macros */
    21     25   #ifdef DEBUG
    22     26   #define APPFS_DEBUG(x...) { fprintf(stderr, "[debug] %s:%i:%s: ", __FILE__, __LINE__, __func__); fprintf(stderr, x); fprintf(stderr, "\n"); }
    23     27   #else
    24     28   #define APPFS_DEBUG(x...) /**/
    25     29   #endif
    26     30   
           31  +/*
           32  + * SHA1 Tcl Package initializer, from sha1.o
           33  + */
           34  +int Sha1_Init(Tcl_Interp *interp);
           35  +
           36  +/*
           37  + * Thread Specific Data (TSD) for Tcl Interpreter for the current thread
           38  + */
    27     39   static pthread_key_t interpKey;
    28     40   
    29         -struct appfs_thread_data {
    30         -	sqlite3 *db;
    31         -	const char *cachedir;
    32         -	time_t boottime;
    33         -	const char *platform;
    34         -	struct {
    35         -		int writable;
    36         -	} options;
    37         -};
           41  +/*
           42  + * Global variables, needed for all threads but only initialized before any
           43  + * FUSE threads are created
           44  + */
           45  +const char *appfs_cachedir;
           46  +time_t appfs_boottime;
           47  +int appfs_fuse_started = 0;
    38     48   
    39         -struct appfs_thread_data globalThread;
    40         -
           49  +/*
           50  + * AppFS Path Type:  Describes the type of path a given file is
           51  + */
    41     52   typedef enum {
    42     53   	APPFS_PATHTYPE_INVALID,
    43     54   	APPFS_PATHTYPE_FILE,
    44     55   	APPFS_PATHTYPE_DIRECTORY,
    45         -	APPFS_PATHTYPE_SYMLINK
           56  +	APPFS_PATHTYPE_SYMLINK,
           57  +	APPFS_PATHTYPE_SOCKET,
           58  +	APPFS_PATHTYPE_FIFO,
    46     59   } appfs_pathtype_t;
    47     60   
    48         -struct appfs_children {
    49         -	struct appfs_children *_next;
    50         -	int counter;
    51         -
    52         -	char name[256];
    53         -};
    54         -
           61  +/*
           62  + * AppFS Path Information:
           63  + *         Completely describes a specific path, how it should be returned to
           64  + *         to the kernel
           65  + */
    55     66   struct appfs_pathinfo {
    56     67   	appfs_pathtype_t type;
    57     68   	time_t time;
    58     69   	char hostname[256];
    59     70   	int packaged;
    60     71   	unsigned long long inode;
    61     72   	union {
    62     73   		struct {
    63     74   			int childcount;
    64     75   		} dir;
    65     76   		struct {
    66     77   			int executable;
    67     78   			off_t size;
    68         -			char sha1[41];
    69     79   		} file;
    70     80   		struct {
    71     81   			off_t size;
    72     82   			char source[256];
    73     83   		} symlink;
    74     84   	} typeinfo;
    75     85   };
    76     86   
    77         -struct appfs_sqlite3_query_cb_handle {
    78         -	struct appfs_children *head;
    79         -	int argc;
    80         -	const char *fmt;
    81         -};
    82         -
    83         -static Tcl_Interp *appfs_create_TclInterp(const char *cachedir) {
           87  +/*
           88  + * Create a new Tcl interpreter and completely initialize it
           89  + */
           90  +static Tcl_Interp *appfs_create_TclInterp(char **error_string) {
    84     91   	Tcl_Interp *interp;
    85     92   	int tcl_ret;
    86     93   
    87     94   	APPFS_DEBUG("Creating new Tcl interpreter for TID = 0x%llx", (unsigned long long) pthread_self());
    88     95   
    89     96   	interp = Tcl_CreateInterp();
    90     97   	if (interp == NULL) {
    91     98   		fprintf(stderr, "Unable to create Tcl Interpreter.  Aborting.\n");
           99  +
          100  +		if (error_string) {
          101  +			*error_string = strdup("Unable to create Tcl interpreter.");
          102  +		}
    92    103   
    93    104   		return(NULL);
    94    105   	}
    95    106   
    96    107   	tcl_ret = Tcl_Init(interp);
    97    108   	if (tcl_ret != TCL_OK) {
    98    109   		fprintf(stderr, "Unable to initialize Tcl.  Aborting.\n");
          110  +		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
          111  +
          112  +		if (error_string) {
          113  +			*error_string = strdup(Tcl_GetStringResult(interp));
          114  +		}
          115  +
          116  +		Tcl_DeleteInterp(interp);
          117  +
          118  +		return(NULL);
          119  +	}
          120  +
          121  +	tcl_ret = Tcl_Eval(interp, "package ifneeded sha1 1.0 [list load {} sha1]");
          122  +	if (tcl_ret != TCL_OK) {
          123  +		fprintf(stderr, "Unable to initialize Tcl SHA1.  Aborting.\n");
          124  +		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
          125  +
          126  +		if (error_string) {
          127  +			*error_string = strdup(Tcl_GetStringResult(interp));
          128  +		}
          129  +
          130  +		Tcl_DeleteInterp(interp);
          131  +
          132  +		return(NULL);
          133  +	}
          134  +
          135  +	tcl_ret = Tcl_Eval(interp, "package ifneeded appfsd 1.0 [list load {} appfsd]");
          136  +	if (tcl_ret != TCL_OK) {
          137  +		fprintf(stderr, "Unable to initialize Tcl AppFS Package.  Aborting.\n");
          138  +		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
          139  +
          140  +		if (error_string) {
          141  +			*error_string = strdup(Tcl_GetStringResult(interp));
          142  +		}
          143  +
          144  +		Tcl_DeleteInterp(interp);
          145  +
          146  +		return(NULL);
          147  +	}
          148  +
          149  +	/*
          150  +	 * Load "pki.tcl" in the same way as appfsd.tcl (see below)
          151  +	 */
          152  +	tcl_ret = Tcl_Eval(interp, ""
          153  +#include "pki.tcl.h"
          154  +	"");
          155  +	if (tcl_ret != TCL_OK) {
          156  +		fprintf(stderr, "Unable to initialize Tcl PKI.  Aborting.\n");
          157  +		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
          158  +
          159  +		if (error_string) {
          160  +			*error_string = strdup(Tcl_GetStringResult(interp));
          161  +		}
    99    162   
   100    163   		Tcl_DeleteInterp(interp);
   101    164   
   102    165   		return(NULL);
   103    166   	}
   104    167   
          168  +	/*
          169  +	 * Load the "appfsd.tcl" script, which is "compiled" into a C header
          170  +	 * so that it does not need to exist on the filesystem and can be
          171  +	 * directly evaluated.
          172  +	 */
   105    173   	tcl_ret = Tcl_Eval(interp, ""
   106    174   #include "appfsd.tcl.h"
   107    175   	"");
   108    176   	if (tcl_ret != TCL_OK) {
   109    177   		fprintf(stderr, "Unable to initialize Tcl AppFS script.  Aborting.\n");
   110    178   		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
          179  +
          180  +		if (error_string) {
          181  +			*error_string = strdup(Tcl_GetStringResult(interp));
          182  +		}
   111    183   
   112    184   		Tcl_DeleteInterp(interp);
   113    185   
   114    186   		return(NULL);
   115    187   	}
   116    188   
   117         -	if (Tcl_SetVar(interp, "::appfs::cachedir", cachedir, TCL_GLOBAL_ONLY) == NULL) {
          189  +	/*
          190  +	 * Set global variables from C to Tcl
          191  +	 */
          192  +	if (Tcl_SetVar(interp, "::appfs::cachedir", appfs_cachedir, TCL_GLOBAL_ONLY) == NULL) {
   118    193   		fprintf(stderr, "Unable to set cache directory.  This should never fail.\n");
          194  +
          195  +		if (error_string) {
          196  +			*error_string = strdup(Tcl_GetStringResult(interp));
          197  +		}
   119    198   
   120    199   		Tcl_DeleteInterp(interp);
   121    200   
   122    201   		return(NULL);
   123    202   	}
   124    203   
          204  +	/*
          205  +	 * Initialize the "appfsd.tcl" environment, which must be done after
          206  +	 * global variables are set.
          207  +	 */
   125    208   	tcl_ret = Tcl_Eval(interp, "::appfs::init");
   126    209   	if (tcl_ret != TCL_OK) {
   127    210   		fprintf(stderr, "Unable to initialize Tcl AppFS script (::appfs::init).  Aborting.\n");
   128    211   		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
          212  +
          213  +		if (error_string) {
          214  +			*error_string = strdup(Tcl_GetStringResult(interp));
          215  +		}
   129    216   
   130    217   		Tcl_DeleteInterp(interp);
   131    218   
   132    219   		return(NULL);
   133    220   	}
   134    221   
   135         -	Tcl_HideCommand(interp, "glob", "glob");
   136         -	Tcl_HideCommand(interp, "exec", "exec");
   137         -	Tcl_HideCommand(interp, "pid", "pid");
          222  +	/*
          223  +	 * Hide some Tcl commands that we do not care to use and which may
          224  +	 * slow down run-time operations.
          225  +	 */
   138    226   	Tcl_HideCommand(interp, "auto_load_index", "auto_load_index");
   139    227   	Tcl_HideCommand(interp, "unknown", "unknown");
          228  +
          229  +	/*
          230  +	 * Return the completely initialized interpreter
          231  +	 */
          232  +	return(interp);
          233  +}
          234  +
          235  +/*
          236  + * Return the thread-specific Tcl interpreter, creating it if needed
          237  + */
          238  +static Tcl_Interp *appfs_TclInterp(void) {
          239  +	Tcl_Interp *interp;
          240  +	int pthread_ret;
          241  +
          242  +	interp = pthread_getspecific(interpKey);
          243  +	if (interp == NULL) {
          244  +		interp = appfs_create_TclInterp(NULL);
          245  +
          246  +		if (interp == NULL) {
          247  +			return(NULL);
          248  +		}
          249  +
          250  +		pthread_ret = pthread_setspecific(interpKey, interp);
          251  +		if (pthread_ret != 0) {
          252  +			Tcl_DeleteInterp(interp);
          253  +
          254  +			return(NULL);
          255  +		}
          256  +	}
   140    257   
   141    258   	return(interp);
   142    259   }
   143    260   
          261  +/*
          262  + * Evaluate a Tcl script constructed by concatenating a bunch of C strings
          263  + * together.
          264  + */
   144    265   static int appfs_Tcl_Eval(Tcl_Interp *interp, int objc, const char *cmd, ...) {
   145    266   	Tcl_Obj **objv;
   146    267   	const char *arg;
   147    268   	va_list argp;
   148    269   	int retval;
   149    270   	int i;
   150    271   
................................................................................
   175    296   	if (retval != TCL_OK) {
   176    297   		APPFS_DEBUG("Tcl command failed, ::errorInfo contains: %s\n", Tcl_GetVar(interp, "::errorInfo", 0));
   177    298   	}
   178    299   
   179    300   	return(retval);
   180    301   }
   181    302   
   182         -static void appfs_update_index(const char *hostname) {
   183         -	Tcl_Interp *interp;
   184         -	int tcl_ret;
   185         -
   186         -	APPFS_DEBUG("Enter: hostname = %s", hostname);
   187         -
   188         -	interp = pthread_getspecific(interpKey);
   189         -	if (interp == NULL) {
   190         -		interp = appfs_create_TclInterp(globalThread.cachedir);
   191         -
   192         -		if (interp == NULL) {
   193         -			return;
   194         -		}
   195         -
   196         -		pthread_setspecific(interpKey, interp);
   197         -	}
   198         -
   199         -	tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::getindex", hostname);
   200         -	if (tcl_ret != TCL_OK) {
   201         -		APPFS_DEBUG("Call to ::appfs::getindex failed: %s", Tcl_GetStringResult(interp));
   202         -
   203         -		return;
   204         -	}
   205         -
   206         -	return;
   207         -}
   208         -
   209         -static const char *appfs_getfile(const char *hostname, const char *sha1) {
   210         -	Tcl_Interp *interp;
   211         -	char *retval;
   212         -	int tcl_ret;
   213         -
   214         -	interp = pthread_getspecific(interpKey);
   215         -	if (interp == NULL) {
   216         -		interp = appfs_create_TclInterp(globalThread.cachedir);
   217         -
   218         -		if (interp == NULL) {
   219         -			return(NULL);
   220         -		}
   221         -
   222         -		pthread_setspecific(interpKey, interp);
   223         -	}
   224         -
   225         -	tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::download", hostname, sha1);
   226         -	if (tcl_ret != TCL_OK) {
   227         -		APPFS_DEBUG("Call to ::appfs::download failed: %s", Tcl_GetStringResult(interp));
   228         -
   229         -		return(NULL);
   230         -	}
   231         -
   232         -	retval = strdup(Tcl_GetStringResult(interp));
   233         -
   234         -	return(retval);
   235         -}
   236         -
   237         -static void appfs_update_manifest(const char *hostname, const char *sha1) {
   238         -	Tcl_Interp *interp;
   239         -	int tcl_ret;
   240         -
   241         -	interp = pthread_getspecific(interpKey);
   242         -	if (interp == NULL) {
   243         -		interp = appfs_create_TclInterp(globalThread.cachedir);
   244         -
   245         -		if (interp == NULL) {
   246         -			return;
   247         -		}
   248         -
   249         -		pthread_setspecific(interpKey, interp);
   250         -	}
   251         -
   252         -	tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::getpkgmanifest", hostname, sha1);
   253         -	if (tcl_ret != TCL_OK) {
   254         -		APPFS_DEBUG("Call to ::appfs::getpkgmanifest failed: %s", Tcl_GetStringResult(interp));
   255         -
   256         -		return;
   257         -	}
   258         -
   259         -	return;
   260         -}
   261         -
   262         -#define appfs_free_list_type(id, type) static void appfs_free_list_ ## id(type *head) { \
   263         -	type *obj, *next; \
   264         -	for (obj = head; obj; obj = next) { \
   265         -		next = obj->_next; \
   266         -		ckfree((void *) obj); \
   267         -	} \
   268         -}
   269         -
   270         -appfs_free_list_type(children, struct appfs_children)
   271         -
          303  +/*
          304  + * Determine the UID for the user making the current FUSE filesystem request.
          305  + * This will be used to lookup the user's home directory so we can search for
          306  + * locally modified files.
          307  + */
   272    308   static uid_t appfs_get_fsuid(void) {
   273    309   	struct fuse_context *ctx;
          310  +
          311  +	if (!appfs_fuse_started) {
          312  +		return(getuid());
          313  +	}
   274    314   
   275    315   	ctx = fuse_get_context();
   276    316   	if (ctx == NULL) {
          317  +		/* Unable to lookup user for some reason */
          318  +		/* Return an unprivileged user ID */
   277    319   		return(1);
   278    320   	}
   279    321   
   280    322   	return(ctx->uid);
   281    323   }
   282    324   
   283         -static const char *appfs_get_homedir(uid_t fsuid) {
          325  +/*
          326  + * Determine the GID for the user making the current FUSE filesystem request.
          327  + * This will be used to lookup the user's home directory so we can search for
          328  + * locally modified files.
          329  + */
          330  +static gid_t appfs_get_fsgid(void) {
          331  +	struct fuse_context *ctx;
          332  +
          333  +	if (!appfs_fuse_started) {
          334  +		return(getgid());
          335  +	}
          336  +
          337  +	ctx = fuse_get_context();
          338  +	if (ctx == NULL) {
          339  +		/* Unable to lookup user for some reason */
          340  +		/* Return an unprivileged user ID */
          341  +		return(1);
          342  +	}
          343  +
          344  +	return(ctx->gid);
          345  +}
          346  +
          347  +static void appfs_simulate_user_fs_enter(void) {
          348  +	setfsuid(appfs_get_fsuid());
          349  +	setfsgid(appfs_get_fsgid());
          350  +}
          351  +
          352  +static void appfs_simulate_user_fs_leave(void) {
          353  +	setfsuid(0);
          354  +	setfsgid(0);
          355  +}
          356  +
          357  +/*
          358  + * Look up the home directory for a given UID
          359  + *        Returns a C string containing the user's home directory or NULL if
          360  + *        the user's home directory does not exist or is not correctly
          361  + *        configured
          362  + */
          363  +static char *appfs_get_homedir(uid_t fsuid) {
   284    364   	struct passwd entry, *result;
   285    365   	struct stat stbuf;
   286    366   	char buf[1024], *retval;
   287    367   	int gpu_ret, stat_ret;
   288    368   
   289    369   	gpu_ret = getpwuid_r(fsuid, &entry, buf, sizeof(buf), &result);
   290    370   	if (gpu_ret != 0) {
................................................................................
   318    398   		    result->pw_dir,
   319    399   		    (unsigned long long) stbuf.st_uid
   320    400   		);
   321    401   
   322    402   		return(NULL);
   323    403   	}
   324    404   
   325         -	retval = sqlite3_mprintf("%s", result->pw_dir);
          405  +	retval = strdup(result->pw_dir);
   326    406   
   327    407   	return(retval);
   328    408   }
   329    409   
   330         -static int appfs_getpackage_name_cb(void *_package_name, int columns, char **values, char **names) {
   331         -	char **package_name;
   332         -
   333         -	if (columns != 1) {
   334         -		return(1);
   335         -	}
   336         -
   337         -	package_name = _package_name;
   338         -
   339         -	*package_name = sqlite3_mprintf("%s", values[0]);
   340         -
   341         -	return(0);
   342         -}
   343         -
   344         -static char *appfs_getpackage_name(const char *hostname, const char *package_hash) {
   345         -	char *sql;
   346         -	int sqlite_ret;
   347         -	char *package_name = NULL;
   348         -
   349         -	sql = sqlite3_mprintf("SELECT package FROM packages WHERE hostname = %Q AND sha1 = %Q LIMIT 1;", hostname, package_hash);
   350         -	if (sql == NULL) {
   351         -		APPFS_DEBUG("Call to sqlite3_mprintf failed.");
   352         -
   353         -		return(sqlite3_mprintf("%s", "unknown-package-name"));
   354         -	}
   355         -	sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_getpackage_name_cb, &package_name, NULL);
   356         -	sqlite3_free(sql);
   357         -
   358         -	if (sqlite_ret != SQLITE_OK) {
   359         -		APPFS_DEBUG("Call to sqlite3_exec failed.");
   360         -
   361         -		return(sqlite3_mprintf("%s", "unknown-package-name"));
   362         -	}
   363         -
   364         -	return(package_name);
   365         -}
   366         -
   367         -static struct appfs_children *appfs_getchildren_fs(struct appfs_children *in_children, const char *fspath) {
   368         -	APPFS_DEBUG("Searching %s", fspath);
   369         -
   370         -	return(in_children);
   371         -}
   372         -
   373         -static int appfs_getchildren_cb(void *_head, int columns, char **values, char **names) {
   374         -	struct appfs_children **head_p, *obj;
   375         -
   376         -	head_p = _head;
   377         -
   378         -	obj = (void *) ckalloc(sizeof(*obj));
   379         -
   380         -	snprintf(obj->name, sizeof(obj->name), "%s", values[0]);
   381         -
   382         -	if (*head_p == NULL) {
   383         -		obj->counter = 0;
          410  +/*
          411  + * Tcl interface to get the home directory for the user making the "current"
          412  + * FUSE I/O request
          413  + */
          414  +static int tcl_appfs_get_homedir(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
          415  +	char *homedir;
          416  +	Tcl_Obj *homedir_obj;
          417  +	uid_t fsuid;
          418  +	static __thread Tcl_Obj *last_homedir_obj = NULL;
          419  +	static __thread uid_t last_fsuid = -1;
          420  +
          421  +        if (objc != 1) {
          422  +                Tcl_WrongNumArgs(interp, 1, objv, NULL);
          423  +                return(TCL_ERROR);
          424  +        }
          425  +
          426  +	fsuid = appfs_get_fsuid();
          427  +
          428  +	if (fsuid == last_fsuid && last_homedir_obj != NULL) {
          429  +		homedir_obj = last_homedir_obj;
   384    430   	} else {
   385         -		obj->counter = (*head_p)->counter + 1;
   386         -	}
   387         -
   388         -	obj->_next = *head_p;
   389         -	*head_p = obj;
   390         -
   391         -	return(0);
   392         -	
   393         -}
   394         -
   395         -static struct appfs_children *appfs_getchildren(const char *hostname, const char *package_hash, const char *path, int *children_count_p) {
   396         -	struct appfs_children *head = NULL;
   397         -	char *sql, *filebuf, *homedir = NULL;
   398         -	int sqlite_ret;
   399         -	uid_t fsuid;
   400         -
   401         -	if (children_count_p == NULL) {
   402         -		return(NULL);
   403         -	}
   404         -
   405         -	appfs_update_index(hostname);
   406         -	appfs_update_manifest(hostname, package_hash);
   407         -
   408         -	sql = sqlite3_mprintf("SELECT file_name FROM files WHERE package_sha1 = %Q AND file_directory = %Q;", package_hash, path);
   409         -	if (sql == NULL) {
   410         -		APPFS_DEBUG("Call to sqlite3_mprintf failed.");
   411         -
   412         -		return(NULL);
   413         -	}
   414         -
   415         -	APPFS_DEBUG("SQL: %s", sql);
   416         -	sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_getchildren_cb, &head, NULL);
   417         -	sqlite3_free(sql);
   418         -
   419         -	if (sqlite_ret != SQLITE_OK) {
   420         -		APPFS_DEBUG("Call to sqlite3_exec failed.");
   421         -
   422         -		appfs_free_list_children(head);
   423         -
   424         -		return(NULL);
   425         -	}
   426         -
   427         -	if (globalThread.options.writable) {
   428         -		/* Determine user of process accessing this file */
   429         -		fsuid = appfs_get_fsuid();
   430         -
   431         -		/* Check filesystem paths for updated files */
   432         -		/** Check the global directory (/etc) **/
   433         -		filebuf = sqlite3_mprintf("/etc/appfs/%z@%s/%s", appfs_getpackage_name(hostname, package_hash), hostname, path);
   434         -		if (filebuf == NULL) {
   435         -			APPFS_DEBUG("Call to sqlite3_mprintf failed.");
   436         -
   437         -			return(NULL);
          431  +		if (last_homedir_obj != NULL) {
          432  +			Tcl_DecrRefCount(last_homedir_obj);
          433  +		}
          434  +
          435  +		homedir = appfs_get_homedir(appfs_get_fsuid());
          436  +
          437  +		if (homedir == NULL) {
          438  +			return(TCL_ERROR);
   438    439   		}
   439    440   
   440         -		head = appfs_getchildren_fs(head, filebuf);
   441         -
   442         -		sqlite3_free(filebuf);
   443         -
   444         -		/** Check the user's directory, if we are not root **/
   445         -		if (fsuid != 0) {
   446         -			homedir = (char *) appfs_get_homedir(fsuid);
   447         -		}
   448         -
   449         -		if (homedir != NULL) {
   450         -			filebuf = sqlite3_mprintf("%z/.appfs/%z@%s/%s", homedir, appfs_getpackage_name(hostname, package_hash), hostname, path);
   451         -
   452         -			if (filebuf == NULL) {
   453         -				APPFS_DEBUG("Call to sqlite3_mprintf failed.");
   454         -
   455         -				return(NULL);
   456         -			}
   457         -
   458         -			head = appfs_getchildren_fs(head, filebuf);
   459         -
   460         -			sqlite3_free(filebuf);
   461         -		}
   462         -	}
   463         -
   464         -	if (head != NULL) {
   465         -		*children_count_p = head->counter + 1;
   466         -	} else {
   467         -		*children_count_p = 0;
   468         -	}
   469         -
   470         -	return(head);
   471         -}
   472         -
   473         -static int appfs_sqlite3_query_cb(void *_cb_handle, int columns, char **values, char **names) {
   474         -	struct appfs_sqlite3_query_cb_handle *cb_handle;
   475         -	struct appfs_children *obj;
   476         -
   477         -	cb_handle = _cb_handle;
   478         -
   479         -	obj = (void *) ckalloc(sizeof(*obj));
   480         -
   481         -	switch (cb_handle->argc) {
   482         -		case 1:
   483         -			snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0]);
   484         -			break;
   485         -		case 2:
   486         -			snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0], values[1]);
   487         -			break;
   488         -		case 3:
   489         -			snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0], values[1], values[2]);
   490         -			break;
   491         -		case 4:
   492         -			snprintf(obj->name, sizeof(obj->name), cb_handle->fmt, values[0], values[1], values[2], values[3]);
   493         -			break;
   494         -	}
   495         -
   496         -	if (cb_handle->head == NULL) {
   497         -		obj->counter = 0;
   498         -	} else {
   499         -		obj->counter = cb_handle->head->counter + 1;
   500         -	}
   501         -
   502         -	obj->_next = cb_handle->head;
   503         -	cb_handle->head = obj;
   504         -
   505         -	return(0);
   506         -}
   507         -
   508         -static struct appfs_children *appfs_sqlite3_query(char *sql, int argc, const char *fmt, int *results_count_p) {
   509         -	struct appfs_sqlite3_query_cb_handle cb_handle;
   510         -	int sqlite_ret;
   511         -
   512         -	if (results_count_p == NULL) {
   513         -		return(NULL);
   514         -	}
   515         -
   516         -	if (sql == NULL) {
   517         -		APPFS_DEBUG("Call to sqlite3_mprintf probably failed.");
   518         -
   519         -		return(NULL);
   520         -	}
   521         -
   522         -	if (fmt == NULL) {
   523         -		fmt = "%s";
   524         -	}
   525         -
   526         -	cb_handle.head = NULL;
   527         -	cb_handle.argc = argc;
   528         -	cb_handle.fmt  = fmt;
   529         -
   530         -	APPFS_DEBUG("SQL: %s", sql);
   531         -	sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_sqlite3_query_cb, &cb_handle, NULL);
   532         -	sqlite3_free(sql);
   533         -
   534         -	if (sqlite_ret != SQLITE_OK) {
   535         -		APPFS_DEBUG("Call to sqlite3_exec failed.");
   536         -
   537         -		return(NULL);
   538         -	}
   539         -
   540         -	if (cb_handle.head != NULL) {
   541         -		*results_count_p = cb_handle.head->counter + 1;
   542         -	}
   543         -
   544         -	return(cb_handle.head);
   545         -}
   546         -
   547         -static int appfs_lookup_package_hash_cb(void *_retval, int columns, char **values, char **names) {
   548         -	char **retval = _retval;
   549         -
   550         -	*retval = strdup(values[0]);
   551         -
   552         -	return(0);
   553         -}
   554         -
   555         -static char *appfs_lookup_package_hash(const char *hostname, const char *package, const char *os, const char *cpuArch, const char *version) {
   556         -	char *sql;
   557         -	char *retval = NULL;
   558         -	int sqlite_ret;
   559         -
   560         -	appfs_update_index(hostname);
   561         -
   562         -	sql = sqlite3_mprintf("SELECT sha1 FROM packages WHERE hostname = %Q AND package = %Q AND os = %Q AND cpuArch = %Q AND version = %Q;",
   563         -		hostname,
   564         -		package,
   565         -		os,
   566         -		cpuArch,
   567         -		version
   568         -	);
   569         -	if (sql == NULL) {
   570         -		APPFS_DEBUG("Call to sqlite3_mprintf failed.");
   571         -
   572         -		return(NULL);
   573         -	}
   574         -
   575         -	APPFS_DEBUG("SQL: %s", sql);
   576         -	sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_lookup_package_hash_cb, &retval, NULL);
   577         -	sqlite3_free(sql);
   578         -
   579         -	if (sqlite_ret != SQLITE_OK) {
   580         -		APPFS_DEBUG("Call to sqlite3_exec failed.");
   581         -
   582         -		return(NULL);
   583         -	}
   584         -
   585         -	return(retval);
   586         -}
   587         -
   588         -static int appfs_getfileinfo_cb(void *_pathinfo, int columns, char **values, char **names) {
   589         -	struct appfs_pathinfo *pathinfo = _pathinfo;
   590         -	const char *type, *time, *source, *size, *perms, *sha1, *rowid;
   591         -
   592         -	type = values[0];
   593         -	time = values[1];
   594         -	source = values[2];
   595         -	size = values[3];
   596         -	perms = values[4];
   597         -	sha1 = values[5];
   598         -	rowid = values[6];
   599         -
   600         -	pathinfo->time = strtoull(time, NULL, 10);
   601         -
   602         -	/* Package file inodes start at 2^32, fake inodes are before then */
   603         -	pathinfo->inode = strtoull(rowid, NULL, 10) + 4294967296ULL;
   604         -
   605         -	if (strcmp(type, "file") == 0) {
   606         -		pathinfo->type = APPFS_PATHTYPE_FILE;
   607         -
   608         -		if (!size) {
   609         -			size = "0";
   610         -		}
   611         -
   612         -		if (!perms) {
   613         -			perms = "";
   614         -		}
   615         -
   616         -		if (!sha1) {
   617         -			sha1 = "";
   618         -		}
   619         -
   620         -		pathinfo->typeinfo.file.size = strtoull(size, NULL, 10);
   621         -		snprintf(pathinfo->typeinfo.file.sha1, sizeof(pathinfo->typeinfo.file.sha1), "%s", sha1);
   622         -
   623         -		if (strcmp(perms, "x") == 0) {
   624         -			pathinfo->typeinfo.file.executable = 1;
   625         -		} else {
   626         -			pathinfo->typeinfo.file.executable = 0;
   627         -		}
   628         -
   629         -		return(0);
   630         -	}
   631         -
   632         -	if (strcmp(type, "directory") == 0) {
   633         -		pathinfo->type = APPFS_PATHTYPE_DIRECTORY;
   634         -		pathinfo->typeinfo.dir.childcount = 0;
   635         -
   636         -		return(0);
   637         -	}
   638         -
   639         -	if (strcmp(type, "symlink") == 0) {
   640         -		pathinfo->type = APPFS_PATHTYPE_SYMLINK;
   641         -		pathinfo->typeinfo.dir.childcount = 0;
   642         -
   643         -		if (!source) {
   644         -			source = ".BADLINK";
   645         -		}
   646         -
   647         -		pathinfo->typeinfo.symlink.size = strlen(source);
   648         -		snprintf(pathinfo->typeinfo.symlink.source, sizeof(pathinfo->typeinfo.symlink.source), "%s", source);
   649         -
   650         -		return(0);
   651         -	}
   652         -
   653         -	return(0);
   654         -
   655         -	/* Until this is used, prevent the compiler from complaining */
   656         -	source = source;
   657         -}
   658         -
   659         -static int appfs_getfileinfo(const char *hostname, const char *package_hash, const char *_path, struct appfs_pathinfo *pathinfo) {
   660         -	char *directory, *file, *path;
   661         -	char *sql;
   662         -	int sqlite_ret;
   663         -
   664         -	if (pathinfo == NULL) {
   665         -		return(-EIO);
   666         -	}
   667         -
   668         -	appfs_update_index(hostname);
   669         -	appfs_update_manifest(hostname, package_hash);
   670         -
   671         -	path = strdup(_path);
   672         -	directory = path;
   673         -	file = strrchr(path, '/');
   674         -	if (file == NULL) {
   675         -		file = path;
   676         -		directory = "";
   677         -	} else {
   678         -		*file = '\0';
   679         -		file++;
   680         -	}
   681         -
   682         -	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);
   683         -	if (sql == NULL) {
   684         -		APPFS_DEBUG("Call to sqlite3_mprintf failed.");
   685         -
   686         -		free(path);
   687         -
   688         -		return(-EIO);
   689         -	}
   690         -
   691         -	free(path);
   692         -
   693         -	pathinfo->type = APPFS_PATHTYPE_INVALID;
   694         -
   695         -	APPFS_DEBUG("SQL: %s", sql);
   696         -	sqlite_ret = sqlite3_exec(globalThread.db, sql, appfs_getfileinfo_cb, pathinfo, NULL);
   697         -	sqlite3_free(sql);
   698         -
   699         -	if (sqlite_ret != SQLITE_OK) {
   700         -		APPFS_DEBUG("Call to sqlite3_exec failed.");
   701         -
   702         -		return(-EIO);
   703         -	}
   704         -
   705         -	if (pathinfo->type == APPFS_PATHTYPE_INVALID) {
   706         -		return(-ENOENT);
   707         -	}
   708         -
   709         -	return(0);
   710         -}
   711         -
   712         -static int appfs_get_path_info_sql(char *sql, int argc, const char *fmt, struct appfs_pathinfo *pathinfo, struct appfs_children **children) {
   713         -	struct appfs_children *node, *dir_children, *dir_child;
   714         -	int dir_children_count = 0;
   715         -
   716         -	dir_children = appfs_sqlite3_query(sql, argc, fmt, &dir_children_count);
   717         -
   718         -	if (dir_children == NULL || dir_children_count == 0) {
   719         -		return(-ENOENT);
   720         -	}
   721         -
   722         -	/* Request for a single hostname */
   723         -	pathinfo->type = APPFS_PATHTYPE_DIRECTORY;
   724         -	pathinfo->typeinfo.dir.childcount = dir_children_count;
   725         -	pathinfo->time = globalThread.boottime;
   726         -
   727         -	if (children) {
   728         -		for (dir_child = dir_children; dir_child; dir_child = dir_child->_next) {
   729         -			node = (void *) ckalloc(sizeof(*node));
   730         -			node->_next = *children;
   731         -			strcpy(node->name, dir_child->name);
   732         -			*children = node;
   733         -		}
   734         -	}
   735         -
   736         -	appfs_free_list_children(dir_children);
   737         -
   738         -	return(0);
   739         -}
   740         -
   741         -static int appfs_add_path_child(const char *name, struct appfs_pathinfo *pathinfo, struct appfs_children **children) {
   742         -	struct appfs_children *new_child;
   743         -
   744         -	pathinfo->typeinfo.dir.childcount++;
   745         -
   746         -	if (children) {
   747         -		new_child = (void *) ckalloc(sizeof(*new_child));
   748         -		new_child->_next = *children;
   749         -
   750         -		snprintf(new_child->name, sizeof(new_child->name), "%s", name);
   751         -
   752         -		*children = new_child;
   753         -	}
   754         -
   755         -	return(0);
   756         -}
   757         -
   758         -/* Generate an inode for a given path */
          441  +		homedir_obj = Tcl_NewStringObj(homedir, -1);
          442  +
          443  +		free(homedir);
          444  +
          445  +		last_homedir_obj = homedir_obj;
          446  +		last_fsuid = fsuid;
          447  +
          448  +		Tcl_IncrRefCount(last_homedir_obj);
          449  +	}
          450  +
          451  +       	Tcl_SetObjResult(interp, homedir_obj);
          452  +
          453  +        return(TCL_OK);
          454  +}
          455  +
          456  +static int tcl_appfs_simulate_user_fs_enter(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
          457  +	appfs_simulate_user_fs_enter();
          458  +
          459  +	return(TCL_OK);
          460  +}
          461  +
          462  +static int tcl_appfs_simulate_user_fs_leave(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
          463  +	appfs_simulate_user_fs_leave();
          464  +
          465  +	return(TCL_OK);
          466  +}
          467  +
          468  +/*
          469  + * Generate an inode for a given path.  The inode should be computed in such
          470  + * a way that it is unlikely to be duplicated and remains the same for a given
          471  + * file
          472  + */
   759    473   static long long appfs_get_path_inode(const char *path) {
   760    474   	long long retval;
   761    475   	const char *p;
   762    476   
   763    477   	retval = 10;
   764    478   
   765    479   	for (p = path; *p; p++) {
................................................................................
   771    485   	retval += 10;
   772    486   	retval %= 4294967296ULL;
   773    487   
   774    488   	return(retval);
   775    489   }
   776    490   
   777    491   /* Get information about a path, and optionally list children */
   778         -static int appfs_get_path_info(const char *_path, struct appfs_pathinfo *pathinfo, struct appfs_children **children) {
   779         -	struct appfs_children *dir_children;
   780         -	char *hostname, *packagename, *os_cpuArch, *os, *cpuArch, *version;
   781         -	char *path, *path_s;
   782         -	char *package_hash;
   783         -	char *sql;
   784         -	int files_count;
   785         -	int fileinfo_ret, retval;
   786         -
   787         -	/* Initialize return */
   788         -	if (children) {
   789         -		*children = NULL;
   790         -	}
   791         -
   792         -	/* Verify that this is a valid request */
   793         -	if (_path == NULL) {
   794         -		return(-ENOENT);
   795         -	}
   796         -
   797         -	if (_path[0] != '/') {
   798         -		return(-ENOENT);
   799         -	}
   800         -
   801         -	/* Note that this is not a "real" directory from a package */
   802         -	pathinfo->packaged = 0;
   803         -
   804         -	if (_path[1] == '\0') {
   805         -		/* Request for the root directory */
   806         -		pathinfo->hostname[0] = '\0';
   807         -		pathinfo->inode = 1;
   808         -
   809         -		sql = sqlite3_mprintf("SELECT DISTINCT hostname FROM packages;");
   810         -
   811         -		retval = appfs_get_path_info_sql(sql, 1, NULL, pathinfo, children);
   812         -
   813         -		/* The root directory always exists, even if it has no subordinates */
   814         -		if (retval != 0) {
   815         -			pathinfo->type = APPFS_PATHTYPE_DIRECTORY;
   816         -			pathinfo->typeinfo.dir.childcount = 0;
   817         -			pathinfo->time = globalThread.boottime;
   818         -
   819         -			retval = 0;
   820         -		}
   821         -
   822         -		return(retval);
   823         -	}
   824         -
   825         -	path = strdup(_path);
   826         -	path_s = path;
   827         -
   828         -	pathinfo->inode = appfs_get_path_inode(path);
   829         -
   830         -	hostname = path + 1;
   831         -	packagename = strchr(hostname, '/');
   832         -
   833         -	if (packagename != NULL) {
   834         -		*packagename = '\0';
   835         -		packagename++;
   836         -	}
   837         -
   838         -	snprintf(pathinfo->hostname, sizeof(pathinfo->hostname), "%s", hostname);
   839         -
   840         -	if (packagename == NULL) {
   841         -		appfs_update_index(hostname);
   842         -
   843         -		sql = sqlite3_mprintf("SELECT DISTINCT package FROM packages WHERE hostname = %Q;", hostname);
   844         -
   845         -		free(path_s);
   846         -
   847         -		return(appfs_get_path_info_sql(sql, 1, NULL, pathinfo, children));
   848         -	}
   849         -
   850         -	os_cpuArch = strchr(packagename, '/');
   851         -
   852         -	if (os_cpuArch != NULL) {
   853         -		*os_cpuArch = '\0';
   854         -		os_cpuArch++;
   855         -	}
   856         -
   857         -	if (os_cpuArch == NULL) {
   858         -		appfs_update_index(hostname);
   859         -
   860         -		sql = sqlite3_mprintf("SELECT DISTINCT os, cpuArch FROM packages WHERE hostname = %Q AND package = %Q;", hostname, packagename);
   861         -
   862         -		free(path_s);
   863         -
   864         -		retval = appfs_get_path_info_sql(sql, 2, "%s-%s", pathinfo, children);
   865         -
   866         -		if (retval != 0) {
   867         -			return(retval);
   868         -		}
   869         -
   870         -		appfs_add_path_child("platform", pathinfo, children);
   871         -
   872         -		return(retval);
   873         -	}
   874         -
   875         -	version = strchr(os_cpuArch, '/');
   876         -
   877         -	if (version != NULL) {
   878         -		*version = '\0';
   879         -		version++;
   880         -	}
   881         -
   882         -	os = os_cpuArch;
   883         -	cpuArch = strchr(os_cpuArch, '-');
   884         -	if (cpuArch) {
   885         -		*cpuArch = '\0';
   886         -		cpuArch++;
   887         -	} else {
   888         -		cpuArch = "";
   889         -	}
   890         -
   891         -	if (version == NULL) {
   892         -		if (strcmp(os, "platform") == 0 && strcmp(cpuArch, "") == 0) {
   893         -			pathinfo->type = APPFS_PATHTYPE_SYMLINK;
   894         -			pathinfo->time = globalThread.boottime;
   895         -			pathinfo->typeinfo.dir.childcount = 0;
   896         -			pathinfo->typeinfo.symlink.size = strlen(globalThread.platform);
   897         -
   898         -			snprintf(pathinfo->typeinfo.symlink.source, sizeof(pathinfo->typeinfo.symlink.source), "%s", globalThread.platform);
   899         -
   900         -			free(path_s);
   901         -
   902         -			return(0);
   903         -		}
   904         -
   905         -		/* Request for version list for a package on an OS/CPU */
   906         -		appfs_update_index(hostname);
   907         -
   908         -		sql = sqlite3_mprintf("SELECT DISTINCT version FROM packages WHERE hostname = %Q AND package = %Q AND os = %Q and cpuArch = %Q;", hostname, packagename, os, cpuArch);
   909         -
   910         -		free(path_s);
   911         -
   912         -		return(appfs_get_path_info_sql(sql, 1, NULL, pathinfo, children));
   913         -	}
   914         -
   915         -	path = strchr(version, '/');
   916         -	if (path == NULL) {
   917         -		path = "";
   918         -	} else {
   919         -		*path = '\0';
   920         -		path++;
   921         -	}
   922         -
   923         -	/* Request for a file in a specific package */
   924         -	pathinfo->packaged = 1;
   925         -	APPFS_DEBUG("Requesting information for hostname = %s, package = %s, os = %s, cpuArch = %s, version = %s, path = %s", 
   926         -		hostname, packagename, os, cpuArch, version, path
   927         -	);
   928         -
   929         -	package_hash = appfs_lookup_package_hash(hostname, packagename, os, cpuArch, version);
   930         -	if (package_hash == NULL) {
   931         -		free(path_s);
          492  +static int appfs_get_path_info(const char *path, struct appfs_pathinfo *pathinfo) {
          493  +	Tcl_Interp *interp;
          494  +	Tcl_Obj *attrs_dict, *attr_value;
          495  +	const char *attr_value_str;
          496  +	Tcl_WideInt attr_value_wide;
          497  +	int attr_value_int;
          498  +	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;
          499  +	int tcl_ret;
          500  +
          501  +	interp = appfs_TclInterp();
          502  +	if (interp == NULL) {
          503  +		return(-EIO);
          504  +	}
          505  +
          506  +	tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::getattr", path);
          507  +	if (tcl_ret != TCL_OK) {
          508  +		APPFS_DEBUG("::appfs::getattr(%s) failed.", path);
          509  +		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
   932    510   
   933    511   		return(-ENOENT);
   934    512   	}
   935    513   
   936         -	APPFS_DEBUG("  ... which hash a hash of %s", package_hash);
   937         -
   938         -	appfs_update_manifest(hostname, package_hash);
   939         -
   940         -	if (strcmp(path, "") == 0) {
   941         -		pathinfo->type = APPFS_PATHTYPE_DIRECTORY;
   942         -		pathinfo->time = globalThread.boottime;
   943         -	} else {
   944         -		fileinfo_ret = appfs_getfileinfo(hostname, package_hash, path, pathinfo);
   945         -		if (fileinfo_ret != 0) {
   946         -			free(path_s);
   947         -
   948         -			return(fileinfo_ret);
   949         -		}
   950         -	}
   951         -
   952         -	if (pathinfo->type == APPFS_PATHTYPE_DIRECTORY) {
   953         -		dir_children = appfs_getchildren(hostname, package_hash, path, &files_count);
   954         -
   955         -		if (dir_children != NULL) {
   956         -			pathinfo->typeinfo.dir.childcount = files_count;
          514  +	if (attr_key_type == NULL) {
          515  +		attr_key_type       = Tcl_NewStringObj("type", -1);
          516  +		attr_key_perms      = Tcl_NewStringObj("perms", -1);
          517  +		attr_key_size       = Tcl_NewStringObj("size", -1);
          518  +		attr_key_time       = Tcl_NewStringObj("time", -1);
          519  +		attr_key_source     = Tcl_NewStringObj("source", -1);
          520  +		attr_key_childcount = Tcl_NewStringObj("childcount", -1);
          521  +		attr_key_packaged   = Tcl_NewStringObj("packaged", -1);
          522  +	}
          523  +
          524  +	attrs_dict = Tcl_GetObjResult(interp);
          525  +	tcl_ret = Tcl_DictObjGet(interp, attrs_dict, attr_key_type, &attr_value);
          526  +	if (tcl_ret != TCL_OK) {
          527  +		APPFS_DEBUG("[dict get \"type\"] failed");
          528  +		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
          529  +
          530  +		return(-EIO);
          531  +	}
          532  +
          533  +	if (attr_value == NULL) {
          534  +		return(-EIO);
          535  +	}
          536  +
          537  +	pathinfo->packaged = 0;
          538  +	pathinfo->inode = appfs_get_path_inode(path);
          539  +
          540  +	attr_value_str = Tcl_GetString(attr_value);
          541  +	switch (attr_value_str[0]) {
          542  +		case 'd': /* directory */
          543  +			pathinfo->type = APPFS_PATHTYPE_DIRECTORY;
          544  +			pathinfo->typeinfo.dir.childcount = 0;
          545  +
          546  +			Tcl_DictObjGet(interp, attrs_dict, attr_key_childcount, &attr_value);
          547  +			if (attr_value != NULL) {
          548  +				tcl_ret = Tcl_GetWideIntFromObj(NULL, attr_value, &attr_value_wide);
          549  +				if (tcl_ret == TCL_OK) {
          550  +					pathinfo->typeinfo.dir.childcount = attr_value_wide;
          551  +				}
          552  +			}
          553  +
          554  +			break;
          555  +		case 'f': /* file */
          556  +			pathinfo->type = APPFS_PATHTYPE_FILE;
          557  +			pathinfo->typeinfo.file.size = 0;
          558  +			pathinfo->typeinfo.file.executable = 0;
          559  +
          560  +			Tcl_DictObjGet(interp, attrs_dict, attr_key_size, &attr_value);
          561  +			if (attr_value != NULL) {
          562  +				tcl_ret = Tcl_GetWideIntFromObj(NULL, attr_value, &attr_value_wide);
          563  +				if (tcl_ret == TCL_OK) {
          564  +					pathinfo->typeinfo.file.size = attr_value_wide;
          565  +				}
          566  +			}
          567  +
          568  +			Tcl_DictObjGet(interp, attrs_dict, attr_key_perms, &attr_value);
          569  +			if (attr_value != NULL) {
          570  +				attr_value_str = Tcl_GetString(attr_value);
          571  +				if (attr_value_str[0] == 'x') {
          572  +					pathinfo->typeinfo.file.executable = 1;
          573  +				}
          574  +			}
          575  +			break;
          576  +		case 's': /* symlink */
          577  +			pathinfo->type = APPFS_PATHTYPE_SYMLINK;
          578  +			pathinfo->typeinfo.symlink.size = 0;
          579  +			pathinfo->typeinfo.symlink.source[0] = '\0';
          580  +
          581  +			Tcl_DictObjGet(interp, attrs_dict, attr_key_source, &attr_value);
          582  +			if (attr_value != NULL) {
          583  +				attr_value_str = Tcl_GetStringFromObj(attr_value, &attr_value_int); 
          584  +
          585  +				if ((attr_value_int + 1) <= sizeof(pathinfo->typeinfo.symlink.source)) {
          586  +					pathinfo->typeinfo.symlink.size = attr_value_int;
          587  +					pathinfo->typeinfo.symlink.source[attr_value_int] = '\0';
          588  +
          589  +					memcpy(pathinfo->typeinfo.symlink.source, attr_value_str, attr_value_int);
          590  +				}
          591  +			}
          592  +			break;
          593  +		case 'F': /* pipe/fifo */
          594  +			pathinfo->type = APPFS_PATHTYPE_FIFO;
          595  +			break;
          596  +		case 'S': /* UNIX domain socket */
          597  +			pathinfo->type = APPFS_PATHTYPE_SOCKET;
          598  +			break;
          599  +		default:
          600  +			return(-EIO);
          601  +	}
          602  +
          603  +	Tcl_DictObjGet(interp, attrs_dict, attr_key_packaged, &attr_value);
          604  +	if (attr_value != NULL) {
          605  +		pathinfo->packaged = 1;
          606  +	}
          607  +
          608  +	Tcl_DictObjGet(interp, attrs_dict, attr_key_time, &attr_value);
          609  +	if (attr_value != NULL) {
          610  +		tcl_ret = Tcl_GetWideIntFromObj(NULL, attr_value, &attr_value_wide);
          611  +		if (tcl_ret == TCL_OK) {
          612  +			pathinfo->time = attr_value_wide;
   957    613   		}
   958         -
   959         -		if (children) {
   960         -			*children = dir_children;
   961         -		} else {
   962         -			appfs_free_list_children(dir_children);
   963         -		}
   964         -	}
   965         -
   966         -	free(path_s);
   967         -
   968         -	return(0);
          614  +	} else {
          615  +		pathinfo->time = 0;
          616  +	}
          617  +
          618  +	return(0);
          619  +}
          620  +
          621  +static char *appfs_prepare_to_create(const char *path) {
          622  +	Tcl_Interp *interp;
          623  +	const char *real_path;
          624  +	int tcl_ret;
          625  +
          626  +	interp = appfs_TclInterp();
          627  +	if (interp == NULL) {
          628  +		return(NULL);
          629  +	}
          630  +
          631  +	tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::prepare_to_create", path);
          632  +	if (tcl_ret != TCL_OK) {
          633  +		APPFS_DEBUG("::appfs::prepare_to_create(%s) failed.", path);
          634  +		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
          635  +
          636  +		return(NULL);
          637  +	}
          638  +
          639  +	real_path = Tcl_GetStringResult(interp);
          640  +	if (real_path == NULL) {
          641  +		return(NULL);
          642  +	}
          643  +
          644  +	return(strdup(real_path));
          645  +}
          646  +
          647  +static char *appfs_localpath(const char *path) {
          648  +	Tcl_Interp *interp;
          649  +	const char *real_path;
          650  +	int tcl_ret;
          651  +
          652  +	interp = appfs_TclInterp();
          653  +	if (interp == NULL) {
          654  +		return(NULL);
          655  +	}
          656  +
          657  +	tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::localpath", path);
          658  +	if (tcl_ret != TCL_OK) {
          659  +		APPFS_DEBUG("::appfs::localpath(%s) failed.", path);
          660  +		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
          661  +
          662  +		return(NULL);
          663  +	}
          664  +
          665  +	real_path = Tcl_GetStringResult(interp);
          666  +	if (real_path == NULL) {
          667  +		return(NULL);
          668  +	}
          669  +
          670  +	return(strdup(real_path));
   969    671   }
   970    672   
   971    673   static int appfs_fuse_readlink(const char *path, char *buf, size_t size) {
   972    674   	struct appfs_pathinfo pathinfo;
   973         -	int res = 0;
          675  +	int retval = 0;
   974    676   
   975    677   	APPFS_DEBUG("Enter (path = %s, ...)", path);
   976    678   
   977    679   	pathinfo.type = APPFS_PATHTYPE_INVALID;
   978    680   
   979         -	res = appfs_get_path_info(path, &pathinfo, NULL);
   980         -	if (res != 0) {
   981         -		return(res);
          681  +	retval = appfs_get_path_info(path, &pathinfo);
          682  +	if (retval != 0) {
          683  +		return(retval);
   982    684   	}
   983    685   
   984    686   	if (pathinfo.type != APPFS_PATHTYPE_SYMLINK) {
   985    687   		return(-EINVAL);
   986    688   	}
   987    689   
   988    690   	if ((strlen(pathinfo.typeinfo.symlink.source) + 1) > size) {
................................................................................
   992    694   	memcpy(buf, pathinfo.typeinfo.symlink.source, strlen(pathinfo.typeinfo.symlink.source) + 1);
   993    695   
   994    696   	return(0);
   995    697   }
   996    698   
   997    699   static int appfs_fuse_getattr(const char *path, struct stat *stbuf) {
   998    700   	struct appfs_pathinfo pathinfo;
   999         -	int res = 0;
          701  +	int retval;
          702  +
          703  +	retval = 0;
  1000    704   
  1001    705   	APPFS_DEBUG("Enter (path = %s, ...)", path);
  1002    706   
  1003    707   	pathinfo.type = APPFS_PATHTYPE_INVALID;
  1004    708   
  1005         -	res = appfs_get_path_info(path, &pathinfo, NULL);
  1006         -	if (res != 0) {
  1007         -		return(res);
          709  +	retval = appfs_get_path_info(path, &pathinfo);
          710  +	if (retval != 0) {
          711  +		return(retval);
  1008    712   	}
  1009    713   
  1010    714   	memset(stbuf, 0, sizeof(struct stat));
  1011    715   
  1012    716   	stbuf->st_mtime = pathinfo.time;
  1013    717   	stbuf->st_ctime = pathinfo.time;
  1014    718   	stbuf->st_atime = pathinfo.time;
  1015    719   	stbuf->st_ino   = pathinfo.inode;
  1016    720   	stbuf->st_mode  = 0;
          721  +	stbuf->st_uid   = appfs_get_fsuid();
          722  +	stbuf->st_gid   = appfs_get_fsgid();
  1017    723   
  1018    724   	switch (pathinfo.type) {
  1019    725   		case APPFS_PATHTYPE_DIRECTORY:
  1020    726   			stbuf->st_mode = S_IFDIR | 0555;
  1021    727   			stbuf->st_nlink = 2 + pathinfo.typeinfo.dir.childcount;
  1022    728   			break;
  1023    729   		case APPFS_PATHTYPE_FILE:
................................................................................
  1030    736   			stbuf->st_nlink = 1;
  1031    737   			stbuf->st_size = pathinfo.typeinfo.file.size;
  1032    738   			break;
  1033    739   		case APPFS_PATHTYPE_SYMLINK:
  1034    740   			stbuf->st_mode = S_IFLNK | 0555;
  1035    741   			stbuf->st_nlink = 1;
  1036    742   			stbuf->st_size = pathinfo.typeinfo.symlink.size;
          743  +			break;
          744  +		case APPFS_PATHTYPE_SOCKET:
          745  +			stbuf->st_mode = S_IFSOCK | 0555;
          746  +			stbuf->st_nlink = 1;
          747  +			stbuf->st_size = 0;
          748  +			break;
          749  +		case APPFS_PATHTYPE_FIFO:
          750  +			stbuf->st_mode = S_IFIFO | 0555;
          751  +			stbuf->st_nlink = 1;
          752  +			stbuf->st_size = 0;
  1037    753   			break;
  1038    754   		case APPFS_PATHTYPE_INVALID:
  1039         -			res = -EIO;
          755  +			retval = -ENOENT;
  1040    756   
  1041    757   			break;
  1042    758   	}
  1043    759   
  1044    760   	if (pathinfo.packaged) {
  1045         -		if (globalThread.options.writable) {
  1046         -			stbuf->st_mode |= 0222;
  1047         -		}
          761  +		stbuf->st_mode |= 0222;
  1048    762   	}
  1049    763   
  1050         -	return res;
          764  +	return(retval);
  1051    765   }
  1052    766   
  1053    767   static int appfs_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {
  1054         -	struct appfs_pathinfo pathinfo;
  1055         -	struct appfs_children *children, *child;
  1056         -	int retval;
          768  +	Tcl_Interp *interp;
          769  +	Tcl_Obj **children;
          770  +	int children_count, idx;
          771  +	int tcl_ret;
  1057    772   
  1058    773   	APPFS_DEBUG("Enter (path = %s, ...)", path);
  1059    774   
  1060         -	retval = appfs_get_path_info(path, &pathinfo, &children);
  1061         -	if (retval != 0) {
  1062         -		return(retval);
          775  +	interp = appfs_TclInterp();
          776  +	if (interp == NULL) {
          777  +		return(0);
  1063    778   	}
  1064    779   
  1065    780   	filler(buf, ".", NULL, 0);
  1066    781   	filler(buf, "..", NULL, 0);
  1067    782   
  1068         -	for (child = children; child; child = child->_next) {
  1069         -		filler(buf, child->name, NULL, 0);
          783  +	tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::getchildren", path);
          784  +	if (tcl_ret != TCL_OK) {
          785  +		APPFS_DEBUG("::appfs::getchildren(%s) failed.", path);
          786  +		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
          787  +
          788  +		return(0);
  1070    789   	}
  1071    790   
  1072         -	appfs_free_list_children(children);
          791  +	tcl_ret = Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp), &children_count, &children);
          792  +	if (tcl_ret != TCL_OK) {
          793  +		APPFS_DEBUG("Parsing list of children on path %s failed.", path);
          794  +		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
          795  +
          796  +		return(0);
          797  +	}
          798  +
          799  +	for (idx = 0; idx < children_count; idx++) {
          800  +		filler(buf, Tcl_GetString(children[idx]), NULL, 0);
          801  +	}
  1073    802   
  1074    803   	return(0);
  1075    804   }
  1076    805   
  1077    806   static int appfs_fuse_open(const char *path, struct fuse_file_info *fi) {
          807  +	Tcl_Interp *interp;
  1078    808   	struct appfs_pathinfo pathinfo;
  1079         -	const char *real_path;
          809  +	const char *real_path, *mode;
          810  +	int gpi_ret, tcl_ret;
  1080    811   	int fh;
  1081         -	int gpi_ret;
  1082    812   
  1083    813   	APPFS_DEBUG("Enter (path = %s, ...)", path);
  1084    814   
  1085         -	if ((fi->flags & 3) != O_RDONLY) {
  1086         -                return(-EACCES);
  1087         -	}
          815  +	gpi_ret = appfs_get_path_info(path, &pathinfo);
  1088    816   
  1089         -	gpi_ret = appfs_get_path_info(path, &pathinfo, NULL);
  1090         -	if (gpi_ret != 0) {
  1091         -		return(gpi_ret);
          817  +	if ((fi->flags & (O_WRONLY|O_CREAT)) == (O_CREAT|O_WRONLY)) {
          818  +		/* The file will be created if it does not exist */
          819  +		if (gpi_ret != 0 && gpi_ret != -ENOENT) {
          820  +			return(gpi_ret);
          821  +		}
          822  +
          823  +		mode = "create";
          824  +	} else {
          825  +		/* The file must already exist */
          826  +		if (gpi_ret != 0) {
          827  +			return(gpi_ret);
          828  +		}
          829  +
          830  +		mode = "";
          831  +
          832  +		if ((fi->flags & O_WRONLY) == O_WRONLY) {
          833  +			mode = "write";
          834  +		}
  1092    835   	}
  1093    836   
  1094    837   	if (pathinfo.type == APPFS_PATHTYPE_DIRECTORY) {
  1095    838   		return(-EISDIR);
  1096    839   	}
  1097    840   
  1098         -	real_path = appfs_getfile(pathinfo.hostname, pathinfo.typeinfo.file.sha1);
          841  +	interp = appfs_TclInterp();
          842  +	if (interp == NULL) {
          843  +		return(-EIO);
          844  +	}
          845  +
          846  +	tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::openpath", path, mode);
          847  +	if (tcl_ret != TCL_OK) {
          848  +		APPFS_DEBUG("::appfs::openpath(%s, %s) failed.", path, mode);
          849  +		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
          850  +
          851  +		return(-EIO);
          852  +	}
          853  +
          854  +	real_path = Tcl_GetStringResult(interp);
  1099    855   	if (real_path == NULL) {
  1100    856   		return(-EIO);
  1101    857   	}
  1102    858   
  1103         -	fh = open(real_path, O_RDONLY);
  1104         -	free((void *) real_path);
          859  +	APPFS_DEBUG("Translated request to open %s to opening %s (mode = \"%s\")", path, real_path, mode);
          860  +
          861  +	fh = open(real_path, fi->flags, 0600);
          862  +
  1105    863   	if (fh < 0) {
  1106    864   		return(-EIO);
  1107    865   	}
  1108    866   
  1109    867   	fi->fh = fh;
  1110    868   
  1111    869   	return(0);
................................................................................
  1134    892   	}
  1135    893   
  1136    894   	read_ret = read(fi->fh, buf, size);
  1137    895   
  1138    896   	return(read_ret);
  1139    897   }
  1140    898   
  1141         -static struct fuse_operations appfs_oper = {
          899  +static int appfs_fuse_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
          900  +	off_t lseek_ret;
          901  +	ssize_t write_ret;
          902  +
          903  +	APPFS_DEBUG("Enter (path = %s, ...)", path);
          904  +
          905  +	lseek_ret = lseek(fi->fh, offset, SEEK_SET);
          906  +	if (lseek_ret != offset) {
          907  +		return(-EIO);
          908  +	}
          909  +
          910  +	write_ret = write(fi->fh, buf, size);
          911  +
          912  +	return(write_ret);
          913  +}
          914  +
          915  +static int appfs_fuse_mknod(const char *path, mode_t mode, dev_t device) {
          916  +	char *real_path;
          917  +	int mknod_ret;
          918  +
          919  +	APPFS_DEBUG("Enter (path = %s, ...)", path);
          920  +
          921  +	if ((mode & S_IFCHR) == S_IFCHR) {
          922  +		return(-EPERM);
          923  +	}
          924  +
          925  +	if ((mode & S_IFBLK) == S_IFBLK) {
          926  +		return(-EPERM);
          927  +	}
          928  +
          929  +	real_path = appfs_prepare_to_create(path);
          930  +	if (real_path == NULL) {
          931  +		return(-EIO);
          932  +	}
          933  +
          934  +	appfs_simulate_user_fs_enter();
          935  +
          936  +	mknod_ret = mknod(real_path, mode, device);
          937  +
          938  +	appfs_simulate_user_fs_leave();
          939  +
          940  +	free(real_path);
          941  +
          942  +	if (mknod_ret != 0) {
          943  +		return(errno * -1);
          944  +	}
          945  +
          946  +	return(0);
          947  +}
          948  +
          949  +static int appfs_fuse_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
          950  +	char *real_path;
          951  +	int fd;
          952  +
          953  +	APPFS_DEBUG("Enter (path = %s, ...)", path);
          954  +
          955  +	if ((mode & S_IFCHR) == S_IFCHR) {
          956  +		return(-EPERM);
          957  +	}
          958  +
          959  +	if ((mode & S_IFBLK) == S_IFBLK) {
          960  +		return(-EPERM);
          961  +	}
          962  +
          963  +	real_path = appfs_prepare_to_create(path);
          964  +	if (real_path == NULL) {
          965  +		return(-EIO);
          966  +	}
          967  +
          968  +	appfs_simulate_user_fs_enter();
          969  +
          970  +	fd = creat(real_path, mode);
          971  +
          972  +	appfs_simulate_user_fs_leave();
          973  +
          974  +	free(real_path);
          975  +
          976  +	if (fd < 0) {
          977  +		return(errno * -1);
          978  +	}
          979  +
          980  +	fi->fh = fd;
          981  +
          982  +	return(0);
          983  +}
          984  +
          985  +static int appfs_fuse_truncate(const char *path, off_t size) {
          986  +	char *real_path;
          987  +	int truncate_ret;
          988  +
          989  +	APPFS_DEBUG("Enter (path = %s, ...)", path);
          990  +
          991  +	real_path = appfs_localpath(path);
          992  +	if (real_path == NULL) {
          993  +		return(-EIO);
          994  +	}
          995  +
          996  +	appfs_simulate_user_fs_enter();
          997  +
          998  +	truncate_ret = truncate(real_path, size);
          999  +
         1000  +	appfs_simulate_user_fs_leave();
         1001  +
         1002  +	free(real_path);
         1003  +
         1004  +	if (truncate_ret != 0) {
         1005  +		return(errno * -1);
         1006  +	}
         1007  +
         1008  +	return(0);
         1009  +}
         1010  +
         1011  +static int appfs_fuse_unlink_rmdir(const char *path) {
         1012  +	Tcl_Interp *interp;
         1013  +	int tcl_ret;
         1014  +
         1015  +	APPFS_DEBUG("Enter (path = %s, ...)", path);
         1016  +
         1017  +	interp = appfs_TclInterp();
         1018  +	if (interp == NULL) {
         1019  +		return(-EIO);
         1020  +	}
         1021  +
         1022  +	tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::unlinkpath", path);
         1023  +	if (tcl_ret != TCL_OK) {
         1024  +		APPFS_DEBUG("::appfs::unlinkpath(%s) failed.", path);
         1025  +		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
         1026  +
         1027  +		return(-EIO);
         1028  +	}
         1029  +
         1030  +	return(0);
         1031  +}
         1032  +
         1033  +static int appfs_fuse_mkdir(const char *path, mode_t mode) {
         1034  +	char *real_path;
         1035  +	int mkdir_ret;
         1036  +
         1037  +	APPFS_DEBUG("Enter (path = %s, ...)", path);
         1038  +
         1039  +
         1040  +	real_path = appfs_prepare_to_create(path);
         1041  +	if (real_path == NULL) {
         1042  +		return(-EIO);
         1043  +	}
         1044  +
         1045  +	appfs_simulate_user_fs_enter();
         1046  +
         1047  +	mkdir_ret = mkdir(real_path, mode);
         1048  +
         1049  +	appfs_simulate_user_fs_leave();
         1050  +
         1051  +	free(real_path);
         1052  +
         1053  +	if (mkdir_ret != 0) {
         1054  +		if (errno != EEXIST) {
         1055  +			return(errno * -1);
         1056  +		}
         1057  +	}
         1058  +
         1059  +	return(0);
         1060  +}
         1061  +
         1062  +static int appfs_fuse_chmod(const char *path, mode_t mode) {
         1063  +	Tcl_Interp *interp;
         1064  +	const char *real_path;
         1065  +	int tcl_ret, chmod_ret;
         1066  +
         1067  +	APPFS_DEBUG("Enter (path = %s, ...)", path);
         1068  +
         1069  +	interp = appfs_TclInterp();
         1070  +	if (interp == NULL) {
         1071  +		return(-EIO);
         1072  +	}
         1073  +
         1074  +	tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::openpath", path, "write");
         1075  +	if (tcl_ret != TCL_OK) {
         1076  +		APPFS_DEBUG("::appfs::openpath(%s, %s) failed.", path, "write");
         1077  +		APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
         1078  +
         1079  +		return(-EIO);
         1080  +	}
         1081  +
         1082  +	real_path = Tcl_GetStringResult(interp);
         1083  +	if (real_path == NULL) {
         1084  +		return(-EIO);
         1085  +	}
         1086  +
         1087  +	appfs_simulate_user_fs_enter();
         1088  +
         1089  +	chmod_ret = chmod(real_path, mode);
         1090  +
         1091  +	appfs_simulate_user_fs_leave();
         1092  +
         1093  +	return(chmod_ret);
         1094  +}
         1095  +
         1096  +/*
         1097  + * SQLite3 mode: Execute raw SQL and return success or failure
         1098  + */
         1099  +static int appfs_sqlite3(const char *sql) {
         1100  +	Tcl_Interp *interp;
         1101  +	const char *sql_ret;
         1102  +	int tcl_ret;
         1103  +
         1104  +	interp = appfs_create_TclInterp(NULL);
         1105  +	if (interp == NULL) {
         1106  +		fprintf(stderr, "Unable to create a Tcl interpreter.  Aborting.\n");
         1107  +
         1108  +		return(1);
         1109  +	}
         1110  +
         1111  +	tcl_ret = appfs_Tcl_Eval(interp, 5, "::appfs::db", "eval", sql, "row", "unset -nocomplain row(*); parray row; puts \"----\"");
         1112  +	sql_ret = Tcl_GetStringResult(interp);
         1113  +
         1114  +	if (tcl_ret != TCL_OK) {
         1115  +		fprintf(stderr, "[error] %s\n", sql_ret);
         1116  +
         1117  +		return(1);
         1118  +	}
         1119  +
         1120  +	if (sql_ret && sql_ret[0] != '\0') {
         1121  +		printf("%s\n", sql_ret);
         1122  +	}
         1123  +
         1124  +	return(0);
         1125  +}
         1126  +
         1127  +/*
         1128  + * Tcl mode: Execute raw Tcl and return success or failure
         1129  + */
         1130  +static int appfs_tcl(const char *tcl) {
         1131  +	Tcl_Interp *interp;
         1132  +	const char *tcl_result;
         1133  +	int tcl_ret;
         1134  +
         1135  +	interp = appfs_create_TclInterp(NULL);
         1136  +	if (interp == NULL) {
         1137  +		fprintf(stderr, "Unable to create a Tcl interpreter.  Aborting.\n");
         1138  +
         1139  +		return(1);
         1140  +	}
         1141  +
         1142  +	tcl_ret = Tcl_Eval(interp, tcl);
         1143  +	tcl_result = Tcl_GetStringResult(interp);
         1144  +
         1145  +	if (tcl_ret != TCL_OK) {
         1146  +		fprintf(stderr, "[error] %s\n", tcl_result);
         1147  +
         1148  +		return(1);
         1149  +	}
         1150  +
         1151  +	if (tcl_result && tcl_result[0] != '\0') {
         1152  +		printf("%s\n", tcl_result);
         1153  +	}
         1154  +
         1155  +	return(0);
         1156  +}
         1157  +
         1158  +/*
         1159  + * AppFSd Package for Tcl:
         1160  + *         Bridge for I/O operations to request information about the current
         1161  + *         transaction
         1162  + */
         1163  +static int Appfsd_Init(Tcl_Interp *interp) {
         1164  +#ifdef USE_TCL_STUBS
         1165  +	if (Tcl_InitStubs(interp, TCL_VERSION, 0) == 0L) {
         1166  +		return(TCL_ERROR);
         1167  +	}
         1168  +#endif
         1169  +
         1170  +	Tcl_CreateObjCommand(interp, "appfsd::get_homedir", tcl_appfs_get_homedir, NULL, NULL);
         1171  +	Tcl_CreateObjCommand(interp, "appfsd::simulate_user_fs_enter", tcl_appfs_simulate_user_fs_enter, NULL, NULL);
         1172  +	Tcl_CreateObjCommand(interp, "appfsd::simulate_user_fs_leave", tcl_appfs_simulate_user_fs_leave, NULL, NULL);
         1173  +
         1174  +	Tcl_PkgProvide(interp, "appfsd", "1.0");
         1175  +
         1176  +	return(TCL_OK);
         1177  +}
         1178  +
         1179  +/*
         1180  + * FUSE operations structure
         1181  + */
         1182  +static struct fuse_operations appfs_operations = {
  1142   1183   	.getattr   = appfs_fuse_getattr,
  1143   1184   	.readdir   = appfs_fuse_readdir,
  1144   1185   	.readlink  = appfs_fuse_readlink,
  1145   1186   	.open      = appfs_fuse_open,
  1146   1187   	.release   = appfs_fuse_close,
  1147         -	.read      = appfs_fuse_read
         1188  +	.read      = appfs_fuse_read,
         1189  +	.write     = appfs_fuse_write,
         1190  +	.mknod     = appfs_fuse_mknod,
         1191  +	.create    = appfs_fuse_create,
         1192  +	.truncate  = appfs_fuse_truncate,
         1193  +	.unlink    = appfs_fuse_unlink_rmdir,
         1194  +	.rmdir     = appfs_fuse_unlink_rmdir,
         1195  +	.mkdir     = appfs_fuse_mkdir,
         1196  +	.chmod     = appfs_fuse_chmod,
  1148   1197   };
  1149   1198   
         1199  +/*
         1200  + * FUSE option parsing callback
         1201  + */
         1202  +static int appfs_fuse_opt_cb(void *data, const char *arg, int key, struct fuse_args *outargs) {
         1203  +	static int seen_cachedir = 0;
         1204  +
         1205  +	if (key == FUSE_OPT_KEY_NONOPT && seen_cachedir == 0) {
         1206  +		seen_cachedir = 1;
         1207  +
         1208  +		appfs_cachedir = strdup(arg);
         1209  +
         1210  +		return(0);
         1211  +	}
         1212  +
         1213  +	return(1);
         1214  +}
         1215  +
         1216  +/*
         1217  + * Entry point into this program.
         1218  + */
  1150   1219   int main(int argc, char **argv) {
  1151         -	const char *cachedir = APPFS_CACHEDIR;
  1152         -	char dbfilename[1024];
  1153         -	int pthread_ret, snprintf_ret, sqlite_ret;
         1220  +	Tcl_Interp *test_interp;
         1221  +	char *test_interp_error;
         1222  +	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
         1223  +	int pthread_ret;
  1154   1224   
  1155         -	globalThread.cachedir = cachedir;
  1156         -	globalThread.boottime = time(NULL);
  1157         -	globalThread.platform = "linux-x86_64";
  1158         -	globalThread.options.writable = 1;
         1225  +	/*
         1226  +	 * Skip passed program name
         1227  +	 */
         1228  +	if (argc == 0 || argv == NULL) {
         1229  +		return(1);
         1230  +	}
         1231  +	argc--;
         1232  +	argv++;
         1233  +
         1234  +	/*
         1235  +	 * Set global variables, these should be configuration options.
         1236  +	 */
         1237  +	appfs_cachedir = APPFS_CACHEDIR;
         1238  +
         1239  +	/*
         1240  +	 * Set global variable for "boot time" to set a time on directories
         1241  +	 * that we fake.
         1242  +	 */
         1243  +	appfs_boottime = time(NULL);
         1244  +
         1245  +	/*
         1246  +	 * Register "sha1" and "appfsd" package with libtcl so that any new
         1247  +	 * interpreters created (which are done dynamically by FUSE) can have
         1248  +	 * the appropriate configuration done automatically.
         1249  +	 */
         1250  +	Tcl_StaticPackage(NULL, "sha1", Sha1_Init, NULL);
         1251  +	Tcl_StaticPackage(NULL, "appfsd", Appfsd_Init, NULL);
  1159   1252   
         1253  +	/*
         1254  +	 * Create a thread-specific-data (TSD) key for each thread to refer
         1255  +	 * to its own Tcl interpreter.  Tcl interpreters must be unique per
         1256  +	 * thread and new threads are dynamically created by FUSE.
         1257  +	 */
  1160   1258   	pthread_ret = pthread_key_create(&interpKey, NULL);
  1161   1259   	if (pthread_ret != 0) {
  1162   1260   		fprintf(stderr, "Unable to create TSD key for Tcl.  Aborting.\n");
  1163   1261   
  1164   1262   		return(1);
  1165   1263   	}
  1166   1264   
  1167         -	snprintf_ret = snprintf(dbfilename, sizeof(dbfilename), "%s/%s", cachedir, "cache.db");
  1168         -	if (snprintf_ret >= sizeof(dbfilename)) {
  1169         -		fprintf(stderr, "Unable to set database filename.  Aborting.\n");
         1265  +	/*
         1266  +	 * Manually specify cache directory, without FUSE callback
         1267  +	 * This option only works when not using FUSE, since we
         1268  +	 * do not process it with FUSEs option processing.
         1269  +	 */
         1270  +	if (argc >= 2) {
         1271  +		if (strcmp(argv[0], "--cachedir") == 0) {
         1272  +			appfs_cachedir = strdup(argv[1]);
         1273  +
         1274  +			argc -= 2;
         1275  +			argv += 2;
         1276  +		}
         1277  +	}
         1278  +
         1279  +	/*
         1280  +	 * SQLite3 mode, for running raw SQL against the cache database
         1281  +	 */
         1282  +	if (argc == 2 && strcmp(argv[0], "--sqlite3") == 0) {
         1283  +		return(appfs_sqlite3(argv[1]));
         1284  +	}
  1170   1285   
  1171         -		return(1);
         1286  +	/*
         1287  +	 * Tcl mode, for running raw Tcl in the same environment AppFSd would
         1288  +	 * run code.
         1289  +	 */
         1290  +	if (argc == 2 && strcmp(argv[0], "--tcl") == 0) {
         1291  +		return(appfs_tcl(argv[1]));
  1172   1292   	}
  1173   1293   
  1174         -	sqlite_ret = sqlite3_open(dbfilename, &globalThread.db);
  1175         -	if (sqlite_ret != SQLITE_OK) {
  1176         -		fprintf(stderr, "Unable to open database: %s\n", dbfilename);
         1294  +	/*
         1295  +	 * Create a Tcl interpreter just to verify that things are in working 
         1296  +	 * order before we become a daemon.
         1297  +	 */
         1298  +	test_interp = appfs_create_TclInterp(&test_interp_error);
         1299  +	if (test_interp == NULL) {
         1300  +		if (test_interp_error == NULL) {
         1301  +			test_interp_error = "Unknown error";
         1302  +		}
         1303  +
         1304  +		fprintf(stderr, "Unable to initialize Tcl interpreter for AppFSd:\n");
         1305  +		fprintf(stderr, "%s\n", test_interp_error);
  1177   1306   
  1178   1307   		return(1);
  1179   1308   	}
         1309  +	Tcl_DeleteInterp(test_interp);
         1310  +
         1311  +	/*
         1312  +	 * Add FUSE arguments which we always supply
         1313  +	 */
         1314  +	fuse_opt_parse(&args, NULL, NULL, appfs_fuse_opt_cb);
         1315  +	fuse_opt_add_arg(&args, "-odefault_permissions,fsname=appfs,subtype=appfsd,use_ino,kernel_cache,entry_timeout=60,attr_timeout=3600,intr,big_writes");
         1316  +
         1317  +	if (getuid() == 0) {
         1318  +		fuse_opt_parse(&args, NULL, NULL, NULL);
         1319  +		fuse_opt_add_arg(&args, "-oallow_other");
         1320  +	}
  1180   1321   
  1181         -	return(fuse_main(argc, argv, &appfs_oper, NULL));
         1322  +	/*
         1323  +	 * Enter the FUSE main loop -- this will process any arguments
         1324  +	 * and start servicing requests.
         1325  +	 */
         1326  +	appfs_fuse_started = 1;
         1327  +	return(fuse_main(args.argc, args.argv, &appfs_operations, NULL));
  1182   1328   }
  1183   1329    

Modified appfsd.tcl from [86cb2b92c8] to [85c7175a51].

     1      1   #! /usr/bin/env tclsh
     2      2   
     3      3   package require http 2.7
     4      4   package require sqlite3
     5         -
     6         -if {[catch {
     7         -	package require sha1
     8         -}]} {
     9         -	@@SHA1.TCL@@
    10         -	package require sha1
    11         -}
            5  +package require sha1
            6  +package require appfsd
            7  +package require platform
            8  +package require pki
    12      9   
    13     10   namespace eval ::appfs {
    14     11   	variable cachedir "/tmp/appfs-cache"
    15     12   	variable ttl 3600
    16     13   	variable nttl 60
           14  +
           15  +	# User-replacable function to convert a hostname/hash/method to an URL
           16  +	proc _construct_url {hostname hash method} {
           17  +		return "http://$hostname/appfs/$method/$hash"
           18  +	}
    17     19   
    18     20   	proc _hash_sep {hash {seps 4}} {
    19     21   		for {set idx 0} {$idx < $seps} {incr idx} {
    20     22   			append retval "[string range $hash [expr {$idx * 2}] [expr {($idx * 2) + 1}]]/"
    21     23   		}
    22     24   		append retval "[string range $hash [expr {$idx * 2}] end]"
    23     25   
................................................................................
    34     36   
    35     37   		file mkdir [file dirname $file]
    36     38   
    37     39   		if {[file exists $file]} {
    38     40   			return $file
    39     41   		}
    40     42   
    41         -		set tmpfile "${file}.[expr {rand()}]"
           43  +		set tmpfile "${file}.[expr {rand()}][clock clicks]"
    42     44   
    43     45   		set fd [open $tmpfile "w"]
    44     46   		fconfigure $fd -translation binary
    45     47   
    46     48   		catch {
    47     49   			set token [::http::geturl $url -channel $fd -binary true]
    48     50   		}
................................................................................
    82     84   		if {![regexp {^[0-9a-f]*$} $value]} {
    83     85   			return false
    84     86   		}
    85     87   
    86     88   		return true
    87     89   	}
    88     90   
    89         -	proc _db {args} {
    90         -		return [uplevel 1 [list ::appfs::db {*}$args]]
    91         -	}
    92         -
    93     91   	proc _normalizeOS {os} {
    94     92   		set os [string tolower [string trim $os]]
    95     93   
    96     94   		switch -- $os {
    97     95   			"linux" - "freebsd" - "openbsd" - "netbsd" {
    98     96   				return $os
    99     97   			}
................................................................................
   121    119   			"noarch" - "none" - "any" - "all" {
   122    120   				return "noarch"
   123    121   			}
   124    122   		}
   125    123   
   126    124   		return -code error "Unable to normalize CPU: $cpu"
   127    125   	}
          126  +
          127  +	proc _as_user {code} {
          128  +		::appfsd::simulate_user_fs_enter
          129  +
          130  +		set retcode [catch [list uplevel $code] retstr]
          131  +
          132  +		::appfsd::simulate_user_fs_leave
          133  +
          134  +		return -code $retcode $retstr
          135  +	}
   128    136   
   129    137   	proc init {} {
   130    138   		if {[info exists ::appfs::init_called]} {
   131    139   			return
   132    140   		}
          141  +
          142  +		# Force [parray] to be loaded
          143  +		catch {
          144  +			parray does_not_exist
          145  +		}
   133    146   
   134    147   		set ::appfs::init_called 1
          148  +
          149  +		# Load configuration file
          150  +		set config_file [file join $::appfs::cachedir config]
          151  +		if {[file exists $config_file]} {
          152  +			source $config_file
          153  +		}
   135    154   
   136    155   		if {![info exists ::appfs::db]} {
   137    156   			file mkdir $::appfs::cachedir
   138    157   
   139    158   			sqlite3 ::appfs::db [file join $::appfs::cachedir cache.db]
   140    159   		}
   141    160   
   142    161   		# Create tables
   143         -		_db eval {CREATE TABLE IF NOT EXISTS sites(hostname PRIMARY KEY, lastUpdate, ttl);}
   144         -		_db eval {CREATE TABLE IF NOT EXISTS packages(hostname, sha1, package, version, os, cpuArch, isLatest, haveManifest);}
   145         -		_db eval {CREATE TABLE IF NOT EXISTS files(package_sha1, type, time, source, size, perms, file_sha1, file_name, file_directory);}
          162  +		db eval {CREATE TABLE IF NOT EXISTS sites(hostname PRIMARY KEY, lastUpdate, ttl);}
          163  +		db eval {CREATE TABLE IF NOT EXISTS packages(hostname, sha1, package, version, os, cpuArch, isLatest, haveManifest);}
          164  +		db eval {CREATE TABLE IF NOT EXISTS files(package_sha1, type, time, source, size, perms, file_sha1, file_name, file_directory);}
   146    165   
   147    166   		# Create indexes
   148         -		_db eval {CREATE INDEX IF NOT EXISTS sites_index ON sites (hostname);}
   149         -		_db eval {CREATE INDEX IF NOT EXISTS packages_index ON packages (hostname, package, version, os, cpuArch);}
   150         -		_db eval {CREATE INDEX IF NOT EXISTS files_index ON files (package_sha1, file_name, file_directory);}
          167  +		db eval {CREATE INDEX IF NOT EXISTS sites_index ON sites (hostname);}
          168  +		db eval {CREATE INDEX IF NOT EXISTS packages_index ON packages (hostname, package, version, os, cpuArch);}
          169  +		db eval {CREATE INDEX IF NOT EXISTS files_index ON files (package_sha1, file_name, file_directory);}
   151    170   	}
   152    171   
   153    172   	proc download {hostname hash {method sha1}} {
   154         -		set url "http://$hostname/appfs/$method/$hash"
          173  +		set url [_construct_url $hostname $hash $method]
   155    174   		set file [_cachefile $url $hash]
   156    175   
   157    176   		if {![file exists $file]} {
   158    177   			return -code error "Unable to fetch (file does not exist: $file)"
   159    178   		}
   160    179   
   161    180   		return $file
   162    181   	}
   163    182   
   164    183   	proc getindex {hostname} {
   165    184   		set now [clock seconds]
   166    185   
   167         -		set lastUpdates [_db eval {SELECT lastUpdate, ttl FROM sites WHERE hostname = $hostname LIMIT 1;}]
          186  +		set lastUpdates [db eval {SELECT lastUpdate, ttl FROM sites WHERE hostname = $hostname LIMIT 1;}]
   168    187   		if {[llength $lastUpdates] == 0} {
   169    188   			set lastUpdate 0
   170    189   			set ttl 0
   171    190   		} else {
   172    191   			set lastUpdate [lindex $lastUpdates 0]
   173    192   			set ttl [lindex $lastUpdates 1]
   174    193   		}
................................................................................
   190    209   			}
   191    210   			::http::reset $token
   192    211   			::http::cleanup $token
   193    212   		}
   194    213   
   195    214   		if {![info exists indexhash_data]} {
   196    215   			# Cache this result for 60 seconds
   197         -			_db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate, ttl) VALUES ($hostname, $now, $::appfs::nttl);}
          216  +			db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate, ttl) VALUES ($hostname, $now, $::appfs::nttl);}
   198    217   
   199    218   			return -code error "Unable to fetch $url"
   200    219   		}
   201    220   
   202    221   		set indexhash [lindex [split $indexhash_data ","] 0]
   203    222   
   204    223   		if {![_isHash $indexhash]} {
................................................................................
   240    259   			if {![_isHash $pkgInfo(hash)]} {
   241    260   				continue
   242    261   			}
   243    262   
   244    263   			lappend curr_packages $pkgInfo(hash)
   245    264   
   246    265   			# Do not do any additional work if we already have this package
   247         -			set existing_packages [_db eval {SELECT package FROM packages WHERE hostname = $hostname AND sha1 = $pkgInfo(hash);}]
          266  +			set existing_packages [db eval {SELECT package FROM packages WHERE hostname = $hostname AND sha1 = $pkgInfo(hash);}]
   248    267   			if {[lsearch -exact $existing_packages $pkgInfo(package)] != -1} {
   249    268   				continue
   250    269   			}
   251    270   
   252    271   			if {$pkgInfo(isLatest)} {
   253         -				_db eval {UPDATE packages SET isLatest = 0 WHERE hostname = $hostname AND package = $pkgInfo($package) AND os = $pkgInfo($package) AND cpuArch = $pkgInfo(cpuArch);}
          272  +				db eval {UPDATE packages SET isLatest = 0 WHERE hostname = $hostname AND package = $pkgInfo($package) AND os = $pkgInfo($package) AND cpuArch = $pkgInfo(cpuArch);}
   254    273   			}
   255    274   
   256         -			_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);}
          275  +			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);}
   257    276   		}
   258    277   
   259    278   		# Look for packages that have been deleted
   260         -		set found_packages [_db eval {SELECT sha1 FROM packages WHERE hostname = $hostname;}]
          279  +		set found_packages [db eval {SELECT sha1 FROM packages WHERE hostname = $hostname;}]
   261    280   		foreach package $found_packages {
   262    281   			set found_packages_arr($package) 1
   263    282   		}
   264    283   
   265    284   		foreach package $curr_packages {
   266    285   			unset -nocomplain found_packages_arr($package)
   267    286   		}
   268    287   
   269    288   		foreach package [array names found_packages_arr] {
   270         -			_db eval {DELETE FROM packages WHERE hostname = $hostname AND sha1 = $package;}
          289  +			db eval {DELETE FROM packages WHERE hostname = $hostname AND sha1 = $package;}
   271    290   		}
   272    291   
   273         -		_db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate, ttl) VALUES ($hostname, $now, $::appfs::ttl);}
          292  +		db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate, ttl) VALUES ($hostname, $now, $::appfs::ttl);}
   274    293   
   275    294   		return COMPLETE
   276    295   	}
   277    296   
   278    297   	proc getpkgmanifest {hostname package_sha1} {
   279         -		set haveManifests [_db eval {SELECT haveManifest FROM packages WHERE sha1 = $package_sha1 LIMIT 1;}]
          298  +		set haveManifests [db eval {SELECT haveManifest FROM packages WHERE sha1 = $package_sha1 LIMIT 1;}]
   280    299   		set haveManifest [lindex $haveManifests 0]
   281    300   
   282    301   		if {$haveManifest} {
   283    302   			return COMPLETE
   284    303   		}
   285    304   
   286    305   		if {![_isHash $package_sha1]} {
................................................................................
   288    307   		}
   289    308   
   290    309   		set file [download $hostname $package_sha1]
   291    310   		set fd [open $file]
   292    311   		set pkgdata [read $fd]
   293    312   		close $fd
   294    313   
   295         -		_db transaction {
          314  +		db transaction {
   296    315   			foreach line [split $pkgdata "\n"] {
   297    316   				set line [string trim $line]
   298    317   
   299    318   				if {$line == ""} {
   300    319   					continue
   301    320   				}
   302    321   
................................................................................
   322    341   				}
   323    342   
   324    343   				set fileInfo(name) [join $work ","]
   325    344   				set fileInfo(name) [split [string trim $fileInfo(name) "/"] "/"]
   326    345   				set fileInfo(directory) [join [lrange $fileInfo(name) 0 end-1] "/"]
   327    346   				set fileInfo(name) [lindex $fileInfo(name) end]
   328    347   
   329         -				_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) );}
   330         -				_db eval {UPDATE packages SET haveManifest = 1 WHERE sha1 = $package_sha1;}
          348  +				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) );}
          349  +				db eval {UPDATE packages SET haveManifest = 1 WHERE sha1 = $package_sha1;}
   331    350   			}
   332    351   		}
   333    352   
   334    353   		return COMPLETE
   335    354   	}
          355  +
          356  +	proc _localpath {package hostname file} {
          357  +		set homedir [::appfsd::get_homedir]
          358  +		set dir [file join $homedir .appfs "./${package}@${hostname}" "./${file}"]
          359  +		return $dir
          360  +	}
          361  +
          362  +	proc _whiteoutpath {package hostname file} {
          363  +		set homedir [::appfsd::get_homedir]
          364  +		set dir [file join $homedir .appfs "./${package}@${hostname}" ".APPFS.WHITEOUT" "./${file}.APPFS.WHITEOUT"]
          365  +		return $dir
          366  +	}
          367  +
          368  +	proc _parsepath {path} {
          369  +		set path [string trim $path "/"]
          370  +		set path [split $path "/"]
          371  +		set pathlen [llength $path]
          372  +
          373  +		array set retval [list _children sites _type toplevel]
          374  +
          375  +		if {$pathlen > 0} {
          376  +			set retval(hostname) [lindex $path 0]
          377  +			set retval(_children) packages
          378  +			set retval(_type) sites
          379  +
          380  +			if {$pathlen > 1} {
          381  +				set package [lindex $path 1]
          382  +				if {[string length $package] == "40" && [regexp {^[a-fA-F0-9]*$} $package]} {
          383  +					set retval(package_sha1) $package
          384  +					set retval(_children) files
          385  +					set retval(_type) files
          386  +
          387  +					::appfs::db eval {SELECT package, os, cpuArch, version FROM packages WHERE sha1 = $retval(package_sha1);} pkginfo {}
          388  +					set retval(package) $pkginfo(package)
          389  +					set retval(os) $pkginfo(os)
          390  +					set retval(cpu) $pkginfo(cpuArch)
          391  +					set retval(version) $pkginfo(version)
          392  +
          393  +					if {$pathlen > 2} {
          394  +						set retval(file) [join [lrange $path 2 end] "/"]
          395  +					} else {
          396  +						set retval(file) ""
          397  +					}
          398  +				} else {
          399  +					set retval(package) $package
          400  +					set retval(_children) os-cpu
          401  +					set retval(_type) packages
          402  +
          403  +					if {$pathlen > 2} {
          404  +						set os_cpu [lindex $path 2]
          405  +						set os_cpu [split $os_cpu "-"]
          406  +
          407  +						set retval(os) [lindex $os_cpu 0]
          408  +						set retval(cpu) [lindex $os_cpu 1]
          409  +						set retval(_children) versions
          410  +						set retval(_type) os-cpu
          411  +
          412  +						if {$pathlen > 3} {
          413  +							set retval(version) [lindex $path 3]
          414  +							set retval(_children) files
          415  +							set retval(_type) versions
          416  +
          417  +							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);}]
          418  +							if {$retval(package_sha1) == ""} {
          419  +								set retval(_children) dead
          420  +								return [array get retval]
          421  +							}
          422  +
          423  +							if {$pathlen > 4} {
          424  +								set retval(_type) files
          425  +								set retval(file) [join [lrange $path 4 end] "/"]
          426  +							} else {
          427  +								set retval(_type) files
          428  +								set retval(file) ""
          429  +							}
          430  +						}
          431  +					}
          432  +				}
          433  +			}
          434  +		}
          435  +
          436  +		return [array get retval]
          437  +	}
          438  +
          439  +	proc getchildren {dir} {
          440  +		array set pathinfo [_parsepath $dir]
          441  +
          442  +		switch -- $pathinfo(_children) {
          443  +			"sites" {
          444  +				return [::appfs::db eval {SELECT DISTINCT hostname FROM packages;}]
          445  +			}
          446  +			"packages" {
          447  +				catch {
          448  +					::appfs::getindex $pathinfo(hostname)
          449  +				}
          450  +
          451  +				return [::appfs::db eval {SELECT DISTINCT package FROM packages WHERE hostname = $pathinfo(hostname);}]
          452  +			}
          453  +			"os-cpu" {
          454  +				set retval [::appfs::db eval {SELECT DISTINCT os || "-" || cpuArch FROM packages WHERE hostname = $pathinfo(hostname) AND package = $pathinfo(package);}]
          455  +
          456  +				lappend retval "platform"
          457  +
          458  +				return $retval
          459  +			}
          460  +			"versions" {
          461  +				set retval [::appfs::db eval {
          462  +					SELECT DISTINCT version FROM packages WHERE hostname = $pathinfo(hostname) AND package = $pathinfo(package) AND os = $pathinfo(os) AND cpuArch = $pathinfo(cpu);
          463  +				}]
          464  +
          465  +				lappend retval "latest"
          466  +
          467  +				return $retval
          468  +			}
          469  +			"files" {
          470  +				catch {
          471  +					::appfs::getpkgmanifest $pathinfo(hostname) $pathinfo(package_sha1)
          472  +				}
          473  +
          474  +				set retval [::appfs::db eval {SELECT DISTINCT file_name FROM files WHERE package_sha1 = $pathinfo(package_sha1) AND file_directory = $pathinfo(file);}]
          475  +
          476  +				if {[info exists pathinfo(package)] && [info exists pathinfo(hostname)] && [info exists pathinfo(file)]} {
          477  +					_as_user {
          478  +						set dir [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)]
          479  +						set whiteoutdir [string range [_whiteoutpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)] 0 end-15]
          480  +
          481  +						foreach file [glob -nocomplain -tails -directory $whiteoutdir {{.,}*.APPFS.WHITEOUT}] {
          482  +							set remove [string range $file 0 end-15]
          483  +							set idx [lsearch -exact $retval $remove]
          484  +							if {$idx != -1} {
          485  +								set retval [lreplace $retval $idx $idx]
          486  +							}
          487  +						}
          488  +
          489  +						foreach file [glob -nocomplain -tails -directory $dir -types {d f l p s} {{.,}*}] {
          490  +							if {$file == "." || $file == ".."} {
          491  +								continue
          492  +							}
          493  +
          494  +							if {$file == ".APPFS.WHITEOUT"} {
          495  +								continue
          496  +							}
          497  +
          498  +							if {[lsearch -exact $retval $file] != -1} {
          499  +								continue
          500  +							}
          501  +
          502  +							lappend retval $file
          503  +						}
          504  +					}
          505  +				}
          506  +
          507  +				return $retval
          508  +			}
          509  +		}
          510  +
          511  +		return -code error "Invalid or unacceptable path: $dir"
          512  +	}
          513  +
          514  +	proc getattr {path} {
          515  +		array set pathinfo [_parsepath $path]
          516  +		array set retval [list]
          517  +
          518  +		catch {
          519  +			::appfs::getindex $pathinfo(hostname)
          520  +			::appfs::getpkgmanifest $pathinfo(hostname) $pathinfo(package_sha1)
          521  +		}
          522  +
          523  +		switch -- $pathinfo(_type) {
          524  +			"toplevel" {
          525  +				set retval(type) directory
          526  +				set retval(childcount) [llength [getchildren $path]]
          527  +			}
          528  +			"sites" {
          529  +				set check [::appfs::db onecolumn {SELECT 1 FROM packages WHERE hostname = $pathinfo(hostname);}]
          530  +				if {$check == "1"} {
          531  +					set retval(type) directory
          532  +					set retval(childcount) [llength [getchildren $path]]
          533  +				}
          534  +			}
          535  +			"packages" {
          536  +				set check [::appfs::db onecolumn {SELECT 1 FROM packages WHERE hostname = $pathinfo(hostname) AND package = $pathinfo(package);}]
          537  +				if {$check == "1"} {
          538  +					set retval(type) directory
          539  +					set retval(childcount) [llength [getchildren $path]]
          540  +				}
          541  +			}
          542  +			"os-cpu" {
          543  +				if {$pathinfo(os) == "platform" && $pathinfo(cpu) == ""} {
          544  +					set retval(type) symlink
          545  +					set retval(source) [platform::generic]
          546  +				} else {
          547  +					set check [::appfs::db onecolumn {
          548  +						SELECT 1 FROM packages WHERE hostname = $pathinfo(hostname) AND package = $pathinfo(package) AND os = $pathinfo(os) AND cpuArch = $pathinfo(cpu);
          549  +					}]
          550  +					if {$check == "1"} {
          551  +						set retval(type) directory
          552  +						set retval(childcount) [llength [getchildren $path]]
          553  +					}
          554  +				}
          555  +			}
          556  +			"versions" {
          557  +				if {$pathinfo(version) == "latest"} {
          558  +					set retval(type) symlink
          559  +					set retval(source) "1.0"
          560  +				} else {
          561  +					if {[info exists pathinfo(package_sha1)] && $pathinfo(package_sha1) != ""} {
          562  +						set retval(type) directory
          563  +						set retval(childcount) [llength [getchildren $path]]
          564  +					}
          565  +				}
          566  +			}
          567  +			"files" {
          568  +				set retval(packaged) 1
          569  +
          570  +				set localpath [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)]
          571  +				set whiteoutpath  [_whiteoutpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)]
          572  +
          573  +				set retval(localpath) $localpath
          574  +				set retval(whiteoutpath) $whiteoutpath
          575  +
          576  +				if {[file exists $localpath]} {
          577  +					set retval(is_localfile) 1
          578  +					catch {
          579  +						_as_user {
          580  +							file lstat $localpath localpathinfo
          581  +						}
          582  +						set retval(time) $localpathinfo(mtime)
          583  +
          584  +						switch -- $localpathinfo(type) {
          585  +							"directory" {
          586  +								set retval(type) "directory"
          587  +								set retval(childcount) [llength [getchildren $path]]
          588  +							}
          589  +							"file" {
          590  +								set retval(type) "file"
          591  +								set retval(size) $localpathinfo(size)
          592  +								_as_user {
          593  +									if {[file executable $localpath]} {
          594  +										set retval(perms) "x"
          595  +									} else {
          596  +										set retval(perms) ""
          597  +									}
          598  +								}
          599  +							}
          600  +							"link" {
          601  +								set retval(type) "symlink"
          602  +
          603  +								_as_user {
          604  +									set retval(source) [file readlink $localpath]
          605  +								}
          606  +							}
          607  +							"fifo" {
          608  +								# Capitalized so that the first char is unique
          609  +								set retval(type) "Fifo"
          610  +							}
          611  +							"socket" {
          612  +								# Capitalized so that the first char is unique
          613  +								set retval(type) "Socket"
          614  +							}
          615  +						}
          616  +					} err
          617  +				} else {
          618  +					if {![file exists $whiteoutpath]} {
          619  +						set retval(is_remotefile) 1
          620  +
          621  +						set work [split $pathinfo(file) "/"]
          622  +						set directory [join [lrange $work 0 end-1] "/"]
          623  +						set file [lindex $work end]
          624  +
          625  +						if {$directory == "" && $file == ""} {
          626  +							array set retval [list type directory]
          627  +						}
          628  +
          629  +						::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 {}
          630  +
          631  +						if {$retval(type) == "directory"} {
          632  +							set retval(childcount) [llength [getchildren $path]]
          633  +						}
          634  +
          635  +						unset -nocomplain retval(*)
          636  +					}
          637  +				}
          638  +
          639  +			}
          640  +		}
          641  +
          642  +		if {![info exists retval(type)]} {
          643  +			return -code error "No such file or directory"
          644  +		}
          645  +
          646  +		return [array get retval]
          647  +	}
          648  +
          649  +	proc openpath {path mode} {
          650  +		array set pathinfo [_parsepath $path]
          651  +
          652  +		if {$pathinfo(_type) != "files"} {
          653  +			return -code error "invalid type"
          654  +		}
          655  +
          656  +		set localpath [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)]
          657  +
          658  +		if {$mode == "create"} {
          659  +			return $localpath
          660  +		}
          661  +
          662  +		if {[file exists $localpath]} {
          663  +			return $localpath
          664  +		}
          665  +
          666  +		set work [split $pathinfo(file) "/"]
          667  +		set directory [join [lrange $work 0 end-1] "/"]
          668  +		set file [lindex $work end]
          669  +		::appfs::db eval {SELECT file_sha1, perms FROM files WHERE package_sha1 = $pathinfo(package_sha1) AND file_name = $file AND file_directory = $directory;} pkgpathinfo {}
          670  +
          671  +		if {$pkgpathinfo(file_sha1) == ""} {
          672  +			return -code error "No such file or directory"
          673  +		}
          674  +
          675  +		set localcachefile [download $pathinfo(hostname) $pkgpathinfo(file_sha1)]
          676  +
          677  +		if {$mode == "write"} {
          678  +			_as_user {
          679  +				set tmplocalpath "${localpath}.[expr rand()][clock clicks]"
          680  +
          681  +				set failed 0
          682  +				if {[catch {
          683  +					file mkdir [file dirname $localpath]
          684  +					file copy -force -- $localcachefile $tmplocalpath
          685  +
          686  +					if {$pkgpathinfo(perms) == "x"} {
          687  +						file attributes $tmplocalpath -permissions +x
          688  +					}
          689  +
          690  +					file rename -force -- $tmplocalpath $localpath
          691  +				} err]} {
          692  +					set failed 1
          693  +				}
          694  +				catch {
          695  +					file delete -force -- $tmplocalpath
          696  +				}
          697  +			}
          698  +
          699  +			if {$failed} {
          700  +				return -code error $err
          701  +			}
          702  +
          703  +			return $localpath
          704  +		}
          705  +
          706  +		return $localcachefile
          707  +	}
          708  +
          709  +	proc localpath {path} {
          710  +		array set pathinfo [_parsepath $path]
          711  +
          712  +		if {$pathinfo(_type) != "files"} {
          713  +			return -code error "invalid type"
          714  +		}
          715  +
          716  +		set localpath [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)]
          717  +
          718  +		return $localpath
          719  +	}
          720  +
          721  +	proc exists {path} {
          722  +		catch {
          723  +			set info [getattr $path]
          724  +		} err
          725  +
          726  +		if {![info exists info]} {
          727  +			if {$err == "No such file or directory"} {
          728  +				return [list]
          729  +			} else {
          730  +				return -code error $err
          731  +			}
          732  +		}
          733  +
          734  +		return $info
          735  +	}
          736  +
          737  +	proc prepare_to_create {path {must_not_exist 1}} {
          738  +		if {$must_not_exist} {
          739  +			if {[exists $path] != ""} {
          740  +				return -code error "File already exists"
          741  +			}
          742  +		}
          743  +
          744  +		set filename [localpath $path]
          745  +
          746  +		set dirname [file dirname $filename]
          747  +
          748  +		_as_user {
          749  +			file mkdir $dirname
          750  +		}
          751  +
          752  +		return $filename
          753  +	}
          754  +
          755  +	proc unlinkpath {path} {
          756  +		array set pathattrs [exists $path]
          757  +
          758  +		if {![info exists pathattrs(packaged)]} {
          759  +			return -code error "invalid type"
          760  +		}
          761  +
          762  +		set localpath $pathattrs(localpath)
          763  +
          764  +		if {[info exists pathattrs(is_localfile)]} {
          765  +			if {[file isdirectory $localpath]} {
          766  +				set children [getchildren $path]
          767  +
          768  +				if {[llength $children] != 0} {
          769  +					return -code error "Asked to delete non-empty directory"
          770  +				}
          771  +			}
          772  +
          773  +			_as_user {
          774  +				file delete -force -- $localpath
          775  +			}
          776  +		} elseif {[info exists pathattrs(is_remotefile)]} {
          777  +			if {$pathattrs(type) == "directory"} {
          778  +				set children [getchildren $path]
          779  +
          780  +				if {[llength $children] != 0} {
          781  +					return -code error "Asked to delete non-empty directory"
          782  +				}
          783  +			}
          784  +		} else {
          785  +			return -code error "Unknown if file is remote or local !?"
          786  +		}
          787  +
          788  +		set whiteoutfile $pathattrs(whiteoutpath)
          789  +		set whiteoutdir [file dirname $whiteoutfile]
          790  +
          791  +		_as_user {
          792  +			file mkdir $whiteoutdir
          793  +			close [open $whiteoutfile w]
          794  +		}
          795  +	}
   336    796   }

Added sha1.c version [a59d293e66].

            1  +/*
            2  +	SHA-1 in C
            3  +	By Steve Reid <steve@edmweb.com>
            4  +	100% Public Domain
            5  +
            6  +Test Vectors (from FIPS PUB 180-1)
            7  +"abc"
            8  +  A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
            9  +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
           10  +  84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
           11  +A million repetitions of "a"
           12  +  34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
           13  +*/
           14  +
           15  +/* #define LITTLE_ENDIAN * This should be #define'd if true. */
           16  +/* #define SHA1HANDSOFF * Copies data before messing with it. */
           17  +#include <tcl.h>
           18  +#include <sys/types.h>
           19  +#include <sys/stat.h>
           20  +#include <unistd.h>
           21  +#include <stdint.h>
           22  +#include <string.h>
           23  +#include <fcntl.h>
           24  +#include <stdio.h>
           25  +
           26  +#define SHA1HANDSOFF 1
           27  +
           28  +typedef struct {
           29  +	uint32_t state[5];
           30  +	uint32_t count[2];
           31  +	uint8_t  buffer[64];
           32  +} SHA1_CTX;
           33  +
           34  +#ifndef __BIG_ENDIAN
           35  +#define __BIG_ENDIAN 4321
           36  +#endif
           37  +#ifndef __LITTLE_ENDIAN
           38  +#define __LITTLE_ENDIAN 1234
           39  +#endif
           40  +
           41  +#ifndef __BYTE_ORDER
           42  +#ifdef WORDS_BIGENDIAN
           43  +#define __BYTE_ORDER __BIG_ENDIAN
           44  +#else
           45  +#define __BYTE_ORDER __LITTLE_ENDIAN
           46  +#endif
           47  +#endif
           48  +
           49  +#if __BYTE_ORDER == __BIG_ENDIAN
           50  +#ifndef BIG_ENDIAN
           51  +#define BIG_ENDIAN 1
           52  +#endif
           53  +#undef LITTLE_ENDIAN
           54  +#else
           55  +#ifndef LITTLE_ENDIAN
           56  +#define LITTLE_ENDIAN 1
           57  +#endif
           58  +#undef BIG_ENDIAN
           59  +#endif
           60  +
           61  +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
           62  +
           63  +/* blk0() and blk() perform the initial expand. */
           64  +/* I got the idea of expanding during the round function from SSLeay */
           65  +#ifdef LITTLE_ENDIAN
           66  +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
           67  +    |(rol(block->l[i],8)&0x00FF00FF))
           68  +#else
           69  +#define blk0(i) block->l[i]
           70  +#endif
           71  +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
           72  +    ^block->l[(i+2)&15]^block->l[i&15],1))
           73  +
           74  +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
           75  +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
           76  +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
           77  +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
           78  +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
           79  +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
           80  +
           81  +
           82  +/* Hash a single 512-bit block. This is the core of the algorithm. */
           83  +
           84  +static void SHA1Transform(uint32_t state[5], uint8_t buffer[64]) {
           85  +	uint32_t a, b, c, d, e;
           86  +	typedef union {
           87  +		uint8_t  c[64];
           88  +		uint32_t l[16];
           89  +	} CHAR64LONG16;
           90  +	CHAR64LONG16* block;
           91  +#ifdef SHA1HANDSOFF
           92  +	uint8_t workspace[sizeof(*block)];
           93  +
           94  +	block = (CHAR64LONG16*)workspace;
           95  +	memcpy(block, buffer, sizeof(*block));
           96  +#else
           97  +	block = (CHAR64LONG16*)buffer;
           98  +#endif
           99  +
          100  +	/* Copy context->state[] to working vars */
          101  +	a = state[0];
          102  +	b = state[1];
          103  +	c = state[2];
          104  +	d = state[3];
          105  +	e = state[4];
          106  +
          107  +	/* 4 rounds of 20 operations each. Loop unrolled. */
          108  +	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);
          109  +	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);
          110  +	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);
          111  +	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);
          112  +	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);
          113  +	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);
          114  +	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);
          115  +	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);
          116  +	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);
          117  +	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);
          118  +	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);
          119  +	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);
          120  +	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);
          121  +	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);
          122  +	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);
          123  +	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);
          124  +	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);
          125  +	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);
          126  +	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);
          127  +	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);
          128  +
          129  +	/* Add the working vars back into context.state[] */
          130  +	state[0] += a;
          131  +	state[1] += b;
          132  +	state[2] += c;
          133  +	state[3] += d;
          134  +	state[4] += e;
          135  +
          136  +	/* Wipe variables */
          137  +	a = b = c = d = e = 0;
          138  +}
          139  +
          140  +
          141  +/* SHA1Init - Initialize new context */
          142  +static void SHA1Init(SHA1_CTX* context) {
          143  +	/* SHA1 initialization constants */
          144  +	context->state[0] = 0x67452301;
          145  +	context->state[1] = 0xEFCDAB89;
          146  +	context->state[2] = 0x98BADCFE;
          147  +	context->state[3] = 0x10325476;
          148  +	context->state[4] = 0xC3D2E1F0;
          149  +	context->count[0] = 0;
          150  +	context->count[1] = 0;
          151  +}
          152  +
          153  +
          154  +/* Run your data through this. */
          155  +static void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int len) {
          156  +	unsigned int i, j;
          157  +
          158  +	j = (context->count[0] >> 3) & 63;
          159  +	if ((context->count[0] += len << 3) < (len << 3)) {
          160  +		context->count[1]++;
          161  +	}
          162  +
          163  +	context->count[1] += (len >> 29);
          164  +
          165  +	if ((j + len) > 63) {
          166  +		memcpy(&context->buffer[j], data, (i = 64-j));
          167  +		SHA1Transform(context->state, context->buffer);
          168  +		for ( ; i + 63 < len; i += 64) {
          169  +			SHA1Transform(context->state, &data[i]);
          170  +		}
          171  +		j = 0;
          172  +	} else {
          173  +		i = 0;
          174  +	}
          175  +
          176  +	memcpy(&context->buffer[j], &data[i], len - i);
          177  +}
          178  +
          179  +
          180  +/* Add padding and return the message digest. */
          181  +static void SHA1Final(unsigned char digest[20], SHA1_CTX* context) {
          182  +	unsigned long i;
          183  +	unsigned char finalcount[8];
          184  +
          185  +	for (i = 0; i < 8; i++) {
          186  +		finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3-(i & 3)) * 8) ) & 255);  /* Endian independent */
          187  +	}
          188  +
          189  +	SHA1Update(context, (unsigned char *) "\200", 1);
          190  +
          191  +	while ((context->count[0] & 504) != 448) {
          192  +		SHA1Update(context, (unsigned char *)"\0", 1);
          193  +	}
          194  +
          195  +	SHA1Update(context, finalcount, 8);  /* Should cause a SHA1Transform() */
          196  +	for (i = 0; i < 20; i++) {
          197  +		digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
          198  +	}
          199  +
          200  +	/* Wipe variables */
          201  +	i = 0;
          202  +
          203  +	memset(context->buffer, 0, 64);
          204  +	memset(context->state, 0, 20);
          205  +	memset(context->count, 0, 8);
          206  +	memset(&finalcount, 0, 8);
          207  +#ifdef SHA1HANDSOFF  /* make SHA1Transform overwrite it's own static vars */
          208  +	SHA1Transform(context->state, context->buffer);
          209  +#endif
          210  +}
          211  +
          212  +static Tcl_Obj* c_sha1__sha1_file(char* file) {
          213  +	SHA1_CTX ctx;
          214  +	unsigned char digest[20];
          215  +	unsigned char buf[4096];
          216  +	int fd;
          217  +	ssize_t read_ret;
          218  +	Tcl_Obj *ret;
          219  +
          220  +	fd = open(file, O_RDONLY);
          221  +	if (fd < 0) {
          222  +		return(NULL);
          223  +	}
          224  +
          225  +	SHA1Init(&ctx);
          226  +
          227  +	while (1) {
          228  +		read_ret = read(fd, buf, sizeof(buf));
          229  +
          230  +		if (read_ret == 0) {
          231  +			break;
          232  +		}
          233  +
          234  +		if (read_ret < 0) {
          235  +			close(fd);
          236  +
          237  +			return(NULL);
          238  +		}
          239  +
          240  +		SHA1Update(&ctx, buf, read_ret);
          241  +	}
          242  +
          243  +	close(fd);
          244  +
          245  +	SHA1Final(digest, &ctx);
          246  +
          247  +	ret = Tcl_NewByteArrayObj(digest, sizeof(digest));
          248  +
          249  +	return(ret);
          250  +}
          251  +
          252  +static int tcl_sha1__sha1_file(ClientData dummy, Tcl_Interp *ip, int objc, Tcl_Obj *CONST objv[]) {
          253  +	char* _file;
          254  +	Tcl_Obj* rv;
          255  +	if (objc != 2) {
          256  +		Tcl_WrongNumArgs(ip, 1, objv, "file");
          257  +		return TCL_ERROR;
          258  +	}
          259  +	_file = Tcl_GetString(objv[1]);
          260  +
          261  +	rv = c_sha1__sha1_file(_file);
          262  +	if (rv == NULL) {
          263  +		return(TCL_ERROR);
          264  +	}
          265  +	Tcl_SetObjResult(ip, rv);
          266  +	return TCL_OK;
          267  +}
          268  +
          269  +static Tcl_Obj* c_sha1__sha1_string(Tcl_Obj* str) {
          270  +	SHA1_CTX ctx;
          271  +	unsigned char digest[20];
          272  +	unsigned char *buf;
          273  +	int buf_len;
          274  +	Tcl_Obj *ret;
          275  +
          276  +	SHA1Init(&ctx);
          277  +
          278  +	buf = Tcl_GetByteArrayFromObj(str, &buf_len);
          279  +	if (buf == NULL) {
          280  +		return(NULL);
          281  +	}
          282  +
          283  +	SHA1Update(&ctx, buf, buf_len);
          284  +
          285  +	SHA1Final(digest, &ctx);
          286  +
          287  +	ret = Tcl_NewByteArrayObj(digest, sizeof(digest));
          288  +
          289  +	return(ret);
          290  +}
          291  +
          292  +static int tcl_sha1__sha1_string(ClientData dummy, Tcl_Interp *ip, int objc, Tcl_Obj *CONST objv[]) {
          293  +	Tcl_Obj* _str;
          294  +	Tcl_Obj* rv;
          295  +	if (objc != 2) {
          296  +		Tcl_WrongNumArgs(ip, 1, objv, "str");
          297  +		return TCL_ERROR;
          298  +	}
          299  +	_str = objv[1];
          300  +
          301  +	rv = c_sha1__sha1_string(_str);
          302  +	if (rv == NULL) {
          303  +		return(TCL_ERROR);
          304  +	}
          305  +	Tcl_SetObjResult(ip, rv);
          306  +	return TCL_OK;
          307  +}
          308  +
          309  +int Sha1_Init(Tcl_Interp *interp) {
          310  +#ifdef USE_TCL_STUBS
          311  +	if (Tcl_InitStubs(interp, TCL_VERSION, 0) == 0L) {
          312  +		return TCL_ERROR;
          313  +	}
          314  +#endif
          315  +	Tcl_CreateObjCommand(interp, "sha1::_sha1_file", tcl_sha1__sha1_file, NULL, NULL);
          316  +	Tcl_CreateObjCommand(interp, "sha1::_sha1_string", tcl_sha1__sha1_string, NULL, NULL);
          317  +	Tcl_Eval(interp,
          318  +#include "sha1.tcl.h"
          319  +	);
          320  +	Tcl_PkgProvide(interp, "sha1", "1.0");
          321  +	return(TCL_OK);
          322  +}

Modified sha1.tcl from [a8b3b2afbe] to [235aac1913].

     1         -# sha1.tcl - 
            1  +#! /usr/bin/env tclsh
            2  +
            3  +proc sha1::sha1 args {
            4  +	set outputmode "hex"
            5  +
            6  +	if {[lindex $args 0] == "-hex"} {
            7  +		set outputmode "hex"
            8  +
            9  +		set args [lrange $args 1 end]
           10  +	} elseif {[lindex $args 0] == "-bin"} {
           11  +		set outputmode "binary"
           12  +
           13  +		set args [lrange $args 1 end]
           14  +	}
     2     15   
     3         -# @@ Meta Begin
     4         -# Package sha1 2.0.3
     5         -# Meta platform           tcl
     6         -# Meta rsk::build::date   2011-03-30
     7         -# Meta description        Part of the Tclib sha1 module
     8         -# Meta require            {Tcl 8.2}
     9         -# @@ Meta End
           16  +	if {[llength $args] == 2} {
           17  +		set mode [lindex $args 0]
           18  +	} elseif {[llength $args] == 1} {
           19  +		set mode "-string"
           20  +	} else {
           21  +		return -code error "wrong # args: sha1::sha1 ?-bin|-hex? ?-channel channel|-file file|string?"
           22  +	}
    10     23   
    11         -#
    12         -# Copyright (C) 2001 Don Libes <libes@nist.gov>
    13         -# Copyright (C) 2003 Pat Thoyts <patthoyts@users.sourceforge.net>
    14         -#
    15         -# SHA1 defined by FIPS 180-1, "The SHA1 Message-Digest Algorithm"
    16         -# HMAC defined by RFC 2104, "Keyed-Hashing for Message Authentication"
    17         -#
    18         -# This is an implementation of SHA1 based upon the example code given in
    19         -# FIPS 180-1 and upon the tcllib MD4 implementation and taking some ideas
    20         -# and methods from the earlier tcllib sha1 version by Don Libes.
    21         -#
    22         -# This implementation permits incremental updating of the hash and 
    23         -# provides support for external compiled implementations either using
    24         -# critcl (sha1c) or Trf.
    25         -#
    26         -# ref: http://www.itl.nist.gov/fipspubs/fip180-1.htm
    27         -#
    28         -# -------------------------------------------------------------------------
    29         -# See the file "license.terms" for information on usage and redistribution
    30         -# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
    31         -# -------------------------------------------------------------------------
    32         -#
    33         -# $Id: sha1.tcl,v 1.22 2009/05/07 00:35:10 patthoyts Exp $
           24  +	switch -- $mode {
           25  +		"-channel" {
           26  +			return -code error "Not implemented"
           27  +		}
           28  +		"-file" {
           29  +			set output [_sha1_file [lindex $args end]]
           30  +		}
           31  +		"-string" {
           32  +			set output [_sha1_string [lindex $args end]]
           33  +		}
           34  +		default {
           35  +			return -code error "invalid mode: $mode, must be one of -channel or -file (or a plain string)"
           36  +		}
           37  +	}
    34     38   
    35         -# @mdgen EXCLUDE: sha1c.tcl
    36         -
    37         -package require Tcl 8.2;                # tcl minimum version
    38         -
    39         -namespace eval ::sha1 {
    40         -    variable  version 2.0.3
    41         -    namespace export sha1 hmac SHA1Init SHA1Update SHA1Final
    42         -    variable uid
    43         -    if {![info exists uid]} {
    44         -        set uid 0
    45         -    }
    46         -}
           39  +	if {$outputmode == "hex"} {
           40  +		binary scan $output H* output
           41  +	}
    47     42   
    48         -proc ::sha1::SHA1Init {} {
    49         -    variable uid
    50         -    set token [namespace current]::[incr uid]
    51         -    upvar #0 $token state
    52         -
    53         -    # FIPS 180-1: 7 - Initialize the hash state
    54         -    array set state \
    55         -        [list \
    56         -             A [expr {int(0x67452301)}] \
    57         -             B [expr {int(0xEFCDAB89)}] \
    58         -             C [expr {int(0x98BADCFE)}] \
    59         -             D [expr {int(0x10325476)}] \
    60         -             E [expr {int(0xC3D2E1F0)}] \
    61         -             n 0 i "" ]
    62         -    return $token
    63         -}
    64         -
    65         -# SHA1Update --
    66         -#
    67         -#   This is called to add more data into the hash. You may call this
    68         -#   as many times as you require. Note that passing in "ABC" is equivalent
    69         -#   to passing these letters in as separate calls -- hence this proc 
    70         -#   permits hashing of chunked data
    71         -#
    72         -#   If we have a C-based implementation available, then we will use
    73         -#   it here in preference to the pure-Tcl implementation.
    74         -#
    75         -proc ::sha1::SHA1Update {token data} {
    76         -    upvar #0 $token state
    77         -
    78         -    # Update the state values
    79         -    incr state(n) [string length $data]
    80         -    append state(i) $data
    81         -
    82         -    # Calculate the hash for any complete blocks
    83         -    set len [string length $state(i)]
    84         -    for {set n 0} {($n + 64) <= $len} {} {
    85         -        SHA1Transform $token [string range $state(i) $n [incr n 64]]
    86         -    }
    87         -
    88         -    # Adjust the state for the blocks completed.
    89         -    set state(i) [string range $state(i) $n end]
    90         -    return
    91         -}
    92         -
    93         -# SHA1Final --
    94         -#
    95         -#    This procedure is used to close the current hash and returns the
    96         -#    hash data. Once this procedure has been called the hash context
    97         -#    is freed and cannot be used again.
    98         -#
    99         -#    Note that the output is 160 bits represented as binary data.
   100         -#
   101         -proc ::sha1::SHA1Final {token} {
   102         -    upvar #0 $token state
   103         -
   104         -    # Padding
   105         -    #
   106         -    set len [string length $state(i)]
   107         -    set pad [expr {56 - ($len % 64)}]
   108         -    if {$len % 64 > 56} {
   109         -        incr pad 64
   110         -    }
   111         -    if {$pad == 0} {
   112         -        incr pad 64
   113         -    }
   114         -    append state(i) [binary format a$pad \x80]
   115         -
   116         -    # Append length in bits as big-endian wide int.
   117         -    set dlen [expr {8 * $state(n)}]
   118         -    append state(i) [binary format II 0 $dlen]
   119         -
   120         -    # Calculate the hash for the remaining block.
   121         -    set len [string length $state(i)]
   122         -    for {set n 0} {($n + 64) <= $len} {} {
   123         -        SHA1Transform $token [string range $state(i) $n [incr n 64]]
   124         -    }
   125         -
   126         -    # Output
   127         -    set r [bytes $state(A)][bytes $state(B)][bytes $state(C)][bytes $state(D)][bytes $state(E)]
   128         -    unset state
   129         -    return $r
   130         -}
   131         -
   132         -# -------------------------------------------------------------------------
   133         -# HMAC Hashed Message Authentication (RFC 2104)
   134         -#
   135         -# hmac = H(K xor opad, H(K xor ipad, text))
   136         -#
   137         -
   138         -# HMACInit --
   139         -#
   140         -#    This is equivalent to the SHA1Init procedure except that a key is
   141         -#    added into the algorithm
   142         -#
   143         -proc ::sha1::HMACInit {K} {
   144         -
   145         -    # Key K is adjusted to be 64 bytes long. If K is larger, then use
   146         -    # the SHA1 digest of K and pad this instead.
   147         -    set len [string length $K]
   148         -    if {$len > 64} {
   149         -        set tok [SHA1Init]
   150         -        SHA1Update $tok $K
   151         -        set K [SHA1Final $tok]
   152         -        set len [string length $K]
   153         -    }
   154         -    set pad [expr {64 - $len}]
   155         -    append K [string repeat \0 $pad]
   156         -
   157         -    # Cacluate the padding buffers.
   158         -    set Ki {}
   159         -    set Ko {}
   160         -    binary scan $K i16 Ks
   161         -    foreach k $Ks {
   162         -        append Ki [binary format i [expr {$k ^ 0x36363636}]]
   163         -        append Ko [binary format i [expr {$k ^ 0x5c5c5c5c}]]
   164         -    }
   165         -
   166         -    set tok [SHA1Init]
   167         -    SHA1Update $tok $Ki;                 # initialize with the inner pad
   168         -    
   169         -    # preserve the Ko value for the final stage.
   170         -    # FRINK: nocheck
   171         -    set [subst $tok](Ko) $Ko
   172         -
   173         -    return $tok
   174         -}
   175         -
   176         -# HMACUpdate --
   177         -#
   178         -#    Identical to calling SHA1Update
   179         -#
   180         -proc ::sha1::HMACUpdate {token data} {
   181         -    SHA1Update $token $data
   182         -    return
   183         -}
   184         -
   185         -# HMACFinal --
   186         -#
   187         -#    This is equivalent to the SHA1Final procedure. The hash context is
   188         -#    closed and the binary representation of the hash result is returned.
   189         -#
   190         -proc ::sha1::HMACFinal {token} {
   191         -    upvar #0 $token state
   192         -
   193         -    set tok [SHA1Init];                 # init the outer hashing function
   194         -    SHA1Update $tok $state(Ko);         # prepare with the outer pad.
   195         -    SHA1Update $tok [SHA1Final $token]; # hash the inner result
   196         -    return [SHA1Final $tok]
   197         -}
   198         -
   199         -# -------------------------------------------------------------------------
   200         -# Description:
   201         -#  This is the core SHA1 algorithm. It is a lot like the MD4 algorithm but
   202         -#  includes an extra round and a set of constant modifiers throughout.
   203         -#
   204         -set ::sha1::SHA1Transform_body {
   205         -    upvar #0 $token state
   206         -
   207         -    # FIPS 180-1: 7a: Process Message in 16-Word Blocks
   208         -    binary scan $msg I* blocks
   209         -    set blockLen [llength $blocks]
   210         -    for {set i 0} {$i < $blockLen} {incr i 16} {
   211         -        set W [lrange $blocks $i [expr {$i+15}]]
   212         -        
   213         -        # FIPS 180-1: 7b: Expand the input into 80 words
   214         -        # For t = 16 to 79 
   215         -        #   let Wt = (Wt-3 ^ Wt-8 ^ Wt-14 ^ Wt-16) <<< 1
   216         -        set t3  12
   217         -        set t8   7
   218         -        set t14  1
   219         -        set t16 -1
   220         -        for {set t 16} {$t < 80} {incr t} {
   221         -            set x [expr {[lindex $W [incr t3]] ^ [lindex $W [incr t8]] ^ \
   222         -                             [lindex $W [incr t14]] ^ [lindex $W [incr t16]]}]
   223         -            lappend W [expr {int(($x << 1) | (($x >> 31) & 1))}]
   224         -        }
   225         -        
   226         -        # FIPS 180-1: 7c: Copy hash state.
   227         -        set A $state(A)
   228         -        set B $state(B)
   229         -        set C $state(C)
   230         -        set D $state(D)
   231         -        set E $state(E)
   232         -
   233         -        # FIPS 180-1: 7d: Do permutation rounds
   234         -        # For t = 0 to 79 do
   235         -        #   TEMP = (A<<<5) + ft(B,C,D) + E + Wt + Kt;
   236         -        #   E = D; D = C; C = S30(B); B = A; A = TEMP;
   237         -
   238         -        # Round 1: ft(B,C,D) = (B & C) | (~B & D) ( 0 <= t <= 19)
   239         -        for {set t 0} {$t < 20} {incr t} {
   240         -            set TEMP [F1 $A $B $C $D $E [lindex $W $t]]
   241         -            set E $D
   242         -            set D $C
   243         -            set C [rotl32 $B 30]
   244         -            set B $A
   245         -            set A $TEMP
   246         -        }
   247         -
   248         -        # Round 2: ft(B,C,D) = (B ^ C ^ D) ( 20 <= t <= 39)
   249         -        for {} {$t < 40} {incr t} {
   250         -            set TEMP [F2 $A $B $C $D $E [lindex $W $t]]
   251         -            set E $D
   252         -            set D $C
   253         -            set C [rotl32 $B 30]
   254         -            set B $A
   255         -            set A $TEMP
   256         -        }
   257         -
   258         -        # Round 3: ft(B,C,D) = ((B & C) | (B & D) | (C & D)) ( 40 <= t <= 59)
   259         -        for {} {$t < 60} {incr t} {
   260         -            set TEMP [F3 $A $B $C $D $E [lindex $W $t]]
   261         -            set E $D
   262         -            set D $C
   263         -            set C [rotl32 $B 30]
   264         -            set B $A
   265         -            set A $TEMP
   266         -         }
   267         -
   268         -        # Round 4: ft(B,C,D) = (B ^ C ^ D) ( 60 <= t <= 79)
   269         -        for {} {$t < 80} {incr t} {
   270         -            set TEMP [F4 $A $B $C $D $E [lindex $W $t]]
   271         -            set E $D
   272         -            set D $C
   273         -            set C [rotl32 $B 30]
   274         -            set B $A
   275         -            set A $TEMP
   276         -        }
   277         -
   278         -        # Then perform the following additions. (That is, increment each
   279         -        # of the four registers by the value it had before this block
   280         -        # was started.)
   281         -        incr state(A) $A
   282         -        incr state(B) $B
   283         -        incr state(C) $C
   284         -        incr state(D) $D
   285         -        incr state(E) $E
   286         -    }
   287         -
   288         -    return
   289         -}
   290         -
   291         -proc ::sha1::F1 {A B C D E W} {
   292         -    expr {(((($A << 5) & 0xffffffff) | (($A >> 27) & 0x1f)) \
   293         -               + ($D ^ ($B & ($C ^ $D))) + $E + $W + 0x5a827999) & 0xffffffff}
   294         -}
   295         -
   296         -proc ::sha1::F2 {A B C D E W} {
   297         -    expr {(((($A << 5) & 0xffffffff) | (($A >> 27) & 0x1f)) \
   298         -               + ($B ^ $C ^ $D) + $E + $W + 0x6ed9eba1) & 0xffffffff}
   299         -}
   300         -
   301         -proc ::sha1::F3 {A B C D E W} {
   302         -    expr {(((($A << 5) & 0xffffffff)| (($A >> 27) & 0x1f)) \
   303         -               + (($B & $C) | ($D & ($B | $C))) + $E + $W + 0x8f1bbcdc) & 0xffffffff}
   304         -}
   305         -
   306         -proc ::sha1::F4 {A B C D E W} {
   307         -    expr {(((($A << 5) & 0xffffffff)| (($A >> 27) & 0x1f)) \
   308         -               + ($B ^ $C ^ $D) + $E + $W + 0xca62c1d6) & 0xffffffff}
   309         -}
   310         -
   311         -proc ::sha1::rotl32 {v n} {
   312         -    return [expr {((($v << $n) \
   313         -                        | (($v >> (32 - $n)) \
   314         -                               & (0x7FFFFFFF >> (31 - $n))))) \
   315         -                      & 0xFFFFFFFF}]
   316         -}
   317         -
   318         -
   319         -# -------------------------------------------------------------------------
   320         -# 
   321         -# In order to get this code to go as fast as possible while leaving
   322         -# the main code readable we can substitute the above function bodies
   323         -# into the transform procedure. This inlines the code for us an avoids
   324         -# a procedure call overhead within the loops.
   325         -#
   326         -# We can do some minor tweaking to improve speed on Tcl < 8.5 where we
   327         -# know our arithmetic is limited to 64 bits. On > 8.5 we may have 
   328         -# unconstrained integer arithmetic and must avoid letting it run away.
   329         -#
   330         -
   331         -regsub -all -line \
   332         -    {\[F1 \$A \$B \$C \$D \$E (\[.*?\])\]} \
   333         -    $::sha1::SHA1Transform_body \
   334         -    {[expr {(rotl32($A,5) + ($D ^ ($B \& ($C ^ $D))) + $E + \1 + 0x5a827999) \& 0xffffffff}]} \
   335         -    ::sha1::SHA1Transform_body_tmp
   336         -
   337         -regsub -all -line \
   338         -    {\[F2 \$A \$B \$C \$D \$E (\[.*?\])\]} \
   339         -    $::sha1::SHA1Transform_body_tmp \
   340         -    {[expr {(rotl32($A,5) + ($B ^ $C ^ $D) + $E + \1 + 0x6ed9eba1) \& 0xffffffff}]} \
   341         -    ::sha1::SHA1Transform_body_tmp
   342         -
   343         -regsub -all -line \
   344         -    {\[F3 \$A \$B \$C \$D \$E (\[.*?\])\]} \
   345         -    $::sha1::SHA1Transform_body_tmp \
   346         -    {[expr {(rotl32($A,5) + (($B \& $C) | ($D \& ($B | $C))) + $E + \1 + 0x8f1bbcdc) \& 0xffffffff}]} \
   347         -    ::sha1::SHA1Transform_body_tmp
   348         -
   349         -regsub -all -line \
   350         -    {\[F4 \$A \$B \$C \$D \$E (\[.*?\])\]} \
   351         -    $::sha1::SHA1Transform_body_tmp \
   352         -    {[expr {(rotl32($A,5) + ($B ^ $C ^ $D) + $E + \1 + 0xca62c1d6) \& 0xffffffff}]} \
   353         -    ::sha1::SHA1Transform_body_tmp
   354         -
   355         -regsub -all -line \
   356         -    {rotl32\(\$A,5\)} \
   357         -    $::sha1::SHA1Transform_body_tmp \
   358         -    {((($A << 5) \& 0xffffffff) | (($A >> 27) \& 0x1f))} \
   359         -    ::sha1::SHA1Transform_body_tmp
   360         -
   361         -regsub -all -line \
   362         -    {\[rotl32 \$B 30\]} \
   363         -    $::sha1::SHA1Transform_body_tmp \
   364         -    {[expr {int(($B << 30) | (($B >> 2) \& 0x3fffffff))}]} \
   365         -    ::sha1::SHA1Transform_body_tmp
   366         -#
   367         -# Version 2 avoids a few truncations to 32 bits in non-essential places.
   368         -#
   369         -regsub -all -line \
   370         -    {\[F1 \$A \$B \$C \$D \$E (\[.*?\])\]} \
   371         -    $::sha1::SHA1Transform_body \
   372         -    {[expr {rotl32($A,5) + ($D ^ ($B \& ($C ^ $D))) + $E + \1 + 0x5a827999}]} \
   373         -    ::sha1::SHA1Transform_body_tmp2
   374         -
   375         -regsub -all -line \
   376         -    {\[F2 \$A \$B \$C \$D \$E (\[.*?\])\]} \
   377         -    $::sha1::SHA1Transform_body_tmp2 \
   378         -    {[expr {rotl32($A,5) + ($B ^ $C ^ $D) + $E + \1 + 0x6ed9eba1}]} \
   379         -    ::sha1::SHA1Transform_body_tmp2
   380         -
   381         -regsub -all -line \
   382         -    {\[F3 \$A \$B \$C \$D \$E (\[.*?\])\]} \
   383         -    $::sha1::SHA1Transform_body_tmp2 \
   384         -    {[expr {rotl32($A,5) + (($B \& $C) | ($D \& ($B | $C))) + $E + \1 + 0x8f1bbcdc}]} \
   385         -    ::sha1::SHA1Transform_body_tmp2
   386         -
   387         -regsub -all -line \
   388         -    {\[F4 \$A \$B \$C \$D \$E (\[.*?\])\]} \
   389         -    $::sha1::SHA1Transform_body_tmp2 \
   390         -    {[expr {rotl32($A,5) + ($B ^ $C ^ $D) + $E + \1 + 0xca62c1d6}]} \
   391         -    ::sha1::SHA1Transform_body_tmp2
   392         -
   393         -regsub -all -line \
   394         -    {rotl32\(\$A,5\)} \
   395         -    $::sha1::SHA1Transform_body_tmp2 \
   396         -    {(($A << 5) | (($A >> 27) \& 0x1f))} \
   397         -    ::sha1::SHA1Transform_body_tmp2
   398         -
   399         -regsub -all -line \
   400         -    {\[rotl32 \$B 30\]} \
   401         -    $::sha1::SHA1Transform_body_tmp2 \
   402         -    {[expr {($B << 30) | (($B >> 2) \& 0x3fffffff)}]} \
   403         -    ::sha1::SHA1Transform_body_tmp2
   404         -
   405         -if {[package vsatisfies [package provide Tcl] 8.5]} {
   406         -    proc ::sha1::SHA1Transform {token msg} $::sha1::SHA1Transform_body_tmp
   407         -} else {
   408         -    proc ::sha1::SHA1Transform {token msg} $::sha1::SHA1Transform_body_tmp2
   409         -}
   410         -
   411         -unset ::sha1::SHA1Transform_body
   412         -unset ::sha1::SHA1Transform_body_tmp
   413         -unset ::sha1::SHA1Transform_body_tmp2
   414         -
   415         -# -------------------------------------------------------------------------
   416         -
   417         -proc ::sha1::byte {n v} {expr {((0xFF << (8 * $n)) & $v) >> (8 * $n)}}
   418         -proc ::sha1::bytes {v} { 
   419         -    #format %c%c%c%c [byte 0 $v] [byte 1 $v] [byte 2 $v] [byte 3 $v]
   420         -    format %c%c%c%c \
   421         -        [expr {((0xFF000000 & $v) >> 24) & 0xFF}] \
   422         -        [expr {(0xFF0000 & $v) >> 16}] \
   423         -        [expr {(0xFF00 & $v) >> 8}] \
   424         -        [expr {0xFF & $v}]
   425         -}
   426         -
   427         -# -------------------------------------------------------------------------
   428         -
   429         -proc ::sha1::Hex {data} {
   430         -    binary scan $data H* result
   431         -    return $result
   432         -}
   433         -
   434         -# -------------------------------------------------------------------------
   435         -
   436         -# Description:
   437         -#  Pop the nth element off a list. Used in options processing.
   438         -#
   439         -proc ::sha1::Pop {varname {nth 0}} {
   440         -    upvar $varname args
   441         -    set r [lindex $args $nth]
   442         -    set args [lreplace $args $nth $nth]
   443         -    return $r
   444         -}
   445         -
   446         -# -------------------------------------------------------------------------
   447         -
   448         -# fileevent handler for chunked file hashing.
   449         -#
   450         -proc ::sha1::Chunk {token channel {chunksize 4096}} {
   451         -    upvar #0 $token state
   452         -    
   453         -    if {[eof $channel]} {
   454         -        fileevent $channel readable {}
   455         -        set state(reading) 0
   456         -    }
   457         -        
   458         -    SHA1Update $token [read $channel $chunksize]
   459         -}
   460         -
   461         -# -------------------------------------------------------------------------
   462         -
   463         -proc ::sha1::sha1 {args} {
   464         -    array set opts {-hex 0 -filename {} -channel {} -chunksize 4096}
   465         -    if {[llength $args] == 1} {
   466         -        set opts(-hex) 1
   467         -    } else {
   468         -        while {[string match -* [set option [lindex $args 0]]]} {
   469         -            switch -glob -- $option {
   470         -                -hex       { set opts(-hex) 1 }
   471         -                -bin       { set opts(-hex) 0 }
   472         -                -file*     { set opts(-filename) [Pop args 1] }
   473         -                -channel   { set opts(-channel) [Pop args 1] }
   474         -                -chunksize { set opts(-chunksize) [Pop args 1] }
   475         -                default {
   476         -                    if {[llength $args] == 1} { break }
   477         -                    if {[string compare $option "--"] == 0} { Pop args; break }
   478         -                    set err [join [lsort [concat -bin [array names opts]]] ", "]
   479         -                    return -code error "bad option $option:\
   480         -                    must be one of $err"
   481         -                }
   482         -            }
   483         -            Pop args
   484         -        }
   485         -    }
   486         -
   487         -    if {$opts(-filename) != {}} {
   488         -        set opts(-channel) [open $opts(-filename) r]
   489         -        fconfigure $opts(-channel) -translation binary
   490         -    }
   491         -
   492         -    if {$opts(-channel) == {}} {
   493         -
   494         -        if {[llength $args] != 1} {
   495         -            return -code error "wrong # args:\
   496         -                should be \"sha1 ?-hex? -filename file | string\""
   497         -        }
   498         -        set tok [SHA1Init]
   499         -        SHA1Update $tok [lindex $args 0]
   500         -        set r [SHA1Final $tok]
   501         -
   502         -    } else {
   503         -
   504         -        set tok [SHA1Init]
   505         -        # FRINK: nocheck
   506         -        set [subst $tok](reading) 1
   507         -        fileevent $opts(-channel) readable \
   508         -            [list [namespace origin Chunk] \
   509         -                 $tok $opts(-channel) $opts(-chunksize)]
   510         -        # FRINK: nocheck
   511         -        vwait [subst $tok](reading)
   512         -        set r [SHA1Final $tok]
   513         -
   514         -        # If we opened the channel - we should close it too.
   515         -        if {$opts(-filename) != {}} {
   516         -            close $opts(-channel)
   517         -        }
   518         -    }
   519         -    
   520         -    if {$opts(-hex)} {
   521         -        set r [Hex $r]
   522         -    }
   523         -    return $r
   524         -}
   525         -
   526         -# -------------------------------------------------------------------------
   527         -
   528         -proc ::sha1::hmac {args} {
   529         -    array set opts {-hex 1 -filename {} -channel {} -chunksize 4096}
   530         -    if {[llength $args] != 2} {
   531         -        while {[string match -* [set option [lindex $args 0]]]} {
   532         -            switch -glob -- $option {
   533         -                -key       { set opts(-key) [Pop args 1] }
   534         -                -hex       { set opts(-hex) 1 }
   535         -                -bin       { set opts(-hex) 0 }
   536         -                -file*     { set opts(-filename) [Pop args 1] }
   537         -                -channel   { set opts(-channel) [Pop args 1] }
   538         -                -chunksize { set opts(-chunksize) [Pop args 1] }
   539         -                default {
   540         -                    if {[llength $args] == 1} { break }
   541         -                    if {[string compare $option "--"] == 0} { Pop args; break }
   542         -                    set err [join [lsort [array names opts]] ", "]
   543         -                    return -code error "bad option $option:\
   544         -                    must be one of $err"
   545         -                }
   546         -            }
   547         -            Pop args
   548         -        }
   549         -    }
   550         -
   551         -    if {[llength $args] == 2} {
   552         -        set opts(-key) [Pop args]
   553         -    }
   554         -
   555         -    if {![info exists opts(-key)]} {
   556         -        return -code error "wrong # args:\
   557         -            should be \"hmac ?-hex? -key key -filename file | string\""
   558         -    }
   559         -
   560         -    if {$opts(-filename) != {}} {
   561         -        set opts(-channel) [open $opts(-filename) r]
   562         -        fconfigure $opts(-channel) -translation binary
   563         -    }
   564         -
   565         -    if {$opts(-channel) == {}} {
   566         -
   567         -        if {[llength $args] != 1} {
   568         -            return -code error "wrong # args:\
   569         -                should be \"hmac ?-hex? -key key -filename file | string\""
   570         -        }
   571         -        set tok [HMACInit $opts(-key)]
   572         -        HMACUpdate $tok [lindex $args 0]
   573         -        set r [HMACFinal $tok]
   574         -
   575         -    } else {
   576         -
   577         -        set tok [HMACInit $opts(-key)]
   578         -        # FRINK: nocheck
   579         -        set [subst $tok](reading) 1
   580         -        fileevent $opts(-channel) readable \
   581         -            [list [namespace origin Chunk] \
   582         -                 $tok $opts(-channel) $opts(-chunksize)]
   583         -        # FRINK: nocheck
   584         -        vwait [subst $tok](reading)
   585         -        set r [HMACFinal $tok]
   586         -
   587         -        # If we opened the channel - we should close it too.
   588         -        if {$opts(-filename) != {}} {
   589         -            close $opts(-channel)
   590         -        }
   591         -    }
   592         -    
   593         -    if {$opts(-hex)} {
   594         -        set r [Hex $r]
   595         -    }
   596         -    return $r
   597         -}
   598         -
   599         -# -------------------------------------------------------------------------
   600         -
   601         -package provide sha1 $::sha1::version
   602         -
   603         -# -------------------------------------------------------------------------
   604         -# Local Variables:
   605         -#   mode: tcl
   606         -#   indent-tabs-mode: nil
   607         -# End:
           43  +	return $output
           44  +}