/* * 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 printed 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. * ino * Prints out "inode" setup (the correspnodence * between inumbers and objects). * * 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 directory "tree" which contains the working tree * corresponding to that commit. * - 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 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; // In the comments, here, $-prefixed variables are metasyntax. $COMMIT // can be 40-char hex (if not bigrepo) or two-char hex, slash, 40-char // hex (if bigrepo). typedef enum { FSO_FS = 1, FSO_REPO_ROOT, // $REPO itself FSO_REPO_BRANCH, // $REPO/branch FSO_REPO_TAG, // $REPO/tag FSO_REPO_COMMIT, // $REPO/commit FSO_REPO_HEAD, // $REPO/HEAD FSO_TAG, // $REPO/tag FSO_BRANCH, // $REPO/branch FSO_BIG_SUB, // $REPO/commit/$XX (if bigrepo) FSO_COMMIT_DIR, // $REPO/commit/$COMMIT FSO_COMMIT_PARENTS, // $REPO/commit/$COMMIT/parents FSO_COMMIT_MESSAGE, // $REPO/commit/$COMMIT/message FSO_COMMIT_ENCODING, // $REPO/commit/$COMMIT/encoding FSO_COMMIT_AUTHOR, // $REPO/commit/$COMMIT/author FSO_COMMIT_TIME, // $REPO/commit/$COMMIT/time FSO_COMMIT_TREE, // $REPO/commit/$COMMIT/tree FSO_PARENT_N, // $REPO/commit/$COMMIT/parents/$N } 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; typedef struct parent PARENT; struct parent { COMMIT *c; FSOBJ *fso; } ; struct commit { unsigned char id[20]; FSOBJ *fso_dir; FSOBJ *fso_parents; FSOBJ *fso_tree; FSOBJ *fso_message; FSOBJ *fso_encoding; FSOBJ *fso_author; FSOBJ *fso_time; PARENT *parents; int nparents; char *msg; int msglen; char *encoding; int enclen; char *author; int authlen; unsigned long long int time; int timelen; } ; 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_* struct { // FSO_BIG_SUB REPO *r; unsigned int inx; } sub; REF *tag; // FSO_TAG REF *branch; // FSO_BRANCH struct { // FSO_COMMIT_* REPO *r; int cx; } commit; struct { // FSO_PARENT_N COMMIT *c; int n; } parent_n; } 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; union { FSOBJ *small; struct { FSOBJ **v; // [257], [256] is containing dir int *counts; // [256] } big; } fso_commit; FSOBJ *fso_HEAD; HEADTYPE ht; union { char commit[40]; struct { char *name; int namelen; } branch; } u_head; REF *tags; REF *branches; COMMIT *commits; unsigned int ncommits; unsigned int ngroup; // undefined unless big is true int big; } ; 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; int bigrepo; } ; 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_INO 0x00000010 #define DBG(x) (debug & DBG_##x) static REPO *repos; static FSOBJ *fsroot; static void *ino_v; static int ino_level; static int ino_n; static char name_HEAD[] = "HEAD"; static char name_author[] = "author"; static char name_branch[] = "branch"; static char name_commit[] = "commit"; static char name_encoding[] = "encoding"; static char name_message[] = "message"; static char name_parents[] = "parents"; static char name_tag[] = "tag"; static char name_time[] = "time"; static char name_tree[] = "tree"; static const char hexdigits[16] = "0123456789abcdef"; #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 int scan_hex(const char *s, unsigned char *v, int nb) { int i; int x; int dv; unsigned char b; i = 0; for (x=0;x> 4; } s[x] = hexdigits[d&15]; } s[40] = '\0'; } static int find_commit(REPO *r, const unsigned char *id) { int l; int m; int h; int c; l = -1; h = r->ncommits; while (h-l > 1) { m = (h + l) >> 1; c = commit_cmp(&id[0],&r->commits[m].id[0]); if (c < 0) { h = m; } else if (c > 0) { l = m; } else { return(m); } } return(-1); } static int find_commit_min(REPO *r, unsigned char x) { int l; int m; int h; l = -1; h = r->ncommits; while (h-l > 1) { m = (h + l) >> 1; if (r->commits[m].id[0] >= x) { h = m; } else { l = m; } } return(h); } 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; } 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; } 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_commit_len(REPO *r) { return(r->big?43:40); } static int repo_HEAD_size(REPO *r) { switch (r->ht) { case HT_COMMIT: // 7 = strlen("commit/") return(7+repo_commit_len(r)); 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_PARENTS: case FSO_COMMIT_TREE: 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->big ? o->u.repo->ngroup : 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 + repo_commit_len(o->u.tag->repo); // ../commit/ va->va_nlink = 1; break; case FSO_COMMIT_DIR: getattr_dir(va,o->ino); va->va_nlink = 4; break; case FSO_BIG_SUB: if (! o->u.sub.r->big) bugcheck("non-big FSO_BIG_SUB"); getattr_dir(va,o->ino); va->va_nlink = 2 + o->u.sub.r->fso_commit.big.counts[o->u.sub.inx]; break; case FSO_PARENT_N: { REPO *r; r = o->u.parent_n.c->fso_dir->u.commit.r; getattr_lnk(va,o->ino); va->va_size = (r->big ? 9 : 6) + repo_commit_len(r); va->va_nlink = 1; } break; case FSO_COMMIT_MESSAGE: getattr_reg(va,o->ino); va->va_size = o->u.commit.r->commits[o->u.commit.cx].msglen; va->va_nlink = 1; break; case FSO_COMMIT_ENCODING: getattr_reg(va,o->ino); va->va_size = o->u.commit.r->commits[o->u.commit.cx].enclen; va->va_nlink = 1; break; case FSO_COMMIT_AUTHOR: getattr_reg(va,o->ino); va->va_size = o->u.commit.r->commits[o->u.commit.cx].authlen; va->va_nlink = 1; break; case FSO_COMMIT_TIME: getattr_reg(va,o->ino); va->va_size = o->u.commit.r->commits[o->u.commit.cx].timelen; 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: case FSO_COMMIT_PARENTS: case FSO_COMMIT_TREE: case FSO_BIG_SUB: break; case FSO_REPO_HEAD: case FSO_TAG: case FSO_BRANCH: case FSO_PARENT_N: case FSO_COMMIT_MESSAGE: case FSO_COMMIT_ENCODING: case FSO_COMMIT_AUTHOR: case FSO_COMMIT_TIME: 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 ++; } *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 (o->u.repo->big) { if (! puffs_nextdent(&dent,"commit",o->u.repo->fso_commit.big.v[256]->ino,DT_DIR,len)) break; } else { if (! puffs_nextdent(&dent,"commit",o->u.repo->fso_commit.small->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; } *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 ++; } } *eofp = 1; } break; case FSO_REPO_COMMIT: if (o->u.repo->big) { int i; char textform[3]; FSOBJ *sub; for (i=at-2;i<256;i++) { sub = o->u.repo->fso_commit.big.v[i]; if (sub) { textform[0] = hexdigits[i>>4]; textform[1] = hexdigits[i&15]; textform[2] = '\0'; if (! puffs_nextdent(&dent,&textform[0],sub->ino,DT_DIR,len)) break <"dir">; } at ++; } } else { int x; int n; char textform[41]; COMMIT *c; n = o->u.repo->ncommits; for (x=at-2;xu.repo->commits[x]; format_id(&textform[0],&c->id[0]); if (! puffs_nextdent(&dent,&textform[0],c->fso_dir->ino,DT_DIR,len)) break <"dir">; at ++; } } *eofp = 1; break; case FSO_BIG_SUB: { int x; int n; int min; char textform[41]; COMMIT *c; if (! o->u.sub.r->big) bugcheck("impossible FSO_BIG_SUB"); min = find_commit_min(o->u.sub.r,o->u.sub.inx); n = o->u.repo->fso_commit.big.counts[o->u.sub.inx]; for (x=at-2;xu.repo->commits[min+x]; if (c->id[0] != o->u.sub.inx) bugcheck("BIG_SUB readdir returning mismatched commit"); format_id(&textform[0],&c->id[0]); if (! puffs_nextdent(&dent,&textform[0],c->fso_dir->ino,DT_DIR,len)) break <"dir">; at ++; } } *eofp = 1; break; case FSO_COMMIT_DIR: if (at == 2) { if (! puffs_nextdent(&dent,"parents",o->u.commit.r->commits[o->u.commit.cx].fso_parents->ino,DT_DIR,len)) break; at = 3; } if (at == 3) { if (! puffs_nextdent(&dent,"tree",o->u.commit.r->commits[o->u.commit.cx].fso_tree->ino,DT_DIR,len)) break; at = 4; } if (at == 4) { if (! puffs_nextdent(&dent,"message",o->u.commit.r->commits[o->u.commit.cx].fso_tree->ino,DT_REG,len)) break; at = 5; } if (at == 5) { if (! puffs_nextdent(&dent,"author",o->u.commit.r->commits[o->u.commit.cx].fso_author->ino,DT_REG,len)) break; at = 6; } if (at == 6) { if (! puffs_nextdent(&dent,"time",o->u.commit.r->commits[o->u.commit.cx].fso_time->ino,DT_REG,len)) break; at = 7; } if ((at == 7) && o->u.commit.r->commits[o->u.commit.cx].encoding) { if (! puffs_nextdent(&dent,"encoding",o->u.commit.r->commits[o->u.commit.cx].fso_encoding->ino,DT_REG,len)) break; at = 8; } *eofp = 1; break; case FSO_COMMIT_PARENTS: { int x; int n; COMMIT *c; PARENT *p; c = &o->u.commit.r->commits[o->u.commit.cx]; n = c->nparents; for (x=at-2;xparents[x]; if (! puffs_nextdent(&dent,p->fso->name,p->fso->ino,DT_LNK,len)) break; at ++; } *eofp = 1; } break; case FSO_COMMIT_TREE: *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: case FSO_COMMIT_PARENTS: case FSO_COMMIT_TREE: case FSO_BIG_SUB: break; case FSO_REPO_HEAD: case FSO_TAG: case FSO_BRANCH: case FSO_PARENT_N: case FSO_COMMIT_MESSAGE: case FSO_COMMIT_ENCODING: case FSO_COMMIT_AUTHOR: case FSO_COMMIT_TIME: 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->big?o->u.repo->fso_commit.big.v[256]:o->u.repo->fso_commit.small); 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]; int i; REPO *r; FSOBJ *o2; r = o->u.repo; if (o->u.repo->big) { if ( (cn->pcn_pkcnp->pkcn_namelen != 2) || !allhex(&cn->pcn_pkcnp->pkcn_name[0],2) || !scan_hex(&cn->pcn_pkcnp->pkcn_name[0],&id[0],2) ) return(ENOENT); o2 = o->u.repo->fso_commit.big.v[id[0]]; if (o2) { puffs_newinfo_setcookie(ni,o2); puffs_newinfo_setvtype(ni,VDIR); return(0); } } else { if ( (cn->pcn_pkcnp->pkcn_namelen != 40) || !allhex(&cn->pcn_pkcnp->pkcn_name[0],40) || !scan_hex(&cn->pcn_pkcnp->pkcn_name[0],&id[0],40) ) return(ENOENT); i = find_commit(r,&id[0]); if (i >= 0) { puffs_newinfo_setcookie(ni,r->commits[i].fso_dir); puffs_newinfo_setvtype(ni,VDIR); return(0); } } } return(ENOENT); break; case FSO_BIG_SUB: { int i; unsigned char id[20]; if ( (cn->pcn_pkcnp->pkcn_namelen != 40) || !allhex(&cn->pcn_pkcnp->pkcn_name[0],40) || !scan_hex(&cn->pcn_pkcnp->pkcn_name[0],&id[0],40) || (id[0] != o->u.sub.inx) ) return(ENOENT); i = find_commit(o->u.sub.r,&id[0]); if (i >= 0) { puffs_newinfo_setcookie(ni,o->u.sub.r->commits[i].fso_dir); puffs_newinfo_setvtype(ni,VDIR); return(0); } } return(ENOENT); break; case FSO_COMMIT_DIR: { COMMIT *c; c = &o->u.commit.r->commits[o->u.commit.cx]; if ( (cn->pcn_pkcnp->pkcn_namelen == 7) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],"parents",7) ) { puffs_newinfo_setcookie(ni,c->fso_parents); puffs_newinfo_setvtype(ni,VDIR); } else if ( (cn->pcn_pkcnp->pkcn_namelen == 4) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],"tree",4) ) { puffs_newinfo_setcookie(ni,c->fso_tree); puffs_newinfo_setvtype(ni,VDIR); } else if ( (cn->pcn_pkcnp->pkcn_namelen == 7) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],"message",7) ) { puffs_newinfo_setcookie(ni,c->fso_message); puffs_newinfo_setvtype(ni,VREG); puffs_newinfo_setsize(ni,c->msglen); } else if ( (cn->pcn_pkcnp->pkcn_namelen == 8) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],"encoding",8) ) { if (c->encoding) { puffs_newinfo_setcookie(ni,c->fso_encoding); puffs_newinfo_setvtype(ni,VREG); puffs_newinfo_setsize(ni,c->enclen); return(0); } } else if ( (cn->pcn_pkcnp->pkcn_namelen == 6) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],"author",6) ) { puffs_newinfo_setcookie(ni,c->fso_author); puffs_newinfo_setvtype(ni,VREG); puffs_newinfo_setsize(ni,c->authlen); } else if ( (cn->pcn_pkcnp->pkcn_namelen == 4) && !bcmp(&cn->pcn_pkcnp->pkcn_name[0],"time",4) ) { puffs_newinfo_setcookie(ni,c->fso_time); puffs_newinfo_setvtype(ni,VREG); puffs_newinfo_setsize(ni,c->timelen); } else { return(ENOENT); } } break; case FSO_COMMIT_PARENTS: { long int v; COMMIT *c; c = &o->u.commit.r->commits[o->u.commit.cx]; v = strtol(&cn->pcn_pkcnp->pkcn_name[0],0,0); if ((v < 1) || (v > c->nparents)) return(ENOENT); puffs_newinfo_setcookie(ni,c->parents[v-1].fso); puffs_newinfo_setvtype(ni,VLNK); puffs_newinfo_setsize(ni,40); return(0); } break; case FSO_COMMIT_TREE: 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; const char *body; int bodylen; int at; int r; char timetmp[64]; 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: case FSO_COMMIT_DIR: case FSO_COMMIT_PARENTS: case FSO_COMMIT_TREE: return(EISDIR); break; case FSO_REPO_HEAD: case FSO_TAG: case FSO_BRANCH: case FSO_PARENT_N: return(EOPNOTSUPP); break; case FSO_COMMIT_MESSAGE: { COMMIT *c; c = &o->u.commit.r->commits[o->u.commit.cx]; body = c->msg; bodylen = c->msglen; } break; case FSO_COMMIT_ENCODING: { COMMIT *c; c = &o->u.commit.r->commits[o->u.commit.cx]; body = c->encoding; bodylen = c->enclen; } break; case FSO_COMMIT_AUTHOR: { COMMIT *c; c = &o->u.commit.r->commits[o->u.commit.cx]; body = c->author; bodylen = c->authlen; } break; case FSO_COMMIT_TIME: { COMMIT *c; c = &o->u.commit.r->commits[o->u.commit.cx]; if (sprintf(&timetmp[0],"%llu\n",c->time) != c->timelen) bugcheck("timelen wrong"); body = &timetmp[0]; bodylen = c->timelen; } break; default: bugcheck("impossible fsobj kind %d",(int)o->kind); break; } if (off < 0) return(EINVAL); if (off >= bodylen) return(0); at = off; r = (*resid > bodylen) ? bodylen : *resid; if (at+r > bodylen) r = bodylen - at; bcopy(body+at,buf,r); *resid -= r; (void)um; (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_threepart(char *link, size_t *linklen, const char *p1, int l1, const char *p2, int l2, const char *p3, int l3) { size_t left; int did; left = *linklen; did = 0; #define DO(n)\ do \ { if (l##n) \ { if (l##n < left) \ { bcopy(p##n,link,l##n); \ link += l##n; \ left -= l##n; \ did += l##n; \ } \ else \ { bcopy(p##n,link,left); \ return; \ } \ } \ } while (0) DO(1); DO(2); DO(3); #undef DO *linklen = did; } static void readlink_commit(char *link, size_t *linklen, int big, const char *pref, int preflen, const char *id) { char mid[3]; int ml; if (big) { mid[0] = id[0]; mid[1] = id[1]; mid[2] = '/'; ml = 3; } else { ml = 0; } readlink_threepart(link,linklen,pref,preflen,&mid[0],ml,id,40); } 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: case FSO_COMMIT_DIR: case FSO_COMMIT_PARENTS: case FSO_COMMIT_TREE: case FSO_COMMIT_MESSAGE: case FSO_COMMIT_ENCODING: case FSO_COMMIT_AUTHOR: case FSO_COMMIT_TIME: return(EINVAL); // XXX wish we had ENOTLINK! break; case FSO_REPO_HEAD: switch (o->u.repo->ht) { case HT_COMMIT: readlink_commit(link,linklen,o->u.repo->big,"commit/",7,&o->u.repo->u_head.commit[0]); break; case HT_BRANCH: readlink_threepart(link,linklen,"branch/",7,o->u.repo->u_head.branch.name,o->u.repo->u_head.branch.namelen,0,0); break; default: bugcheck("impossible repo HEAD type %d",(int)o->u.repo->ht); break; } break; case FSO_TAG: readlink_commit(link,linklen,o->u.tag->repo->big,"../commit/",10,&o->u.tag->commit[0]); break; case FSO_BRANCH: readlink_commit(link,linklen,o->u.branch->repo->big,"../commit/",10,&o->u.branch->commit[0]); break; case FSO_PARENT_N: { char txt[41]; REPO *r; r = o->u.parent_n.c->fso_dir->u.commit.r; format_id(&txt[0],&o->u.parent_n.c->parents[o->u.parent_n.n].c->id[0]); readlink_commit(link,linklen,r->big,"../../../",r->big?9:6,&txt[0]); } 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: switch (s[o]) { case 'a': if (! bcmp(s+o,"all",3)) bit = DBG_ALL; break; case 'i': if (! bcmp(s+o,"ino",3)) bit = DBG_INO; break; } 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(CONF_CTX *c, const char *path, const char *repo) { REPO *r; int i; r = malloc(sizeof(REPO)); r->path = strdup(path); r->repo = strdup(repo); r->plen = strlen(path); r->rlen = strlen(repo); r->big = c->bigrepo; if (r->big) { r->fso_commit.big.v = malloc(257*sizeof(FSOBJ *)); r->fso_commit.big.counts = malloc(256*sizeof(int)); for (i=257-1;i>=0;i--) r->fso_commit.big.v[i] = 0; for (i=256-1;i>=0;i--) r->fso_commit.big.counts[i] = 0; r->ngroup = 0; } r->link = repos; repos = r; if (DBG(CONF)) 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(c,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_set_bigrepo(CONF_CTX *c, const char *body, int bodylen) { int x; int x0; int new; for (x=0;(x= bodylen) { config_err(c,"no argument to %%bigrepo"); return; } for (x0=x;(xbigrepo = new; } 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; } if (! strncmp(text,"%bigrepo",8)) { config_set_bigrepo(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(); if (DBG(INO)) 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; int k; COMMIT t; unsigned char s[20]; FSOBJ *o; ES e; int at; char *base; char *nl; int sz; int ll; COMMIT *c; if (len % 41) { fprintf(stderr,"%s: commit list for %s size wrong\n",__progname,r->repo); return; } es_init(&e); r->ncommits = len / 41; r->commits = malloc(r->ncommits*sizeof(*r->commits)); for (i=r->ncommits-1;i>=0;i--) { if (! scan_hex(text+(i*41),&r->commits[i].id[0],40)) { 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--) { c = &r->commits[i]; if (r->big) { if (! r->fso_commit.big.v[c->id[0]]) { o = new_fsobj(); if (DBG(INO)) printf("setup %d: %p: commit container %d for %s\n",o->ino,(void *)o,c->id[0],r->path); r->fso_commit.big.v[c->id[0]] = o; o->kind = FSO_BIG_SUB; o->name = malloc(3); sprintf(o->name,"%02x",c->id[0]); o->namelen = 2; o->u.sub.r = r; o->u.sub.inx = c->id[0]; o->parent = r->fso_commit.big.v[256]; r->ngroup ++; } r->fso_commit.big.counts[c->id[0]] ++; } o = new_fsobj(); if (DBG(INO)) printf("setup %d: %p: commit %d for %s, root\n",o->ino,(void *)o,i,r->path); c->fso_dir = o; o->kind = FSO_COMMIT_DIR; o->name = malloc(41); format_id(o->name,&c->id[0]); o->namelen = 41; o->u.commit.r = r; o->u.commit.cx = i; o->parent = r->big ? r->fso_commit.big.v[c->id[0]] : r->fso_commit.small; o = new_fsobj(); if (DBG(INO)) printf("setup %d: %p: commit %d for %s, parents\n",o->ino,(void *)o,i,r->path); c->fso_parents = o; o->kind = FSO_COMMIT_PARENTS; o->name = &name_parents[0]; o->namelen = sizeof(name_parents) - 1; o->u.commit.r = r; o->u.commit.cx = i; o->parent = c->fso_dir; o = new_fsobj(); if (DBG(INO)) printf("setup %d: %p: commit %d for %s, tree\n",o->ino,(void *)o,i,r->path); c->fso_tree = o; o->kind = FSO_COMMIT_TREE; o->name = &name_tree[0]; o->namelen = sizeof(name_tree) - 1; o->u.commit.r = r; o->u.commit.cx = i; o->parent = c->fso_dir; } for (i=r->ncommits-1;i>=0;i--) { c = &r->commits[i]; c->encoding = 0; c->enclen = 0; c->fso_encoding = o; c->author = 0; es_clear(&e); run_git(&e,0,r->gitarg,"cat-file","commit",c->fso_dir->name,(char *)0); c->nparents = 0; c->parents = 0; base = es_buf(&e); sz = es_len(&e); at = 0; while (at < sz) { nl = memchr(base+at,'\n',sz-at); if (! nl) { fprintf(stderr,"%s: repo %s commit %s has no message\n",__progname,r->repo,c->fso_dir->name); exit(1); } ll = nl - (base + at); if (ll == 0) break; if ((ll == 47) && !bcmp(base+at,"parent ",7) && scan_hex(base+at+7,&s[0],40)) { k = find_commit(r,&s[0]); if (k < 0) { fprintf(stderr,"%s: repo %s, commit %s parent [%d] not found\n", __progname,r->repo,c->fso_dir->name,c->nparents); exit(1); } if (k >= r->ncommits) bugcheck("found commit out of range"); j = c->nparents++; c->parents = realloc(c->parents,c->nparents*sizeof(*c->parents)); c->parents[j].c = &r->commits[k]; o = new_fsobj(); if (DBG(INO)) printf("setup %d: %p: commit %d for %s, parent [%d]\n",o->ino,(void *)o,i,r->path,j); c->parents[j].fso = o; o->kind = FSO_PARENT_N; o->namelen = asprintf(&o->name,"%d",j+1); o->u.parent_n.c = c; o->u.parent_n.n = j; o->parent = c->fso_parents; } else if ((ll >= 9) && !bcmp(base+at,"encoding ",9)) { if (c->encoding) { fprintf(stderr,"%s: repo %s, commit %s has multiple encodings\n",__progname,r->repo,c->fso_dir->name); exit(1); } // XXX Should memoize! c->enclen = ll - 9 + 1; c->encoding = malloc(c->enclen); bcopy(base+at+9,c->encoding,c->enclen-1); c->encoding[c->enclen-1] = '\n'; o = new_fsobj(); if (DBG(INO)) printf("setup %d: %p: commit %d for %s, encoding\n",o->ino,(void *)o,i,r->path); c->fso_encoding = o; o->kind = FSO_COMMIT_ENCODING; o->name = &name_encoding[0]; o->namelen = sizeof(name_encoding) - 1; o->u.commit.r = r; o->u.commit.cx = i; o->parent = c->fso_dir; } else if ((ll >= 7) && !bcmp(base+at,"author ",7)) { char timebuf[64]; if (c->author) { fprintf(stderr,"%s: repo %s, commit %s has multiple authors\n",__progname,r->repo,c->fso_dir->name); exit(1); } // XXX Should memoize! for (j=at+ll-1;(j>at)&&(base[j]!=' ');j--) ; for (k=j-1;(k>at)&&(base[k]!=' ');k--) ; if (k <= at) { fprintf(stderr,"%s: repo %s, commit %s has too few author fields\n",__progname,r->repo,c->fso_dir->name); exit(1); } c->time = strtol(base+k,0,0); c->timelen = sprintf(&timebuf[0],"%llu\n",c->time); c->authlen = k - (at + 7) + 1; c->author = malloc(c->authlen); bcopy(base+at+7,c->author,c->authlen-1); c->author[c->authlen-1] = '\n'; o = new_fsobj(); if (DBG(INO)) printf("setup %d: %p: commit %d for %s, author\n",o->ino,(void *)o,i,r->path); c->fso_author = o; o->kind = FSO_COMMIT_AUTHOR; o->name = &name_author[0]; o->namelen = sizeof(name_author) - 1; o->u.commit.r = r; o->u.commit.cx = i; o->parent = c->fso_dir; o = new_fsobj(); if (DBG(INO)) printf("setup %d: %p: commit %d for %s, time\n",o->ino,(void *)o,i,r->path); c->fso_time = o; o->kind = FSO_COMMIT_TIME; o->name = &name_time[0]; o->namelen = sizeof(name_time) - 1; o->u.commit.r = r; o->u.commit.cx = i; o->parent = c->fso_dir; } at = (nl - base) + 1; } if (! c->author) { fprintf(stderr,"%s: repo %s, commit %s has no author\n",__progname,r->repo,c->fso_dir->name); exit(1); } at = (nl - base) + 1; c->msglen = sz - at; c->msg = malloc(c->msglen+1); bcopy(base+at,c->msg,c->msglen); o = new_fsobj(); if (DBG(INO)) printf("setup %d: %p: commit %d for %s, message\n",o->ino,(void *)o,i,r->path); c->fso_message = o; o->kind = FSO_COMMIT_MESSAGE; o->name = &name_message[0]; o->namelen = sizeof(name_message) - 1; o->u.commit.r = r; o->u.commit.cx = i; o->parent = c->fso_dir; } es_done(&e); } static void setup_repo(REPO *r, FSOBJ *o) { char *p; FILE *f; char rbuf[256]; int rlen; ES e; r->fso_root = o; o = new_fsobj(); if (DBG(INO)) 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(); if (DBG(INO)) 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(); if (DBG(INO)) printf("setup %d: %p: commit for %s\n",o->ino,(void *)o,r->path); if (r->big) { r->fso_commit.big.v[256] = o; } else { r->fso_commit.small = 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(); if (DBG(INO)) 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],40); } 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; if (DBG(INO)) 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; if (DBG(INO)) 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->big?o->u.repo->fso_commit.big.v[256]:o->u.repo->fso_commit.small,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; c.bigrepo = 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(); if (DBG(INO)) 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) { int e; handleargs(ac,av); load_config(); setup_puffs(); e = puffs_mainloop(um); if (e) printf("puffs_mainloop returned %d\n",e); return(0); }