/* * Using puffs to provide gitfs filesystems. * * Command line: $0 [options] config-file mount-point * * Options are: * * -config PATH * Specifies config-file unambiguously. * -mount PATH * Specifies point-point unambiguously. * -d THING[,THING[,THING...]] * Turns on debugging code. Each THING can be * * verb * Turns on verbosity in the gitfs implementations * of the filesystem operations. * dump * Turns on PUFFS_FLAG_OPDUMP to puffs_init, * causing text dumps of filesystem operations to * be prinetd by the puffs infrastructure. * all * Turns on PUFFS_KFLAG_ALLOPS to puffs_init, * causing all operations, even ones we don't * implement, to be passed to userland. This is * useful mostly in conjunction with dump. * conf * Dumps out various information about the * configuration file, its parsing, and its parsed * form. * * The config file lists pathnames relative to the mount point, each * one with a path to the git repo that is to back it. For example, * if the config file looks like * * my-sw/thing1 /home/me/thing1 * my-sw/thing2 /home/chris/thing2 * thing3 /home/pat/thing3 * common-src/dist/prog /shared/repos/common/prog * * then the mount point will be a directory containing three entries: * my-sw, thing3, and common-src. my-sw will contain subdirectories * thing1 and thing2; common-src will contain dist, which will contain * prog. Each of those four will be a directory which exports the * corresponding git repo. In the config file, lines which are empty, * entirely blank, or whose first nonblank character is #, ;, or NUL * are comments and are ignored. Lines whose first nonblank character * is a(n unquoted) % specify control information rather than repos. * In non-comment lines, characters can be quoted with \, " ", or ' ', * and the separator between the mount-point directory and the repo * path in repo-spec lines is one or more *unquoted* whitespace * characters. Non-comment lines which end with \ followed by zero or * more whitespace characters have the \, the whitespace (if any) * after it, and the newline deleted, thus pasting the lines together. * (Comment lines are deleted before line continuation processing * happens.) * * % lines can be: * * %include PATH * Reads PATH and effectively replaces the %include line * with the contents of that file. * * An exported repo's directory contains: * * - A directory "branch", which contains one symlink per branch, * whose name is the branch name and which links to the head * commit for that branch. Branches containing slashes in * their names (eg, origin/master) are omitted. * - A directory "tag", which contains one symlink per tag, whose * name is the tag name and which links to the commit the tag * points to. * - A directory "commit", which contains commits (see below). * Each commit directory contains: * - A directory "parents" which contains zero or more * symlinks, whose names are "1", "2", etc. Each one * symlinks to the relevant parent commit. (A root * commit will have still an parents directory, but it * will be empty. A non-merge commit will have only * the "1" entry in its parents directory.) * - A file "message" which contains the commit message. * - If the commit has an "encoding" header, a file * "encoding" which contains the encoding name. * - A file "time" which contains the commit's timestamp, * as a decimal ASCII number of seconds past the UNIX * epoch. * - A file "author" containing the commit's author name * and email. * - A directory "tree" which contains the working tree * corresponding to that commit. * - A symlink "HEAD" which symlinks to the current HEAD, in the * branch directory if on a branch or in the commit directory * if not. * * By default, the commit directory contains subdirectories named with * the commits' SHA1 hashes, in text form. For use with large repos, * an option is planned with would split them up into up-to-256 * subdirectories, with suitable changes in things that link to * commits. */ #include #include #include #include #include #include extern const char *__progname; #include "es.h" #define INO_BASE_SHIFT 2 #define INO_BASE (1 << INO_BASE_SHIFT) typedef struct puffs_pathobj PUFFS_PATHOBJ; typedef struct puffs_pathinfo PUFFS_PATHINFO; typedef struct puffs_kcache PUFFS_KCACHE; typedef struct puffs_node PUFFS_NODE; typedef struct puffs_usermount PUFFS_USERMOUNT; typedef struct puffs_cn PUFFS_CN; typedef struct puffs_ops PUFFS_OPS; typedef struct puffs_cred PUFFS_CRED; typedef struct puffs_newinfo PUFFS_NEWINFO; typedef struct statvfs STATVFS; typedef struct vattr VATTR; typedef struct dirent DIRENT; // These are documented but not actually defined(!) // These seem to be the actual values used. #define PUFFSLOOKUP_LOOKUP 0 #define PUFFSLOOKUP_CREATE 1 #define PUFFSLOOKUP_DELETE 2 #define PUFFSLOOKUP_RENAME 3 static PUFFS_USERMOUNT *um; typedef enum { FSO_FS = 1, FSO_REPO_ROOT, } FSOKIND; typedef struct conf_ctx CONF_CTX; typedef struct conf_istk CONF_ISTK; typedef struct repo REPO; typedef struct fsobj FSOBJ; struct fsobj { FSOKIND kind; char *name; int namelen; int ino; FSOBJ *parent; FSOBJ *sibling; union { struct { // FSO_FS FSOBJ *children; int nchildren; } fs; REPO *repo; // FSO_REPO_ROOT } u; } ; struct repo { REPO *link; char *path; char *repo; int plen; int rlen; time_t expire; FSOBJ *fso_root; FSOBJ *fso_branch; FSOBJ *fso_tag; FSOBJ *fso_commit; FSOBJ *fso_HEAD; } ; struct conf_istk { CONF_ISTK *link; const char *path; FILE *f; int rlno; int at; } ; struct conf_ctx { CONF_ISTK *istack; ES line; int partial; ES partbuf; const char *etext; int elno1; int elno2; int errs; } ; static char *config_path = 0; static char *mount_path = 0; static unsigned int debug = 0; #define DBG_VERB 0x00000001 #define DBG_DUMP 0x00000002 #define DBG_ALL 0x00000004 #define DBG_CONF 0x00000008 #define DBG(x) (debug & DBG_##x) static REPO *repos; static FSOBJ *fsroot; static void *ino_v; static int ino_level; static int ino_n; #define Cisspace(x) isspace((unsigned char)(x)) static void usage(void) { fprintf(stderr,"Usage: %s [options] config-file mount-path\n",__progname); fprintf(stderr,"\ options can be:\n\ -config PATH\n\ Specifies config-file, but unambiguously.\n\ -mount PATH\n\ Specifies mount-path, but unambiguously.\n\ "); } static void verb(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void verb(const char *fmt, ...) { va_list ap; char *s; int l; va_start(ap,fmt); l = vasprintf(&s,fmt,ap); va_end(ap); printf("%s\n",s); free(s); } #define bugcheck(...) bugcheck_(__func__,__LINE__,__VA_ARGS__) static void bugcheck_(const char *, int, const char *, ...) __attribute__((__format__(__printf__,3,4),__noreturn__)); static void bugcheck_(const char *fn, int lno, const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"bugcheck: line %d in %s: %s\n",lno,fn,s); fflush(0); abort(); } static const char *cookie_str(puffs_cookie_t cook) { static char rbuf[64]; sprintf(&rbuf[0],"%p",(void *)cook); return(&rbuf[0]); } static const char *access_mode_str(int m) { static char rbuf[64]; switch (m) { case PUFFS_VREAD: return("READ"); break; case PUFFS_VWRITE: return("WRITE"); break; case PUFFS_VEXEC: return("EXEC"); break; } sprintf(&rbuf[0],"?%d",m); return(&rbuf[0]); } static const char *lookup_nameiop_str(uint32_t op) { static char rbuf[64]; switch (op) { case PUFFSLOOKUP_LOOKUP: return("LOOKUP"); break; case PUFFSLOOKUP_CREATE: return("CREATE"); break; case PUFFSLOOKUP_DELETE: return("DELETE"); break; case PUFFSLOOKUP_RENAME: return("RENAME"); break; } sprintf(&rbuf[0],"?%lld",(unsigned long long int)op); return(&rbuf[0]); } static int op_fs_unmount(PUFFS_USERMOUNT *um, int flags) { if (DBG(VERB)) verb("%s, flags %#x",__func__,(unsigned int)flags); (void)um; (void)flags; return(0); } static int op_fs_statvfs(PUFFS_USERMOUNT *um, STATVFS *s) { if (DBG(VERB)) verb("%s",__func__); (void)um; s->f_bsize = 512; s->f_frsize = 512; s->f_blocks = 0; s->f_bfree = 0; s->f_bavail = 0; s->f_bresvd = 0; s->f_files = ino_n; s->f_ffree = 0; s->f_favail = 0; s->f_fresvd = 0; return(0); } static int op_fs_sync(PUFFS_USERMOUNT *um, int waitfor, const PUFFS_CRED *cr) { const char *wfs; switch (waitfor) { case MNT_WAIT: wfs = "WAIT"; break; case MNT_NOWAIT: wfs = "NOWAIT"; break; case MNT_LAZY: wfs = "LAZY"; break; default: { static char buf[64]; sprintf(&buf[0],"?%d",waitfor); wfs = &buf[0]; } break; } if (DBG(VERB)) verb("%s: waitfor %s, cr %p",__func__,wfs,(const void *)cr); (void)um; (void)waitfor; (void)cr; return(0); } static int op_fs_fhtonode(PUFFS_USERMOUNT *um, void *fh, size_t fhsize, PUFFS_NEWINFO *ni) { if (DBG(VERB)) verb("%s",__func__); (void)um; (void)fh; (void)fhsize; (void)ni; return(EOPNOTSUPP); } static int op_fs_nodetofh(PUFFS_USERMOUNT *um, puffs_cookie_t cook, void *fh, size_t *fhsize) { if (DBG(VERB)) verb("%s",__func__); (void)um; (void)cook; (void)fh; (void)fhsize; return(EOPNOTSUPP); } static void op_fs_suspend(PUFFS_USERMOUNT *um, int status) { const char *ss; switch (status) { case PUFFS_SUSPEND_START: ss = "START"; break; case PUFFS_SUSPEND_SUSPENDED: ss = "SUSPENDED"; break; case PUFFS_SUSPEND_RESUME: ss = "RESUME"; break; case PUFFS_SUSPEND_ERROR: ss = "ERROR"; break; default: { static char buf[64]; sprintf(&buf[0],"?%d",status); ss = &buf[0]; } break; } if (DBG(VERB)) verb("%s: waitfor %s",__func__,ss); (void)um; (void)status; } static int op_fs_extattrctl(PUFFS_USERMOUNT *um, int a, puffs_cookie_t b, int c, int d, const char *e) { if (DBG(VERB)) verb("%s",__func__); (void)um; (void)a; (void)b; (void)c; (void)d; (void)e; return(EOPNOTSUPP); } static int op_node_getattr(PUFFS_USERMOUNT *um, puffs_cookie_t cook, VATTR *va, const PUFFS_CRED *cr) { struct timeval tv; FSOBJ *o; if (DBG(VERB)) verb("%s cookie %s va %p cr %p",__func__,cookie_str(cook),(void *)va, (const void *)cr); (void)um; gettimeofday(&tv,0); o = (void *)cook; va->va_type = VDIR; va->va_mode = S_IFDIR | 0555; va->va_uid = 0; va->va_gid = 0; va->va_fileid = o->ino; va->va_size = 512; va->va_blocksize = 512; va->va_atime.tv_sec = tv.tv_sec; va->va_atime.tv_nsec = tv.tv_usec * 1000; va->va_mtime = va->va_atime; va->va_ctime = va->va_atime; va->va_bytes = 512; switch (o->kind) { case FSO_FS: va->va_nlink = 2 + o->u.fs.nchildren; break; case FSO_REPO_ROOT: va->va_nlink = 5; break; default: bugcheck("impossible fsobj kind %d",(int)o->kind); break; } va->va_fsid = 0; va->va_birthtime.tv_sec = 0; va->va_birthtime.tv_nsec = 0; va->va_gen = 0; va->va_flags = 0; va->va_rdev = 0; va->va_filerev = 0; va->va_vaflags = 0; va->va_spare = 0; return(0); } static int op_node_inactive(PUFFS_USERMOUNT *um, puffs_cookie_t cook) { if (DBG(VERB)) verb("%s cookie %s",__func__,cookie_str(cook)); (void)um; (void)cook; return(0); } static int op_node_access(PUFFS_USERMOUNT *um, puffs_cookie_t cook, int mode, const PUFFS_CRED *cr) { if (DBG(VERB)) verb("%s cookie %s mode %s cred %p",__func__,cookie_str(cook),access_mode_str(mode),(const void *)cr); (void)um; (void)cook; (void)mode; (void)cr; switch (mode) { case PUFFS_VREAD: return(0); break; case PUFFS_VWRITE: return(EROFS); break; case PUFFS_VEXEC: return(0); break; } return(EINVAL); } static int op_node_open(PUFFS_USERMOUNT *um, puffs_cookie_t cook, int mode, const PUFFS_CRED *cr) { if (DBG(VERB)) verb("%s cookie %s mode %#x cr %p",__func__,cookie_str(cook),(unsigned int)mode,(const void *)cr); (void)um; (void)cook; (void)mode; (void)cr; if (mode & FWRITE) return(EROFS); return(0); } static int op_node_seek(PUFFS_USERMOUNT *um, puffs_cookie_t cook, off_t old, off_t new, const PUFFS_CRED *cr) { if (DBG(VERB)) verb("%s cookie %s old %#llx new %#llx cr %p",__func__,cookie_str(cook),(unsigned long long int)old,(unsigned long long int)new,(const void *)cr); (void)um; (void)cook; (void)old; (void)new; (void)cr; return(0); } static int op_node_readdir( PUFFS_USERMOUNT *um, puffs_cookie_t cook, DIRENT *dent, off_t *off, size_t *len, const PUFFS_CRED *cr, int *eofp, off_t *cookies, size_t *ncookies ) { FSOBJ *o; FSOBJ *k; int i; off_t at; if (DBG(VERB)) verb("%s cookie %s ents %p off %p (%lld) spc %p (%lld) cr %p eofp %p (%d)",__func__,cookie_str(cook),(void *)dent,(void *)off,(unsigned long long int)*off,(void *)len,(unsigned long long int)*len,(const void *)cr,(void *)eofp,*eofp); (void)um; (void)cookies; (void)ncookies; at = *off; o = (void *)cook; do <"dir"> { if (at == 0) { if (! puffs_nextdent(&dent,".",o->ino,DT_DIR,len)) break; at = 1; } if (at == 1) { if (! puffs_nextdent(&dent,"..",o->parent?o->parent->ino:o->ino,DT_DIR,len)) break; at = 2; } switch (o->kind) { case FSO_FS: k = o->u.fs.children; i = 2; while (k) { if (i >= at) { if (! puffs_nextdent(&dent,k->name,k->ino,DT_DIR,len)) break <"dir">; at ++; } k = k->sibling; i ++; } if (! k) *eofp = 1; break; case FSO_REPO_ROOT: *eofp = 1; break; default: bugcheck("impossible fsobj kind %d",(int)o->kind); break; } } while (0); *off = at; return(0); } static int op_node_close(PUFFS_USERMOUNT *um, puffs_cookie_t cook, int oflags, const PUFFS_CRED *cr) { if (DBG(VERB)) verb("%s cookie %s oflags %#x cr %p",__func__,cookie_str(cook),(unsigned int)oflags,(const void *)cr); (void)um; (void)cook; (void)oflags; (void)cr; return(0); } static int op_node_lookup(PUFFS_USERMOUNT *um, puffs_cookie_t cook, PUFFS_NEWINFO *ni, const PUFFS_CN *cn) { FSOBJ *o; FSOBJ *k; if (DBG(VERB)) verb("%s lookup %s ni %p cn %p [op %s flags %x name %s len %llu consume %llu]", __func__, cookie_str(cook), (void *)ni, (const void *)cn, lookup_nameiop_str(cn->pcn_pkcnp->pkcn_nameiop), (unsigned int)cn->pcn_pkcnp->pkcn_flags, &cn->pcn_pkcnp->pkcn_name[0], (unsigned long long int)cn->pcn_pkcnp->pkcn_namelen, (unsigned long long int)cn->pcn_pkcnp->pkcn_consume); (void)um; switch (cn->pcn_pkcnp->pkcn_nameiop) { case PUFFSLOOKUP_LOOKUP: break; case PUFFSLOOKUP_CREATE: case PUFFSLOOKUP_DELETE: case PUFFSLOOKUP_RENAME: return(EROFS); break; } o = (void *)cook; if ( (cn->pcn_pkcnp->pkcn_namelen == 1) && (cn->pcn_pkcnp->pkcn_name[0] == '.') ) { puffs_newinfo_setcookie(ni,o); puffs_newinfo_setvtype(ni,VDIR); } else if ( (cn->pcn_pkcnp->pkcn_namelen == 2) && (cn->pcn_pkcnp->pkcn_name[0] == '.') && (cn->pcn_pkcnp->pkcn_name[1] == '.') ) { puffs_newinfo_setcookie(ni,o->parent?o->parent:o); puffs_newinfo_setvtype(ni,VDIR); } else { switch (o->kind) { case FSO_FS: k = o->u.fs.children; while <"search"> (k) { if ( (cn->pcn_pkcnp->pkcn_namelen == k->namelen) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],k->name,k->namelen) ) { puffs_newinfo_setcookie(ni,k); puffs_newinfo_setvtype(ni,VDIR); return(0); } k = k->sibling; } return(ENOENT); break; case FSO_REPO_ROOT: return(ENOENT); break; default: bugcheck("impossible fsobj kind %d",(int)o->kind); break; } } return(0); } static int op_node_read(PUFFS_USERMOUNT *um, puffs_cookie_t cook, uint8_t *buf, off_t off, size_t *resid, const PUFFS_CRED *cr, int ioflag) { #if 1 (void)um; (void)cook; (void)buf; (void)off; (void)resid; (void)cr; (void)ioflag; return(EIO); #else int o; int r; if (DBG(VERB)) verb("%s read %s buf %p off %lld resid %p (%llu) cr %p ioflag %d", __func__, cookie_str(cook), (void *)buf, (long long int)off, (void *)resid, (unsigned long long int)*resid, (const void *)cr, ioflag); if (cook == &cookie_root) { return(EISDIR); } else if (cook == &cookie_file) { } else { return(EINVAL); } if (off < 0) return(EINVAL); if (off >= 6) return(0); o = off; r = (*resid > 6) ? 6 : *resid; if (o+r > 6) r = 6 - o; bcopy("hello\n"+o,buf,r); *resid -= r; (void)um; (void)cook; (void)buf; (void)off; (void)resid; (void)cr; (void)ioflag; return(0); #endif } static int op_node_fsync(PUFFS_USERMOUNT *um, puffs_cookie_t cook, const PUFFS_CRED *cr, int flags, off_t offlo, off_t offhi) { if (DBG(VERB)) verb("%s cookie %s cr %p flags %d off lo %lld hi %lld",__func__,cookie_str(cook),(const void *)cr,flags,(long long int)offlo,(long long int)offhi); (void)um; (void)cook; (void)cr; (void)flags; (void)offlo; (void)offhi; return(0); } static int op_node_reclaim(PUFFS_USERMOUNT *um, puffs_cookie_t cook) { if (DBG(VERB)) verb("%s cookie %s",__func__,cookie_str(cook)); (void)um; (void)cook; return(0); } static PUFFS_OPS ops = { .puffs_fs_unmount = &op_fs_unmount, .puffs_fs_statvfs = &op_fs_statvfs, .puffs_fs_sync = &op_fs_sync, .puffs_fs_fhtonode = &op_fs_fhtonode, .puffs_fs_nodetofh = &op_fs_nodetofh, .puffs_fs_suspend = &op_fs_suspend, .puffs_fs_extattrctl = &op_fs_extattrctl, .puffs_node_lookup = &op_node_lookup, // create // mknod .puffs_node_open = &op_node_open, .puffs_node_close = &op_node_close, .puffs_node_access = &op_node_access, .puffs_node_getattr = &op_node_getattr, // setattr // poll // mmap .puffs_node_fsync = &op_node_fsync, .puffs_node_seek = &op_node_seek, // remove // link // rename // mkdir // rmdir // symlink .puffs_node_readdir = &op_node_readdir, // readlink .puffs_node_reclaim = &op_node_reclaim, .puffs_node_inactive = &op_node_inactive, // print // pathconf // advlock .puffs_node_read = &op_node_read, // write // abortop // getextattr // setextattr // listextattr // deleteextattr }; static int set_string(char **vp, char *arg, const char *what) { if (*vp) { fprintf(stderr,"%s: %s already specified\n",__progname,what); return(1); } *vp = arg; return(0); } static int set_debug(const char *s) { int o; char *comma; int l; int no; unsigned int bit; o = 0; while (1) { comma = index(s+o,','); if (comma) { l = comma - (s+o); no = (comma + 1) - s; } else { l = strlen(s+o); no = -1; } bit = 0; switch (l) { case 3: if (! bcmp(s+o,"all",3)) bit = DBG_ALL; break; case 4: switch (s[o]) { case 'c': if (! bcmp(s+o,"conf",4)) bit = DBG_CONF; break; case 'd': if (! bcmp(s+o,"dump",4)) bit = DBG_DUMP; break; case 'v': if (! bcmp(s+o,"verb",4)) bit = DBG_VERB; break; } break; } if (! bit) { fprintf(stderr,"%s: unrecognized debugging flag: %.*s\n",__progname,l,s+o); return(1); } debug |= bit; if (no < 0) return(0); o = no; } } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! config_path) { config_path = *av; } else if (! mount_path) { mount_path = *av; } else { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs = 1; } continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs = 1; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-config")) { WANTARG(); errs |= set_string(&config_path,av[skip],"config file"); continue; } if (!strcmp(*av,"-mount")) { WANTARG(); errs |= set_string(&mount_path,av[skip],"mount point"); continue; } if (!strcmp(*av,"-d")) { WANTARG(); errs |= set_debug(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (!config_path || !mount_path) { usage(); errs = 1; } if (errs) exit(1); } static void setup_puffs(void) { int e; // puffs_init frees the ops struct(!); this is undocumented(!!). { PUFFS_OPS *op; op = malloc(sizeof(PUFFS_OPS)); *op = ops; um = puffs_init(op,config_path,"gitfs",0,PUFFS_KFLAG_NOCACHE|(DBG(ALL)?PUFFS_KFLAG_ALLOPS:0)|(DBG(DUMP)?PUFFS_FLAG_OPDUMP:0)); } if (! um) { fprintf(stderr,"puffs_init failed: %s\n",strerror(errno)); exit(1); } // puffs_setncookiehash if appropriate e = puffs_mount(um,"/mnt",MNT_RDONLY|MNT_NOEXEC|MNT_NOCOREDUMP|MNT_SYMPERM|MNT_SYNCHRONOUS,fsroot); if (e) { printf("puffs_mount returned %d\n",e); exit(1); } // puffs_getselectable(um) // puffs_setblockingmode(um,xxx) // puffs_setroot if puffs_path framework is used // puffs_sethfsize if NFS-exportable } static void add_repo(const char *path, const char *repo) { REPO *r; r = malloc(sizeof(REPO)); r->path = strdup(path); r->repo = strdup(repo); r->plen = strlen(path); r->rlen = strlen(repo); r->link = repos; repos = r; } static void config_err(CONF_CTX *c, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void config_err(CONF_CTX *c, const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); if (c->etext) { if (c->elno1 == c->elno2) { fprintf(stderr,"%s: \"%s\", line %d: %s: %s\n",__progname,c->istack->path,c->elno1,s,c->etext); } else { fprintf(stderr,"%s: \"%s\", lines %d-%d: %s: %s\n",__progname,c->istack->path,c->elno1,c->elno2,s,c->etext); } } else if (es_len(&c->line)) { fprintf(stderr,"%s: \"%s\", line %d: %s: %s\n",__progname,c->istack->path,c->istack->rlno,s,es_buf(&c->line)); } else { fprintf(stderr,"%s: \"%s\", line %d: %s\n",__progname,c->istack->path,c->istack->rlno,s); } c->errs = 1; } static int parse_quoted_string(CONF_CTX *c, ES *s, const char *body, int x0, int blen) { int x; int q; es_clear(s); for (x=x0;(x (x < blen) { switch (q) { case 0: switch (body[x]) { case ' ': case '\t': break <"str">; case '\\': case '"': case '\'': q = body[x]; break; default: es_append_1(s,body[x]); break; } break; case '\\': es_append_1(s,body[x]); q = 0; break; case '"': case '\'': if (body[x] == q) { q = 0; } else { es_append_1(s,body[x]); } break; } x ++; } switch (q) { case 0: break; case '\\': config_err(c,"\\ at end-of-line"); return(-1); break; case '"': case '\'': config_err(c,"end-of-lien inside %c %c",q,q); return(-1); break; } return(x); } static int skip_ws(const char *text, int x, int len) { while ((x < len) && Cisspace(text[x])) x ++; return(x); } static const char *bad_mount_path(const char *p, int l) { int state; int x; state = 0; for (x=0;x= len) { config_err(c,"missing repo spec"); break; } es_init(&repo); do { x = parse_quoted_string(c,&repo,text,x,len); if (x < 0) break; x = skip_ws(text,x,len); if (x < len) { config_err(c,"junk after repo spec"); break; } p = es_buf(&path); l = es_len(&path); if (p[0] == '/') { config_err(c,"mount-point path must not be absolute"); break; } if ((l > 0) && (p[l-1] == '/')) { config_err(c,"mount-point path must not end with a /"); break; } em = bad_mount_path(p,l); if (em) { config_err(c,"mount-point path must not contain %s",em); break; } add_repo(es_buf(&path),es_buf(&repo)); } while (0); es_done(&repo); } while (0); es_done(&path); } static void config_push_ifile(CONF_CTX *c, const char *path) { FILE *f; CONF_ISTK *i; f = fopen(path,"r"); if (! f) { config_err(c,"can't open config file %s: %s",path,strerror(errno)); return; } i = malloc(sizeof(CONF_ISTK)); i->path = path; i->f = f; i->rlno = 0; i->link = c->istack; c->istack = i; } static void config_pct_include(CONF_CTX *c, const char *body, int blen) { int x; ES fn; es_init(&fn); x = parse_quoted_string(c,&fn,body,0,blen); if (x < 0) { es_done(&fn); return; } x = skip_ws(body,x,blen); if (x < blen) { es_done(&fn); config_err(c,"junk after %%include filename"); return; } printf("%%include: %s\n",es_buf(&fn)); config_push_ifile(c,es_buf(&fn)); es_done(&fn); } static void config_line(CONF_CTX *c, const char *text, int len) { c->etext = text; if (DBG(CONF)) printf("eff - %s\n",text); if (len < 1) bugcheck("empty config_line [%s line %d]",c->istack->path,c->istack->rlno); do { if (text[0] == '%') { if (! strncmp(text,"%include",8)) { config_pct_include(c,text+8,len-8); break; } config_err(c,"unrecognized %% line"); break; } config_repo_spec(c,text,len); } while (0); c->etext = 0; } static void config_line_raw(CONF_CTX *c) { const char *l; int n; int x; int e; l = es_buf(&c->line); n = es_len(&c->line); if (DBG(CONF)) printf("raw - \"%s\", line %d: %s\n",c->istack->path,c->istack->rlno,l); for (x=0;(x= n) return; switch (l[x]) { case '#': case ';': case '\0': return; break; } for (e=n-1;(e>=0)&&Cisspace(l[e]);e--) ; if (e < 0) bugcheck("can't find end of line [%s line %d]",c->istack->path,c->istack->rlno); if (c->partial) { if (l[e] == '\\') { es_append_n(&c->partbuf,l,e); } else { es_append_n(&c->partbuf,l,e+1); c->elno2 = c->istack->rlno; config_line(c,es_buf(&c->partbuf),es_len(&c->partbuf)); c->partial = 0; } } else { if (l[e] == '\\') { es_clear(&c->partbuf); es_append_n(&c->partbuf,l+x,e-x); c->elno1 = c->istack->rlno; c->partial = 1; } else { c->elno1 = c->istack->rlno; c->elno2 = c->elno1; config_line(c,l+x,e+1-x); } } } static void config_pop_ifile(CONF_CTX *c) { CONF_ISTK *i; i = c->istack; fclose(i->f); c->istack = i->link; free(i); } static void read_config(CONF_CTX *ctx) { int c; while (ctx->istack) { c = getc(ctx->istack->f); switch (c) { case EOF: if (feof(ctx->istack->f)) { if (es_len(&ctx->line)) { config_err(ctx,"last line has no trailing newline"); } else if (ctx->partial) { es_clear(&ctx->line); config_err(ctx,"last line ends with continuation"); } } else if (ferror(ctx->istack->f)) { config_err(ctx,"read error"); } else { bugcheck("got EOF but neither feof nor ferror"); } ctx->partial = 0; config_pop_ifile(ctx); break; case '\n': ctx->istack->rlno ++; config_line_raw(ctx); es_clear(&ctx->line); break; default: es_append_1(&ctx->line,c); break; } } } static void check_repos(void) { REPO *r1; REPO *r2; for (r1=repos;r1;r1=r1->link) { for (r2=repos;r2;r2=r2->link) { if (r2 == r1) continue; if ( (r1->plen < r2->plen) && (r2->path[r1->plen] == '/') && !bcmp(r1->path,r2->path,r1->plen) ) { fprintf(stderr,"%s: repo specs: %s is a prefix of %s\n",__progname,r1->path,r2->path); exit(1); } } } } static REPO *sort_repo_list(REPO *list) { REPO *a; REPO *b; REPO *t; REPO **lp; if (!list || !list->link) return(list); a = 0; b = 0; while (list) { t = list; list = t->link; t->link = a; a = b; b = t; } a = sort_repo_list(a); b = sort_repo_list(b); lp = &list; while (1) { if (a && (!b || (strcmp(a->path,b->path) < 0))) { t = a; a = a->link; } else if (b) { t = b; b = b->link; } else { break; } *lp = t; lp = &t->link; } *lp = 0; return(list); } static void ino_set(int x, FSOBJ *o) { int i; int j; int k; void *v; void *n; while (x >> (ino_level*INO_BASE_SHIFT)) { n = malloc(INO_BASE*sizeof(void *)); for (k=INO_BASE-1;k>0;k--) ((void **)n)[k] = 0; ((void **)n)[0] = ino_v; ino_v = n; ino_level ++; } v = ino_v; for (i=ino_level-1;i>0;i--) { j = (x >> (i * INO_BASE_SHIFT)) & (INO_BASE - 1); if (! ((void **)v)[j]) { n = malloc(INO_BASE*sizeof(void *)); for (k=INO_BASE-1;k>=0;k--) ((void **)n)[k] = 0; ((void **)v)[j] = n; v = n; } else { v = ((void **)v)[j]; } } ((void **)v)[x&(INO_BASE-1)] = o; } #if 0 static FSOBJ *ino_get(int x) { int i; void *v; while (x >> (ino_level*INO_BASE_SHIFT)) { v = malloc(INO_BASE*sizeof(void *)); for (i=INO_BASE-1;i>0;i--) ((void **)v)[i] = 0; ((void **)v)[0] = ino_v; ino_v = v; ino_level ++; } v = ino_v; for (i=ino_level-1;i>=0;i--) v = ((void **)v)[(x>>(i*INO_BASE_SHIFT))&(INO_BASE-1)]; return(v); } #endif static FSOBJ *new_fsobj(void) { FSOBJ *o; o = malloc(sizeof(FSOBJ)); o->ino = ino_n; ino_n ++; ino_set(ino_n-1,o); return(o); } static void setup_repo(REPO *r, FSOBJ *o) { r->fso_root = o; // r->fso_branch = new_fsobj(); // o->kind = FSO_REPO_ADMIN; // XXX XXX XXX;;; } static REPO *build_admin(REPO *repos, const char *pref, int preflen, FSOBJ *parent) { REPO *r; void *slash; FSOBJ *o; int l; if (parent->kind != FSO_FS) bugcheck("build_admin: impossible parent"); r = repos; while (r && (r->plen >= preflen) && !bcmp(r->path,pref,preflen)) { slash = index(r->path+preflen,'/'); if (slash) { l = ((const char *)slash) - (r->path + preflen); o = new_fsobj(); o->kind = FSO_FS; o->name = malloc(l+1); bcopy(r->path+preflen,o->name,l); o->name[l] = '\0'; o->namelen = l; o->u.fs.children = 0; o->u.fs.nchildren = 0; r = build_admin(r,r->path,preflen+l+1,o); } else { o = new_fsobj(); o->kind = FSO_REPO_ROOT; o->name = strdup(r->path+preflen); o->namelen = strlen(o->name); o->u.repo = r; setup_repo(r,o); r = r->link; } o->parent = parent; o->sibling = parent->u.fs.children; parent->u.fs.children = o; parent->u.fs.nchildren ++; } return(r); } static void dump_admin(FSOBJ *o, int indent) { FSOBJ *k; printf("%*s%p [%d]:",indent,"",(void *)o,o->ino); printf(" name=%s parent=%p kind=",o->name?o->name:"(nil)",(void *)o->parent); switch (o->kind) { case FSO_FS: printf("FS\n"); for (k=o->u.fs.children;k;k=k->sibling) dump_admin(k,indent+2); break; case FSO_REPO_ROOT: printf("REPO_ROOT %s\n",o->u.repo->repo); break; default: bugcheck("dump_admin: impossible admin kind %d",(int)o->kind); break; } } static void load_config(void) { CONF_CTX c; repos = 0; c.istack = 0; es_init(&c.line); c.partial = 0; es_init(&c.partbuf); c.errs = 0; config_push_ifile(&c,config_path); read_config(&c); check_repos(); repos = sort_repo_list(repos); ino_v = malloc(INO_BASE*sizeof(void *)); ino_level = 1; ino_n = 0; ino_set(0,0); ino_set(1,0); ino_n = 2; ino_level = 1; fsroot = new_fsobj(); fsroot->name = 0; fsroot->namelen = 0; fsroot->parent = 0; fsroot->sibling = 0; fsroot->kind = FSO_FS; fsroot->u.fs.children = 0; fsroot->u.fs.nchildren = 0; build_admin(repos,0,0,fsroot); if (DBG(CONF)) { printf("Config:\n"); dump_admin(fsroot,2); } } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); load_config(); setup_puffs(); printf("puffs_mainloop returned %d\n",puffs_mainloop(um)); return(0); }