Check-in [83dcb7cd52]
Overview
Comment:Added comments
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | tcl-ops
Files: files | file ages | folders
SHA1: 83dcb7cd523520a4bf51a48d3e3484e81f96577a
User & Date: rkeene on 2014-11-07 06:14:42
Other Links: branch diff | manifest | tags
Context
2014-11-07
06:47
Added start of supplying default options check-in: a7e9dac6ce user: rkeene tags: tcl-ops
06:14
Added comments check-in: 83dcb7cd52 user: rkeene tags: tcl-ops
05:42
Added more functionality to "appfs-cache" control system check-in: 82982300d8 user: rkeene tags: tcl-ops
Changes

Modified appfsd.c from [e20407f29f] to [616c3164a5].

9
10
11
12
13
14
15
16
17
18
19
20
21
22

23
24
25
26
27
28








29
30



31
32
33
34
35
36
37
38

39
40



41
42
43
44
45
46
47



48
49
50
51
52
53
54





55
56
57
58
59
60
61
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <fuse.h>
#include <pwd.h>
#include <tcl.h>

/* From sha1.c */
int Sha1_Init(Tcl_Interp *interp);

#ifndef APPFS_CACHEDIR
#define APPFS_CACHEDIR "/var/cache/appfs"
#endif


#ifdef DEBUG
#define APPFS_DEBUG(x...) { fprintf(stderr, "[debug] %s:%i:%s: ", __FILE__, __LINE__, __func__); fprintf(stderr, x); fprintf(stderr, "\n"); }
#else
#define APPFS_DEBUG(x...) /**/
#endif









static pthread_key_t interpKey;




struct appfs_thread_data {
	const char *cachedir;
	time_t boottime;
	struct {
		int writable;
	} options;
};


struct appfs_thread_data globalThread;




typedef enum {
	APPFS_PATHTYPE_INVALID,
	APPFS_PATHTYPE_FILE,
	APPFS_PATHTYPE_DIRECTORY,
	APPFS_PATHTYPE_SYMLINK
} appfs_pathtype_t;




struct appfs_children {
	struct appfs_children *_next;
	int counter;

	char name[256];
};






struct appfs_pathinfo {
	appfs_pathtype_t type;
	time_t time;
	char hostname[256];
	int packaged;
	unsigned long long inode;
	union {







|
|
|




>






>
>
>
>
>
>
>
>


>
>
>








>


>
>
>







>
>
>


<
<



>
>
>
>
>







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <fuse.h>
#include <pwd.h>
#include <tcl.h>

/*
 * Default cache directory
 */
#ifndef APPFS_CACHEDIR
#define APPFS_CACHEDIR "/var/cache/appfs"
#endif

/* Debugging macros */
#ifdef DEBUG
#define APPFS_DEBUG(x...) { fprintf(stderr, "[debug] %s:%i:%s: ", __FILE__, __LINE__, __func__); fprintf(stderr, x); fprintf(stderr, "\n"); }
#else
#define APPFS_DEBUG(x...) /**/
#endif

/*
 * SHA1 Tcl Package initializer, from sha1.o
 */
int Sha1_Init(Tcl_Interp *interp);

/*
 * Thread Specific Data (TSD) for Tcl Interpreter for the current thread
 */
static pthread_key_t interpKey;

/*
 * Data for a given thread (most likely these will become just globable variables soon.)
 */
struct appfs_thread_data {
	const char *cachedir;
	time_t boottime;
	struct {
		int writable;
	} options;
};

/* The global thread data (likely to go away) */
struct appfs_thread_data globalThread;

/*
 * AppFS Path Type:  Describes the type of path a given file is
 */
typedef enum {
	APPFS_PATHTYPE_INVALID,
	APPFS_PATHTYPE_FILE,
	APPFS_PATHTYPE_DIRECTORY,
	APPFS_PATHTYPE_SYMLINK
} appfs_pathtype_t;

/*
 * AppFS Children Files linked-list
 */
struct appfs_children {
	struct appfs_children *_next;


