/* * 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 (or * possibly more, eg, 4096) subdirectories, with suitable changes in * things that link to commits. */ #include #include #include #include #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, FSO_REPO_BRANCH, FSO_REPO_TAG, FSO_REPO_COMMIT, FSO_REPO_HEAD, FSO_TAG, FSO_BRANCH, FSO_COMMIT_DIR, // FSO_COMMIT_PARENTS, // FSO_COMMIT_MESSAGE, // FSO_COMMIT_ENCODING, // FSO_COMMIT_AUTHOR, // FSO_COMMIT_TIME, // FSO_COMMIT_TREE, } FSOKIND; typedef enum { HT_COMMIT = 1, HT_BRANCH, } HEADTYPE; typedef struct conf_ctx CONF_CTX; typedef struct conf_istk CONF_ISTK; typedef struct repo REPO; typedef struct fsobj FSOBJ; typedef struct ref REF; typedef struct commit COMMIT; struct commit { unsigned char id[20]; FSOBJ *fso; } ; 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_* REF *tag; // FSO_TAG REF *branch; // FSO_BRANCH struct { // FSO_COMMIT_* REPO *r; int cx; } commit; } u; } ; struct ref { REF *link; REPO *repo; FSOBJ *fso; char *name; int namelen; char commit[40]; } ; struct repo { REPO *link; char *path; char *repo; int plen; int rlen; char *gitarg; time_t expire; FSOBJ *fso_root; FSOBJ *fso_branch; FSOBJ *fso_tag; FSOBJ *fso_commit; FSOBJ *fso_HEAD; HEADTYPE ht; union { char commit[42]; struct { char *name; int namelen; } branch; } u_head; REF *tags; REF *branches; COMMIT *commits; unsigned int ncommits; } ; 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)) #define HEAP_L(x) (((x)*2)+1) #define HEAP_R(x) (((x)+1)*2) #define HEAP_U(x) (((x)-1)>>1) 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 int allhex(const char *s, int n) { for (;n>0;n--,s++) { switch (*s) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': break; default: return(0); break; } } return(1); } static int commit_cmp(const unsigned char *a, const unsigned char *b) { int i; for (i=0;i<20;i++) { if (a[i] != b[i]) return((int)a[i]-(int)b[i]); } return(0); } 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 void getattr_dir(VATTR *va, int ino) { struct timeval tv; gettimeofday(&tv,0); va->va_type = VDIR; va->va_mode = S_IFDIR | 0555; va->va_uid = 0; va->va_gid = 0; va->va_fileid = 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; } #if 0 static void getattr_reg(VATTR *va, int ino) { struct timeval tv; gettimeofday(&tv,0); va->va_type = VREG; va->va_mode = S_IFREG | 0444; va->va_uid = 0; va->va_gid = 0; va->va_fileid = ino; 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; } #endif static void getattr_lnk(VATTR *va, int ino) { struct timeval tv; gettimeofday(&tv,0); va->va_type = VLNK; va->va_mode = S_IFLNK | 0555; va->va_uid = 0; va->va_gid = 0; va->va_fileid = ino; 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; } static int repo_HEAD_size(REPO *r) { switch (r->ht) { case HT_COMMIT: // 7 = strlen("commit/"), 40 = length of commit ID text return(7+40); break; case HT_BRANCH: // 7 = strlen("branch/") return(7+r->u_head.branch.namelen); break; default: bugcheck("impossible repo HEAD type %d",(int)r->ht); break; } } static int op_node_getattr(PUFFS_USERMOUNT *um, puffs_cookie_t cook, VATTR *va, const PUFFS_CRED *cr) { FSOBJ *o; if (DBG(VERB)) verb("%s cookie %s va %p cr %p",__func__,cookie_str(cook),(void *)va, (const void *)cr); (void)um; o = (void *)cook; switch (o->kind) { case FSO_FS: getattr_dir(va,o->ino); va->va_nlink = 2 + o->u.fs.nchildren; break; case FSO_REPO_ROOT: getattr_dir(va,o->ino); va->va_nlink = 5; break; case FSO_REPO_BRANCH: case FSO_REPO_TAG: case FSO_COMMIT_DIR: getattr_dir(va,o->ino); va->va_nlink = 2; break; case FSO_REPO_COMMIT: getattr_dir(va,o->ino); va->va_nlink = 2 + o->u.repo->ncommits; break; case FSO_REPO_HEAD: getattr_lnk(va,o->ino); va->va_size = repo_HEAD_size(o->u.repo); va->va_nlink = 1; break; case FSO_TAG: case FSO_BRANCH: getattr_lnk(va,o->ino); va->va_size = 10 + 40; // ../commit/ va->va_nlink = 1; 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; 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; switch (o->kind) { case FSO_FS: case FSO_REPO_ROOT: case FSO_REPO_BRANCH: case FSO_REPO_TAG: case FSO_REPO_COMMIT: case FSO_COMMIT_DIR: break; case FSO_REPO_HEAD: case FSO_TAG: case FSO_BRANCH: return(ENOTDIR); break; default: bugcheck("impossible fsobj kind %d",(int)o->kind); break; } 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: { FSOBJ *k; 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: if (at == 2) { if (! puffs_nextdent(&dent,"branch",o->u.repo->fso_branch->ino,DT_DIR,len)) break; at = 3; } if (at == 3) { if (! puffs_nextdent(&dent,"tag",o->u.repo->fso_tag->ino,DT_DIR,len)) break; at = 4; } if (at == 4) { if (! puffs_nextdent(&dent,"commit",o->u.repo->fso_commit->ino,DT_DIR,len)) break; at = 5; } if (at == 5) { if (! puffs_nextdent(&dent,"HEAD",o->u.repo->fso_HEAD->ino,DT_DIR,len)) break; at = 6; } if (at >= 6) *eofp = 1; break; case FSO_REPO_BRANCH: { REF *ref; ref = o->u.repo->branches; if (0) { case FSO_REPO_TAG: ref = o->u.repo->tags; } for (i=2;ref;ref=ref->link,i++) { if (i >= at) { if (! puffs_nextdent(&dent,ref->name,ref->fso->ino,DT_LNK,len)) break <"dir">; at ++; } } if (! ref) *eofp = 1; } break; case FSO_REPO_COMMIT: { int x; int n; char textform[41]; COMMIT *c; n = o->u.repo->ncommits; for (x=at-2;xu.repo->commits[x]; sprintf(&textform[0],"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", c->id[ 0], c->id[ 1], c->id[ 2], c->id[ 3], c->id[ 4], c->id[ 5], c->id[ 6], c->id[ 7], c->id[ 8], c->id[ 9], c->id[10], c->id[11], c->id[12], c->id[13], c->id[14], c->id[15], c->id[16], c->id[17], c->id[18], c->id[19] ); if (! puffs_nextdent(&dent,&textform[0],c->fso->ino,DT_REG,len)) break <"dir">; at ++; } } break; case FSO_COMMIT_DIR: *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; 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; switch (o->kind) { case FSO_FS: case FSO_REPO_ROOT: case FSO_REPO_BRANCH: case FSO_REPO_TAG: case FSO_REPO_COMMIT: case FSO_COMMIT_DIR: break; case FSO_REPO_HEAD: case FSO_TAG: case FSO_BRANCH: return(ENOTDIR); break; default: bugcheck("impossible fsobj kind %d",(int)o->kind); break; } 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: { FSOBJ *k; 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: if ( (cn->pcn_pkcnp->pkcn_namelen == 6) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],"branch",6) ) { puffs_newinfo_setcookie(ni,o->u.repo->fso_branch); puffs_newinfo_setvtype(ni,VDIR); } else if ( (cn->pcn_pkcnp->pkcn_namelen == 3) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],"tag",3) ) { puffs_newinfo_setcookie(ni,o->u.repo->fso_tag); puffs_newinfo_setvtype(ni,VDIR); } else if ( (cn->pcn_pkcnp->pkcn_namelen == 6) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],"commit",6) ) { puffs_newinfo_setcookie(ni,o->u.repo->fso_commit); puffs_newinfo_setvtype(ni,VDIR); } else if ( (cn->pcn_pkcnp->pkcn_namelen == 4) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],"HEAD",4) ) { puffs_newinfo_setcookie(ni,o->u.repo->fso_HEAD); puffs_newinfo_setvtype(ni,VLNK); puffs_newinfo_setsize(ni,repo_HEAD_size(o->u.repo)); } else { return(ENOENT); } break; case FSO_REPO_BRANCH: { REF *ref; ref = o->u.repo->branches; if (0) { case FSO_REPO_TAG: ref = o->u.repo->tags; } for (;ref;ref=ref->link) { if ( (cn->pcn_pkcnp->pkcn_namelen == ref->namelen) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],ref->name,ref->namelen) ) { puffs_newinfo_setcookie(ni,ref->fso); puffs_newinfo_setvtype(ni,VLNK); puffs_newinfo_setsize(ni,10+40); return(0); } } } return(ENOENT); break; case FSO_REPO_COMMIT: { unsigned char id[20]; unsigned int iv[20]; int i; int l; int h; int m; REPO *r; r = o->u.repo; if ( (cn->pcn_pkcnp->pkcn_namelen != 40) || !allhex(&cn->pcn_pkcnp->pkcn_name[0],40) || (sscanf(&cn->pcn_pkcnp->pkcn_name[0], "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x", &iv[ 0],&iv[ 1],&iv[ 2],&iv[ 3],&iv[ 4],&iv[ 5],&iv[ 6],&iv[ 7],&iv[ 8],&iv[ 9], &iv[10],&iv[11],&iv[12],&iv[13],&iv[14],&iv[15],&iv[16],&iv[17],&iv[18],&iv[19]) != 20) ) return(ENOENT); for (i=20-1;i>=0;i--) id[i] = iv[i]; l = -1; h = r->ncommits; while (h-l > 1) { m = (h + l) >> 1; i = commit_cmp(&id[0],&r->commits[m].id[0]); if (i < 0) { h = m; } else if (i > 0) { l = m; } else { puffs_newinfo_setcookie(ni,r->commits[m].fso); puffs_newinfo_setvtype(ni,VDIR); return(0); } } } return(ENOENT); break; case FSO_COMMIT_DIR: 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) { FSOBJ *o; 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); o = (void *)cook; switch (o->kind) { case FSO_FS: case FSO_REPO_ROOT: case FSO_REPO_BRANCH: case FSO_REPO_TAG: case FSO_REPO_COMMIT: return(EISDIR); break; case FSO_REPO_HEAD: case FSO_TAG: case FSO_BRANCH: return(EOPNOTSUPP); break; default: bugcheck("impossible fsobj kind %d",(int)o->kind); break; } #if 0 int at; int r; if (off < 0) return(EINVAL); if (off >= 41) return(0); at = off; r = (*resid > 41) ? 41 : *resid; if (at+r > 41) r = 41 - at; bcopy(&o->u.repo->HEAD_text[at],buf,r); *resid -= r; #endif (void)um; (void)cook; (void)buf; (void)off; (void)resid; (void)cr; (void)ioflag; return(0); } 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 void readlink_twopart(char *link, size_t *linklen, const char *p1, int l1, const char *p2, int l2) { if (l1+l2 <= *linklen) { bcopy(p1,link,l1); bcopy(p2,link+l1,l2); *linklen = l1 + l2; } else if (l1 < *linklen) { bcopy(p1,link,l1); bcopy(p2,link+l1,linklen[0]-l1); } else { bcopy(p1,link,*linklen); } } static int op_node_readlink(PUFFS_USERMOUNT *um, puffs_cookie_t cook, const PUFFS_CRED *cr, char *link, size_t *linklen) { FSOBJ *o; if (DBG(VERB)) verb("%s cookie %s cr %p link %p len %p (%llu)",__func__,cookie_str(cook),(const void *)cr,(void *)link,(void *)linklen,(unsigned long long int)*linklen); (void)um; o = (void *)cook; switch (o->kind) { case FSO_FS: case FSO_REPO_ROOT: case FSO_REPO_BRANCH: case FSO_REPO_TAG: case FSO_REPO_COMMIT: return(EINVAL); // XXX wish we had ENOTLINK! break; case FSO_REPO_HEAD: switch (o->u.repo->ht) { case HT_COMMIT: readlink_twopart(link,linklen,"commit/",7,&o->u.repo->u_head.commit[0],40); break; case HT_BRANCH: readlink_twopart(link,linklen,"branch/",7,o->u.repo->u_head.branch.name,o->u.repo->u_head.branch.namelen); break; default: bugcheck("impossible repo HEAD type %d",(int)o->u.repo->ht); break; } break; case FSO_TAG: readlink_twopart(link,linklen,"../commit/",10,&o->u.tag->commit[0],40); break; case FSO_BRANCH: readlink_twopart(link,linklen,"../commit/",10,&o->u.branch->commit[0],40); break; default: bugcheck("impossible fsobj kind %d",(int)o->kind); break; } 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, .puffs_node_readlink = &op_node_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; printf("repo %p: %s %s\n",(void *)r,path,repo); } 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; if (x >> (ino_level*INO_BASE_SHIFT)) return(0); v = ino_v; for (i=ino_level-1;i>=0;i--) { if (! v) return(0); v = ((void **)v)[(x>>(i*INO_BASE_SHIFT))&(INO_BASE-1)]; } return(v); } #endif static FSOBJ *new_fsobj_i(int ino) { FSOBJ *o; o = malloc(sizeof(FSOBJ)); o->ino = ino; ino_set(ino,o); return(o); } static FSOBJ *new_fsobj(void) { return(new_fsobj_i(ino_n++)); } static void run_git(ES *es_o, ES *es_e, ...) { typedef struct o O; struct o { int fd; ES *es; const char *what; } ; va_list ap; int totlen; char *arg; int nargs; char **args; char *allargs; int ao; int ax; int op[2]; int ep[2]; int xp[2]; pid_t kid; pid_t dead; int e; int n; struct pollfd pfd[2]; O o[2]; ES own[2]; int no; int i; unsigned char rbuf[512]; int nr; nargs = 0; totlen = 3 + 1; // strlen("git")+1 va_start(ap,es_e); while (1) { arg = va_arg(ap,char *); if (! arg) break; totlen += strlen(arg) + 1; nargs ++; } va_end(ap); args = malloc((1+nargs+1)*sizeof(char *)); allargs = malloc(totlen); ao = 0; ax = 0; strcpy(allargs+ao,"git"); args[ax++] = allargs + ao; ao += 3 + 1; va_start(ap,es_e); while (1) { arg = va_arg(ap,char *); if (! arg) break; strcpy(allargs+ao,arg); args[ax++] = allargs + ao; ao += strlen(arg) + 1; } va_end(ap); if (ax != nargs+1) bugcheck("run_git: args count mismatch"); if (ao != totlen) bugcheck("run_git: args length mismatch"); args[ax] = 0; if ((pipe(&op[0]) < 0) || (pipe(&ep[0]) < 0)) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } if (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[0]) < 0) { fprintf(stderr,"%s: exec socketpair: %s\n",__progname,strerror(errno)); exit(1); } fflush(0); kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { close(op[0]); close(ep[0]); close(xp[0]); fcntl(xp[1],F_SETFD,1); dup2(op[1],1); close(op[1]); dup2(ep[1],2); close(ep[1]); execvp("git",args); e = errno; write(xp[1],&e,sizeof(int)); _exit(1); } close(op[1]); close(ep[1]); close(xp[1]); n = recv(xp[0],&e,sizeof(int),MSG_WAITALL); if (n < 0) { fprintf(stderr,"%s: exec recv: %s\n",__progname,strerror(e)); exit(1); } if (n == sizeof(int)) { fprintf(stderr,"%s: exec git: %s\n",__progname,strerror(e)); exit(1); } if (n != 0) { fprintf(stderr,"%s: exec protocol error: expected %d or 0, got %d\n",__progname,(int)sizeof(int),n); exit(1); } close(xp[0]); es_init(&own[0]); es_init(&own[1]); fcntl(op[0],F_SETFL,fcntl(op[0],F_GETFL,0)|O_NONBLOCK); fcntl(ep[0],F_SETFL,fcntl(ep[0],F_GETFL,0)|O_NONBLOCK); o[0] = (O){ .fd = op[0], .what = "output" }; o[1] = (O){ .fd = ep[0], .what = "error" }; o[0].es = es_o ? es_o : &own[0]; o[1].es = es_e ? es_e : &own[1]; no = 2; while (no > 0) { if (o[0].fd < 0) { if (no < 2) break; o[0] = o[1]; no = 1; continue; } if ((no > 1) && (o[1].fd < 0)) no = 1; pfd[0] = (struct pollfd){ .fd = o[0].fd, .events = POLLIN | POLLRDNORM }; if (no > 1) pfd[1] = (struct pollfd){ .fd = o[1].fd, .events = POLLIN | POLLRDNORM }; n = poll(&pfd[0],no,INFTIM); if (n < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } for (i=no-1;i>=0;i--) { if (pfd[i].revents & (POLLIN|POLLRDNORM|POLLERR|POLLHUP|POLLNVAL)) { nr = read(o[i].fd,&rbuf[0],sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: continue; break; } fprintf(stderr,"%s: reading git standard %s: %s\n",__progname,o[i].what,strerror(errno)); exit(1); } if (nr == 0) { close(o[i].fd); o[i].fd = -1; } else { es_append_n(o[i].es,&rbuf[0],nr); } } } } e = 0; if (!es_o && es_len(&own[0])) { fprintf(stderr,"%s: git produced unexpected standard output:\n",__progname); fwrite(es_buf(&own[0]),1,es_len(&own[0]),stderr); e = 1; } if (!es_e && es_len(&own[1])) { fprintf(stderr,"%s: git produced unexpected standard error:\n",__progname); fwrite(es_buf(&own[1]),1,es_len(&own[1]),stderr); e = 1; } if (e) exit(1); while (1) { dead = wait(0); if (dead < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: wait: %s\n",__progname,strerror(errno)); exit(1); } if (dead == kid) break; } } static int check_exists(const char *dir, const char *name, mode_t type) { struct stat stb; char *p; asprintf(&p,"%s/%s",dir,name); if (stat(p,&stb) < 0) { free(p); return(0); } free(p); if (type && ((stb.st_mode & S_IFMT) != type)) return(0); return(1); } static void setup_refs(REPO *r, const char *text, int len) { int at; char *nl; int e; REF *ref; FSOKIND k; int l; REF **head; FSOBJ *parent; const char *textkind; r->tags = 0; r->branches = 0; at = 0; while (at < len) { nl = memchr(text+at,'\n',len-at); if (! nl) { fprintf(stderr,"%s: %s: no trailing newline in git show-ref --tags output\n",__progname,r->repo); exit(1); } e = nl - text; if (e-at < 42) { fprintf(stderr,"%s: %s: git show-ref --tags output line too short\n",__progname,r->repo); exit(1); } if ( (text[at+40] == ' ') && allhex(text+at,40) ) { do { if (! bcmp(text+at+41,"refs/tags/",10)) { k = FSO_TAG; l = 10; parent = r->fso_tag; head = &r->tags; textkind = "tag"; } else if (! bcmp(text+at+41,"refs/heads/",11)) { k = FSO_BRANCH; l = 11; parent = r->fso_branch; head = &r->branches; textkind = "branch"; } else { break; } ref = malloc(sizeof(REF)); ref->repo = r; ref->namelen = e - (at + 41 + l); ref->name = malloc(ref->namelen+1); bcopy(text+at+41+l,ref->name,ref->namelen); ref->name[ref->namelen] = '\0'; bcopy(text+at,&ref->commit[0],40); ref->fso = new_fsobj(); printf("setup %d: %p: %s %s for %s\n",ref->fso->ino,(void *)ref->fso,textkind,ref->name,r->path); ref->fso->kind = k; ref->fso->name = ref->name; ref->fso->namelen = ref->namelen; switch (k) { case FSO_TAG: ref->fso->u.tag = ref; break; case FSO_BRANCH: ref->fso->u.branch = ref; break; default: bugcheck("impossible kind %d",(int)k); break; } ref->fso->parent = parent; ref->link = *head; *head = ref; } while (0); } at = e + 1; } } static void commit_heap_down(COMMIT *v, int n, int x) { int x0; int l; int r; int s; COMMIT t; x0 = x; t = v[x]; while (1) { l = HEAP_L(x); r = HEAP_R(x); if ((l >= n) || (commit_cmp(&t.id[0],&v[l].id[0]) >= 0)) { if ((r >= n) || (commit_cmp(&t.id[0],&v[r].id[0]) >= 0)) { if (x != x0) v[x] = t; return; } else { s = r; } } else { if ((r >= n) || (commit_cmp(&t.id[0],&v[r].id[0]) >= 0)) { s = l; } else { s = (commit_cmp(&v[r].id[0],&v[l].id[0]) >= 0) ? r : l; } } v[x] = v[s]; x = s; } } static void setup_commits(REPO *r, const char *text, int len) { int i; int j; COMMIT t; unsigned int s[20]; FSOBJ *o; if (len % 41) { fprintf(stderr,"%s: commit list for %s size wrong\n",__progname,r->repo); return; } r->ncommits = len / 41; r->commits = malloc(r->ncommits*sizeof(*r->commits)); for (i=r->ncommits-1;i>=0;i--) { if (sscanf(text+(i*41),"%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x", &s[ 0], &s[ 1], &s[ 2], &s[ 3], &s[ 4], &s[ 5], &s[ 6], &s[ 7], &s[ 8], &s[ 9], &s[10], &s[11], &s[12], &s[13], &s[14], &s[15], &s[16], &s[17], &s[18], &s[19]) == 20) { for (j=20-1;j>=0;j--) r->commits[i].id[j] = s[j]; } else { if (DBG(CONF)) printf("Note: config line %d didn't scan\n",i); if (i < r->ncommits-1) { r->commits[i] = r->commits[r->ncommits-1]; } r->ncommits --; } } for (i=HEAP_U(r->ncommits-1);i>=0;i--) commit_heap_down(r->commits,r->ncommits,i); for (i=r->ncommits-1;i>0;i--) { t = r->commits[0]; r->commits[0] = r->commits[i]; r->commits[i] = t; commit_heap_down(r->commits,i,0); } if (DBG(CONF)) { printf("Commit list [%d]\n",r->ncommits); for (i=0;incommits;i++) { printf(" %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", r->commits[i].id[ 0], r->commits[i].id[ 1], r->commits[i].id[ 2], r->commits[i].id[ 3], r->commits[i].id[ 4], r->commits[i].id[ 5], r->commits[i].id[ 6], r->commits[i].id[ 7], r->commits[i].id[ 8], r->commits[i].id[ 9], r->commits[i].id[10], r->commits[i].id[11], r->commits[i].id[12], r->commits[i].id[13], r->commits[i].id[14], r->commits[i].id[15], r->commits[i].id[16], r->commits[i].id[17], r->commits[i].id[18], r->commits[i].id[19] ); } } ino_n += r->ncommits; for (i=r->ncommits-1;i>=0;i--) { o = new_fsobj(); printf("setup %d: %p: commit %d for %s\n",o->ino,(void *)o,i,r->path); r->commits[i].fso = o; o->kind = FSO_COMMIT_DIR; asprintf(&o->name,"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", r->commits[i].id[ 0], r->commits[i].id[ 1], r->commits[i].id[ 2], r->commits[i].id[ 3], r->commits[i].id[ 4], r->commits[i].id[ 5], r->commits[i].id[ 6], r->commits[i].id[ 7], r->commits[i].id[ 8], r->commits[i].id[ 9], r->commits[i].id[10], r->commits[i].id[11], r->commits[i].id[12], r->commits[i].id[13], r->commits[i].id[14], r->commits[i].id[15], r->commits[i].id[16], r->commits[i].id[17], r->commits[i].id[18], r->commits[i].id[19]); o->namelen = 41; o->u.commit.r = r; o->u.commit.cx = i; o->parent = r->fso_commit; } } static void setup_repo(REPO *r, FSOBJ *o) { static char name_branch[] = "branch"; static char name_tag[] = "tag"; static char name_commit[] = "commit"; static char name_HEAD[] = "HEAD"; char *p; FILE *f; char rbuf[256]; int rlen; ES e; r->fso_root = o; o = new_fsobj(); printf("setup %d: %p: branch for %s\n",o->ino,(void *)o,r->path); r->fso_branch = o; o->kind = FSO_REPO_BRANCH; o->name = &name_branch[0]; o->namelen = sizeof(name_branch) - 1; o->u.repo = r; o->parent = r->fso_root; o = new_fsobj(); printf("setup %d: %p: tag for %s\n",o->ino,(void *)o,r->path); r->fso_tag = o; o->kind = FSO_REPO_TAG; o->name = &name_tag[0]; o->namelen = sizeof(name_tag) - 1; o->u.repo = r; o->parent = r->fso_root; o = new_fsobj(); printf("setup %d: %p: commit for %s\n",o->ino,(void *)o,r->path); r->fso_commit = o; o->kind = FSO_REPO_COMMIT; o->name = &name_commit[0]; o->namelen = sizeof(name_commit) - 1; o->u.repo = r; o->parent = r->fso_root; o = new_fsobj(); printf("setup %d: %p: HEAD for %s\n",o->ino,(void *)o,r->path); r->fso_HEAD = o; o->kind = FSO_REPO_HEAD; o->name = &name_HEAD[0]; o->namelen = sizeof(name_HEAD) - 1; o->u.repo = r; o->parent = r->fso_root; if (!( check_exists(r->repo,".",S_IFDIR) && check_exists(r->repo,"HEAD",0) && check_exists(r->repo,"branches",S_IFDIR) && check_exists(r->repo,"config",S_IFREG) && check_exists(r->repo,"objects",S_IFDIR) )) { fprintf(stderr,"%s: %s: doesn't look like a git repo\n",__progname,r->repo); exit(1); } asprintf(&r->gitarg,"--git-dir=%s",r->repo); asprintf(&p,"%s/HEAD",r->repo); f = fopen(p,"r"); if (! f) { fprintf(stderr,"%s: %s: can't fopen HEAD: %s\n",__progname,r->repo,strerror(errno)); exit(1); } rlen = fread(&rbuf[0],1,sizeof(rbuf),f); if (rlen == sizeof(rbuf)) { fprintf(stderr,"%s: %s: HEAD is unreasonably large\n",__progname,r->repo); exit(1); } if (ferror(f)) { fprintf(stderr,"%s: %s: error reading HEAD: %s\n",__progname,r->repo,strerror(errno)); exit(1); } fclose(f); if ( (rlen == 41) && (rbuf[40] == '\n') && allhex(&rbuf[0],40) ) { r->ht = HT_COMMIT; bcopy(&rbuf[0],&r->u_head.commit[0],41); r->u_head.commit[41] = '\0'; } if ( (rlen > 17) && (rbuf[rlen-1] == '\n') && !bcmp(&rbuf[0],"ref: refs/heads/",16) && !memchr(&rbuf[16],'/',rlen-17) ) { r->ht = HT_BRANCH; r->u_head.branch.namelen = rlen - 17; r->u_head.branch.name = malloc((rlen-17)+1); bcopy(&rbuf[16],r->u_head.branch.name,rlen-17); r->u_head.branch.name[rlen-17] = '\0'; } es_init(&e); run_git(&e,0,r->gitarg,"show-ref","--tags","--heads",(char *)0); setup_refs(r,es_buf(&e),es_len(&e)); es_clear(&e); run_git(&e,0,r->gitarg,"log","--format=tformat:%H","--all",(char *)0); setup_commits(r,es_buf(&e),es_len(&e)); es_done(&e); } static REPO *build_fs(REPO *repos, const char *pref, int preflen, FSOBJ *parent) { REPO *r; void *slash; FSOBJ *o; int l; if (parent->kind != FSO_FS) bugcheck("build_fs: 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; printf("setup %d: %p: fs %s\n",o->ino,(void *)o,o->name); r = build_fs(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; printf("setup %d: %p: repo root %s\n",o->ino,(void *)o,r->path); 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_fs(FSOBJ *o, int indent) { 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: { FSOBJ *k; printf("FS\n"); for (k=o->u.fs.children;k;k=k->sibling) dump_fs(k,indent+2); } break; case FSO_REPO_ROOT: printf("REPO_ROOT %s\n",o->u.repo->repo); dump_fs(o->u.repo->fso_branch,indent+2); dump_fs(o->u.repo->fso_tag,indent+2); dump_fs(o->u.repo->fso_commit,indent+2); dump_fs(o->u.repo->fso_HEAD,indent+2); break; case FSO_REPO_BRANCH: { REF *r; printf("REPO_BRANCH %s\n",o->u.repo->repo); for (r=o->u.repo->branches;r;r=r->link) dump_fs(r->fso,indent+2); } break; case FSO_REPO_TAG: { REF *r; printf("REPO_TAG %s\n",o->u.repo->repo); for (r=o->u.repo->tags;r;r=r->link) dump_fs(r->fso,indent+2); } break; case FSO_REPO_COMMIT: printf("REPO_COMMIT %s\n",o->u.repo->repo); break; case FSO_REPO_HEAD: printf("REPO_HEAD %s\n",o->u.repo->repo); break; case FSO_TAG: printf("TAG %s %s\n",o->u.tag->repo->repo,o->u.tag->name); break; case FSO_BRANCH: printf("BRANCH %s %s\n",o->u.branch->repo->repo,o->u.branch->name); break; default: bugcheck("dump_fs: impossible fsobj 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(); printf("setup %d: %p: mountpoint root\n",fsroot->ino,(void *)fsroot); 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_fs(repos,0,0,fsroot); if (DBG(CONF)) { printf("Config:\n"); dump_fs(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); }