1. use short to long and long to short avl trees to lookup translations, rather than a linear (and unsorted) search. 2. -l flag controls the maximum file length (our file servers have a maximum of 53). -f controls if frogs (spaces) are allowed. 3. new program relnfs changes max length, or frog setting. Reference: /n/atom/patch/applied2013/lnfs Date: Wed May 29 22:25:44 CES 2013 Signed-off-by: quanstro@quanstro.net # rm /sys/src/cmd/lnfs.c # rm /sys/src/cmd/unlnfs.c --- /sys/man/4/lnfs Wed May 29 22:54:29 2013 +++ /sys/man/4/lnfs Wed May 29 22:30:06 2013 @@ -1,10 +1,14 @@ .TH LNFS 4 .SH NAME -lnfs, unlnfs \- long name file system +lnfs, relnfs, unlnfs \- long name file system .SH SYNOPSIS .B lnfs [ -.B -r +.B -fr +] +[ +.B -l +maxlen ] [ .B -s @@ -12,6 +16,14 @@ ] .I mountpoint .br +.B relnfs +[ +.B -f +][ +.B -l +.I maxlen +] +.br .B unlnfs .I mountpoint .SH DESCRIPTION @@ -21,9 +33,9 @@ on .IR mountpoint . It presents a filtered view of the files under the mount -point, allowing users to use long file names -on file servers that do not support file names -longer than 27 bytes. +point, allowing users to use long file names, or file +names with spaces on file servers not supporting either. +The default maximum file length is 27 bytes. .PP The names used in the underlying file system are the base32 encoding of the md5 hash of the longer @@ -40,6 +52,12 @@ .PP The options are: .TP +.B -f +the underlying file system allows spaces. +.TP +.B -m\ \fImaxlen\fP +set the maximum file length allowed. +.TP .B -r allow only read access to the file system .TP @@ -53,12 +71,13 @@ .I Unlnfs renames files with shortened names to their actual long names. It is useful once you have moved to a file server with true long name support. +.I Relnfs +changes the maximum file length or support for spaces in +file names in the underlying file system. .SH FILES .B .longnames .SH SOURCE -.B /sys/src/cmd/lnfs.c -.PP -.B /sys/src/cmd/unlnfs.c +.B /sys/src/cmd/lnfs .SH BUGS This exists only to shame us into getting a real long name file server working. --- /rc/bin/unlnfs Thu Jan 1 00:00:00 1970 +++ /rc/bin/unlnfs Wed May 29 21:53:57 2013 @@ -0,0 +1,3 @@ +#!/bin/rc + +relnfs -f -m512 $* # && rm .longnames Binary files lnfs.1.orig and lnfs.1 differ Binary files /386/bin/relnfs and relnfs differ --- /sys/src/cmd/lnfs/ln2short.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/lnfs/ln2short.c Wed May 29 21:47:45 2013 @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +enum +{ + Enclen = 26, +}; + +void +long2short(char shortname[Enclen+1], char *longname) +{ + uchar digest[MD5dlen]; + + md5((uchar*)longname, strlen(longname), digest, nil); + enc32(shortname, Enclen+1, digest, MD5dlen); +} + +void +ln2short(int fd, Biobuf *o) +{ + char *s, enc[Enclen+1]; + Biobuf i; + + if(Binit(&i, fd, OREAD) == -1) + sysfatal("ln2short: Binit: %r"); + for(; s = Brdstr(&i, '\n', 1); free(s)){ + long2short(enc, s); + Bprint(o, "%s\n", enc); + } +} + +void +usage(void) +{ + fprint(2, "ln2short < long\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + Biobuf o; + + ARGBEGIN{ + default: + usage(); + }ARGEND + + if(Binit(&o, 1, OWRITE) == -1) + sysfatal("ln2short: Binit: %r"); + + ln2short(0, &o); + exits(""); +} --- /sys/src/cmd/lnfs/lnfs.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/lnfs/lnfs.c Wed May 29 22:08:26 2013 @@ -0,0 +1,903 @@ +#include +#include +#include +#include +#include +#include +#include + +enum +{ + Maxfdata = 8192, + Namelen = 28, +}; + +typedef struct Fid Fid; +struct Fid +{ + short busy; + int fid; + Fid *next; + char *user; + String *path; /* complete path */ + + int fd; /* set on open or create */ + Qid qid; /* set on open or create */ + int attach; /* this is an attach fd */ + + ulong diroff; /* directory offset */ + Dir *dir; /* directory entries */ + int ndir; /* number of directory entries */ +}; + +Fid *fids; +int mfd[2]; +char *user; +uchar mdata[IOHDRSZ+Maxfdata]; +uchar rdata[Maxfdata]; /* buffer for data in reply */ +uchar statbuf[STATMAX]; +Fcall thdr; +Fcall rhdr; +int messagesize = sizeof mdata; +int readonly; +char *srvname; +int debug; +int namelen = Namelen; +int frogs; + +Fid * newfid(int); +void io(void); +void *erealloc(void*, ulong); +void *emalloc(usize); +char *estrdup(char*); +void usage(void); +void fidqid(Fid*, Qid*); +char* short2long(char*); +char* long2short(char*, int); +void readnames(void); +void post(char*, int); + +char *rflush(Fid*), *rversion(Fid*), *rauth(Fid*), + *rattach(Fid*), *rwalk(Fid*), + *ropen(Fid*), *rcreate(Fid*), + *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*), + *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*); + +char *(*fcalls[])(Fid*) = { + [Tversion] rversion, + [Tflush] rflush, + [Tauth] rauth, + [Tattach] rattach, + [Twalk] rwalk, + [Topen] ropen, + [Tcreate] rcreate, + [Tread] rread, + [Twrite] rwrite, + [Tclunk] rclunk, + [Tremove] rremove, + [Tstat] rstat, + [Twstat] rwstat, +}; + +char Eperm[] = "permission denied"; +char Enotdir[] = "not a directory"; +char Enoauth[] = "lnfs: authentication not required"; +char Enotexist[] = "file does not exist"; +char Einuse[] = "file in use"; +char Eexist[] = "file exists"; +char Eisdir[] = "file is a directory"; +char Enotowner[] = "not owner"; +char Eisopen[] = "file already open for I/O"; +char Excl[] = "exclusive use file already open"; +char Ename[] = "illegal name"; +char Eversion[] = "unknown 9P version"; + +void +usage(void) +{ + fprint(2, "usage: %s [-l len] [-r] [-s srvname] mountpoint\n", argv0); + exits("usage"); +} + +void +notifyf(void *a, char *s) +{ + USED(a); + if(strncmp(s, "interrupt", 9) == 0) + noted(NCONT); + noted(NDFLT); +} + +void +main(int argc, char *argv[]) +{ + char *defmnt; + int p[2]; + Dir *d; + + ARGBEGIN{ + case 'f': + frogs = 1; + break; + case 'l': + namelen = atoi(EARGF(usage())); + break; + case 'r': + readonly = 1; + break; + case 'd': + debug = 1; + break; + case 's': + srvname = EARGF(usage()); + break; + default: + usage(); + }ARGEND + + if(argc < 1) + usage(); + defmnt = argv[0]; + d = dirstat(defmnt); + if(d == nil || !(d->qid.type & QTDIR)) + sysfatal("mountpoint must be an accessible directory"); + free(d); + + if(pipe(p) < 0) + sysfatal("pipe failed"); + mfd[0] = p[0]; + mfd[1] = p[0]; + + user = getuser(); + notify(notifyf); + + if(srvname != nil) + post(srvname, p[1]); + + if(debug) + fmtinstall('F', fcallfmt); + switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){ + case -1: + sysfatal("fork: %r"); + case 0: + close(p[1]); + chdir(defmnt); + io(); + break; + default: + close(p[0]); /* don't deadlock if child fails */ + if(mount(p[1], -1, defmnt, MREPL|MCREATE, "") < 0) + sysfatal("mount failed: %r"); + } + exits(0); +} + +void +post(char *srvname, int pfd) +{ + char name[128]; + int fd; + + snprint(name, sizeof name, "#s/%s", srvname); + fd = create(name, OWRITE, 0666); + if(fd < 0) + sysfatal("create of %s failed: %r", srvname); + sprint(name, "%d", pfd); + if(write(fd, name, strlen(name)) < 0) + sysfatal("writing %s: %r", srvname); +} +char* +rversion(Fid*) +{ + Fid *f; + + for(f = fids; f; f = f->next) + if(f->busy) + rclunk(f); + if(thdr.msize > sizeof mdata) + rhdr.msize = sizeof mdata; + else + rhdr.msize = thdr.msize; + messagesize = rhdr.msize; + if(strncmp(thdr.version, "9P2000", 6) != 0) + return Eversion; + rhdr.version = "9P2000"; + return nil; +} + +char* +rauth(Fid*) +{ + return Enoauth; +} + +char* +rflush(Fid *f) +{ + USED(f); + return nil; +} + +char* +rattach(Fid *f) +{ + /* no authentication! */ + f->busy = 1; + if(thdr.uname[0]) + f->user = estrdup(thdr.uname); + else + f->user = "none"; + if(strcmp(user, "none") == 0) + user = f->user; + if(f->path) + s_free(f->path); + f->path = s_copy("."); + fidqid(f, &rhdr.qid); + f->attach = 1; + return nil; +} + +char* +clone(Fid *f, Fid **nf) +{ + if(f->fd >= 0) + return Eisopen; + *nf = newfid(thdr.newfid); + (*nf)->busy = 1; + if((*nf)->path) + s_free((*nf)->path); + (*nf)->path = s_clone(f->path); + (*nf)->fd = -1; + (*nf)->user = f->user; + (*nf)->attach = 0; + return nil; +} + +char* +rwalk(Fid *f) +{ + char *name; + Fid *nf; + char *err; + int i; + String *npath; + Dir *d; + char *cp; + Qid qid; + + err = nil; + nf = nil; + rhdr.nwqid = 0; + if(rhdr.newfid != rhdr.fid){ + err = clone(f, &nf); + if(err) + return err; + f = nf; /* walk the new fid */ + } + readnames(); + npath = s_clone(f->path); + if(thdr.nwname > 0){ + for(i=0; iptr = cp; + } + } else { + s_append(npath, "/"); + s_append(npath, name); + } + d = dirstat(s_to_c(npath)); + if(d == nil) + break; + rhdr.nwqid++; + qid = d->qid; + rhdr.wqid[i] = qid; + free(d); + } + if(i==0 && err == nil) + err = Enotexist; + } + + /* if there was an error and we cloned, get rid of the new fid */ + if(nf != nil && (err!=nil || rhdr.nwqidbusy = 0; + s_free(npath); + } + + /* update the fid after a successful walk */ + if(rhdr.nwqid == thdr.nwname){ + s_free(f->path); + f->path = npath; + } + return err; +} + +static char* +passerror(void) +{ + static char err[256]; + + rerrstr(err, sizeof err); + return err; +} + +char* +ropen(Fid *f) +{ + if(readonly && (thdr.mode & 3)) + return Eperm; + if(f->fd >= 0) + return Eisopen; + f->fd = open(s_to_c(f->path), thdr.mode); + if(f->fd < 0) + return passerror(); + fidqid(f, &rhdr.qid); + f->qid = rhdr.qid; + rhdr.iounit = messagesize-IOHDRSZ; + return nil; +} + +char* +rcreate(Fid *f) +{ + char *name; + + if(readonly) + return Eperm; + readnames(); + name = long2short(thdr.name, 1); + if(f->fd >= 0) + return Eisopen; + s_append(f->path, "/"); + s_append(f->path, name); + f->fd = create(s_to_c(f->path), thdr.mode, thdr.perm); + if(f->fd < 0) + return passerror(); + fidqid(f, &rhdr.qid); + f->qid = rhdr.qid; + rhdr.iounit = messagesize-IOHDRSZ; + return nil; +} + +char* +rreaddir(Fid *f) +{ + int i; + int n; + + /* reread the directory */ + if(thdr.offset == 0){ + if(f->dir) + free(f->dir); + f->dir = nil; + if(f->diroff != 0) + seek(f->fd, 0, 0); + f->ndir = dirreadall(f->fd, &f->dir); + f->diroff = 0; + if(f->ndir < 0) + return passerror(); + readnames(); + for(i = 0; i < f->ndir; i++) + f->dir[i].name = short2long(f->dir[i].name); + } + + /* copy in as many directory entries as possible */ + for(n = 0; f->diroff < f->ndir; n += i){ + i = convD2M(&f->dir[f->diroff], rdata+n, thdr.count - n); + if(i <= BIT16SZ) + break; + f->diroff++; + } + rhdr.data = (char*)rdata; + rhdr.count = n; + return nil; +} + +char* +rread(Fid *f) +{ + long n; + + if(f->fd < 0) + return Enotexist; + if(thdr.count > messagesize-IOHDRSZ) + thdr.count = messagesize-IOHDRSZ; + if(f->qid.type & QTDIR) + return rreaddir(f); + n = pread(f->fd, rdata, thdr.count, thdr.offset); + if(n < 0) + return passerror(); + rhdr.data = (char*)rdata; + rhdr.count = n; + return nil; +} + +char* +rwrite(Fid *f) +{ + long n; + + if(readonly || (f->qid.type & QTDIR)) + return Eperm; + if(f->fd < 0) + return Enotexist; + if(thdr.count > messagesize-IOHDRSZ) /* shouldn't happen, anyway */ + thdr.count = messagesize-IOHDRSZ; + n = pwrite(f->fd, thdr.data, thdr.count, thdr.offset); + if(n < 0) + return passerror(); + rhdr.count = n; + return nil; +} + +char* +rclunk(Fid *f) +{ + f->busy = 0; + close(f->fd); + f->fd = -1; + f->path = s_reset(f->path); + if(f->attach){ + free(f->user); + f->user = nil; + } + f->attach = 0; + if(f->dir != nil){ + free(f->dir); + f->dir = nil; + } + f->diroff = f->ndir = 0; + return nil; +} + +char* +rremove(Fid *f) +{ + if(remove(s_to_c(f->path)) < 0) + return passerror(); + return nil; +} + +char* +rstat(Fid *f) +{ + int n; + Dir *d; + + d = dirstat(s_to_c(f->path)); + if(d == nil) + return passerror(); + d->name = short2long(d->name); + n = convD2M(d, statbuf, sizeof statbuf); + free(d); + if(n <= BIT16SZ) + return passerror(); + rhdr.nstat = n; + rhdr.stat = statbuf; + return nil; +} + +char* +rwstat(Fid *f) +{ + int n; + Dir d; + + if(readonly) + return Eperm; + convM2D(thdr.stat, thdr.nstat, &d, (char*)rdata); + d.name = long2short(d.name, 1); + n = dirwstat(s_to_c(f->path), &d); + if(n < 0) + return passerror(); + return nil; +} + +Fid * +newfid(int fid) +{ + Fid *f, *ff; + + ff = 0; + for(f = fids; f; f = f->next) + if(f->fid == fid) + return f; + else if(!ff && !f->busy) + ff = f; + if(ff){ + ff->fid = fid; + return ff; + } + f = emalloc(sizeof *f); + f->path = s_reset(f->path); + f->fd = -1; + f->fid = fid; + f->next = fids; + fids = f; + return f; +} + +void +io(void) +{ + char *err; + int n, pid; + + pid = getpid(); + + for(;;){ + /* + * reading from a pipe or a network device + * will give an error after a few eof reads. + * however, we cannot tell the difference + * between a zero-length read and an interrupt + * on the processes writing to us, + * so we wait for the error. + */ + n = read9pmsg(mfd[0], mdata, messagesize); + if(n < 0) + exits(""); + if(n == 0) + continue; + if(convM2S(mdata, n, &thdr) == 0) + continue; + + if(debug) + fprint(2, "%s %d:<-%F\n", argv0, pid, &thdr); + + if(!fcalls[thdr.type]) + err = "bad fcall type"; + else + err = (*fcalls[thdr.type])(newfid(thdr.fid)); + if(err){ + rhdr.type = Rerror; + rhdr.ename = err; + }else{ + rhdr.type = thdr.type + 1; + rhdr.fid = thdr.fid; + } + rhdr.tag = thdr.tag; + if(debug) + fprint(2, "%s %d:->%F\n", argv0, pid, &rhdr);/**/ + n = convS2M(&rhdr, mdata, messagesize); + if(n == 0) + sysfatal("convS2M error on write"); + if(write(mfd[1], mdata, n) != n) + sysfatal("mount write"); + } +} + +void * +emalloc(usize n) +{ + void *p; + + p = malloc(n); + if(p == nil) + sysfatal("out of memory"); + memset(p, 0, n); + return p; +} + +void * +erealloc(void *p, ulong n) +{ + p = realloc(p, n); + if(p == nil) + sysfatal("out of memory"); + return p; +} + +char * +estrdup(char *q) +{ + char *p; + int n; + + n = strlen(q)+1; + p = malloc(n); + if(p == nil) + sysfatal("out of memory"); + memmove(p, q, n); + return p; +} + +void +fidqid(Fid *f, Qid *q) +{ + Dir *d; + + d = dirstat(s_to_c(f->path)); + if(d == nil) + *q = (Qid){0, 0, QTFILE}; + else { + *q = d->qid; + free(d); + } +} + +/* + * table of name translations + * + * the file ./.longnames contains all the known long names. + * the short name is the first namelen bytes of the base64 + * encoding of the MD5 hash of the longname. + */ +#include + +typedef struct Name Name; +typedef struct Aname Aname; + +enum { + Short, + Long, + Nname, +}; + +struct Name{ + int ref; + char *s[Nname]; +}; + +struct Aname{ + Avl; + int type; + Name *name; +}; + +void +anamefree(void *v) +{ + Aname *a; + + a = v; + if(a->name->ref-- == 0) + free(a->name); /* assume 1 allocation */ + free(a); +} + +int +namecmp(Avl *va, Avl *vb) +{ + int r, type; + Name *a, *b; + + type = ((Aname*)va)->type; + a = ((Aname*)va)->name; + b = ((Aname*)vb)->name; + r = strcmp(a->s[type], b->s[type]); + if(r > 0) + r = 1; + if(r < 0) + r = -1; + return r; +} + +static void +insert(Avltree *t, Name *n, int type) +{ + Avl *old; + Aname *a; + + a = emalloc(sizeof *a); + n->ref++; + a->name = n; + a->type = type; + insertavl(t, a, &old); + assert(old == nil); +} + +static Name* +lookup(Avltree *t, char *s, int type) +{ + Name n; + Aname a, *p; + + memset(&n, 0, sizeof n); + n.s[type] = s; + memset(&a, 0, sizeof a); + a.name = &n; + a.type = type; + if(p = (Aname*)lookupavl(t, &a)) + return p->name; + return nil; +} + +static void +delete(Avltree *t, char *s, int type) +{ + Name n; + Aname a, *p; + + memset(&n, 0, sizeof n); + n.s[type] = s; + memset(&a, 0, sizeof a); + a.name = &n; + a.type = type; + deleteavl(t, &a, (Avl**)&p); + assert(p != nil); + + anamefree(p); +} + +/* move to libavl */ +struct Avltree +{ + Avl *root; + int (*cmp)(Avl*, Avl*); + Avlwalk *walks; +}; + +static void +freeavl(Avl *a, void (freefn)(void*)) +{ + if(a == nil) + return; + freeavl(a->n[0], freefn); + freeavl(a->n[1], freefn); + freefn(a); +} + +static void +freeavltree(Avltree *t, void (freefn)(void*)) +{ + if(t == nil) + return; + freeavl(t->root, freefn); + free(t); +} + +Dir *dbstat; /* last stat of the name file */ +char *namefile = "./.longnames"; +static Avltree *shorttolong; +static Avltree *longtoshort; + +Name* +newname(char *lname, int writeflag) +{ + int l, fd; + Name *n; + uchar digest[MD5dlen]; + + l = strlen(lname); + md5((uchar*)lname, l, digest, nil); + n = emalloc(sizeof *n + (l + 1) + namelen); + n->s[Long] = ((char*)n) + sizeof *n; + n->s[Short] = ((char*)n) + sizeof *n + (l + 1); + memcpy(n->s[Long], lname, (l + 1)); + enc32(n->s[Short], namelen, digest, MD5dlen); + + insert(shorttolong, n, Short); + insert(longtoshort, n, Long); + + /* don't change namefile if we're read only */ + if(!writeflag) + return n; + + /* add to namefile */ + fd = open(namefile, OWRITE); + if(fd >= 0){ + seek(fd, 0, 2); + fprint(fd, "%s\n", lname); + close(fd); + } + return n; +} + +void +newnames(void) +{ + freeavltree(longtoshort, anamefree); + freeavltree(shorttolong, anamefree); + + shorttolong = mkavltree(namecmp); + longtoshort = mkavltree(namecmp); +} + +/* + * reread the file if the qid.path has changed. + * read any new entries if length has changed. + * bug: we force ourselves to reread this. :-( + */ +void +readnames(void) +{ + char *p; + int fd; + vlong offset; + Dir *d; + Biobuf *b; + static int once; + + /* not multi-user safe. alternative: lock files, etc. */ + if(once != 0) + return; + newnames(); + once = 1; + + d = dirstat(namefile); + if(d == nil){ + if(readonly) + return; + + /* create file if it doesn't exist */ + fd = create(namefile, OREAD, DMAPPEND|0666); + if(fd < 0) + return; + if(dbstat != nil) + free(dbstat); + dbstat = nil; + close(fd); + return; + } + + /* up to date? */ + offset = 0; + if(dbstat != nil){ + if(d->qid.path == dbstat->qid.path){ + if(d->length <= dbstat->length){ + free(d); + return; + } + offset = dbstat->length; + } else { + newnames(); + } + free(dbstat); + dbstat = nil; + } + + /* read file */ + b = Bopen(namefile, OREAD); + if(b == nil){ + free(d); + return; + } + Bseek(b, offset, 0); + while((p = Brdline(b, '\n')) != nil){ + p[Blinelen(b)-1] = 0; + if(lookup(longtoshort, p, Long) == nil) + newname(p, 0); + } + Bterm(b); + dbstat = d; +} + +/* + * look up a long name, if it doesn't exist in the + * file, add an entry to the file if the writeflag is + * non-zero. Return a pointer to the short name. + */ +char* +long2short(char *lname, int writeflag) +{ + Name *n; + + if(strlen(lname) < namelen && (frogs || strpbrk(lname, " ")==nil)) + return lname; + if((n = lookup(longtoshort, lname, Long)) != nil) + return n->s[Short]; + + if(!writeflag) + return lname; + return newname(lname, !readonly)->s[Short]; +} + +/* + * look up a short name, if it doesn't exist, return the + * longname. + */ +char* +short2long(char *sname) +{ + Name *n; + + if((n = lookup(shorttolong, sname, Short)) != nil) + return n->s[Long]; + return sname; +} --- /sys/src/cmd/lnfs/mkfile Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/lnfs/mkfile Wed May 29 21:53:40 2013 @@ -0,0 +1,23 @@ + +#include +#include +#include + +enum +{ + Enclen = 26, +}; + +typedef struct Name Name; +struct Name { + char shortname[Enclen + 1]; + char* longname; + Name* next; +}; + +static Name *names; +static Name *newnames; +static int newmax = 53; +static char flag[128]; + +void +long2short(char shortname[Enclen+1], char *longname) +{ + uchar digest[MD5dlen]; + + md5((uchar*)longname, strlen(longname), digest, nil); + enc32(shortname, Enclen+1, digest, MD5dlen); +} + +Name* +addname(Name **list, char *longname) +{ + Name *n; + + n = malloc(sizeof *n); + if(n == nil) + sysfatal("relnfs: malloc: %r"); + memset(n, 0, sizeof *n); + n->longname = longname; + long2short(n->shortname, longname); + n->next = *list; + return *list = n; +} + +void +readnames(Name **list, char *lnfile) +{ + char *s; + Biobuf *b; + + b = Bopen(lnfile, OREAD); + if(b == nil) + sysfatal("relnfs: cannot open %s: %r", lnfile); + while((s = Brdstr(b, '\n', 1)) != nil) + addname(list, s); + Bterm(b); +} + +void +writenames(Name **list, char *lnfile) +{ + int fd; + Name *n; + Biobuf b; + + fd = create(lnfile, OWRITE, 0664); + if(fd == -1) + sysfatal("relnfs: can't rewrite: %s: %r", lnfile); + if(Binit(&b, fd, OWRITE) == -1) + sysfatal("relnfs: Binit: %s: %r", lnfile); + for(n = *list; n != nil; n = n->next) + Bprint(&b, "%s\n", n->longname); + Bterm(&b); + close(fd); +} + +void +rename(char *d, char *old, char *new) +{ + char *p; + Dir dir; + + p = smprint("%s/%s", d, old); + nulldir(&dir); + dir.name = new; + if(dirwstat(p, &dir) == -1) + fprint(2, "relnfs: cannot rename %s to %s: %r\n", p, new); + if(flag['v']) + fprint(2, "%s/%s → %s\n", d, old, new); + free(p); +} + +Name* +lookupshort(Name *list, char *s) +{ + Name *n; + + if(strlen(s) != Enclen) + return nil; + for(n = list; n != nil; n = n->next) + if(strcmp(n->shortname, s) == 0) + return n; + return nil; +} + +static int +froggy(char *s) +{ + return !flag['f'] && strchr(s, ' ') != 0; +} + +void +renamedir(char *d) +{ + char *sub, *name; + int fd, i, n; + Dir *dir; + Name *na; + + fd = open(d, OREAD); + if(fd == -1) + return; + while((n = dirread(fd, &dir)) > 0){ + for(i = 0; i < n; i++){ + if(dir[i].mode & DMDIR){ + sub = smprint("%s/%s", d, dir[i].name); + renamedir(sub); + free(sub); + } + name = dir[i].name; + if((na = lookupshort(names, name)) != nil) + name = na->longname; + if(strlen(name) > newmax || froggy(name)) + name = addname(&newnames, strdup(name))->shortname; + if(strcmp(dir[i].name, name) != 0) + rename(d, dir[i].name, name); + } + free(dir); + } + close(fd); +} + +void +usage(void) +{ + fprint(2, "usage: relnfs [-f] [-l maxlen] dir\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char *lnfile, *d; + int i, j; + Name *n; + + ARGBEGIN{ + default: + usage(); + case 'l': + newmax = atoi(EARGF(usage())); + break; + case 'f': + case 'v': + flag[ARGC()] = 1; + break; + }ARGEND + + switch(argc){ + default: + usage(); + case 0: + d = "."; + break; + case 1: + d = argv[0]; + break; + } + + lnfile = smprint("%s/.longnames", d); + readnames(&names, lnfile); + + renamedir(d); + writenames(&newnames, lnfile); + + i = 0; + for(n = names; n != nil; n = n->next) + i++; + j = 0; + for(n = newnames; n != nil; n = n->next) + j++; + + fprint(2, "%d old long names; %d new long names\n", i, j); + + free(lnfile); + exits(""); +}