	char name[256];
};

/*
 * AppFS Path Information:
 *         Completely describes a specific path, how it should be returned to
 *         to the kernel
 */
struct appfs_pathinfo {
	appfs_pathtype_t type;
	time_t time;
	char hostname[256];
	int packaged;
	unsigned long long inode;
	union {
69
70
71
72
73
74
75



76
77
78
79
80
81
82
		struct {
			off_t size;
			char source[256];
		} symlink;
	} typeinfo;
};




static Tcl_Interp *appfs_create_TclInterp(void) {
	Tcl_Interp *interp;
	const char *cachedir = globalThread.cachedir;
	int tcl_ret;

	APPFS_DEBUG("Creating new Tcl interpreter for TID = 0x%llx", (unsigned long long) pthread_self());








>
>
>







91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
		struct {
			off_t size;
			char source[256];
		} symlink;
	} typeinfo;
};

/*
 * Create a new Tcl interpreter and completely initialize it
 */
static Tcl_Interp *appfs_create_TclInterp(void) {
	Tcl_Interp *interp;
	const char *cachedir = globalThread.cachedir;
	int tcl_ret;

	APPFS_DEBUG("Creating new Tcl interpreter for TID = 0x%llx", (unsigned long long) pthread_self());

113
114
115
116
117
118
119





120
121
122
123
124
125
126
127
128
129
130
131



132
133
134
135
136
137
138
139




140
141
142
143
144
145
146
147
148
149




150
151
152



153
154
155



156
157

158
159
160
161
162
163
164
165
166
167





168
169
170
171
172




173
174
175
176
177
178
179
		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));

		Tcl_DeleteInterp(interp);

		return(NULL);
	}






	tcl_ret = Tcl_Eval(interp, ""
#include "appfsd.tcl.h"
	"");
	if (tcl_ret != TCL_OK) {
		fprintf(stderr, "Unable to initialize Tcl AppFS script.  Aborting.\n");
		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));

		Tcl_DeleteInterp(interp);

		return(NULL);
	}




	if (Tcl_SetVar(interp, "::appfs::cachedir", cachedir, TCL_GLOBAL_ONLY) == NULL) {
		fprintf(stderr, "Unable to set cache directory.  This should never fail.\n");

		Tcl_DeleteInterp(interp);

		return(NULL);
	}





	tcl_ret = Tcl_Eval(interp, "::appfs::init");
	if (tcl_ret != TCL_OK) {
		fprintf(stderr, "Unable to initialize Tcl AppFS script (::appfs::init).  Aborting.\n");
		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));

		Tcl_DeleteInterp(interp);

		return(NULL);
	}





	Tcl_HideCommand(interp, "auto_load_index", "auto_load_index");
	Tcl_HideCommand(interp, "unknown", "unknown");




	return(interp);
}




static Tcl_Interp *appfs_TclInterp(void) {
	Tcl_Interp *interp;


	interp = pthread_getspecific(interpKey);
	if (interp == NULL) {
		interp = appfs_create_TclInterp();

		if (interp == NULL) {
			return(NULL);
		}

		pthread_setspecific(interpKey, interp);





	}

	return(interp);
}





