summaryrefslogtreecommitdiff
path: root/sys/src/cmd/dossrv
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/dossrv
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/dossrv')
-rwxr-xr-xsys/src/cmd/dossrv/chat.c36
-rwxr-xr-xsys/src/cmd/dossrv/dat.h255
-rwxr-xr-xsys/src/cmd/dossrv/devio.c65
-rwxr-xr-xsys/src/cmd/dossrv/dosfs.c916
-rwxr-xr-xsys/src/cmd/dossrv/dosfs.h10
-rwxr-xr-xsys/src/cmd/dossrv/dossubs.c1940
-rwxr-xr-xsys/src/cmd/dossrv/errstr.h17
-rwxr-xr-xsys/src/cmd/dossrv/fns.h70
-rwxr-xr-xsys/src/cmd/dossrv/iotrack.c316
-rwxr-xr-xsys/src/cmd/dossrv/iotrack.h69
-rwxr-xr-xsys/src/cmd/dossrv/lock.c38
-rwxr-xr-xsys/src/cmd/dossrv/mkfile29
-rwxr-xr-xsys/src/cmd/dossrv/xfile.c255
-rwxr-xr-xsys/src/cmd/dossrv/xfssrv.c204
14 files changed, 4220 insertions, 0 deletions
diff --git a/sys/src/cmd/dossrv/chat.c b/sys/src/cmd/dossrv/chat.c
new file mode 100755
index 000000000..7f1174307
--- /dev/null
+++ b/sys/src/cmd/dossrv/chat.c
@@ -0,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include "iotrack.h"
+#include "dat.h"
+#include "fns.h"
+
+#define SIZE 1024
+int chatty;
+extern int doabort;
+
+void
+chat(char *fmt, ...)
+{
+ va_list arg;
+
+ if (!chatty)
+ return;
+ va_start(arg, fmt);
+ vfprint(2, fmt, arg);
+ va_end(arg);
+}
+
+void
+panic(char *fmt, ...)
+{
+ va_list arg;
+
+ fprint(2, "%s %d: panic ", argv0, getpid());
+ va_start(arg, fmt);
+ vfprint(2, fmt, arg);
+ va_end(arg);
+ fprint(2, ": %r\n");
+ if(doabort)
+ abort();
+ exits("panic");
+}
diff --git a/sys/src/cmd/dossrv/dat.h b/sys/src/cmd/dossrv/dat.h
new file mode 100755
index 000000000..45202c69a
--- /dev/null
+++ b/sys/src/cmd/dossrv/dat.h
@@ -0,0 +1,255 @@
+typedef struct Dosboot Dosboot;
+typedef struct Dosboot32 Dosboot32;
+typedef struct Dosbpb Dosbpb;
+typedef struct Dosdir Dosdir;
+typedef struct Dospart Dospart;
+typedef struct Dosptr Dosptr;
+typedef struct Fatinfo Fatinfo;
+typedef struct Xfs Xfs;
+typedef struct Xfile Xfile;
+
+struct Dospart{
+ uchar active;
+ uchar hstart;
+ uchar cylstart[2];
+ uchar type;
+ uchar hend;
+ uchar cylend[2];
+ uchar start[4];
+ uchar length[4];
+};
+
+enum
+{
+ /*
+ * dos partition types
+ */
+ FAT12 = 0x01,
+ FAT16 = 0x04, /* partitions smaller than 32MB */
+ FATHUGE = 0x06, /* fat16 partitions larger than 32MB */
+ FAT32 = 0x0b,
+ FAT32X = 0x0c,
+ FATHUGEX = 0x0e,
+ DMDDO = 0x54,
+
+ FATRESRV = 2, /* number of reserved fat entries */
+};
+
+/*
+ * dos boot sector, the start of every dos partition
+ */
+struct Dosboot{
+ uchar magic[3];
+ uchar version[8];
+ uchar sectsize[2];
+ uchar clustsize;
+ uchar nresrv[2];
+ uchar nfats;
+ uchar rootsize[2];
+ uchar volsize[2];
+ uchar mediadesc;
+ uchar fatsize[2];
+ uchar trksize[2];
+ uchar nheads[2];
+ uchar nhidden[4];
+ uchar bigvolsize[4]; /* same as Dosboot32 up to here */
+ uchar driveno;
+ uchar reserved0;
+ uchar bootsig;
+ uchar volid[4];
+ uchar label[11];
+ uchar reserved1[8];
+};
+
+/*
+ * dos boot sector for FAT32
+ */
+enum
+{
+ NOFATMIRROR = 0x0080, /* masks for extflags */
+ ACTFATMASK = 0x000f,
+};
+
+struct Dosboot32{
+ uchar magic[3];
+ uchar version[8];
+ uchar sectsize[2];
+ uchar clustsize;
+ uchar nresrv[2];
+ uchar nfats;
+ uchar rootsize[2];
+ uchar volsize[2];
+ uchar mediadesc;
+ uchar fatsize[2];
+ uchar trksize[2];
+ uchar nheads[2];
+ uchar nhidden[4];
+ uchar bigvolsize[4]; /* same as Dosboot up to here */
+ uchar fatsize32[4]; /* sectors per fat */
+ uchar extflags[2]; /* active fat flags */
+ uchar version1[2]; /* fat32 version; major & minor bytes */
+ uchar rootstart[4]; /* starting cluster of root dir */
+ uchar infospec[2]; /* fat allocation info sector */
+ uchar backupboot[2]; /* backup boot sector */
+ uchar reserved[12];
+};
+
+/*
+ * optional FAT32 info sector
+ */
+enum
+{
+ FATINFOSIG1 = 0x41615252UL,
+ FATINFOSIG = 0x61417272UL,
+};
+
+struct Fatinfo
+{
+ uchar sig1[4];
+ uchar pad[480];
+ uchar sig[4];
+ uchar freeclust[4]; /* num frre clusters; -1 is unknown */
+ uchar nextfree[4]; /* most recently allocated cluster */
+ uchar resrv[4*3];
+};
+
+/*
+ * BIOS paramater block
+ */
+struct Dosbpb{
+ MLock; /* access to fat */
+ int sectsize; /* in bytes */
+ int clustsize; /* in sectors */
+ int nresrv; /* sectors */
+ int nfats; /* usually 2; modified to 1 if fat mirroring disabled */
+ int rootsize; /* number of entries, for fat12 and fat16 */
+ long volsize; /* in sectors */
+ int mediadesc;
+ long fatsize; /* in sectors */
+ int fatclusters;
+ int fatbits; /* 12, 16, or 32 */
+ long fataddr; /* sector number of first valid fat entry */
+ long rootaddr; /* for fat16 or fat12, sector of root dir */
+ long rootstart; /* for fat32, cluster of root dir */
+ long dataaddr; /* initial sector of data clusters */
+ long freeptr; /* next free cluster candidate */
+ long freeclusters; /* count of free clusters, for fat32 */
+ int fatinfo; /* fat info sector location; 0 => none */
+};
+
+enum
+{
+ DOSDIRSIZE = 32,
+ DOSEMPTY = 0xe5, /* first char in name if entry is unused */
+ DOSRUNE = 13, /* runes per dosdir in a long file name */
+ DOSNAMELEN = 261 /* max dos file name length */
+};
+
+struct Dosdir{
+ uchar name[8];
+ uchar ext[3];
+ uchar attr;
+ uchar reserved[1];
+ uchar ctime[3]; /* creation time */
+ uchar cdate[2]; /* creation date */
+ uchar adate[2]; /* last access date */
+ uchar hstart[2]; /* high bits of start for fat32 */
+ uchar time[2]; /* last modified time */
+ uchar date[2]; /* last modified date */
+ uchar start[2];
+ uchar length[4];
+};
+
+enum
+{
+ DRONLY = 0x01,
+ DHIDDEN = 0x02,
+ DSYSTEM = 0x04,
+ DVLABEL = 0x08,
+ DDIR = 0x10,
+ DARCH = 0x20,
+};
+
+#define GSHORT(p) (((p)[0])|(p)[1]<<8)
+#define GLONG(p) (((long)(p)[0])|(p)[1]<<8|(p)[2]<<16|(p)[3]<<24)
+#define PSHORT(p,v) ((p)[0]=(v),(p)[1]=(v)>>8)
+#define PLONG(p,v) ((p)[0]=(v),(p)[1]=(v)>>8,(p)[2]=(v)>>16,(p)[3]=(v)>>24)
+
+struct Dosptr{
+ ulong addr; /* sector & entry within of file's directory entry */
+ ulong offset;
+ ulong paddr; /* of parent's directory entry */
+ ulong poffset;
+ ulong iclust; /* ordinal within file */
+ ulong clust;
+ ulong naddr; /* next block in directory (for writing multi entry elements) */
+ ulong prevaddr;
+ Iosect *p;
+ Dosdir *d;
+};
+
+#define QIDPATH(p) ((p)->addr*(Sectorsize/DOSDIRSIZE) + \
+ (p)->offset/DOSDIRSIZE)
+
+struct Xfs{
+ Xfs *next;
+ int omode; /* of file containing external fs */
+ char *name; /* of file containing external f.s. */
+ Qid qid; /* of file containing external f.s. */
+ long ref; /* attach count */
+ Qid rootqid; /* of plan9 constructed root directory */
+ uchar isfat32; /* is a fat 32 file system? */
+ short dev;
+ short fmt;
+ long offset;
+ void *ptr;
+};
+
+struct Xfile{
+ Xfile *next; /* in hash bucket */
+ long fid;
+ ulong flags;
+ Qid qid;
+ Xfs *xf;
+ Dosptr *ptr;
+};
+
+enum{
+ Asis, Clean, Clunk
+};
+
+enum{
+ Invalid, Short, ShortLower, Long
+};
+
+enum{ /* Xfile flags */
+ Oread = 1,
+ Owrite = 2,
+ Orclose = 4,
+ Omodes = 3,
+};
+
+enum{
+ Enevermind,
+ Eformat,
+ Eio,
+ Enoauth,
+ Enomem,
+ Enonexist,
+ Eperm,
+ Enofilsys,
+ Eauth,
+ Econtig,
+ Ebadfcall,
+ Ebadstat,
+ Eversion,
+ Etoolong,
+ Eerrstr,
+ ESIZE
+};
+
+extern int chatty;
+extern int errno;
+extern int readonly;
+extern char *deffile;
+extern int trspaces; \ No newline at end of file
diff --git a/sys/src/cmd/dossrv/devio.c b/sys/src/cmd/dossrv/devio.c
new file mode 100755
index 000000000..0ec1681ef
--- /dev/null
+++ b/sys/src/cmd/dossrv/devio.c
@@ -0,0 +1,65 @@
+#include <u.h>
+#include <libc.h>
+#include "iotrack.h"
+#include "dat.h"
+#include "fns.h"
+
+int readonly;
+
+static int
+deverror(char *name, Xfs *xf, long addr, long n, long nret)
+{
+ errno = Eio;
+ if(nret < 0){
+ chat("%s errstr=\"%r\"...", name);
+ close(xf->dev);
+ xf->dev = -1;
+ return -1;
+ }
+ fprint(2, "dev %d sector %ld, %s: %ld, should be %ld\n", xf->dev, addr, name, nret, n);
+ return -1;
+}
+
+int
+devread(Xfs *xf, long addr, void *buf, long n)
+{
+ long nread;
+
+ if(xf->dev < 0)
+ return -1;
+ nread = pread(xf->dev, buf, n, xf->offset+(vlong)addr*Sectorsize);
+ if (nread == n)
+ return 0;
+ return deverror("read", xf, addr, n, nread);
+}
+
+int
+devwrite(Xfs *xf, long addr, void *buf, long n)
+{
+ long nwrite;
+
+ if(xf->omode==OREAD)
+ return -1;
+
+ if(xf->dev < 0)
+ return -1;
+ nwrite = pwrite(xf->dev, buf, n, xf->offset+(vlong)addr*Sectorsize);
+ if (nwrite == n)
+ return 0;
+ return deverror("write", xf, addr, n, nwrite);
+}
+
+int
+devcheck(Xfs *xf)
+{
+ char buf[Sectorsize];
+
+ if(xf->dev < 0)
+ return -1;
+ if(pread(xf->dev, buf, Sectorsize, 0) != Sectorsize){
+ close(xf->dev);
+ xf->dev = -1;
+ return -1;
+ }
+ return 0;
+}
diff --git a/sys/src/cmd/dossrv/dosfs.c b/sys/src/cmd/dossrv/dosfs.c
new file mode 100755
index 000000000..f30d1ab8a
--- /dev/null
+++ b/sys/src/cmd/dossrv/dosfs.c
@@ -0,0 +1,916 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include "iotrack.h"
+#include "dat.h"
+#include "dosfs.h"
+#include "fns.h"
+
+void
+rversion(void)
+{
+ if(req->msize > Maxiosize)
+ rep->msize = Maxiosize;
+ else
+ rep->msize = req->msize;
+ rep->version = "9P2000";
+}
+
+void
+rauth(void)
+{
+ errno = Enoauth;
+}
+
+void
+rflush(void)
+{
+}
+
+void
+rattach(void)
+{
+ Xfs *xf;
+ Xfile *root;
+ Dosptr *dp;
+
+ root = xfile(req->fid, Clean);
+ if(!root){
+ errno = Enomem;
+ goto error;
+ }
+ root->xf = xf = getxfs(req->uname, req->aname);
+ if(!xf)
+ goto error;
+ if(xf->fmt == 0 && dosfs(xf) < 0){
+ errno = Eformat;
+ goto error;
+ }
+ root->qid.type = QTDIR;
+ root->qid.path = 0;
+ root->qid.vers = 0;
+ root->xf->rootqid = root->qid;
+ dp = malloc(sizeof(Dosptr));
+ if(dp == nil){
+ errno = Enomem;
+ goto error;
+ }
+ root->ptr = dp;
+ rootfile(root);
+ rep->qid = root->qid;
+ return;
+error:
+ if(root)
+ xfile(req->fid, Clunk);
+}
+
+Xfile*
+doclone(Xfile *of, int newfid)
+{
+ Xfile *nf, *next;
+ Dosptr *dp;
+
+ nf = xfile(newfid, Clean);
+ if(!nf){
+ errno = Enomem;
+ return nil;
+ }
+ dp = malloc(sizeof(Dosptr));
+ if(dp == nil){
+ errno = Enomem;
+ return nil;
+ }
+ next = nf->next;
+ *nf = *of;
+ nf->next = next;
+ nf->fid = req->newfid;
+ nf->ptr = dp;
+ refxfs(nf->xf, 1);
+ memmove(dp, of->ptr, sizeof(Dosptr));
+ dp->p = nil;
+ dp->d = nil;
+ return nf;
+}
+
+void
+rwalk(void)
+{
+ Xfile *f, *nf;
+ Dosptr dp[1], savedp[1];
+ int r, longtype;
+ Qid saveqid;
+
+ rep->nwqid = 0;
+ nf = nil;
+ f = xfile(req->fid, Asis);
+ if(f == nil){
+ chat("\tno xfile\n");
+ goto error2;
+ }
+ if(req->fid != req->newfid){
+ nf = doclone(f, req->newfid);
+ if(nf == nil){
+ chat("\tclone failed\n");
+ goto error2;
+ }
+ f = nf;
+ }
+
+ saveqid = f->qid;
+ memmove(savedp, f->ptr, sizeof(Dosptr));
+ for(; rep->nwqid < req->nwname && rep->nwqid < MAXWELEM; rep->nwqid++){
+ chat("\twalking %s\n", req->wname[rep->nwqid]);
+ if(!(f->qid.type & QTDIR)){
+ chat("\tnot dir: type=%#x\n", f->qid.type);
+ goto error;
+ }
+ if(strcmp(req->wname[rep->nwqid], ".") == 0){
+ ;
+ }else if(strcmp(req->wname[rep->nwqid], "..") == 0){
+ if(f->qid.path != f->xf->rootqid.path){
+ r = walkup(f, dp);
+ if(r < 0)
+ goto error;
+ memmove(f->ptr, dp, sizeof(Dosptr));
+ if(isroot(dp->addr))
+ f->qid.path = f->xf->rootqid.path;
+ else
+ f->qid.path = QIDPATH(dp);
+ }
+ }else{
+ fixname(req->wname[rep->nwqid]);
+ longtype = classifyname(req->wname[rep->nwqid]);
+ if(longtype==Invalid || getfile(f) < 0)
+ goto error;
+
+ /*
+ * always do a search for the long name,
+ * because it could be filed as such
+ */
+ r = searchdir(f, req->wname[rep->nwqid], dp, 0, longtype);
+ putfile(f);
+ if(r < 0)
+ goto error;
+ memmove(f->ptr, dp, sizeof(Dosptr));
+ f->qid.path = QIDPATH(dp);
+ f->qid.type = QTFILE;
+ if(isroot(dp->addr))
+ f->qid.path = f->xf->rootqid.path;
+ else if(dp->d->attr & DDIR)
+ f->qid.type = QTDIR;
+ else if(dp->d->attr & DSYSTEM){
+ f->qid.type |= QTEXCL;
+ if(iscontig(f->xf, dp->d))
+ f->qid.type |= QTAPPEND;
+ }
+//ZZZ maybe use other bits than qtexcl & qtapppend
+ putfile(f);
+ }
+ rep->wqid[rep->nwqid] = f->qid;
+ }
+ return;
+error:
+ f->qid = saveqid;
+ memmove(f->ptr, savedp, sizeof(Dosptr));
+ if(nf != nil)
+ xfile(req->newfid, Clunk);
+error2:
+ if(!errno && !rep->nwqid)
+ errno = Enonexist;
+}
+
+void
+ropen(void)
+{
+ Xfile *f;
+ Iosect *p;
+ Dosptr *dp;
+ int attr, omode;
+
+ f = xfile(req->fid, Asis);
+ if(!f || (f->flags&Omodes)){
+ errno = Eio;
+ return;
+ }
+ dp = f->ptr;
+ omode = 0;
+ if(!isroot(dp->paddr) && (req->mode & ORCLOSE)){
+ /*
+ * check on parent directory of file to be deleted
+ */
+ p = getsect(f->xf, dp->paddr);
+ if(p == nil){
+ errno = Eio;
+ return;
+ }
+ attr = ((Dosdir *)&p->iobuf[dp->poffset])->attr;
+ putsect(p);
+ if(attr & DRONLY){
+ errno = Eperm;
+ return;
+ }
+ omode |= Orclose;
+ }else if(req->mode & ORCLOSE)
+ omode |= Orclose;
+ if(getfile(f) < 0){
+ errno = Enonexist;
+ return;
+ }
+ if(!isroot(dp->addr))
+ attr = dp->d->attr;
+ else
+ attr = DDIR;
+ switch(req->mode & 7){
+ case OREAD:
+ case OEXEC:
+ omode |= Oread;
+ break;
+ case ORDWR:
+ omode |= Oread;
+ /* fall through */
+ case OWRITE:
+ omode |= Owrite;
+ if(attr & DRONLY){
+ errno = Eperm;
+ goto out;
+ }
+ break;
+ default:
+ errno = Eio;
+ goto out;
+ }
+ if(req->mode & OTRUNC){
+ if(attr & DDIR || attr & DRONLY){
+ errno = Eperm;
+ goto out;
+ }
+ if(truncfile(f, 0) < 0){
+ errno = Eio;
+ goto out;
+ }
+ }
+ f->flags |= omode;
+ rep->qid = f->qid;
+ rep->iounit = 0;
+out:
+ putfile(f);
+}
+
+static int
+mk8dot3name(Xfile *f, Dosptr *ndp, char *name, char *sname)
+{
+ Dosptr tmpdp;
+ int i, longtype;
+
+ if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
+ return Invalid;
+
+ /*
+ * always do a search for the long name,
+ * because it could be filed as such
+ */
+ fixname(name);
+ longtype = classifyname(name);
+ if(longtype==Invalid || searchdir(f, name, ndp, 1, longtype) < 0)
+ return Invalid;
+
+ if(longtype==Short)
+ return Short;
+
+ if(longtype==ShortLower){
+ /*
+ * alias is the upper-case version, which we
+ * already know does not exist.
+ */
+ strcpy(sname, name);
+ for(i=0; sname[i]; i++)
+ if('a' <= sname[i] && sname[i] <= 'z')
+ sname[i] += 'A'-'a';
+ return ShortLower;
+ }
+
+ /*
+ * find alias for the long name
+ */
+ for(i=1;; i++){
+ mkalias(name, sname, i);
+ if(searchdir(f, sname, &tmpdp, 0, 0) < 0)
+ return Long;
+ putsect(tmpdp.p);
+ }
+}
+
+/*
+ * fill in a directory entry for a new file
+ */
+static int
+mkdentry(Xfs *xf, Dosptr *ndp, char *name, char *sname, int longtype, int nattr, long start, long length)
+{
+ Dosdir *nd;
+
+ /*
+ * fill in the entry
+ */
+ ndp->p = getsect(xf, ndp->addr);
+ if(ndp->p == nil
+ || longtype!=Short && putlongname(xf, ndp, name, sname) < 0){
+ errno = Eio;
+ return -1;
+ }
+
+ ndp->d = (Dosdir *)&ndp->p->iobuf[ndp->offset];
+ nd = ndp->d;
+ memset(nd, 0, DOSDIRSIZE);
+
+ if(longtype!=Short)
+ name = sname;
+ putname(name, nd);
+
+ nd->attr = nattr;
+ puttime(nd, 0);
+ putstart(xf, nd, start);
+ nd->length[0] = length;
+ nd->length[1] = length>>8;
+ nd->length[2] = length>>16;
+ nd->length[3] = length>>24;
+
+ ndp->p->flags |= BMOD;
+
+ return 0;
+}
+
+void
+rcreate(void)
+{
+ Dosbpb *bp;
+ Xfile *f;
+ Dosptr *pdp, *ndp;
+ Iosect *xp;
+ Dosdir *pd, *xd;
+ char sname[13];
+ long start;
+ int longtype, attr, omode, nattr;
+
+ f = xfile(req->fid, Asis);
+ if(!f || (f->flags&Omodes) || getfile(f)<0){
+ errno = Eio;
+ return;
+ }
+ pdp = f->ptr;
+ pd = pdp->d;
+ /*
+ * perm check
+ */
+ if(isroot(pdp->addr) && pd != nil)
+ panic("root pd != nil");
+ attr = pd ? pd->attr : DDIR;
+ if(!(attr & DDIR) || (attr & DRONLY)){
+badperm:
+ putfile(f);
+ errno = Eperm;
+ return;
+ }
+ omode = 0;
+ if(req->mode & ORCLOSE)
+ omode |= Orclose;
+ switch(req->mode & 7){
+ case OREAD:
+ case OEXEC:
+ omode |= Oread;
+ break;
+ case ORDWR:
+ omode |= Oread;
+ /* fall through */
+ case OWRITE:
+ omode |= Owrite;
+ if(req->perm & DMDIR)
+ goto badperm;
+ break;
+ default:
+ goto badperm;
+ }
+
+ /*
+ * check the name, find the slot for the dentry,
+ * and find a good alias for a long name
+ */
+ ndp = malloc(sizeof(Dosptr));
+ if(ndp == nil){
+ putfile(f);
+ errno = Enomem;
+ return;
+ }
+ longtype = mk8dot3name(f, ndp, req->name, sname);
+ chat("rcreate %s longtype %d...\n", req->name, longtype);
+ if(longtype == Invalid){
+ free(ndp);
+ goto badperm;
+ }
+
+ /*
+ * allocate first cluster, if making directory
+ */
+ start = 0;
+ bp = nil;
+ if(req->perm & DMDIR){
+ bp = f->xf->ptr;
+ mlock(bp);
+ start = falloc(f->xf);
+ unmlock(bp);
+ if(start <= 0){
+ free(ndp);
+ putfile(f);
+ errno = Eio;
+ return;
+ }
+ }
+
+ /*
+ * make the entry
+ */
+ nattr = 0;
+ if((req->perm & 0222) == 0)
+ nattr |= DRONLY;
+ if(req->perm & DMDIR)
+ nattr |= DDIR;
+
+ if(mkdentry(f->xf, ndp, req->name, sname, longtype, nattr, start, 0) < 0){
+ if(ndp->p != nil)
+ putsect(ndp->p);
+ free(ndp);
+ if(start > 0)
+ ffree(f->xf, start);
+ putfile(f);
+ return;
+ }
+
+ if(pd != nil){
+ puttime(pd, 0);
+ pdp->p->flags |= BMOD;
+ }
+
+ /*
+ * fix up the fid
+ */
+ f->ptr = ndp;
+ f->qid.type = QTFILE;
+ f->qid.path = QIDPATH(ndp);
+
+//ZZZ set type for excl, append?
+ if(req->perm & DMDIR){
+ f->qid.type = QTDIR;
+ xp = getsect(f->xf, clust2sect(bp, start));
+ if(xp == nil){
+ errno = Eio;
+ goto badio;
+ }
+ xd = (Dosdir *)&xp->iobuf[0];
+ memmove(xd, ndp->d, DOSDIRSIZE);
+ memset(xd->name, ' ', sizeof xd->name+sizeof xd->ext);
+ xd->name[0] = '.';
+ xd = (Dosdir *)&xp->iobuf[DOSDIRSIZE];
+ if(pd)
+ memmove(xd, pd, DOSDIRSIZE);
+ else{
+ memset(xd, 0, DOSDIRSIZE);
+ puttime(xd, 0);
+ xd->attr = DDIR;
+ }
+ memset(xd->name, ' ', sizeof xd->name+sizeof xd->ext);
+ xd->name[0] = '.';
+ xd->name[1] = '.';
+ xp->flags |= BMOD;
+ putsect(xp);
+ }
+
+ f->flags |= omode;
+ rep->qid = f->qid;
+ rep->iounit = 0;
+
+badio:
+ putfile(f);
+ putsect(pdp->p);
+ free(pdp);
+}
+
+void
+rread(void)
+{
+ Xfile *f;
+ int r;
+
+ if (!(f=xfile(req->fid, Asis)) || !(f->flags&Oread))
+ goto error;
+ if(req->count > sizeof repdata)
+ req->count = sizeof repdata;
+ if(f->qid.type & QTDIR){
+ if(getfile(f) < 0)
+ goto error;
+ r = readdir(f, repdata, req->offset, req->count);
+ }else{
+ if(getfile(f) < 0)
+ goto error;
+ r = readfile(f, repdata, req->offset, req->count);
+ }
+ putfile(f);
+ if(r < 0){
+error:
+ errno = Eio;
+ }else{
+ rep->count = r;
+ rep->data = (char*)repdata;
+ }
+}
+
+void
+rwrite(void)
+{
+ Xfile *f;
+ int r;
+
+ if (!(f=xfile(req->fid, Asis)) || !(f->flags&Owrite))
+ goto error;
+ if(getfile(f) < 0)
+ goto error;
+ r = writefile(f, req->data, req->offset, req->count);
+ putfile(f);
+ if(r < 0){
+error:
+ errno = Eio;
+ }else{
+ rep->count = r;
+ }
+}
+
+void
+rclunk(void)
+{
+ xfile(req->fid, Clunk);
+ sync();
+}
+
+/*
+ * wipe out a dos directory entry
+ */
+static void
+doremove(Xfs *xf, Dosptr *dp)
+{
+ Iosect *p;
+ int prevdo;
+
+ dp->p->iobuf[dp->offset] = DOSEMPTY;
+ dp->p->flags |= BMOD;
+ for(prevdo = dp->offset-DOSDIRSIZE; prevdo >= 0; prevdo -= DOSDIRSIZE){
+ if(dp->p->iobuf[prevdo+11] != 0xf)
+ break;
+ dp->p->iobuf[prevdo] = DOSEMPTY;
+ }
+ if(prevdo < 0 && dp->prevaddr != -1){
+ p = getsect(xf, dp->prevaddr);
+ for(prevdo = ((Dosbpb*)xf->ptr)->sectsize-DOSDIRSIZE; prevdo >= 0; prevdo -= DOSDIRSIZE){
+ if(p->iobuf[prevdo+11] != 0xf)
+ break;
+ p->iobuf[prevdo] = DOSEMPTY;
+ p->flags |= BMOD;
+ }
+ putsect(p);
+ }
+}
+
+void
+rremove(void)
+{
+ Xfile *f;
+ Dosptr *dp;
+ Iosect *parp;
+ Dosdir *pard;
+
+ f = xfile(req->fid, Asis);
+ parp = nil;
+ if(f == nil){
+ errno = Eio;
+ goto out;
+ }
+ dp = f->ptr;
+ if(isroot(dp->addr)){
+ errno = Eperm;
+ goto out;
+ }
+
+ /*
+ * can't remove if parent is read only,
+ * it's a non-empty directory,
+ * or it's a read only file in the root directory
+ */
+ parp = getsect(f->xf, dp->paddr);
+ if(parp == nil
+ || getfile(f) < 0){
+ errno = Eio;
+ goto out;
+ }
+ pard = (Dosdir *)&parp->iobuf[dp->poffset];
+ if(!isroot(dp->paddr) && (pard->attr & DRONLY)
+ || (dp->d->attr & DDIR) && emptydir(f) < 0
+ || isroot(dp->paddr) && (dp->d->attr&DRONLY)){
+ errno = Eperm;
+ goto out;
+ }
+ if(truncfile(f, 0) < 0){
+ errno = Eio;
+ goto out;
+ }
+ doremove(f->xf, f->ptr);
+ if(!isroot(dp->paddr)){
+ puttime(pard, 0);
+ parp->flags |= BMOD;
+ }
+out:
+ if(parp != nil)
+ putsect(parp);
+ if(f != nil)
+ putfile(f);
+ xfile(req->fid, Clunk);
+ sync();
+}
+
+static void
+dostat(Xfile *f, Dir *d)
+{
+ Dosptr *dp;
+ Iosect *p;
+ char *name, namebuf[DOSNAMELEN];
+ int islong, sum, prevdo;
+
+ dp = f->ptr;
+ if(isroot(dp->addr)){
+ memset(d, 0, sizeof(Dir));
+ d->name = "/";
+ d->qid.type = QTDIR;
+ d->qid.path = f->xf->rootqid.path;
+ d->mode = DMDIR|0777;
+ d->uid = "bill";
+ d->muid = "bill";
+ d->gid = "trog";
+ }else{
+ /*
+ * assemble any long file name
+ */
+ sum = aliassum(dp->d);
+ islong = 0;
+ name = namebuf;
+ for(prevdo = dp->offset-DOSDIRSIZE; prevdo >= 0; prevdo -= DOSDIRSIZE){
+ if(dp->p->iobuf[prevdo+11] != 0xf)
+ break;
+ name = getnamesect(namebuf, name, &dp->p->iobuf[prevdo], &islong, &sum, -1);
+ }
+ if(prevdo < 0 && dp->prevaddr != -1){
+ p = getsect(f->xf, dp->prevaddr);
+ for(prevdo = ((Dosbpb*)f->xf->ptr)->sectsize-DOSDIRSIZE; prevdo >= 0; prevdo -= DOSDIRSIZE){
+ if(p->iobuf[prevdo+11] != 0xf)
+ break;
+ name = getnamesect(namebuf, name, &p->iobuf[prevdo], &islong, &sum, -1);
+ }
+ putsect(p);
+ }
+ getdir(f->xf, d, dp->d, dp->addr, dp->offset);
+ if(islong && sum == -1 && nameok(namebuf))
+ strcpy(d->name, namebuf);
+ }
+}
+
+void
+rstat(void)
+{
+ Dir dir;
+ Xfile *f;
+
+ f = xfile(req->fid, Asis);
+ if(!f || getfile(f) < 0){
+ errno = Eio;
+ return;
+ }
+
+ dir.name = repdata;
+ dostat(f, &dir);
+
+ rep->nstat = convD2M(&dir, statbuf, sizeof statbuf);
+ rep->stat = statbuf;
+ putfile(f);
+}
+
+void
+rwstat(void)
+{
+ Dir dir, wdir;
+ Xfile *f, pf;
+ Dosptr *dp, ndp, pdp;
+ Iosect *parp;
+ Dosdir *pard, *d, od;
+ char sname[13];
+ ulong oaddr, ooffset;
+ long start, length;
+ int i, longtype, changes, attr;
+
+ f = xfile(req->fid, Asis);
+ if(!f || getfile(f) < 0){
+ errno = Eio;
+ return;
+ }
+ dp = f->ptr;
+
+ if(isroot(dp->addr)){
+ errno = Eperm;
+ goto out;
+ }
+
+ changes = 0;
+ dir.name = repdata;
+ dostat(f, &dir);
+ if(convM2D(req->stat, req->nstat, &wdir, (char*)statbuf) != req->nstat){
+ errno = Ebadstat;
+ goto out;
+ }
+
+ /*
+ * To change length, must have write permission on file.
+ * we only allow truncates for now.
+ */
+ if(wdir.length!=~0 && wdir.length!=dir.length){
+ if(wdir.length > dir.length || !dir.mode & 0222){
+ errno = Eperm;
+ goto out;
+ }
+ }
+
+ /*
+ * no chown or chgrp
+ */
+ if(wdir.uid[0] != '\0' && strcmp(dir.uid, wdir.uid) != 0
+ || wdir.gid[0] != '\0' && strcmp(dir.gid, wdir.gid) != 0){
+ errno = Eperm;
+ goto out;
+ }
+
+ /*
+ * mode/mtime allowed
+ */
+ if(wdir.mtime != ~0 && dir.mtime != wdir.mtime)
+ changes = 1;
+
+ /*
+ * Setting DMAPPEND (make system file contiguous)
+ * requires setting DMEXCL (system file).
+ */
+ if(wdir.mode != ~0){
+ if((wdir.mode & 7) != ((wdir.mode >> 3) & 7)
+ || (wdir.mode & 7) != ((wdir.mode >> 6) & 7)){
+ errno = Eperm;
+ goto out;
+ }
+ if((dir.mode^wdir.mode) & (DMEXCL|DMAPPEND|0777))
+ changes = 1;
+ if((dir.mode^wdir.mode) & DMAPPEND) {
+ if((wdir.mode & (DMEXCL|DMAPPEND)) == DMAPPEND) {
+ errno = Eperm;
+ goto out;
+ }
+ if((wdir.mode & DMAPPEND) && makecontig(f, 0) < 0) {
+ errno = Econtig;
+ goto out;
+ }
+ }
+ }
+
+
+ /*
+ * to rename:
+ * 1) make up a fake clone
+ * 2) walk to parent
+ * 3) remove the old entry
+ * 4) create entry with new name
+ * 5) write correct mode/mtime info
+ * we need to remove the old entry before creating the new one
+ * to avoid a lock loop.
+ */
+ if(wdir.name[0] != '\0' && strcmp(dir.name, wdir.name) != 0){
+ if(utflen(wdir.name) >= DOSNAMELEN){
+ errno = Etoolong;
+ goto out;
+ }
+
+ /*
+ * grab parent directory of file to be changed and check for write perm
+ * rename also disallowed for read-only files in root directory
+ */
+ parp = getsect(f->xf, dp->paddr);
+ if(parp == nil){
+ errno = Eio;
+ goto out;
+ }
+ pard = (Dosdir *)&parp->iobuf[dp->poffset];
+ if(!isroot(dp->paddr) && (pard->attr & DRONLY)
+ || isroot(dp->paddr) && (dp->d->attr&DRONLY)){
+ putsect(parp);
+ errno = Eperm;
+ goto out;
+ }
+
+ /*
+ * retrieve info from old entry
+ */
+ oaddr = dp->addr;
+ ooffset = dp->offset;
+ d = dp->d;
+ od = *d;
+ start = getstart(f->xf, d);
+ length = GLONG(d->length);
+ attr = d->attr;
+
+ /*
+ * temporarily release file to allow other directory ops:
+ * walk to parent, validate new name
+ * then remove old entry
+ */
+ putfile(f);
+ pf = *f;
+ memset(&pdp, 0, sizeof(Dosptr));
+ pdp.prevaddr = -1;
+ pdp.naddr = -1;
+ pdp.addr = dp->paddr;
+ pdp.offset = dp->poffset;
+ pdp.p = parp;
+ if(!isroot(pdp.addr))
+ pdp.d = (Dosdir *)&parp->iobuf[pdp.offset];
+ pf.ptr = &pdp;
+ longtype = mk8dot3name(&pf, &ndp, wdir.name, sname);
+ if(longtype==Invalid){
+ putsect(parp);
+ errno = Eperm;
+ return;
+ }
+ if(getfile(f) < 0){
+ putsect(parp);
+ errno = Eio;
+ return;
+ }
+ doremove(f->xf, dp);
+ putfile(f);
+
+ /*
+ * search for dir entry again, since we may be able to use the old slot,
+ * and we need to set up the naddr field if a long name spans the block.
+ * create new entry.
+ */
+ if(searchdir(&pf, wdir.name, dp, 1, longtype) < 0
+ || mkdentry(pf.xf, dp, wdir.name, sname, longtype, attr, start, length) < 0){
+ putsect(parp);
+ errno = Eio;
+ goto out;
+ }
+
+ /*
+ * copy invisible fields
+ */
+ d = dp->d;
+ for(i = 0; i < 2; i++)
+ d->ctime[i] = od.ctime[i];
+ for(i = 0; i < nelem(od.cdate); i++)
+ d->cdate[i] = od.cdate[i];
+ for(i = 0; i < nelem(od.adate); i++)
+ d->adate[i] = od.adate[i];
+
+ putsect(parp);
+
+ /*
+ * relocate up other fids to the same file, if it moved
+ */
+ f->qid.path = QIDPATH(dp);
+ if(oaddr != dp->addr || ooffset != dp->offset)
+ dosptrreloc(f, dp, oaddr, ooffset);
+
+ /*
+ * copy fields that are not supposed to change
+ */
+ if(wdir.mtime == ~0)
+ wdir.mtime = dir.mtime;
+ if(wdir.mode == ~0)
+ wdir.mode = dir.mode;
+ changes = 1;
+ }
+
+ /*
+ * do the actual truncate
+ */
+ if(wdir.length != ~0 && wdir.length != dir.length && truncfile(f, wdir.length) < 0)
+ errno = Eio;
+
+ if(changes){
+ putdir(dp->d, &wdir);
+ dp->p->flags |= BMOD;
+ }
+
+out:
+ putfile(f);
+ sync();
+}
diff --git a/sys/src/cmd/dossrv/dosfs.h b/sys/src/cmd/dossrv/dosfs.h
new file mode 100755
index 000000000..3f933039b
--- /dev/null
+++ b/sys/src/cmd/dossrv/dosfs.h
@@ -0,0 +1,10 @@
+enum
+{
+ Maxfdata = 8192,
+ Maxiosize = IOHDRSZ+Maxfdata,
+};
+
+extern Fcall *req;
+extern Fcall *rep;
+extern char repdata[Maxfdata];
+extern uchar statbuf[STATMAX];
diff --git a/sys/src/cmd/dossrv/dossubs.c b/sys/src/cmd/dossrv/dossubs.c
new file mode 100755
index 000000000..86d95674c
--- /dev/null
+++ b/sys/src/cmd/dossrv/dossubs.c
@@ -0,0 +1,1940 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <fcall.h>
+#include "iotrack.h"
+#include "dat.h"
+#include "fns.h"
+
+static uchar isdos[256];
+
+int
+isdosfs(uchar *buf)
+{
+ /*
+ * When dynamic disc managers move the disc partition,
+ * they make it start with 0xE9.
+ */
+ if(buf[0] == 0xE9)
+ return 1;
+
+ /*
+ * Check if the jump displacement (magic[1]) is too short for a FAT.
+ *
+ * check now omitted due to digital cameras that use a 0 jump.
+ * the ecma-107 standard says this is okay and that interoperable fat
+ * implementations shouldn't assume this:
+ * http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-107.pdf,
+ * page 11.
+ */
+ if(buf[0] == 0xEB && buf[2] == 0x90 /* && buf[1] >= 0x30 */)
+ return 1;
+ if(chatty)
+ fprint(2, "bad sig %.2ux %.2ux %.2uxn", buf[0], buf[1], buf[2]);
+
+ return 0;
+}
+
+int
+dosfs(Xfs *xf)
+{
+ Iosect *p, *p1;
+ Dosboot *b;
+ Fatinfo *fi;
+ Dosboot32 *b32;
+ Dosbpb *bp;
+ long fisec, extflags;
+ int i;
+
+ if(!isdos['a']){
+ for(i = 'a'; i <= 'z'; i++)
+ isdos[i] = 1;
+ for(i = 'A'; i <= 'Z'; i++)
+ isdos[i] = 1;
+ for(i = '0'; i <= '9'; i++)
+ isdos[i] = 1;
+ isdos['$'] = 1;
+ isdos['%'] = 1;
+ isdos['''] = 1;
+ isdos['-'] = 1;
+ isdos['_'] = 1;
+ isdos['@'] = 1;
+ isdos['~'] = 1;
+ isdos['`'] = 1;
+ isdos['!'] = 1;
+ isdos['('] = 1;
+ isdos[')'] = 1;
+ isdos['{'] = 1;
+ isdos['}'] = 1;
+ isdos['^'] = 1;
+ isdos['#'] = 1;
+ isdos['&'] = 1;
+ }
+
+ p = getsect(xf, 0);
+ if(p == 0)
+ return -1;
+
+ b = (Dosboot*)p->iobuf;
+ if(b->clustsize == 0 || isdosfs(p->iobuf) == 0){
+ putsect(p);
+ return -1;
+ }
+
+ bp = malloc(sizeof(Dosbpb));
+ memset(bp, 0, sizeof(Dosbpb)); /* clear lock */
+ xf->ptr = bp;
+ xf->fmt = 1;
+
+ bp->sectsize = GSHORT(b->sectsize);
+ bp->clustsize = b->clustsize;
+ bp->nresrv = GSHORT(b->nresrv);
+ bp->nfats = b->nfats;
+ bp->rootsize = GSHORT(b->rootsize);
+ bp->volsize = GSHORT(b->volsize);
+ if(bp->volsize == 0)
+ bp->volsize = GLONG(b->bigvolsize);
+ bp->mediadesc = b->mediadesc;
+ bp->fatsize = GSHORT(b->fatsize);
+ bp->fataddr = GSHORT(b->nresrv);
+
+ bp->fatinfo = 0;
+
+ if(bp->fatsize == 0){ /* is FAT32 */
+ if(chatty)
+ bootsecdump32(2, xf, (Dosboot32*)b);
+ xf->isfat32 = 1;
+ b32 = (Dosboot32*)b;
+ bp->fatsize = GLONG(b32->fatsize32);
+ if(bp->fatsize == 0){
+ putsect(p);
+ if(chatty)
+ fprint(2, "fatsize 0\n");
+ return -1;
+ }
+ bp->dataaddr = bp->fataddr + bp->nfats*bp->fatsize;
+ bp->rootaddr = 0;
+ bp->rootstart = GLONG(b32->rootstart);
+
+ /*
+ * disable fat mirroring?
+ */
+ extflags = GSHORT(b32->extflags);
+ if(extflags & 0x0080){
+ for(i = 0; i < 4; i++){
+ if(extflags & (1 << i)){
+ bp->fataddr += i * bp->fatsize;
+ bp->nfats = 1;
+ break;
+ }
+ }
+ }
+
+ /*
+ * fat free list info
+ */
+ bp->freeptr = FATRESRV;
+ fisec = GSHORT(b32->infospec);
+ if(fisec != 0 && fisec < GSHORT(b32->nresrv)){
+ p1 = getsect(xf, fisec);
+ if(p1 != nil){
+ fi = (Fatinfo*)p1->iobuf;
+ if(GLONG(fi->sig1) == FATINFOSIG1 && GLONG(fi->sig) == FATINFOSIG){
+ bp->fatinfo = fisec;
+ bp->freeptr = GLONG(fi->nextfree);
+ bp->freeclusters = GLONG(fi->freeclust);
+ chat("fat info: %ld free clusters, next free %ld\n", bp->freeclusters, bp->freeptr);
+ }
+ putsect(p1);
+ }
+ }
+ }else{
+ if(chatty)
+ bootdump(2, b);
+ bp->rootaddr = bp->fataddr + bp->nfats*bp->fatsize;
+ bp->rootstart = 0;
+ i = bp->rootsize*DOSDIRSIZE + bp->sectsize-1;
+ i /= bp->sectsize;
+ bp->dataaddr = bp->rootaddr + i;
+ bp->freeptr = FATRESRV;
+ }
+ bp->fatclusters = FATRESRV+(bp->volsize - bp->dataaddr)/bp->clustsize;
+
+ if(xf->isfat32)
+ bp->fatbits = 32;
+ else if(bp->fatclusters < 4087)
+ bp->fatbits = 12;
+ else
+ bp->fatbits = 16;
+
+ chat("fatbits=%d (%d clusters)...", bp->fatbits, bp->fatclusters);
+ for(i=0; i<b->nfats; i++)
+ chat("fat %d: %ld...", i, bp->fataddr+i*bp->fatsize);
+ chat("root: %ld...", bp->rootaddr);
+ chat("data: %ld...", bp->dataaddr);
+ putsect(p);
+ return 0;
+}
+
+/*
+ * initialize f to the root directory
+ * this file has no Dosdir entry,
+ * so we special case it all over.
+ */
+void
+rootfile(Xfile *f)
+{
+ Dosptr *dp;
+
+ dp = f->ptr;
+ memset(dp, 0, sizeof(Dosptr));
+ dp->prevaddr = -1;
+}
+
+int
+isroot(ulong addr)
+{
+ return addr == 0;
+}
+
+int
+getfile(Xfile *f)
+{
+ Dosptr *dp;
+ Iosect *p;
+
+ dp = f->ptr;
+ if(dp->p)
+ panic("getfile");
+ p = getsect(f->xf, dp->addr);
+ if(p == nil)
+ return -1;
+
+ /*
+ * we could also make up a Dosdir for the root
+ */
+ dp->d = nil;
+ if(!isroot(dp->addr)){
+ if(f->qid.path != QIDPATH(dp)){
+ chat("qid mismatch f=%#llux d=%#lux...", f->qid.path, QIDPATH(dp));
+ putsect(p);
+ errno = Enonexist;
+ return -1;
+ }
+ dp->d = (Dosdir *)&p->iobuf[dp->offset];
+ }
+ dp->p = p;
+ return 0;
+}
+
+void
+putfile(Xfile *f)
+{
+ Dosptr *dp;
+
+ dp = f->ptr;
+ if(!dp->p)
+ panic("putfile");
+ putsect(dp->p);
+ dp->p = nil;
+ dp->d = nil;
+}
+
+long
+getstart(Xfs *xf, Dosdir *d)
+{
+ long start;
+
+ start = GSHORT(d->start);
+ if(xf->isfat32)
+ start |= GSHORT(d->hstart)<<16;
+ return start;
+}
+
+void
+putstart(Xfs *xf, Dosdir *d, long start)
+{
+ PSHORT(d->start, start);
+ if(xf->isfat32)
+ PSHORT(d->hstart, start>>16);
+}
+
+/*
+ * return the disk cluster for the iclust cluster in f
+ */
+long
+fileclust(Xfile *f, long iclust, int cflag)
+{
+ Dosbpb *bp;
+ Dosptr *dp;
+ Dosdir *d;
+ long start, clust, nskip, next;
+
+ bp = f->xf->ptr;
+ dp = f->ptr;
+ d = dp->d;
+ next = 0;
+
+ /*
+ * asking for the cluster of the root directory
+ * is not a well-formed question, since the root directory
+ * does not begin on a cluster boundary.
+ */
+ if(!f->xf->isfat32 && isroot(dp->addr))
+ return -1;
+
+ if(f->xf->isfat32 && isroot(dp->addr)){
+ start = bp->rootstart;
+ }else{
+ start = getstart(f->xf, d);
+ if(start == 0){
+ if(!cflag)
+ return -1;
+ mlock(bp);
+ start = falloc(f->xf);
+ unmlock(bp);
+ if(start <= 0)
+ return -1;
+ puttime(d, 0);
+ putstart(f->xf, d, start);
+ dp->p->flags |= BMOD;
+ dp->clust = 0;
+ }
+ }
+ if(dp->clust == 0 || iclust < dp->iclust){
+ clust = start;
+ nskip = iclust;
+ }else{
+ clust = dp->clust;
+ nskip = iclust - dp->iclust;
+ }
+ if(chatty > 1 && nskip > 0)
+ chat("clust %#lx, skip %ld...", clust, nskip);
+ if(clust <= 0)
+ return -1;
+ if(nskip > 0){
+ mlock(bp);
+ while(--nskip >= 0){
+ next = getfat(f->xf, clust);
+ if(chatty > 1)
+ chat("->%#lx", next);
+ if(next > 0){
+ clust = next;
+ continue;
+ }else if(!cflag)
+ break;
+ if(d && (d->attr&DSYSTEM)){
+ next = cfalloc(f);
+ if(next < 0)
+ break;
+ /* cfalloc will call putfat for us, since clust may change */
+ } else {
+ next = falloc(f->xf);
+ if(next < 0)
+ break;
+ putfat(f->xf, clust, next);
+ }
+ clust = next;
+ }
+ unmlock(bp);
+ if(next <= 0)
+ return -1;
+ dp->clust = clust;
+ dp->iclust = iclust;
+ }
+ if(chatty > 1)
+ chat(" clust(%#lx)=%#lx...", iclust, clust);
+ return clust;
+}
+
+/*
+ * return the disk sector for the isect disk sector in f
+ */
+long
+fileaddr(Xfile *f, long isect, int cflag)
+{
+ Dosbpb *bp;
+ Dosptr *dp;
+ long clust;
+
+ bp = f->xf->ptr;
+ dp = f->ptr;
+ if(!f->xf->isfat32 && isroot(dp->addr)){
+ if(isect*bp->sectsize >= bp->rootsize*DOSDIRSIZE)
+ return -1;
+ return bp->rootaddr + isect;
+ }
+ clust = fileclust(f, isect/bp->clustsize, cflag);
+ if(clust < 0)
+ return -1;
+
+ return clust2sect(bp, clust) + isect%bp->clustsize;
+}
+
+/*
+ * translate names
+ */
+void
+fixname(char *buf)
+{
+ int c;
+ char *p;
+
+ p = buf;
+ while(c = *p){
+ if(c == ':' && trspaces)
+ *p = ' ';
+ p++;
+ }
+}
+
+/*
+ * classify the file name as one of
+ * Invalid - contains a bad character
+ * Short - short valid 8.3 name, no lowercase letters
+ * ShortLower - short valid 8.3 name except for lowercase letters
+ * Long - long name
+ */
+int
+classifyname(char *buf)
+{
+ char *p, *dot;
+ int c, isextended, is8dot3, islower, ndot;
+
+ p = buf;
+ isextended = 0;
+ islower = 0;
+ dot = nil;
+ ndot = 0;
+ while(c = (uchar)*p){
+ if(c&0x80) /* UTF8 */
+ isextended = 1;
+ else if(c == '.'){
+ dot = p;
+ ndot++;
+ }else if(strchr("+,:;=[] ", c))
+ isextended = 1;
+ else if(!isdos[c])
+ return Invalid;
+ if('a' <= c && c <= 'z')
+ islower = 1;
+ p++;
+ }
+
+ is8dot3 = (ndot==0 && p-buf <= 8) || (ndot==1 && dot-buf <= 8 && p-(dot+1) <= 3);
+
+ if(!isextended && is8dot3){
+ if(islower)
+ return ShortLower;
+ return Short;
+ }
+ return Long;
+}
+
+/*
+ * make an alias for a valid long file name
+ */
+void
+mkalias(char *name, char *sname, int id)
+{
+ Rune r;
+ char *s, *e, sid[10];
+ int i, esuf, v;
+
+ e = strrchr(name, '.');
+ if(e == nil)
+ e = strchr(name, '\0');
+
+ s = name;
+ i = 0;
+ while(s < e && i < 6){
+ if(isdos[(uchar)*s])
+ sname[i++] = *s++;
+ else
+ s += chartorune(&r, s);
+ }
+
+ v = snprint(sid, 10, "%d", id);
+ if(i + 1 + v > 8)
+ i = 8 - 1 - v;
+ sname[i++] = '~';
+ strcpy(&sname[i], sid);
+ i += v;
+
+ sname[i++] = '.';
+ esuf = i + 3;
+ if(esuf > 12)
+ panic("bad mkalias");
+ while(*e && i < esuf){
+ if(isdos[(uchar)*e])
+ sname[i++] = *e++;
+ else
+ e += chartorune(&r, e);
+ }
+ if(sname[i-1] == '.')
+ i--;
+ sname[i] = '\0';
+}
+
+/*
+ * check for valid plan 9 names,
+ * rewrite ' ' to ':'
+ */
+char isfrog[256]={
+ /*NUL*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ /*BKS*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ /*DLE*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ /*CAN*/ 1, 1, 1, 1, 1, 1, 1, 1,
+/* [' '] 1, let's try this -rsc */
+ ['/'] 1,
+ [0x7f] 1,
+};
+
+int
+nameok(char *elem)
+{
+ while(*elem) {
+ if(*elem == ' ' && trspaces)
+ *elem = ':';
+ if(isfrog[*(uchar*)elem])
+ return 0;
+ elem++;
+ }
+ return 1;
+}
+
+/*
+ * look for a directory entry matching name
+ * always searches for long names which match a short name
+ */
+int
+searchdir(Xfile *f, char *name, Dosptr *dp, int cflag, int longtype)
+{
+ Xfs *xf;
+ Iosect *p;
+ Dosbpb *bp;
+ Dosdir *d;
+ char buf[261], *bname;
+ int isect, addr, o, addr1, addr2, prevaddr, prevaddr1, o1, islong, have, need, sum;
+
+ xf = f->xf;
+ bp = xf->ptr;
+ addr1 = -1;
+ addr2 = -1;
+ prevaddr1 = -1;
+ o1 = 0;
+ islong = 0;
+ sum = -1;
+
+ need = 1;
+ if(longtype!=Short && cflag)
+ need += (utflen(name) + DOSRUNE-1) / DOSRUNE;
+
+ memset(dp, 0, sizeof(Dosptr));
+ dp->prevaddr = -1;
+ dp->naddr = -1;
+ dp->paddr = ((Dosptr *)f->ptr)->addr;
+ dp->poffset = ((Dosptr *)f->ptr)->offset;
+
+ have = 0;
+ addr = -1;
+ bname = nil;
+ for(isect=0;; isect++){
+ prevaddr = addr;
+ addr = fileaddr(f, isect, cflag);
+ if(addr < 0)
+ break;
+ p = getsect(xf, addr);
+ if(p == 0)
+ break;
+ for(o=0; o<bp->sectsize; o+=DOSDIRSIZE){
+ d = (Dosdir *)&p->iobuf[o];
+ if(d->name[0] == 0x00){
+ chat("end dir(0)...");
+ putsect(p);
+ if(!cflag)
+ return -1;
+
+ /*
+ * addr1 & o1 are the start of the dirs
+ * addr2 is the optional second cluster used if the long name
+ * entry does not fit within the addr1 cluster
+ *
+ * have tells us the number of contiguous free dirs
+ * starting at addr1.o1; need are necessary to hold the long name.
+ */
+ if(addr1 < 0){
+ addr1 = addr;
+ prevaddr1 = prevaddr;
+ o1 = o;
+ }
+ if(addr2 < 0 && (bp->sectsize-o)/DOSDIRSIZE + have < need){
+ addr2 = fileaddr(f, isect+1, cflag);
+ if(addr2 < 0)
+ goto breakout;
+ }else if(addr2 < 0)
+ addr2 = addr;
+ if(addr2 == addr1)
+ addr2 = -1;
+ dp->addr = addr1;
+ dp->offset = o1;
+ dp->prevaddr = prevaddr1;
+ dp->naddr = addr2;
+ return 0;
+ }
+ if(d->name[0] == DOSEMPTY){
+ if(chatty)
+ fprint(2, "empty dir\n");
+
+ have++;
+ if(addr1 == -1){
+ addr1 = addr;
+ o1 = o;
+ prevaddr1 = prevaddr;
+ }
+ if(addr2 == -1 && have >= need)
+ addr2 = addr;
+ continue;
+ }
+ have = 0;
+ if(addr2 == -1)
+ addr1 = -1;
+
+ dirdump(d);
+ if((d->attr & 0xf) == 0xf){
+ bname = getnamesect(buf, bname, p->iobuf + o, &islong, &sum, 1);
+ continue;
+ }
+ if(d->attr & DVLABEL){
+ islong = 0;
+ continue;
+ }
+ if(islong != 1 || sum != aliassum(d) || cistrcmp(bname, name) != 0){
+ bname = buf;
+ getname(buf, d);
+ }
+ islong = 0;
+ if(cistrcmp(bname, name) != 0)
+ continue;
+ if(chatty)
+ fprint(2, "found\n");
+ if(cflag){
+ putsect(p);
+ return -1;
+ }
+ dp->addr = addr;
+ dp->prevaddr = prevaddr;
+ dp->offset = o;
+ dp->p = p;
+ dp->d = d;
+ return 0;
+ }
+ putsect(p);
+ }
+breakout:
+ chat("end dir(1)...");
+ return -1;
+}
+
+int
+emptydir(Xfile *f)
+{
+ Xfs *xf = f->xf;
+ Dosbpb *bp = xf->ptr;
+ int isect, addr, o;
+ Iosect *p;
+ Dosdir *d;
+
+ for(isect=0;; isect++){
+ addr = fileaddr(f, isect, 0);
+ if(addr < 0)
+ break;
+ p = getsect(xf, addr);
+ if(p == 0)
+ return -1;
+ for(o=0; o<bp->sectsize; o+=DOSDIRSIZE){
+ d = (Dosdir *)&p->iobuf[o];
+ if(d->name[0] == 0x00){
+ putsect(p);
+ return 0;
+ }
+ if(d->name[0] == DOSEMPTY)
+ continue;
+ if(d->name[0] == '.')
+ continue;
+ if(d->attr&DVLABEL)
+ continue;
+ putsect(p);
+ return -1;
+ }
+ putsect(p);
+ }
+ return 0;
+}
+
+long
+readdir(Xfile *f, void *vbuf, long offset, long count)
+{
+ Xfs *xf;
+ Dosbpb *bp;
+ Dir dir;
+ int isect, addr, o, islong, sum;
+ Iosect *p;
+ Dosdir *d;
+ long rcnt, n;
+ char *name, snamebuf[8+1+3+1], namebuf[DOSNAMELEN];
+ uchar *buf;
+
+ buf = vbuf;
+ rcnt = 0;
+ xf = f->xf;
+ bp = xf->ptr;
+ if(count <= 0)
+ return 0;
+ islong = 0;
+ sum = -1;
+ name = nil;
+ for(isect=0;; isect++){
+ addr = fileaddr(f, isect, 0);
+ if(addr < 0)
+ break;
+ p = getsect(xf, addr);
+ if(p == 0)
+ return -1;
+ for(o=0; o<bp->sectsize; o+=DOSDIRSIZE){
+ d = (Dosdir *)&p->iobuf[o];
+ if(d->name[0] == 0x00){
+ putsect(p);
+ return rcnt;
+ }
+ if(d->name[0] == DOSEMPTY)
+ continue;
+ dirdump(d);
+ if(d->name[0] == '.'){
+ if(d->name[1] == ' ' || d->name[1] == 0)
+ continue;
+ if(d->name[1] == '.' &&
+ (d->name[2] == ' ' || d->name[2] == 0))
+ continue;
+ }
+ if((d->attr & 0xf) == 0xf){
+ name = getnamesect(namebuf, name, p->iobuf+o, &islong, &sum, 1);
+ continue;
+ }
+ if(d->attr & DVLABEL){
+ islong = 0;
+ continue;
+ }
+ dir.name = snamebuf;
+ getdir(xf, &dir, d, addr, o);
+ if(islong == 1 && nameok(name) && sum == aliassum(d))
+ dir.name = name;
+ islong = 0;
+ n = convD2M(&dir, &buf[rcnt], count - rcnt);
+ name = nil;
+ if(n <= BIT16SZ){ /* no room for next entry */
+ putsect(p);
+ return rcnt;
+ }
+ rcnt += n;
+ if(offset > 0){
+ offset -= rcnt;
+ rcnt = 0;
+ islong = 0;
+ continue;
+ }
+ if(rcnt == count){
+ putsect(p);
+ return rcnt;
+ }
+ }
+ putsect(p);
+ }
+ return rcnt;
+}
+
+/*
+ * set up ndp for a directory's parent
+ * the hardest part is setting up paddr
+ */
+int
+walkup(Xfile *f, Dosptr *ndp)
+{
+ Dosbpb *bp;
+ Dosptr *dp;
+ Dosdir *xd;
+ Iosect *p;
+ long k, o, so, start, pstart, ppstart, st, ppclust;
+
+ bp = f->xf->ptr;
+ dp = f->ptr;
+ memset(ndp, 0, sizeof(Dosptr));
+ ndp->prevaddr = -1;
+ ndp->naddr = -1;
+ ndp->addr = dp->paddr;
+ ndp->offset = dp->poffset;
+
+ chat("walkup: paddr=%#lx...", dp->paddr);
+
+ /*
+ * root's paddr is always itself
+ */
+ if(isroot(dp->paddr))
+ return 0;
+
+ /*
+ * find the start of our parent's directory
+ */
+ p = getsect(f->xf, dp->paddr);
+ if(p == nil)
+ goto error;
+ xd = (Dosdir *)&p->iobuf[dp->poffset];
+ dirdump(xd);
+ start = getstart(f->xf, xd);
+ chat("start=%#lx...", start);
+ if(start == 0)
+ goto error;
+ putsect(p);
+
+ /*
+ * verify that parent's . points to itself
+ */
+ p = getsect(f->xf, clust2sect(bp, start));
+ if(p == nil)
+ goto error;
+ xd = (Dosdir *)p->iobuf;
+ dirdump(xd);
+ st = getstart(f->xf, xd);
+ if(xd->name[0]!='.' || xd->name[1]!=' ' || start!=st)
+ goto error;
+
+ /*
+ * parent's .. is the next entry, and has start of parent's parent
+ */
+ xd++;
+ dirdump(xd);
+ if(xd->name[0] != '.' || xd->name[1] != '.')
+ goto error;
+ pstart = getstart(f->xf, xd);
+ putsect(p);
+
+ /*
+ * we're done if parent is root
+ */
+ if(pstart == 0 || f->xf->isfat32 && pstart == bp->rootstart)
+ return 0;
+
+ /*
+ * verify that parent's . points to itself
+ */
+ p = getsect(f->xf, clust2sect(bp, pstart));
+ if(p == 0){
+ chat("getsect %ld failed\n", pstart);
+ goto error;
+ }
+ xd = (Dosdir *)p->iobuf;
+ dirdump(xd);
+ st = getstart(f->xf, xd);
+ if(xd->name[0]!='.' || xd->name[1]!=' ' || pstart!=st)
+ goto error;
+
+ /*
+ * parent's parent's .. is the next entry, and has start of parent's parent's parent
+ */
+ xd++;
+ dirdump(xd);
+ if(xd->name[0] != '.' || xd->name[1] != '.')
+ goto error;
+ ppstart = getstart(f->xf, xd);
+ putsect(p);
+
+ /*
+ * open parent's parent's parent, and walk through it until parent's parent is found
+ * need this to find parent's parent's addr and offset
+ */
+ ppclust = ppstart;
+ if(f->xf->isfat32 && ppclust == 0){
+ ppclust = bp->rootstart;
+ chat("ppclust 0, resetting to rootstart\n");
+ }
+ k = ppclust ? clust2sect(bp, ppclust) : bp->rootaddr;
+ p = getsect(f->xf, k);
+ if(p == nil){
+ chat("getsect %ld failed\n", k);
+ goto error;
+ }
+ xd = (Dosdir *)p->iobuf;
+ dirdump(xd);
+ if(ppstart){
+ st = getstart(f->xf, xd);
+ if(xd->name[0]!='.' || xd->name[1]!=' ' || ppstart!=st)
+ goto error;
+ }
+ for(so=1;; so++){
+ for(o=0; o<bp->sectsize; o+=DOSDIRSIZE){
+ xd = (Dosdir *)&p->iobuf[o];
+ if(xd->name[0] == 0x00){
+ chat("end dir\n");
+ goto error;
+ }
+ if(xd->name[0] == DOSEMPTY)
+ continue;
+ st = getstart(f->xf, xd);
+ if(st == pstart)
+ goto out;
+ }
+ if(ppclust){
+ if(so%bp->clustsize == 0){
+ mlock(bp);
+ ppclust = getfat(f->xf, ppclust);
+ unmlock(bp);
+ if(ppclust < 0){
+ chat("getfat %ld failed\n", ppclust);
+ goto error;
+ }
+ }
+ k = clust2sect(bp, ppclust) +
+ so%bp->clustsize;
+ }else{
+ if(so*bp->sectsize >= bp->rootsize*DOSDIRSIZE)
+ goto error;
+ k = bp->rootaddr + so;
+ }
+ putsect(p);
+ p = getsect(f->xf, k);
+ if(p == 0){
+ chat("getsect %ld failed\n", k);
+ goto error;
+ }
+ }
+out:
+ putsect(p);
+ ndp->paddr = k;
+ ndp->poffset = o;
+ return 0;
+
+error:
+ if(p)
+ putsect(p);
+ return -1;
+}
+
+long
+readfile(Xfile *f, void *vbuf, long offset, long count)
+{
+ Xfs *xf = f->xf;
+ Dosbpb *bp = xf->ptr;
+ Dosptr *dp = f->ptr;
+ Dosdir *d = dp->d;
+ int isect, addr, o, c;
+ Iosect *p;
+ uchar *buf;
+ long length, rcnt;
+
+ rcnt = 0;
+ length = GLONG(d->length);
+ buf = vbuf;
+ if(offset >= length)
+ return 0;
+ if(offset+count >= length)
+ count = length - offset;
+ isect = offset/bp->sectsize;
+ o = offset%bp->sectsize;
+ while(count > 0){
+ addr = fileaddr(f, isect++, 0);
+ if(addr < 0)
+ break;
+ c = bp->sectsize - o;
+ if(c > count)
+ c = count;
+ p = getsect(xf, addr);
+ if(p == 0)
+ return -1;
+ memmove(&buf[rcnt], &p->iobuf[o], c);
+ putsect(p);
+ count -= c;
+ rcnt += c;
+ o = 0;
+ }
+ return rcnt;
+}
+
+long
+writefile(Xfile *f, void *vbuf, long offset, long count)
+{
+ Xfs *xf = f->xf;
+ Dosbpb *bp = xf->ptr;
+ Dosptr *dp = f->ptr;
+ Dosdir *d = dp->d;
+ int isect, addr = 0, o, c;
+ Iosect *p;
+ uchar *buf;
+ long length, rcnt = 0, dlen;
+
+ buf = vbuf;
+ isect = offset/bp->sectsize;
+ o = offset%bp->sectsize;
+ while(count > 0){
+ addr = fileaddr(f, isect++, 1);
+ if(addr < 0)
+ break;
+ c = bp->sectsize - o;
+ if(c > count)
+ c = count;
+ if(c == bp->sectsize){
+ p = getosect(xf, addr);
+ p->flags = 0;
+ }else{
+ p = getsect(xf, addr);
+ if(p == nil)
+ return -1;
+ }
+ memmove(&p->iobuf[o], &buf[rcnt], c);
+ p->flags |= BMOD;
+ putsect(p);
+ count -= c;
+ rcnt += c;
+ o = 0;
+ }
+ if(rcnt <= 0 && addr < 0)
+ return -1;
+ length = 0;
+ dlen = GLONG(d->length);
+ if(rcnt > 0)
+ length = offset+rcnt;
+ else if(dp->addr && dp->clust){
+ c = bp->clustsize*bp->sectsize;
+ if(dp->iclust > (dlen+c-1)/c)
+ length = c*dp->iclust;
+ }
+ if(length > dlen)
+ PLONG(d->length, length);
+ puttime(d, 0);
+ dp->p->flags |= BMOD;
+ return rcnt;
+}
+
+int
+truncfile(Xfile *f, long length)
+{
+ Xfs *xf = f->xf;
+ Dosbpb *bp = xf->ptr;
+ Dosptr *dp = f->ptr;
+ Dosdir *d = dp->d;
+ long clust, next, n;
+
+ mlock(bp);
+ clust = getstart(f->xf, d);
+ n = length;
+ if(n <= 0)
+ putstart(f->xf, d, 0);
+ else
+ n -= bp->sectsize;
+ while(clust > 0){
+ next = getfat(xf, clust);
+ if(n <= 0)
+ putfat(xf, clust, 0);
+ else
+ n -= bp->clustsize*bp->sectsize;
+ clust = next;
+ }
+ unmlock(bp);
+ PLONG(d->length, length);
+ dp->iclust = 0;
+ dp->clust = 0;
+ dp->p->flags |= BMOD;
+ return 0;
+}
+
+void
+putdir(Dosdir *d, Dir *dp)
+{
+ if(dp->mode != ~0){
+ if(dp->mode & 2)
+ d->attr &= ~DRONLY;
+ else
+ d->attr |= DRONLY;
+ if(dp->mode & DMEXCL)
+ d->attr |= DSYSTEM;
+ else
+ d->attr &= ~DSYSTEM;
+ }
+ if(dp->mtime != ~0)
+ puttime(d, dp->mtime);
+}
+
+/*
+ * should extend this to deal with
+ * creation and access dates
+ */
+void
+getdir(Xfs *xfs, Dir *dp, Dosdir *d, int addr, int offset)
+{
+ if(d == nil || addr == 0)
+ panic("getdir on root");
+ dp->type = 0;
+ dp->dev = 0;
+ getname(dp->name, d);
+
+ dp->qid.path = addr*(Sectorsize/DOSDIRSIZE) +
+ offset/DOSDIRSIZE;
+ dp->qid.vers = 0;
+
+ if(d->attr & DRONLY)
+ dp->mode = 0444;
+ else
+ dp->mode = 0666;
+ dp->atime = gtime(d);
+ dp->mtime = dp->atime;
+ dp->qid.type = QTFILE;
+ if(d->attr & DDIR){
+ dp->qid.type = QTDIR;
+ dp->mode |= DMDIR|0111;
+ dp->length = 0;
+ }else
+ dp->length = GLONG(d->length);
+ if(d->attr & DSYSTEM){
+ dp->mode |= DMEXCL;
+ if(iscontig(xfs, d))
+ dp->mode |= DMAPPEND;
+ }
+
+ dp->uid = "bill";
+ dp->muid = "bill";
+ dp->gid = "trog";
+}
+
+void
+getname(char *p, Dosdir *d)
+{
+ int c, i;
+
+ for(i=0; i<8; i++){
+ c = d->name[i];
+ if(c == '\0' || c == ' ')
+ break;
+ if(i == 0 && c == 0x05)
+ c = 0xe5;
+ *p++ = c;
+ }
+ for(i=0; i<3; i++){
+ c = d->ext[i];
+ if(c == '\0' || c == ' ')
+ break;
+ if(i == 0)
+ *p++ = '.';
+ *p++ = c;
+ }
+ *p = 0;
+}
+
+static char*
+getnamerunes(char *dst, uchar *buf, int step)
+{
+ int i;
+ Rune r;
+ char dbuf[DOSRUNE * UTFmax + 1], *d;
+
+ d = dbuf;
+ r = 1;
+ for(i = 1; r && i < 11; i += 2){
+ r = buf[i] | (buf[i+1] << 8);
+ d += runetochar(d, &r);
+ }
+ for(i = 14; r && i < 26; i += 2){
+ r = buf[i] | (buf[i+1] << 8);
+ d += runetochar(d, &r);
+ }
+ for(i = 28; r && i < 32; i += 2){
+ r = buf[i] | (buf[i+1] << 8);
+ d += runetochar(d, &r);
+ }
+
+ if(step == 1)
+ dst -= d - dbuf;
+
+ memmove(dst, dbuf, d - dbuf);
+
+ if(step == -1){
+ dst += d - dbuf;
+ *dst = '\0';
+ }
+
+ return dst;
+}
+
+char*
+getnamesect(char *dbuf, char *d, uchar *buf, int *islong, int *sum, int step)
+{
+ /*
+ * validation checks to make sure we're
+ * making up a consistent name
+ */
+ if(buf[11] != 0xf || buf[12] != 0){
+ *islong = 0;
+ return nil;
+ }
+ if(step == 1){
+ if((buf[0] & 0xc0) == 0x40){
+ *islong = buf[0] & 0x3f;
+ *sum = buf[13];
+ d = dbuf + DOSNAMELEN;
+ *--d = '\0';
+ }else if(*islong && *islong == buf[0] + 1 && *sum == buf[13]){
+ *islong = buf[0];
+ }else{
+ *islong = 0;
+ return nil;
+ }
+ }else{
+ if(*islong + 1 == (buf[0] & 0xbf) && *sum == buf[13]){
+ *islong = buf[0] & 0x3f;
+ if(buf[0] & 0x40)
+ *sum = -1;
+ }else{
+ *islong = 0;
+ *sum = -1;
+ return nil;
+ }
+ }
+ if(*islong > 20){
+ *islong = 0;
+ *sum = -1;
+ return nil;
+ }
+
+ return getnamerunes(d, buf, step);
+}
+
+void
+putname(char *p, Dosdir *d)
+{
+ int i;
+
+ memset(d->name, ' ', sizeof d->name+sizeof d->ext);
+ for(i=0; i<sizeof d->name; i++){
+ if(*p == 0 || *p == '.')
+ break;
+ d->name[i] = toupper(*p++);
+ }
+ p = strrchr(p, '.');
+ if(p){
+ for(i=0; i<sizeof d->ext; i++){
+ if(*++p == 0)
+ break;
+ d->ext[i] = toupper(*p);
+ }
+ }
+}
+
+static void
+putnamesect(uchar *slot, Rune *longname, int curslot, int first, int sum)
+{
+ Rune r;
+ Dosdir ds;
+ int i, j;
+
+ memset(&ds, 0xff, sizeof ds);
+ ds.attr = 0xf;
+ ds.reserved[0] = 0;
+ ds.reserved[1] = sum;
+ ds.start[0] = 0;
+ ds.start[1] = 0;
+ if(first)
+ ds.name[0] = 0x40 | curslot;
+ else
+ ds.name[0] = curslot;
+ memmove(slot, &ds, sizeof ds);
+
+ j = (curslot-1) * DOSRUNE;
+
+ for(i = 1; i < 11; i += 2){
+ r = longname[j++];
+ slot[i] = r;
+ slot[i+1] = r >> 8;
+ if(r == 0)
+ return;
+ }
+ for(i = 14; i < 26; i += 2){
+ r = longname[j++];
+ slot[i] = r;
+ slot[i+1] = r >> 8;
+ if(r == 0)
+ return;
+ }
+ for(i = 28; i < 32; i += 2){
+ r = longname[j++];
+ slot[i] = r;
+ slot[i+1] = r >> 8;
+ if(r == 0)
+ return;
+ }
+}
+
+int
+aliassum(Dosdir *d)
+{
+ int i, sum;
+
+ if(d == nil)
+ return -1;
+ sum = 0;
+ for(i = 0; i < 11; i++)
+ sum = (((sum&1)<<7) | ((sum&0xfe)>>1)) + d->name[i];
+ return sum & 0xff;
+}
+
+int
+putlongname(Xfs *xf, Dosptr *ndp, char *name, char sname[13])
+{
+ Dosbpb *bp;
+ Dosdir tmpd;
+ Rune longname[DOSNAMELEN+1];
+ int i, first, sum, nds, len;
+
+ /* calculate checksum */
+ putname(sname, &tmpd);
+ sum = aliassum(&tmpd);
+
+ bp = xf->ptr;
+ first = 1;
+ len = utftorunes(longname, name, DOSNAMELEN);
+ if(chatty){
+ chat("utftorunes %s =", name);
+ for(i=0; i<len; i++)
+ chat(" %.4X", longname[i]);
+ chat("\n");
+ }
+ for(nds = (len + DOSRUNE-1) / DOSRUNE; nds > 0; nds--){
+ putnamesect(&ndp->p->iobuf[ndp->offset], longname, nds, first, sum);
+ first = 0;
+ ndp->offset += 32;
+ if(ndp->offset == bp->sectsize){
+ chat("long name moving over sector boundary\n");
+ ndp->p->flags |= BMOD;
+ putsect(ndp->p);
+ ndp->p = nil;
+
+ /*
+ * switch to the next cluster for a long entry
+ * naddr should be set up correctly by searchdir
+ */
+ ndp->prevaddr = ndp->addr;
+ ndp->addr = ndp->naddr;
+ ndp->naddr = -1;
+ if(ndp->addr == -1)
+ return -1;
+ ndp->p = getsect(xf, ndp->addr);
+ if(ndp->p == nil)
+ return -1;
+ ndp->offset = 0;
+ ndp->d = (Dosdir *)&ndp->p->iobuf[ndp->offset];
+ }
+ }
+ return 0;
+}
+
+long
+getfat(Xfs *xf, int n)
+{
+ Dosbpb *bp = xf->ptr;
+ Iosect *p;
+ ulong k, sect;
+ int o, fb;
+
+ if(n < FATRESRV || n >= bp->fatclusters)
+ return -1;
+ fb = bp->fatbits;
+ k = (fb * n) >> 3;
+ if(k >= bp->fatsize*bp->sectsize)
+ panic("getfat");
+ sect = k/bp->sectsize + bp->fataddr;
+ o = k%bp->sectsize;
+ p = getsect(xf, sect);
+ if(p == nil)
+ return -1;
+ k = p->iobuf[o++];
+ if(o >= bp->sectsize){
+ putsect(p);
+ p = getsect(xf, sect+1);
+ if(p == nil)
+ return -1;
+ o = 0;
+ }
+ k |= p->iobuf[o++]<<8;
+ if(fb == 32){
+ /* fat32 is really fat28 */
+ k |= p->iobuf[o++] << 16;
+ k |= (p->iobuf[o] & 0x0f) << 24;
+ fb = 28;
+ }
+ putsect(p);
+ if(fb == 12){
+ if(n&1)
+ k >>= 4;
+ else
+ k &= 0xfff;
+ }
+ if(chatty > 1)
+ chat("fat(%#x)=%#lx...", n, k);
+
+ /*
+ * This is a very strange check for out of range.
+ * As a concrete example, for a 16-bit FAT,
+ * FFF8 through FFFF all signify ``end of cluster chain.''
+ * This generalizes to other-sized FATs.
+ */
+ if(k >= (1 << fb) - 8)
+ return -1;
+
+ return k;
+}
+
+void
+putfat(Xfs *xf, int n, ulong val)
+{
+ Fatinfo *fi;
+ Dosbpb *bp;
+ Iosect *p;
+ ulong k, sect, esect;
+ int o;
+
+ bp = xf->ptr;
+ if(n < FATRESRV || n >= bp->fatclusters)
+ panic("putfat n=%d", n);
+ k = (bp->fatbits * n) >> 3;
+ if(k >= bp->fatsize*bp->sectsize)
+ panic("putfat");
+ sect = k/bp->sectsize + bp->fataddr;
+ esect = sect + bp->nfats * bp->fatsize;
+ for(; sect<esect; sect+=bp->fatsize){
+ o = k%bp->sectsize;
+ p = getsect(xf, sect);
+ if(p == nil)
+ continue;
+ switch(bp->fatbits){
+ case 12:
+ if(n&1){
+ p->iobuf[o] &= 0x0f;
+ p->iobuf[o++] |= val<<4;
+ if(o >= bp->sectsize){
+ p->flags |= BMOD;
+ putsect(p);
+ p = getsect(xf, sect+1);
+ if(p == nil)
+ continue;
+ o = 0;
+ }
+ p->iobuf[o] = val>>4;
+ }else{
+ p->iobuf[o++] = val;
+ if(o >= bp->sectsize){
+ p->flags |= BMOD;
+ putsect(p);
+ p = getsect(xf, sect+1);
+ if(p == nil)
+ continue;
+ o = 0;
+ }
+ p->iobuf[o] &= 0xf0;
+ p->iobuf[o] |= (val>>8) & 0x0f;
+ }
+ break;
+ case 16:
+ p->iobuf[o++] = val;
+ p->iobuf[o] = val>>8;
+ break;
+ case 32: /* fat32 is really fat28 */
+ p->iobuf[o++] = val;
+ p->iobuf[o++] = val>>8;
+ p->iobuf[o++] = val>>16;
+ p->iobuf[o] = (p->iobuf[o] & 0xf0) | ((val>>24) & 0x0f);
+ break;
+ default:
+ panic("putfat fatbits");
+ }
+ p->flags |= BMOD;
+ putsect(p);
+ }
+
+ if(val == 0)
+ bp->freeclusters++;
+ else
+ bp->freeclusters--;
+
+ if(bp->fatinfo){
+ p = getsect(xf, bp->fatinfo);
+ if(p != nil){
+ fi = (Fatinfo*)p->iobuf;
+ PLONG(fi->nextfree, bp->freeptr);
+ PLONG(fi->freeclust, bp->freeclusters);
+ p->flags |= BMOD;
+ putsect(p);
+ }
+ }
+}
+
+/*
+ * Contiguous falloc; if we can, use lastclust+1.
+ * Otherwise, move the file to get some space.
+ * If there just isn't enough contiguous space
+ * anywhere on disk, fail.
+ */
+int
+cfalloc(Xfile *f)
+{
+ int l;
+
+ if((l=makecontig(f, 8)) >= 0)
+ return l;
+ return makecontig(f, 1);
+}
+
+/*
+ * Check whether a file is contiguous.
+ */
+int
+iscontig(Xfs *xf, Dosdir *d)
+{
+ long clust, next;
+
+ clust = getstart(xf, d);
+ if(clust <= 0)
+ return 1;
+
+ for(;;) {
+ next = getfat(xf, clust);
+ if(next < 0)
+ return 1;
+ if(next != clust+1)
+ return 0;
+ clust = next;
+ }
+}
+
+/*
+ * Make a file contiguous, with nextra clusters of
+ * free space after it for later expansion.
+ * Return the number of the first new cluster.
+ */
+int
+makecontig(Xfile *f, int nextra)
+{
+ Dosbpb *bp;
+ Dosdir *d;
+ Dosptr *dp;
+ Xfs *xf;
+ Iosect *wp, *rp;
+ long clust, next, last, start, rclust, wclust, eclust, ostart;
+ int isok, i, n, nclust, nrun, rs, ws;
+
+ xf = f->xf;
+ bp = xf->ptr;
+ dp = f->ptr;
+ d = dp->d;
+
+ isok = 1;
+ nclust = 0;
+ clust = fileclust(f, 0, 0);
+ chat("clust %#lux", clust);
+ if(clust != -1) {
+ for(;;) {
+ nclust++;
+ chat(".");
+ next = getfat(xf, clust);
+ if(next <= 0)
+ break;
+ if(next != clust+1)
+ isok = 0;
+ clust = next;
+ }
+ }
+ chat("nclust %d\n", nclust);
+
+ if(isok && clust != -1) {
+ eclust = clust+1; /* eclust = first cluster past file */
+ assert(eclust == fileclust(f, 0, 0)+nclust);
+ for(i=0; i<nextra; i++)
+ if(getfat(xf, eclust+i) != 0)
+ break;
+ if(i == nextra) { /* they were all free */
+ chat("eclust=%#lx, getfat eclust-1 = %#lux\n", eclust, getfat(xf, eclust-1));
+ assert(getfat(xf, eclust-1) == 0xffffffff);
+ putfat(xf, eclust-1, eclust);
+ putfat(xf, eclust, 0xffffffff);
+ bp->freeptr = clust+1; /* to help keep the blocks free */
+ return eclust;
+ }
+ }
+
+ /* need to search for nclust+nextra contiguous free blocks */
+ last = -1;
+ n = bp->freeptr;
+ nrun = 0;
+ for(;;){
+ if(getfat(xf, n) == 0) {
+ if(last+1 == n)
+ nrun++;
+ else
+ nrun = 1;
+ if(nrun >= nclust+nextra)
+ break;
+ last = n;
+ }
+ if(++n >= bp->fatclusters)
+ n = FATRESRV;
+ if(n == bp->freeptr) {
+ errno = Econtig;
+ return -1;
+ }
+ }
+ bp->freeptr = n+1;
+
+ /* copy old data over */
+ start = n+1 - nrun;
+
+ /* sanity check */
+ for(i=0; i<nclust+nextra; i++)
+ assert(getfat(xf, start+i) == 0);
+
+ chat("relocate chain %lux -> 0x%lux len %d\n", fileclust(f, 0, 0), start, nclust);
+
+ wclust = start;
+ for(rclust = fileclust(f, 0, 0); rclust > 0; rclust = next){
+ rs = clust2sect(bp, rclust);
+ ws = clust2sect(bp, wclust);
+ for(i=0; i<bp->clustsize; i++, rs++, ws++){
+ rp = getsect(xf, rs);
+ if(rp == nil)
+ return -1;
+ wp = getosect(xf, ws);
+ assert(wp != nil);
+ memmove(wp->iobuf, rp->iobuf, bp->sectsize);
+ wp->flags = BMOD;
+ putsect(rp);
+ putsect(wp);
+ }
+ chat("move cluster %#lx -> %#lx...", rclust, wclust);
+ next = getfat(xf, rclust);
+ putfat(xf, wclust, wclust+1);
+ wclust++;
+ }
+
+ /* now wclust points at the first new cluster; chain it in */
+ chat("wclust 0x%lux start 0x%lux (fat->0x%lux) nclust %d\n", wclust, start, getfat(xf, start), nclust);
+ assert(wclust == start+nclust);
+ putfat(xf, wclust, 0xffffffff); /* end of file */
+
+ /* update directory entry to point at new start */
+ ostart = fileclust(f, 0, 0);
+ putstart(xf, d, start);
+
+ /* check our work */
+ i = 0;
+ clust = fileclust(f, 0, 0);
+ if(clust != -1) {
+ for(;;) {
+ i++;
+ next = getfat(xf, clust);
+ if(next <= 0)
+ break;
+ assert(next == clust+1);
+ clust = next;
+ }
+ }
+ chat("chain check: len %d\n", i);
+ assert(i == nclust+1);
+
+ /* succeeded; remove old chain. */
+ for(rclust = ostart; rclust > 0; rclust = next){
+ next = getfat(xf, rclust);
+ putfat(xf, rclust, 0); /* free cluster */
+ }
+
+ return start+nclust;
+}
+
+int
+falloc(Xfs *xf)
+{
+ Dosbpb *bp = xf->ptr;
+ Iosect *p;
+ int n, i, k;
+
+ n = bp->freeptr;
+ for(;;){
+ if(getfat(xf, n) == 0)
+ break;
+ if(++n >= bp->fatclusters)
+ n = FATRESRV;
+ if(n == bp->freeptr)
+ return -1;
+ }
+ bp->freeptr = n+1;
+ if(bp->freeptr >= bp->fatclusters)
+ bp->freeptr = FATRESRV;
+ putfat(xf, n, 0xffffffff);
+ k = clust2sect(bp, n);
+ for(i=0; i<bp->clustsize; i++){
+ p = getosect(xf, k+i);
+ memset(p->iobuf, 0, bp->sectsize);
+ p->flags = BMOD;
+ putsect(p);
+ }
+ return n;
+}
+
+void
+ffree(Xfs *xf, long start)
+{
+ putfat(xf, start, 0);
+}
+
+long
+clust2sect(Dosbpb *bp, long clust)
+{
+ return bp->dataaddr + (clust - FATRESRV) * bp->clustsize;
+}
+
+long
+sect2clust(Dosbpb *bp, long sect)
+{
+ long c;
+
+ c = (sect - bp->dataaddr) / bp->clustsize + FATRESRV;
+ assert(sect == clust2sect(bp, c));
+ return c;
+}
+
+void
+puttime(Dosdir *d, long s)
+{
+ Tm *t;
+ ushort x;
+
+ if(s == 0)
+ s = time(0);
+ t = localtime(s);
+
+ x = (t->hour<<11) | (t->min<<5) | (t->sec>>1);
+ PSHORT(d->time, x);
+ x = ((t->year-80)<<9) | ((t->mon+1)<<5) | t->mday;
+ PSHORT(d->date, x);
+}
+
+long
+gtime(Dosdir *dp)
+{
+ Tm tm;
+ int i;
+
+ i = GSHORT(dp->time);
+ tm.hour = i >> 11;
+ tm.min = (i >> 5) & 63;
+ tm.sec = (i & 31) << 1;
+ i = GSHORT(dp->date);
+ tm.year = 80 + (i >> 9);
+ tm.mon = ((i >> 5) & 15) - 1;
+ tm.mday = i & 31;
+ tm.zone[0] = '\0';
+ tm.tzoff = 0;
+ tm.yday = 0;
+
+ return tm2sec(&tm);
+}
+
+/*
+ * structure dumps for debugging
+ */
+void
+bootdump(int fd, Dosboot *b)
+{
+ Biobuf bp;
+
+ Binit(&bp, fd, OWRITE);
+ Bprint(&bp, "magic: 0x%2.2x 0x%2.2x 0x%2.2x\n",
+ b->magic[0], b->magic[1], b->magic[2]);
+ Bprint(&bp, "version: \"%8.8s\"\n", (char*)b->version);
+ Bprint(&bp, "sectsize: %d\n", GSHORT(b->sectsize));
+ Bprint(&bp, "clustsize: %d\n", b->clustsize);
+ Bprint(&bp, "nresrv: %d\n", GSHORT(b->nresrv));
+ Bprint(&bp, "nfats: %d\n", b->nfats);
+ Bprint(&bp, "rootsize: %d\n", GSHORT(b->rootsize));
+ Bprint(&bp, "volsize: %d\n", GSHORT(b->volsize));
+ Bprint(&bp, "mediadesc: 0x%2.2x\n", b->mediadesc);
+ Bprint(&bp, "fatsize: %d\n", GSHORT(b->fatsize));
+ Bprint(&bp, "trksize: %d\n", GSHORT(b->trksize));
+ Bprint(&bp, "nheads: %d\n", GSHORT(b->nheads));
+ Bprint(&bp, "nhidden: %ld\n", GLONG(b->nhidden));
+ Bprint(&bp, "bigvolsize: %ld\n", GLONG(b->bigvolsize));
+ Bprint(&bp, "driveno: %d\n", b->driveno);
+ Bprint(&bp, "reserved0: 0x%2.2x\n", b->reserved0);
+ Bprint(&bp, "bootsig: 0x%2.2x\n", b->bootsig);
+ Bprint(&bp, "volid: 0x%8.8lux\n", GLONG(b->volid));
+ Bprint(&bp, "label: \"%11.11s\"\n", (char*)b->label);
+ Bterm(&bp);
+}
+
+void
+bootdump32(int fd, Dosboot32 *b)
+{
+ Biobuf bp;
+
+ Binit(&bp, fd, OWRITE);
+ Bprint(&bp, "magic: 0x%2.2x 0x%2.2x 0x%2.2x\n",
+ b->magic[0], b->magic[1], b->magic[2]);
+ Bprint(&bp, "version: \"%8.8s\"\n", (char*)b->version);
+ Bprint(&bp, "sectsize: %d\n", GSHORT(b->sectsize));
+ Bprint(&bp, "clustsize: %d\n", b->clustsize);
+ Bprint(&bp, "nresrv: %d\n", GSHORT(b->nresrv));
+ Bprint(&bp, "nfats: %d\n", b->nfats);
+ Bprint(&bp, "rootsize: %d\n", GSHORT(b->rootsize));
+ Bprint(&bp, "volsize: %d\n", GSHORT(b->volsize));
+ Bprint(&bp, "mediadesc: 0x%2.2x\n", b->mediadesc);
+ Bprint(&bp, "fatsize: %d\n", GSHORT(b->fatsize));
+ Bprint(&bp, "trksize: %d\n", GSHORT(b->trksize));
+ Bprint(&bp, "nheads: %d\n", GSHORT(b->nheads));
+ Bprint(&bp, "nhidden: %ld\n", GLONG(b->nhidden));
+ Bprint(&bp, "bigvolsize: %ld\n", GLONG(b->bigvolsize));
+ Bprint(&bp, "fatsize32: %ld\n", GLONG(b->fatsize32));
+ Bprint(&bp, "extflags: %d\n", GSHORT(b->extflags));
+ Bprint(&bp, "version: %d\n", GSHORT(b->version1));
+ Bprint(&bp, "rootstart: %ld\n", GLONG(b->rootstart));
+ Bprint(&bp, "infospec: %d\n", GSHORT(b->infospec));
+ Bprint(&bp, "backupboot: %d\n", GSHORT(b->backupboot));
+ Bprint(&bp, "reserved: %d %d %d %d %d %d %d %d %d %d %d %d\n",
+ b->reserved[0], b->reserved[1], b->reserved[2], b->reserved[3],
+ b->reserved[4], b->reserved[5], b->reserved[6], b->reserved[7],
+ b->reserved[8], b->reserved[9], b->reserved[10], b->reserved[11]);
+ Bterm(&bp);
+}
+
+void
+bootsecdump32(int fd, Xfs *xf, Dosboot32 *b32)
+{
+ Fatinfo *fi;
+ Iosect *p1;
+ int fisec, bsec, res;
+
+ fprint(fd, "\nfat32\n");
+ bootdump32(fd, b32);
+ res = GSHORT(b32->nresrv);
+ bsec = GSHORT(b32->backupboot);
+ if(bsec < res && bsec != 0){
+ p1 = getsect(xf, bsec);
+ if(p1 == nil)
+ fprint(fd, "\ncouldn't get backup boot sector: %r\n");
+ else{
+ fprint(fd, "\nbackup boot\n");
+ bootdump32(fd, (Dosboot32*)p1->iobuf);
+ putsect(p1);
+ }
+ }else if(bsec != 0xffff)
+ fprint(fd, "bad backup boot sector: %d reserved %d\n", bsec, res);
+ fisec = GSHORT(b32->infospec);
+ if(fisec < res && fisec != 0){
+ p1 = getsect(xf, fisec);
+ if(p1 == nil)
+ fprint(fd, "\ncouldn't get fat info sector: %r\n");
+ else{
+ fprint(fd, "\nfat info %d\n", fisec);
+ fi = (Fatinfo*)p1->iobuf;
+ fprint(fd, "sig1: 0x%lux sb 0x%lux\n", GLONG(fi->sig1), FATINFOSIG1);
+ fprint(fd, "sig: 0x%lux sb 0x%lux\n", GLONG(fi->sig), FATINFOSIG);
+ fprint(fd, "freeclust: %lud\n", GLONG(fi->freeclust));
+ fprint(fd, "nextfree: %lud\n", GLONG(fi->nextfree));
+ fprint(fd, "reserved: %lud %lud %lud\n", GLONG(fi->resrv), GLONG(fi->resrv+4), GLONG(fi->resrv+8));
+ putsect(p1);
+ }
+ }else if(fisec != 0xffff)
+ fprint(2, "bad fat info sector: %d reserved %d\n", bsec, res);
+}
+
+void
+dirdump(void *vdbuf)
+{
+ static char attrchar[] = "rhsvda67";
+ Dosdir *d;
+ char *name, namebuf[DOSNAMELEN];
+ char buf[128], *s, *ebuf;
+ uchar *dbuf;
+ int i;
+
+ if(!chatty)
+ return;
+
+ d = vdbuf;
+
+ ebuf = buf + sizeof(buf);
+ if(d->attr == 0xf){
+ dbuf = vdbuf;
+ name = namebuf + DOSNAMELEN;
+ *--name = '\0';
+ name = getnamerunes(name, dbuf, 1);
+ seprint(buf, ebuf, "\"%s\" %2.2x %2.2ux %2.2ux %d", name, dbuf[0], dbuf[12], dbuf[13], GSHORT(d->start));
+ }else{
+ s = seprint(buf, ebuf, "\"%.8s.%.3s\" ", (char*)d->name, (char*)d->ext);
+ for(i=7; i>=0; i--)
+ *s++ = d->attr&(1<<i) ? attrchar[i] : '-';
+
+ i = GSHORT(d->time);
+ s = seprint(s, ebuf, " %2.2d:%2.2d:%2.2d", i>>11, (i>>5)&63, (i&31)<<1);
+ i = GSHORT(d->date);
+ s = seprint(s, ebuf, " %2.2d.%2.2d.%2.2d", 80+(i>>9), (i>>5)&15, i&31);
+
+ i = GSHORT(d->ctime);
+ s = seprint(s, ebuf, " %2.2d:%2.2d:%2.2d", i>>11, (i>>5)&63, (i&31)<<1);
+ i = GSHORT(d->cdate);
+ s = seprint(s, ebuf, " %2.2d.%2.2d.%2.2d", 80+(i>>9), (i>>5)&15, i&31);
+
+ i = GSHORT(d->adate);
+ s = seprint(s, ebuf, " %2.2d.%2.2d.%2.2d", 80+(i>>9), (i>>5)&15, i&31);
+
+ seprint(s, ebuf, " %d %d", GSHORT(d->start), GSHORT(d->length));
+ }
+ chat("%s\n", buf);
+}
+
+int
+cistrcmp(char *s1, char *s2)
+{
+ int c1, c2;
+
+ while(*s1){
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if(c1 >= 'A' && c1 <= 'Z')
+ c1 -= 'A' - 'a';
+
+ if(c2 >= 'A' && c2 <= 'Z')
+ c2 -= 'A' - 'a';
+
+ if(c1 != c2)
+ return c1 - c2;
+ }
+ return -*s2;
+}
+
+int
+utftorunes(Rune *rr, char *s, int n)
+{
+ Rune *r, *re;
+ int c;
+
+ r = rr;
+ re = r + n - 1;
+ while(c = (uchar)*s){
+ if(c < Runeself){
+ *r = c;
+ s++;
+ }else
+ s += chartorune(r, s);
+ r++;
+ if(r >= re)
+ break;
+ }
+ *r = 0;
+ return r - rr;
+}
diff --git a/sys/src/cmd/dossrv/errstr.h b/sys/src/cmd/dossrv/errstr.h
new file mode 100755
index 000000000..951e55bc0
--- /dev/null
+++ b/sys/src/cmd/dossrv/errstr.h
@@ -0,0 +1,17 @@
+char *errmsg[ESIZE] = {
+ [Enevermind] "never mind",
+ [Eformat] "unknown format",
+ [Eio] "I/O error",
+ [Enoauth] "dossrv: authentication not required",
+ [Enomem] "server out of memory",
+ [Enonexist] "file does not exist",
+ [Eperm] "permission denied",
+ [Enofilsys] "no file system device specified",
+ [Eauth] "authentication failed",
+ [Econtig] "out of contiguous disk space",
+ [Ebadfcall] "bad fcall type",
+ [Ebadstat] "bad stat format",
+ [Etoolong] "file name too long",
+ [Eversion] "unknown 9P version",
+ [Eerrstr] "system call error",
+};
diff --git a/sys/src/cmd/dossrv/fns.h b/sys/src/cmd/dossrv/fns.h
new file mode 100755
index 000000000..181d10564
--- /dev/null
+++ b/sys/src/cmd/dossrv/fns.h
@@ -0,0 +1,70 @@
+int aliassum(Dosdir*);
+void bootdump32(int, Dosboot32*);
+void bootdump(int, Dosboot*);
+void bootsecdump32(int fd, Xfs *xf, Dosboot32 *b32);
+int cfalloc(Xfile*);
+void chat(char*, ...);
+#pragma varargck argpos chat 1
+int cistrcmp(char*, char*);
+int classifyname(char*);
+Xfile *clean(Xfile*);
+long clust2sect(Dosbpb*, long);
+void dirdump(void*);
+int dosfs(Xfs*);
+void dosptrreloc(Xfile *f, Dosptr *dp, ulong addr, ulong offset);
+int emptydir(Xfile*);
+int eqqid(Qid, Qid);
+int falloc(Xfs*);
+void ffree(Xfs *xf, long start);
+long fileaddr(Xfile*, long, int);
+void fixname(char*);
+void getdir(Xfs*, Dir*, Dosdir*, int, int);
+long getfat(Xfs*, int);
+int getfile(Xfile*);
+void getname(char*, Dosdir*);
+char *getnamesect(char*, char*, uchar*, int*, int*, int);
+long getstart(Xfs *xf, Dosdir *d);
+Xfs *getxfs(char*, char*);
+long gtime(Dosdir *d);
+void io(int srvfd);
+int iscontig(Xfs *xf, Dosdir *d);
+int isroot(ulong addr);
+int makecontig(Xfile*, int);
+void mkalias(char*, char*, int);
+int nameok(char*);
+void panic(char*, ...);
+#pragma varargck argpos panic 1
+void putdir(Dosdir*, Dir*);
+void putfat(Xfs*, int, ulong);
+void putfile(Xfile*);
+int putlongname(Xfs *f, Dosptr *ndp, char *name, char sname[13]);
+void putname(char*, Dosdir*);
+void putstart(Xfs *xf, Dosdir *d, long start);
+void puttime(Dosdir*, long);
+void rattach(void);
+void rauth(void);
+void rclone(void);
+void rclunk(void);
+void rcreate(void);
+long readdir(Xfile*, void*, long, long);
+long readfile(Xfile*, void*, long, long);
+void refxfs(Xfs*, int);
+void rflush(void);
+void rootfile(Xfile*);
+void ropen(void);
+void rread(void);
+void rremove(void);
+void rstat(void);
+void rwalk(void);
+void rwrite(void);
+void rwstat(void);
+void rversion(void);
+int searchdir(Xfile*, char*, Dosptr*, int, int);
+long sect2clust(Dosbpb*, long);
+int truncfile(Xfile*, long length);
+int utftorunes(Rune*, char*, int);
+int walkup(Xfile*, Dosptr*);
+long writefile(Xfile*, void*, long, long);
+char *xerrstr(int);
+Xfile *xfile(int, int);
+int xfspurge(void);
diff --git a/sys/src/cmd/dossrv/iotrack.c b/sys/src/cmd/dossrv/iotrack.c
new file mode 100755
index 000000000..2815fa8c7
--- /dev/null
+++ b/sys/src/cmd/dossrv/iotrack.c
@@ -0,0 +1,316 @@
+#include <u.h>
+#include <libc.h>
+#include "iotrack.h"
+#include "dat.h"
+#include "fns.h"
+
+#define HIOB 31 /* a prime */
+#define NIOBUF 80
+
+static Iotrack hiob[HIOB+1]; /* hash buckets + lru list */
+static Iotrack iobuf[NIOBUF]; /* the real ones */
+
+#define UNLINK(p, nx, pr) ((p)->pr->nx = (p)->nx, (p)->nx->pr = (p)->pr)
+
+#define LINK(h, p, nx, pr) ((p)->nx = (h)->nx, (p)->pr = (h), \
+ (h)->nx->pr = (p), (h)->nx = (p))
+
+#define HTOFRONT(h, p) ((h)->hnext != (p) && (UNLINK(p,hnext,hprev), LINK(h,p,hnext,hprev)))
+
+#define TOFRONT(h, p) ((h)->next != (p) && (UNLINK(p, next, prev), LINK(h,p, next, prev)))
+
+Iosect *
+getsect(Xfs *xf, long addr)
+{
+ return getiosect(xf, addr, 1);
+}
+
+Iosect *
+getosect(Xfs *xf, long addr)
+{
+ return getiosect(xf, addr, 0);
+}
+
+Iosect *
+getiosect(Xfs *xf, long addr, int rflag)
+{
+ Iotrack *t;
+ long taddr;
+ int toff;
+ Iosect *p;
+
+ toff = addr % Sect2trk;
+ taddr = addr - toff;
+ t = getiotrack(xf, taddr);
+ if(rflag && (t->flags&BSTALE)){
+ if(tread(t) < 0){
+ unmlock(&t->lock);
+ return 0;
+ }
+ t->flags &= ~BSTALE;
+ }
+ t->ref++;
+ p = t->tp->p[toff];
+ if(p == 0){
+ p = newsect();
+ t->tp->p[toff] = p;
+ p->flags = t->flags&BSTALE;
+ p->lock.key = 0;
+ p->t = t;
+ p->iobuf = t->tp->buf[toff];
+ }
+ unmlock(&t->lock);
+ mlock(&p->lock);
+ return p;
+}
+
+void
+putsect(Iosect *p)
+{
+ Iotrack *t;
+
+ if(canmlock(&p->lock))
+ panic("putsect");
+ t = p->t;
+ mlock(&t->lock);
+ t->flags |= p->flags;
+ p->flags = 0;
+ t->ref--;
+ if(t->flags & BIMM){
+ if(t->flags & BMOD)
+ twrite(t);
+ t->flags &= ~(BMOD|BIMM);
+ }
+ unmlock(&t->lock);
+ unmlock(&p->lock);
+}
+
+Iotrack *
+getiotrack(Xfs *xf, long addr)
+{
+ Iotrack *hp, *p;
+ Iotrack *mp = &hiob[HIOB];
+ long h;
+/*
+ * chat("iotrack %d,%d...", dev, addr);
+ */
+ h = (xf->dev<<24) ^ addr;
+ if(h < 0)
+ h = ~h;
+ h %= HIOB;
+ hp = &hiob[h];
+
+loop:
+
+/*
+ * look for it in the active list
+ */
+ mlock(&hp->lock);
+ for(p=hp->hnext; p != hp; p=p->hnext){
+ if(p->addr != addr || p->xf != xf)
+ continue;
+ unmlock(&hp->lock);
+ mlock(&p->lock);
+ if(p->addr == addr && p->xf == xf)
+ goto out;
+ unmlock(&p->lock);
+ goto loop;
+ }
+ unmlock(&hp->lock);
+/*
+ * not found
+ * take oldest unref'd entry
+ */
+ mlock(&mp->lock);
+ for(p=mp->prev; p != mp; p=p->prev)
+ if(p->ref == 0 && canmlock(&p->lock)){
+ if(p->ref == 0)
+ break;
+ unmlock(&p->lock);
+ }
+ unmlock(&mp->lock);
+ if(p == mp){
+ print("iotrack all ref'd\n");
+ goto loop;
+ }
+ if(p->flags & BMOD){
+ twrite(p);
+ p->flags &= ~(BMOD|BIMM);
+ unmlock(&p->lock);
+ goto loop;
+ }
+ purgetrack(p);
+ p->addr = addr;
+ p->xf = xf;
+ p->flags = BSTALE;
+out:
+ mlock(&hp->lock);
+ HTOFRONT(hp, p);
+ unmlock(&hp->lock);
+ mlock(&mp->lock);
+ TOFRONT(mp, p);
+ unmlock(&mp->lock);
+ return p;
+}
+
+void
+purgetrack(Iotrack *t)
+{
+ int i, ref = Sect2trk;
+ Iosect *p;
+
+ for(i=0; i<Sect2trk; i++){
+ p = t->tp->p[i];
+ if(p == 0){
+ --ref;
+ continue;
+ }
+ if(canmlock(&p->lock)){
+ freesect(p);
+ --ref;
+ t->tp->p[i] = 0;
+ }
+ }
+ if(t->ref != ref)
+ panic("purgetrack");
+}
+
+int
+twrite(Iotrack *t)
+{
+ int i, ref;
+
+ chat("[twrite %ld...", t->addr);
+ if(t->flags & BSTALE){
+ for(ref=0,i=0; i<Sect2trk; i++)
+ if(t->tp->p[i])
+ ++ref;
+ if(ref < Sect2trk){
+ if(tread(t) < 0){
+ chat("error]");
+ return -1;
+ }
+ }else
+ t->flags &= ~BSTALE;
+ }
+ if(devwrite(t->xf, t->addr, t->tp->buf, Trksize) < 0){
+ chat("error]");
+ return -1;
+ }
+ chat(" done]");
+ return 0;
+}
+
+int
+tread(Iotrack *t)
+{
+ int i, ref = 0;
+ uchar buf[Sect2trk][Sectorsize];
+
+ for(i=0; i<Sect2trk; i++)
+ if(t->tp->p[i])
+ ++ref;
+ chat("[tread %ld+%ld...", t->addr, t->xf->offset);
+ if(ref == 0){
+ if(devread(t->xf, t->addr, t->tp->buf, Trksize) < 0){
+ chat("error]");
+ return -1;
+ }
+ chat("done]");
+ t->flags &= ~BSTALE;
+ return 0;
+ }
+ if(devread(t->xf, t->addr, buf, Trksize) < 0){
+ chat("error]");
+ return -1;
+ }
+ for(i=0; i<Sect2trk; i++)
+ if(t->tp->p[i] == 0){
+ memmove(t->tp->buf[i], buf[i], Sectorsize);
+ chat("%d ", i);
+ }
+ chat("done]");
+ t->flags &= ~BSTALE;
+ return 0;
+}
+
+void
+purgebuf(Xfs *xf)
+{
+ Iotrack *p;
+
+ for(p=&iobuf[0]; p<&iobuf[NIOBUF]; p++){
+ if(p->xf != xf)
+ continue;
+ mlock(&p->lock);
+ if(p->xf == xf){
+ if(p->flags & BMOD)
+ twrite(p);
+ p->flags = BSTALE;
+ purgetrack(p);
+ }
+ unmlock(&p->lock);
+ }
+}
+
+void
+sync(void)
+{
+ Iotrack *p;
+
+ for(p=&iobuf[0]; p<&iobuf[NIOBUF]; p++){
+ if(!(p->flags & BMOD))
+ continue;
+ mlock(&p->lock);
+ if(p->flags & BMOD){
+ twrite(p);
+ p->flags &= ~(BMOD|BIMM);
+ }
+ unmlock(&p->lock);
+ }
+}
+
+void
+iotrack_init(void)
+{
+ Iotrack *mp, *p;
+
+ for (mp=&hiob[0]; mp<&hiob[HIOB]; mp++)
+ mp->hprev = mp->hnext = mp;
+ mp->prev = mp->next = mp;
+
+ for (p=&iobuf[0]; p<&iobuf[NIOBUF]; p++) {
+ p->hprev = p->hnext = p;
+ p->prev = p->next = p;
+ TOFRONT(mp, p);
+ p->tp = sbrk(sizeof(Track));
+ memset(p->tp->p, 0, sizeof p->tp->p);
+ }
+}
+
+static MLock freelock;
+static Iosect * freelist;
+
+Iosect *
+newsect(void)
+{
+ Iosect *p;
+
+ mlock(&freelock);
+ if(p = freelist) /* assign = */
+ freelist = p->next;
+ else
+ p = malloc(sizeof(Iosect));
+ unmlock(&freelock);
+ p->next = 0;
+ return p;
+}
+
+void
+freesect(Iosect *p)
+{
+ mlock(&freelock);
+ p->next = freelist;
+ freelist = p;
+ unmlock(&freelock);
+}
diff --git a/sys/src/cmd/dossrv/iotrack.h b/sys/src/cmd/dossrv/iotrack.h
new file mode 100755
index 000000000..39b39c306
--- /dev/null
+++ b/sys/src/cmd/dossrv/iotrack.h
@@ -0,0 +1,69 @@
+typedef struct MLock MLock;
+typedef struct Iosect Iosect;
+typedef struct Iotrack Iotrack;
+typedef struct Track Track;
+typedef struct Xfs Xfs;
+
+struct MLock
+{
+ char key;
+};
+
+struct Iosect
+{
+ Iosect *next;
+ short flags;
+ MLock lock;
+ Iotrack *t;
+ uchar * iobuf;
+};
+
+struct Iotrack
+{
+ short flags;
+ Xfs * xf;
+ long addr;
+ Iotrack *next; /* in lru list */
+ Iotrack *prev;
+ Iotrack *hnext; /* in hash list */
+ Iotrack *hprev;
+ MLock lock;
+ int ref;
+ Track *tp;
+};
+
+enum{
+ Sectorsize = 512,
+ Sect2trk = 9,
+ Trksize = Sectorsize*Sect2trk
+};
+
+struct Track
+{
+ Iosect *p[Sect2trk];
+ uchar buf[Sect2trk][Sectorsize];
+};
+
+#define BMOD (1<<0)
+#define BIMM (1<<1)
+#define BSTALE (1<<2)
+
+Iosect* getiosect(Xfs*, long, int);
+Iosect* getosect(Xfs*, long);
+Iosect* getsect(Xfs*, long);
+Iosect* newsect(void);
+Iotrack* getiotrack(Xfs*, long);
+int canmlock(MLock*);
+int devcheck(Xfs*);
+int devread(Xfs*, long, void*, long);
+int devwrite(Xfs*, long, void*, long);
+int tread(Iotrack*);
+int twrite(Iotrack*);
+void freesect(Iosect*);
+void iotrack_init(void);
+void mlock(MLock*);
+void purgebuf(Xfs*);
+void purgetrack(Iotrack*);
+void putsect(Iosect*);
+void sync(void);
+void unmlock(MLock*);
diff --git a/sys/src/cmd/dossrv/lock.c b/sys/src/cmd/dossrv/lock.c
new file mode 100755
index 000000000..20d77ccd6
--- /dev/null
+++ b/sys/src/cmd/dossrv/lock.c
@@ -0,0 +1,38 @@
+#include <u.h>
+#include <libc.h>
+#include "iotrack.h"
+#include "dat.h"
+#include "fns.h"
+
+void
+mlock(MLock *l)
+{
+
+ if(l->key != 0 && l->key != 1)
+ panic("uninitialized lock");
+ if (l->key)
+ panic("lock");
+ l->key = 1;
+}
+
+void
+unmlock(MLock *l)
+{
+
+ if(l->key != 0 && l->key != 1)
+ panic("uninitialized unlock");
+ if (!l->key)
+ panic("unlock");
+ l->key = 0;
+}
+
+int
+canmlock(MLock *l)
+{
+ if(l->key != 0 && l->key != 1)
+ panic("uninitialized canlock");
+ if (l->key)
+ return 0;
+ l->key = 1;
+ return 1;
+}
diff --git a/sys/src/cmd/dossrv/mkfile b/sys/src/cmd/dossrv/mkfile
new file mode 100755
index 000000000..932018319
--- /dev/null
+++ b/sys/src/cmd/dossrv/mkfile
@@ -0,0 +1,29 @@
+</$objtype/mkfile
+
+TARG=dossrv
+OFILES=\
+ xfssrv.$O\
+ xfile.$O\
+ dosfs.$O\
+ dossubs.$O\
+ iotrack.$O\
+ lock.$O\
+ chat.$O\
+ devio.$O\
+
+HFILES=dat.h\
+ dosfs.h\
+ fns.h\
+ iotrack.h\
+
+BIN=/$objtype/bin
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${TARG:%=/386/bin/%}\
+
+</sys/src/cmd/mkone
+
+xfssrv.$O: errstr.h
diff --git a/sys/src/cmd/dossrv/xfile.c b/sys/src/cmd/dossrv/xfile.c
new file mode 100755
index 000000000..6dbfb40d4
--- /dev/null
+++ b/sys/src/cmd/dossrv/xfile.c
@@ -0,0 +1,255 @@
+#include <u.h>
+#include <libc.h>
+#include "iotrack.h"
+#include "dat.h"
+#include "fns.h"
+
+#define FIDMOD 127 /* prime */
+
+static Xfs *xhead;
+static Xfile *xfiles[FIDMOD], *freelist;
+static MLock xlock, xlocks[FIDMOD], freelock;
+
+static int
+okmode(int omode, int fmode)
+{
+ if(omode == OREAD)
+ return fmode & 4;
+ /* else ORDWR */
+ return (fmode & 6) == 6;
+}
+
+Xfs *
+getxfs(char *user, char *name)
+{
+ Xfs *xf, *fxf;
+ Dir *dir;
+ Qid dqid;
+ char *p, *q;
+ long offset;
+ int fd, omode;
+
+ USED(user);
+ if(name==nil || name[0]==0)
+ name = deffile;
+ if(name == nil){
+ errno = Enofilsys;
+ return 0;
+ }
+
+ /*
+ * If the name passed is of the form 'name:offset' then
+ * offset is used to prime xf->offset. This allows accessing
+ * a FAT-based filesystem anywhere within a partition.
+ * Typical use would be to mount a filesystem in the presence
+ * of a boot manager programme at the beginning of the disc.
+ */
+ offset = 0;
+ if(p = strrchr(name, ':')){
+ *p++ = 0;
+ offset = strtol(p, &q, 0);
+ chat("name %s, offset %ld\n", p, offset);
+ if(offset < 0 || p == q){
+ errno = Enofilsys;
+ return 0;
+ }
+ offset *= Sectorsize;
+ }
+
+ if(readonly)
+ omode = OREAD;
+ else
+ omode = ORDWR;
+ fd = open(name, omode);
+
+ if(fd < 0 && omode==ORDWR){
+ omode = OREAD;
+ fd = open(name, omode);
+ }
+
+ if(fd < 0){
+ chat("can't open %s: %r\n", name);
+ errno = Eerrstr;
+ return 0;
+ }
+ dir = dirfstat(fd);
+ if(dir == nil){
+ errno = Eio;
+ close(fd);
+ return 0;
+ }
+
+ dqid = dir->qid;
+ free(dir);
+ mlock(&xlock);
+ for(fxf=0,xf=xhead; xf; xf=xf->next){
+ if(xf->ref == 0){
+ if(fxf == 0)
+ fxf = xf;
+ continue;
+ }
+ if(!eqqid(xf->qid, dqid))
+ continue;
+ if(strcmp(xf->name, name) != 0 || xf->dev < 0)
+ continue;
+ if(devcheck(xf) < 0) /* look for media change */
+ continue;
+ if(offset && xf->offset != offset)
+ continue;
+ chat("incref \"%s\", dev=%d...", xf->name, xf->dev);
+ ++xf->ref;
+ unmlock(&xlock);
+ close(fd);
+ return xf;
+ }
+ if(fxf == nil){
+ fxf = malloc(sizeof(Xfs));
+ if(fxf == nil){
+ unmlock(&xlock);
+ close(fd);
+ errno = Enomem;
+ return nil;
+ }
+ fxf->next = xhead;
+ xhead = fxf;
+ }
+ chat("alloc \"%s\", dev=%d...", name, fd);
+ fxf->name = strdup(name);
+ fxf->ref = 1;
+ fxf->qid = dqid;
+ fxf->dev = fd;
+ fxf->fmt = 0;
+ fxf->offset = offset;
+ fxf->ptr = nil;
+ fxf->isfat32 = 0;
+ fxf->omode = omode;
+ unmlock(&xlock);
+ return fxf;
+}
+
+void
+refxfs(Xfs *xf, int delta)
+{
+ mlock(&xlock);
+ xf->ref += delta;
+ if(xf->ref == 0){
+ chat("free \"%s\", dev=%d...", xf->name, xf->dev);
+ free(xf->name);
+ free(xf->ptr);
+ purgebuf(xf);
+ if(xf->dev >= 0){
+ close(xf->dev);
+ xf->dev = -1;
+ }
+ }
+ unmlock(&xlock);
+}
+
+Xfile *
+xfile(int fid, int flag)
+{
+ Xfile **hp, *f, *pf;
+ int k;
+
+ k = ((ulong)fid) % FIDMOD;
+ hp = &xfiles[k];
+ mlock(&xlocks[k]);
+ pf = nil;
+ for(f=*hp; f; f=f->next){
+ if(f->fid == fid)
+ break;
+ pf = f;
+ }
+ if(f && pf){
+ pf->next = f->next;
+ f->next = *hp;
+ *hp = f;
+ }
+ switch(flag){
+ default:
+ panic("xfile");
+ case Asis:
+ unmlock(&xlocks[k]);
+ return (f && f->xf && f->xf->dev < 0) ? nil : f;
+ case Clean:
+ break;
+ case Clunk:
+ if(f){
+ *hp = f->next;
+ unmlock(&xlocks[k]);
+ clean(f);
+ mlock(&freelock);
+ f->next = freelist;
+ freelist = f;
+ unmlock(&freelock);
+ } else
+ unmlock(&xlocks[k]);
+ return nil;
+ }
+ unmlock(&xlocks[k]);
+ if(f)
+ return clean(f);
+ mlock(&freelock);
+ if(f = freelist){ /* assign = */
+ freelist = f->next;
+ unmlock(&freelock);
+ } else {
+ unmlock(&freelock);
+ f = malloc(sizeof(Xfile));
+ if(f == nil){
+ errno = Enomem;
+ return nil;
+ }
+ }
+ mlock(&xlocks[k]);
+ f->next = *hp;
+ *hp = f;
+ unmlock(&xlocks[k]);
+ f->fid = fid;
+ f->flags = 0;
+ f->qid = (Qid){0,0,0};
+ f->xf = nil;
+ f->ptr = nil;
+ return f;
+}
+
+Xfile *
+clean(Xfile *f)
+{
+ if(f->ptr){
+ free(f->ptr);
+ f->ptr = nil;
+ }
+ if(f->xf){
+ refxfs(f->xf, -1);
+ f->xf = nil;
+ }
+ f->flags = 0;
+ f->qid = (Qid){0,0,0};
+ return f;
+}
+
+/*
+ * the file at <addr, offset> has moved
+ * relocate the dos entries of all fids in the same file
+ */
+void
+dosptrreloc(Xfile *f, Dosptr *dp, ulong addr, ulong offset)
+{
+ int i;
+ Xfile *p;
+ Dosptr *xdp;
+
+ for(i=0; i < FIDMOD; i++){
+ for(p = xfiles[i]; p != nil; p = p->next){
+ xdp = p->ptr;
+ if(p != f && p->xf == f->xf
+ && xdp != nil && xdp->addr == addr && xdp->offset == offset){
+ memmove(xdp, dp, sizeof(Dosptr));
+ xdp->p = nil;
+ xdp->d = nil;
+ p->qid.path = QIDPATH(xdp);
+ }
+ }
+ }
+}
diff --git a/sys/src/cmd/dossrv/xfssrv.c b/sys/src/cmd/dossrv/xfssrv.c
new file mode 100755
index 000000000..3766a170d
--- /dev/null
+++ b/sys/src/cmd/dossrv/xfssrv.c
@@ -0,0 +1,204 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include "iotrack.h"
+#include "dat.h"
+#include "dosfs.h"
+#include "fns.h"
+
+#include "errstr.h"
+
+#define Reqsize (sizeof(Fcall)+Maxfdata)
+Fcall *req;
+Fcall *rep;
+
+uchar mdata[Maxiosize];
+char repdata[Maxfdata];
+uchar statbuf[STATMAX];
+int errno;
+char errbuf[ERRMAX];
+void rmservice(void);
+char srvfile[64];
+char *deffile;
+int doabort;
+int trspaces;
+
+void (*fcalls[])(void) = {
+ [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,
+};
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-v] [-s] [-f devicefile] [srvname]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int stdio, srvfd, pipefd[2];
+
+ rep = malloc(sizeof(Fcall));
+ req = malloc(Reqsize);
+ if(rep == nil || req == nil)
+ panic("out of memory");
+ stdio = 0;
+ ARGBEGIN{
+ case ':':
+ trspaces = 1;
+ break;
+ case 'r':
+ readonly = 1;
+ break;
+ case 'v':
+ ++chatty;
+ break;
+ case 'f':
+ deffile = ARGF();
+ break;
+ case 's':
+ stdio = 1;
+ break;
+ case 'p':
+ doabort = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc == 0)
+ strcpy(srvfile, "#s/dos");
+ else if(argc == 1)
+ snprint(srvfile, sizeof srvfile, "#s/%s", argv[0]);
+ else
+ usage();
+
+ if(stdio){
+ pipefd[0] = 0;
+ pipefd[1] = 1;
+ }else{
+ close(0);
+ close(1);
+ open("/dev/null", OREAD);
+ open("/dev/null", OWRITE);
+ if(pipe(pipefd) < 0)
+ panic("pipe");
+ srvfd = create(srvfile, OWRITE|ORCLOSE, 0600);
+ if(srvfd < 0)
+ panic(srvfile);
+ fprint(srvfd, "%d", pipefd[0]);
+ close(pipefd[0]);
+ atexit(rmservice);
+ fprint(2, "%s: serving %s\n", argv0, srvfile);
+ }
+ srvfd = pipefd[1];
+
+ switch(rfork(RFNOWAIT|RFNOTEG|RFFDG|RFPROC|RFNAMEG)){
+ case -1:
+ panic("fork");
+ default:
+ _exits(0);
+ case 0:
+ break;
+ }
+
+ iotrack_init();
+
+ if(!chatty){
+ close(2);
+ open("#c/cons", OWRITE);
+ }
+
+ io(srvfd);
+ exits(0);
+}
+
+void
+io(int srvfd)
+{
+ int n, pid;
+
+ pid = getpid();
+
+ fmtinstall('F', fcallfmt);
+ 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(srvfd, mdata, sizeof mdata);
+ if(n < 0)
+ break;
+ if(n == 0)
+ continue;
+ if(convM2S(mdata, n, req) == 0)
+ continue;
+
+ if(chatty)
+ fprint(2, "dossrv %d:<-%F\n", pid, req);
+
+ errno = 0;
+ if(!fcalls[req->type])
+ errno = Ebadfcall;
+ else
+ (*fcalls[req->type])();
+ if(errno){
+ rep->type = Rerror;
+ rep->ename = xerrstr(errno);
+ }else{
+ rep->type = req->type + 1;
+ rep->fid = req->fid;
+ }
+ rep->tag = req->tag;
+ if(chatty)
+ fprint(2, "dossrv %d:->%F\n", pid, rep);
+ n = convS2M(rep, mdata, sizeof mdata);
+ if(n == 0)
+ panic("convS2M error on write");
+ if(write(srvfd, mdata, n) != n)
+ panic("mount write");
+ }
+ chat("server shut down");
+}
+
+void
+rmservice(void)
+{
+ remove(srvfile);
+}
+
+char *
+xerrstr(int e)
+{
+ if (e < 0 || e >= sizeof errmsg/sizeof errmsg[0])
+ return "no such error";
+ if(e == Eerrstr){
+ errstr(errbuf, sizeof errbuf);
+ return errbuf;
+ }
+ return errmsg[e];
+}
+
+int
+eqqid(Qid q1, Qid q2)
+{
+ return q1.path == q2.path && q1.type == q2.type && q1.vers == q2.vers;
+}