Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -1,2 +1,5 @@ appfs appfs.o +appfs-test +appfs-test.o +appfs.tcl.h Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -1,24 +1,44 @@ CC = gcc PKG_CONFIG = pkg-config -CFLAGS = $(shell $(PKG_CONFIG) --cflags fuse) -LIBS = $(shell $(PKG_CONFIG) --libs fuse) +TCL_CFLAGS = +TCL_LDFLAGS = +TCL_LIBS = -ltcl +CFLAGS = -Wall -g3 $(shell $(PKG_CONFIG) --cflags fuse) $(TCL_CFLAGS) +LDFLAGS = $(TCL_LDFLAGS) +LIBS = $(shell $(PKG_CONFIG) --libs fuse) $(TCL_LIBS) PREFIX = /usr/local prefix = $(PREFIX) bindir = $(prefix)/bin all: appfs appfs: appfs.o $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfs appfs.o $(LIBS) -appfs.o: appfs.c +appfs-test: appfs-test.o + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfs-test appfs-test.o $(LIBS) + +appfs.o: appfs.c appfs.tcl.h + $(CC) $(CPPFLAGS) $(CFLAGS) -o appfs.o -c appfs.c + +appfs-test.o: appfs.c appfs.tcl.h + $(CC) $(CPPFLAGS) $(CFLAGS) -DAPPFS_TEST_DRIVER=1 -o appfs-test.o -c appfs.c + +appfs.tcl.h: appfs.tcl stringify.tcl + ./stringify.tcl appfs.tcl > appfs.tcl.h.new + mv appfs.tcl.h.new appfs.tcl.h install: appfs cp appfs $(bindir) +test: appfs-test + ./appfs-test + clean: rm -f appfs appfs.o + rm -f appfs-test appfs-test.o + rm -f appfs.tcl.h distclean: clean -.PHONY: all clean distclean install +.PHONY: all test clean distclean install Index: README.md ================================================================== --- README.md +++ README.md @@ -7,21 +7,22 @@ ----- AppFS should normally be mounted on "/opt/appfs". /opt/appfs/hostname Fetches: http://hostname/appfs/index - Contains CSV file: type,extraData - type == package; extraData = package,version,os,cpuArch,sha1 - type == latest; extradata = package,version,os,cpuArch + Contains CSV file: hash,extraData + + Fetches: http://hostname/appfs/sha1/ + Contains CSV file: package,version,os,cpuArch,sha1,isLatest /opt/appfs/hostname/package/os-cpuArch/version /opt/appfs/hostname/sha1/ - Fetches: http://hostname/appfs/ + Fetches: http://hostname/appfs/sha1/ Contains CSV file: type,time,extraData,name type == directory; extraData = (null) type == symlink; extraData = source type == file; extraData = size,sha1 /opt/appfs/hostname/{sha1,package/os-cpuArch/version}/file - Fetches: http://hostname/appfs/ + Fetches: http://hostname/appfs/sha1/ Index: appfs.c ================================================================== --- appfs.c +++ appfs.c @@ -1,56 +1,232 @@ #define FUSE_USE_VERSION 26 -#include +#include #include -#include #include +#include +#include +#include + +#define APPFS_DEBUG(x...) { fprintf(stderr, "%i:%s: ", __LINE__, __func__); fprintf(stderr, x); fprintf(stderr, "\n"); } + +Tcl_Interp *interp; + +typedef enum { + APPFS_OS_UNKNOWN, + APPFS_OS_ALL, + APPFS_OS_LINUX, + APPFS_OS_MACOSX, + APPFS_OS_FREEBSD, + APPFS_OS_OPENBSD, + APPFS_OS_SOLARIS +} appfs_os_t; + +typedef enum { + APPFS_CPU_UNKNOWN, + APPFS_CPU_ALL, + APPFS_CPU_AMD64, + APPFS_CPU_I386, + APPFS_CPU_ARM +} appfs_cpuArch_t; + +struct appfs_package { + char name[128]; + char version[64]; + appfs_os_t os; + appfs_cpuArch_t cpuArch; + int isLatest; +}; + +static appfs_os_t appfs_convert_os_fromString(const char *os) { + if (strcasecmp(os, "Linux") == 0) { + return(APPFS_OS_LINUX); + } + + if (strcasecmp(os, "Darwin") == 0 || strcasecmp(os, "Mac OS") == 0 || strcasecmp(os, "Mac OS X") == 0) { + return(APPFS_OS_MACOSX); + } + + if (strcasecmp(os, "noarch") == 0) { + return(APPFS_OS_ALL); + } + + return(APPFS_OS_UNKNOWN); +} + +static const char *appfs_convert_os_toString(appfs_os_t os) { + switch (os) { + case APPFS_OS_ALL: + return("noarch"); + case APPFS_OS_LINUX: + return("linux"); + case APPFS_OS_MACOSX: + return("macosx"); + case APPFS_OS_FREEBSD: + return("freebsd"); + case APPFS_OS_OPENBSD: + return("openbsd"); + case APPFS_OS_SOLARIS: + return("freebsd"); + case APPFS_CPU_UNKNOWN: + return("unknown"); + } + + return("unknown"); +} + +static appfs_cpuArch_t appfs_convert_cpu_fromString(const char *cpu) { + if (strcasecmp(cpu, "amd64") == 0 || strcasecmp(cpu, "x86_64") == 0) { + return(APPFS_CPU_AMD64); + } + + if (strcasecmp(cpu, "i386") == 0 || \ + strcasecmp(cpu, "i486") == 0 || \ + strcasecmp(cpu, "i586") == 0 || \ + strcasecmp(cpu, "i686") == 0 || \ + strcasecmp(cpu, "ix86") == 0) { + return(APPFS_CPU_I386); + } + + if (strcasecmp(cpu, "arm") == 0) { + return(APPFS_CPU_ARM); + } + + if (strcasecmp(cpu, "noarch") == 0) { + return(APPFS_CPU_ALL); + } + + return(APPFS_CPU_UNKNOWN); +} + +static const char *appfs_convert_cpu_toString(appfs_cpuArch_t cpu) { + switch (cpu) { + case APPFS_CPU_ALL: + return("noarch"); + case APPFS_CPU_AMD64: + return("amd64"); + case APPFS_CPU_I386: + return("ix86"); + case APPFS_CPU_ARM: + return("arm"); + case APPFS_CPU_UNKNOWN: + return("unknown"); + } + + return("unknown"); +} + +static struct appfs_package *appfs_getindex(const char *hostname, int *package_count_p) { + Tcl_Obj *objv[2]; + int tcl_ret; + + if (package_count_p == NULL) { + return(NULL); + } + + objv[0] = Tcl_NewStringObj("::appfs::getindex", -1); + objv[1] = Tcl_NewStringObj(hostname, -1); + + tcl_ret = Tcl_EvalObjv(interp, 2, &objv, 0); + if (tcl_ret != TCL_OK) { + APPFS_DEBUG("Call to ::appfs::getindex failed: %s", Tcl_GetStringResult(interp)); + + return(NULL); + } + + printf("result: %s\n", Tcl_GetStringResult(interp)); + + return(NULL); +} + +static int appfs_getfile(const char *hostname, const char *sha1) { +} + +static int appfs_getmanifest(const char *hostname, const char *sha1) { +} static int appfs_getattr(const char *path, struct stat *stbuf) { int res = 0; + APPFS_DEBUG("Enter (path = %s, ...)", path); + memset(stbuf, 0, sizeof(struct stat)); - if (strcmp(path, "/") == 0) { - stbuf->st_mode = S_IFDIR | 0755; - stbuf->st_nlink = 2; - } else { - res = -ENOENT; - } + + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; return res; } static int appfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { - if (strcmp(path, "/") != 0) { - return(-ENOENT); - } + APPFS_DEBUG("Enter (path = %s, ...)", path); filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); return 0; } static int appfs_open(const char *path, struct fuse_file_info *fi) { return(-ENOENT); - - if ((fi->flags & 3) != O_RDONLY) - return -EACCES; - - return 0; } static int appfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { return(-ENOENT); } + +#ifdef APPFS_TEST_DRIVER +static int appfs_test_driver(void) { + struct appfs_package *packages; + int packages_count = 0; + + packages = appfs_getindex("rkeene.org", &packages_count); + if (packages == NULL || packages_count == 0) { + fprintf(stderr, "Unable to fetch package index from rkeene.org.\n"); + + return(1); + } + + return(0); +} +#endif static struct fuse_operations appfs_oper = { .getattr = appfs_getattr, .readdir = appfs_readdir, .open = appfs_open, .read = appfs_read, }; int main(int argc, char **argv) { + int tcl_ret; + + interp = Tcl_CreateInterp(); + if (interp == NULL) { + fprintf(stderr, "Unable to create Tcl Interpreter. Aborting.\n"); + + return(1); + } + + tcl_ret = Tcl_Init(interp); + if (tcl_ret != TCL_OK) { + fprintf(stderr, "Unable to initialize Tcl. Aborting.\n"); + + return(1); + } + + tcl_ret = Tcl_Eval(interp, "" +#include "appfs.tcl.h" + ""); + if (tcl_ret != TCL_OK) { + fprintf(stderr, "Unable to initialize Tcl AppFS Script. Aborting.\n"); + + return(1); + } + +#ifdef APPFS_TEST_DRIVER + return(appfs_test_driver()); +#else return(fuse_main(argc, argv, &appfs_oper, NULL)); +#endif } ADDED appfs.tcl Index: appfs.tcl ================================================================== --- /dev/null +++ appfs.tcl @@ -0,0 +1,117 @@ +#! /usr/bin/env tclsh + +package require http + +namespace eval ::appfs { + variable sites [list] + variable cachedir "/tmp/appfs-cache" + + proc _hash_sep {hash {seps 4}} { + for {set idx 0} {$idx < $seps} {incr idx} { + append retval "[string range $hash [expr {$idx * 2}] [expr {($idx * 2) + 1}]]/" + } + append retval "[string range $hash [expr {$idx * 2}] end]" + + return $retval + } + + proc _cachefile {url key {keyIsHash 1}} { + if {$keyIsHash} { + set key [_hash_sep $key] + } + + set file [file join $::appfs::cachedir $key] + + file mkdir -- [file dirname $file] + + if {![file exists $file]} { + set tmpfile "${file}.new" + + set fd [open $tmpfile "w"] + + set token [::http::geturl $url -channel $fd] + set ncode [::http::ncode $token] + ::http::reset $token + close $fd + + if {$ncode == "200"} { + file rename -force -- $tmpfile $file + } else { + file delete -force -- $tmpfile + } + } + + return $file + } + + proc getindex {hostname} { + if {[string match "*\[/~\]*" $hostname]} { + return -code error "Invalid hostname" + } + + set url "http://$hostname/appfs/index" + + set indexcachefile [_cachefile $url "SERVERS/[string tolower $hostname]" 0] + + if {![file exists $indexcachefile]} { + return -code error "Unable to fetch $url" + } + + set fd [open $indexcachefile] + gets $fd indexhash_data + set indexhash [lindex [split $indexhash_data ","] 0] + close $fd + + set file [download $hostname $indexhash] + set fd [open $file] + set data [read $fd] + close $fd + + array set packages [list] + foreach line [split $data "\n"] { + set line [string trim $line] + + if {[string match "*/*" $line]} { + continue + } + + if {$line == ""} { + continue + } + + set work [split $line ","] + + unset -nocomplain pkgInfo + set pkgInfo(package) [lindex $work 0] + set pkgInfo(version) [lindex $work 1] + set pkgInfo(os) [lindex $work 2] + set pkgInfo(cpuArch) [lindex $work 3] + set pkgInfo(hash) [string tolower [lindex $work 4]] + set pkgInfo(hash_type) "sha1" + set pkgInfo(isLatest) [expr {!![lindex $work 5]}] + + if {[string length $pkgInfo(hash)] != 40} { + continue + } + + if {![regexp {^[0-9a-f]*$} $pkgInfo(hash)]} { + continue + } + + set packages($pkgInfo(package)) [array get pkgInfo] + } + + return [array get packages] + } + + proc download {hostname hash {method sha1}} { + set url "http://$hostname/appfs/$method/$hash" + set file [_cachefile $url $hash] + + if {![file exists $file]} { + return -code error "Unable to fetch" + } + + return $file + } +} ADDED stringify.tcl Index: stringify.tcl ================================================================== --- /dev/null +++ stringify.tcl @@ -0,0 +1,27 @@ +#! /usr/bin/env tclsh + +proc stringifyfile {filename {key 0}} { + catch { + set fd [open $filename r] + } + + if {![info exists fd]} { + return "" + } + + set data [read -nonewline $fd] + close $fd + + foreach line [split $data \n] { + set line [string map [list "\\" "\\\\" "\"" "\\\""] $line] + append ret " \"$line\\n\"\n" + } + + return $ret +} + +foreach file $argv { + puts -nonewline [stringifyfile $file] +} + +exit 0