static int appfs_Tcl_Eval(Tcl_Interp *interp, int objc, const char *cmd, ...) {
	Tcl_Obj **objv;
	const char *arg;
	va_list argp;
	int retval;
	int i;








>
>
>
>
>












>
>
>








>
>
>
>










>
>
>
>



>
>
>



>
>
>


>









|
>
>
>
>
>





>
>
>
>







138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));

		Tcl_DeleteInterp(interp);

		return(NULL);
	}

	/*
	 * Load the "appfsd.tcl" script, which is "compiled" into a C header
	 * so that it does not need to exist on the filesystem and can be
	 * directly evaluated.
	 */
	tcl_ret = Tcl_Eval(interp, ""
#include "appfsd.tcl.h"
	"");
	if (tcl_ret != TCL_OK) {
		fprintf(stderr, "Unable to initialize Tcl AppFS script.  Aborting.\n");
		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));

		Tcl_DeleteInterp(interp);

		return(NULL);
	}

	/*
	 * Set global variables from C to Tcl
	 */
	if (Tcl_SetVar(interp, "::appfs::cachedir", cachedir, TCL_GLOBAL_ONLY) == NULL) {
		fprintf(stderr, "Unable to set cache directory.  This should never fail.\n");

		Tcl_DeleteInterp(interp);

		return(NULL);
	}

	/*
	 * Initialize the "appfsd.tcl" environment, which must be done after
	 * global variables are set.
	 */
	tcl_ret = Tcl_Eval(interp, "::appfs::init");
	if (tcl_ret != TCL_OK) {
		fprintf(stderr, "Unable to initialize Tcl AppFS script (::appfs::init).  Aborting.\n");
		fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));

		Tcl_DeleteInterp(interp);

		return(NULL);
	}

	/*
	 * Hide some Tcl commands that we do not care to use and which may
	 * slow down run-time operations.
	 */
	Tcl_HideCommand(interp, "auto_load_index", "auto_load_index");
	Tcl_HideCommand(interp, "unknown", "unknown");

	/*
	 * Return the completely initialized interpreter
	 */
	return(interp);
}

/*
 * Return the thread-specific Tcl interpreter, creating it if needed
 */
static Tcl_Interp *appfs_TclInterp(void) {
	Tcl_Interp *interp;
	int pthread_ret;

	interp = pthread_getspecific(interpKey);
	if (interp == NULL) {
		interp = appfs_create_TclInterp();

		if (interp == NULL) {
			return(NULL);
		}

		pthread_ret = pthread_setspecific(interpKey, interp);
		if (pthread_ret != 0) {
			Tcl_DeleteInterp(interp);

			return(NULL);
		}
	}

	return(interp);
}

/*
 * Evaluate a Tcl script constructed by concatenating a bunch of C strings
 * together.
 */
static int appfs_Tcl_Eval(Tcl_Interp *interp, int objc, const char *cmd, ...) {
	Tcl_Obj **objv;
	const char *arg;
	va_list argp;
	int retval;
	int i;

204
205
206
207
208
209
210



211
212
213
214
215
216
217
	if (retval != TCL_OK) {
		APPFS_DEBUG("Tcl command failed, ::errorInfo contains: %s\n", Tcl_GetVar(interp, "::errorInfo", 0));
	}

	return(retval);
}




static void appfs_update_index(const char *hostname) {
	Tcl_Interp *interp;
	int tcl_ret;

	APPFS_DEBUG("Enter: hostname = %s", hostname);

	interp = appfs_TclInterp();







>
>
>







261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
	if (retval != TCL_OK) {
		APPFS_DEBUG("Tcl command failed, ::errorInfo contains: %s\n", Tcl_GetVar(interp, "::errorInfo", 0));
	}

	return(retval);
}

/*
 * AppFS: Request that a host's package index be updated locally
 */
static void appfs_update_index(const char *hostname) {
	Tcl_Interp *interp;
	int tcl_ret;

	APPFS_DEBUG("Enter: hostname = %s", hostname);

	interp = appfs_TclInterp();
225
226
227
228
229
230
231




232
233
234
235
236
237
238

		return;
	}

	return;
}





static const char *appfs_getfile(const char *hostname, const char *sha1) {
	Tcl_Interp *interp;
	char *retval;
	int tcl_ret;

	interp = appfs_TclInterp();
	if (interp == NULL) {







>
>
>
>







285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302

		return;
	}

	return;
}

/*
 * AppFS: Get a SHA1 from a host
 *         Returns a local file name, or NULL if it cannot be fetched
 */
static const char *appfs_getfile(const char *hostname, const char *sha1) {
	Tcl_Interp *interp;
	char *retval;
	int tcl_ret;

	interp = appfs_TclInterp();
	if (interp == NULL) {
247
248
249
250
251
252
253




254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272





273
274
275
276
277
278
279
280
281
282
283
284
285






286
287
288
289
290
291
292
	}

	retval = strdup(Tcl_GetStringResult(interp));

	return(retval);
}





static void appfs_update_manifest(const char *hostname, const char *sha1) {
	Tcl_Interp *interp;
	int tcl_ret;

	interp = appfs_TclInterp();
	if (interp == NULL) {
		return;
	}

	tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::getpkgmanifest", hostname, sha1);
	if (tcl_ret != TCL_OK) {
		APPFS_DEBUG("Call to ::appfs::getpkgmanifest failed: %s", Tcl_GetStringResult(interp));

		return;
	}

	return;
}






static uid_t appfs_get_fsuid(void) {
	struct fuse_context *ctx;

	ctx = fuse_get_context();
	if (ctx == NULL) {
		/* Unable to lookup user for some reason */
		/* Return an unprivileged user ID */
		return(1);
	}

	return(ctx->uid);
}







static char *appfs_get_homedir(uid_t fsuid) {
	struct passwd entry, *result;
	struct stat stbuf;
	char buf[1024], *retval;
	int gpu_ret, stat_ret;

	gpu_ret = getpwuid_r(fsuid, &entry, buf, sizeof(buf), &result);







>
>
>
>



















>
>
>
>
>













>
>
>
>
>
>







311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
	}

	retval = strdup(Tcl_GetStringResult(interp));

	return(retval);
}

/*
 * AppFS: Update the manifest for a specific package (by the package SHA1) on
 * a given host
 */
static void appfs_update_manifest(const char *hostname, const char *sha1) {
	Tcl_Interp *interp;
	int tcl_ret;

	interp = appfs_TclInterp();
	if (interp == NULL) {
		return;
	}

	tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::getpkgmanifest", hostname, sha1);
	if (tcl_ret != TCL_OK) {
		APPFS_DEBUG("Call to ::appfs::getpkgmanifest failed: %s", Tcl_GetStringResult(interp));

		return;
	}

	return;
}

/*
 * Determine the UID for the user making the current FUSE filesystem request.
 * This will be used to lookup the user's home directory so we can search for
 * locally modified files.
 */
static uid_t appfs_get_fsuid(void) {
	struct fuse_context *ctx;

	ctx = fuse_get_context();
	if (ctx == NULL) {
		/* Unable to lookup user for some reason */
		/* Return an unprivileged user ID */
		return(1);
	}

	return(ctx->uid);
}

/*
 * Look up the home directory for a given UID
 *        Returns a C string containing the user's home directory or NULL if
 *        the user's home directory does not exist or is not correctly
 *        configured
 */
static char *appfs_get_homedir(uid_t fsuid) {
	struct passwd entry, *result;
	struct stat stbuf;
	char buf[1024], *retval;
	int gpu_ret, stat_ret;

	gpu_ret = getpwuid_r(fsuid, &entry, buf, sizeof(buf), &result);
326
327
328
329
330
331
332




333
334
335
336
337
338
339
	}

	retval = strdup(result->pw_dir);

	return(retval);
}





static int tcl_appfs_get_homedir(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	char *homedir;

        if (objc != 1) {
                Tcl_WrongNumArgs(interp, 1, objv, NULL);
                return(TCL_ERROR);
        }







>
>
>
>







405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
	}

	retval = strdup(result->pw_dir);

	return(retval);
}

/*
 * Tcl interface to get the home directory for the user making the "current"
 * FUSE I/O request
 */
static int tcl_appfs_get_homedir(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	char *homedir;

        if (objc != 1) {
                Tcl_WrongNumArgs(interp, 1, objv, NULL);
                return(TCL_ERROR);
        }
347
348
349
350
351
352
353

354



355
356
357
358
359
360
361
        Tcl_SetObjResult(interp, Tcl_NewStringObj(homedir, -1));

	free(homedir);

        return(TCL_OK);
}


/* Generate an inode for a given path */



static long long appfs_get_path_inode(const char *path) {
	long long retval;
	const char *p;

	retval = 10;

	for (p = path; *p; p++) {







>
|
>
>
>







430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
        Tcl_SetObjResult(interp, Tcl_NewStringObj(homedir, -1));

	free(homedir);

        return(TCL_OK);
}

/*
 * Generate an inode for a given path.  The inode should be computed in such
 * a way that it is unlikely to be duplicated and remains the same for a given
 * file
 */
static long long appfs_get_path_inode(const char *path) {
	long long retval;
	const char *p;

	retval = 10;

	for (p = path; *p; p++) {
543
544
545
546
547
548
549



550
551
552
553
554
555
556
	}

	read_ret = read(fi->fh, buf, size);

	return(read_ret);
}




static int appfs_sqlite3(const char *sql) {
	Tcl_Interp *interp;
	const char *sql_ret;
	int tcl_ret;

	interp = appfs_create_TclInterp();
	if (interp == NULL) {







>
>
>







630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
	}

	read_ret = read(fi->fh, buf, size);

	return(read_ret);
}

/*
 * SQLite3 mode: Execute raw SQL and return success or failure
 */
static int appfs_sqlite3(const char *sql) {
	Tcl_Interp *interp;
	const char *sql_ret;
	int tcl_ret;

	interp = appfs_create_TclInterp();
	if (interp == NULL) {
571
572
573
574
575
576
577



578
579
580
581
582
583
584
	if (sql_ret && sql_ret[0] != '\0') {
		printf("%s\n", sql_ret);
	}

	return(0);
}




static int appfs_tcl(const char *tcl) {
	Tcl_Interp *interp;
	const char *tcl_result;
	int tcl_ret;

	interp = appfs_create_TclInterp();
	if (interp == NULL) {







>
>
>







661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
	if (sql_ret && sql_ret[0] != '\0') {
		printf("%s\n", sql_ret);
	}

	return(0);
}

/*
 * Tcl mode: Execute raw Tcl and return success or failure
 */
static int appfs_tcl(const char *tcl) {
	Tcl_Interp *interp;
	const char *tcl_result;
	int tcl_ret;

	interp = appfs_create_TclInterp();
	if (interp == NULL) {
599
600
601
602
603
604
605





606
607
608
609
610
611
612
613
614
615
616
617
618
619



620
621
622
623
624
625
626
627
628



629
630
631
632



633
634
635
636











637
638
639





640
641
642
643
644
645
646



647
648
649
650




651
652
653
654




655
656
657
	if (tcl_result && tcl_result[0] != '\0') {
		printf("%s\n", tcl_result);
	}

	return(0);
}






static int Appfsd_Init(Tcl_Interp *interp) {
#ifdef USE_TCL_STUBS
	if (Tcl_InitStubs(interp, TCL_VERSION, 0) == 0L) {
		return(TCL_ERROR);
	}
#endif

	Tcl_CreateObjCommand(interp, "appfsd::get_homedir", tcl_appfs_get_homedir, NULL, NULL);

	Tcl_PkgProvide(interp, "appfsd", "1.0");

	return(TCL_OK);
}




static struct fuse_operations appfs_oper = {
	.getattr   = appfs_fuse_getattr,
	.readdir   = appfs_fuse_readdir,
	.readlink  = appfs_fuse_readlink,
	.open      = appfs_fuse_open,
	.release   = appfs_fuse_close,
	.read      = appfs_fuse_read
};




int main(int argc, char **argv) {
	const char *cachedir = APPFS_CACHEDIR;
	int pthread_ret;




	globalThread.cachedir = cachedir;
	globalThread.boottime = time(NULL);
	globalThread.options.writable = 1;












	Tcl_StaticPackage(NULL, "sha1", Sha1_Init, NULL);
	Tcl_StaticPackage(NULL, "appfsd", Appfsd_Init, NULL);






	pthread_ret = pthread_key_create(&interpKey, NULL);
	if (pthread_ret != 0) {
		fprintf(stderr, "Unable to create TSD key for Tcl.  Aborting.\n");

		return(1);
	}




	if (argc == 3 && strcmp(argv[1], "-sqlite3") == 0) {
		return(appfs_sqlite3(argv[2]));
	}





	if (argc == 3 && strcmp(argv[1], "-tcl") == 0) {
		return(appfs_tcl(argv[2]));
	}





	return(fuse_main(argc, argv, &appfs_oper, NULL));
}
 







>
>
>
>
>














>
>
>









>
>
>




>
>
>

<


>
>
>
>
>
>
>
>
>
>
>



>
>
>
>
>







>
>
>




>
>
>
>




>
>
>
>



692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740

741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
	if (tcl_result && tcl_result[0] != '\0') {
		printf("%s\n", tcl_result);
	}

	return(0);
}

/*
 * AppFSd Package for Tcl:
 *         Bridge for I/O operations to request information about the current
 *         transaction
 */
static int Appfsd_Init(Tcl_Interp *interp) {
#ifdef USE_TCL_STUBS
	if (Tcl_InitStubs(interp, TCL_VERSION, 0) == 0L) {
		return(TCL_ERROR);
	}
#endif

	Tcl_CreateObjCommand(interp, "appfsd::get_homedir", tcl_appfs_get_homedir, NULL, NULL);

	Tcl_PkgProvide(interp, "appfsd", "1.0");

	return(TCL_OK);
}

/*
 * FUSE operations structure
 */
static struct fuse_operations appfs_oper = {
	.getattr   = appfs_fuse_getattr,
	.readdir   = appfs_fuse_readdir,
	.readlink  = appfs_fuse_readlink,
	.open      = appfs_fuse_open,
	.release   = appfs_fuse_close,
	.read      = appfs_fuse_read
};

/*
 * Entry point into this program.
 */
int main(int argc, char **argv) {
	const char *cachedir = APPFS_CACHEDIR;
	int pthread_ret;

	/*
	 * Set global variables, these should be configuration options.
	 */
	globalThread.cachedir = cachedir;

	globalThread.options.writable = 1;

	/*
	 * Set global variable for "boot time" to set a time on directories
	 * that we fake.
	 */
	globalThread.boottime = time(NULL);

	/*
	 * Register "sha1" and "appfsd" package with libtcl so that any new
	 * interpreters created (which are done dynamically by FUSE) can have
	 * the appropriate configuration done automatically.
	 */
	Tcl_StaticPackage(NULL, "sha1", Sha1_Init, NULL);
	Tcl_StaticPackage(NULL, "appfsd", Appfsd_Init, NULL);

	/*
	 * Create a thread-specific-data (TSD) key for each thread to refer
	 * to its own Tcl interpreter.  Tcl interpreters must be unique per
	 * thread and new threads are dynamically created by FUSE.
	 */
	pthread_ret = pthread_key_create(&interpKey, NULL);
	if (pthread_ret != 0) {
		fprintf(stderr, "Unable to create TSD key for Tcl.  Aborting.\n");

		return(1);
	}

	/*
	 * SQLite3 mode, for running raw SQL against the cache database
	 */
	if (argc == 3 && strcmp(argv[1], "-sqlite3") == 0) {
		return(appfs_sqlite3(argv[2]));
	}

	/*
	 * Tcl mode, for running raw Tcl in the same environment AppFSd would
	 * run code.
	 */
	if (argc == 3 && strcmp(argv[1], "-tcl") == 0) {
		return(appfs_tcl(argv[2]));
	}

	/*
	 * Enter the FUSE main loop -- this will process any arguments
	 * and start servicing requests.
	 */
	return(fuse_main(argc, argv, &appfs_oper, NULL));
}