implement Dossrv; include "sys.m"; sys: Sys; include "draw.m"; include "arg.m"; include "daytime.m"; daytime: Daytime; include "styx.m"; styx: Styx; Tmsg, Rmsg: import styx; Dossrv: module { init: fn(ctxt: ref Draw->Context, args: list of string); system: fn(ctxt: ref Draw->Context, args: list of string): string; }; arg0 := "dossrv"; deffile: string; pflag := 0; debug := 0; usage(iscmd: int): string { sys->fprint(sys->fildes(2), "usage: %s [-v] [-s] [-F] [-c] [-S secpertrack] [-f devicefile] [-m mountpoint]\n", arg0); if(iscmd) raise "fail:usage"; return "usage"; } init(nil: ref Draw->Context, args: list of string) { e := init2(nil, args, 1); if(e != nil){ sys->fprint(sys->fildes(2), "%s: %s\n", arg0, e); raise "fail:error"; } } system(nil: ref Draw->Context, args: list of string): string { e := init2(nil, args, 0); if(e != nil) sys->fprint(sys->fildes(2), "%s: %s\n", arg0, e); return e; } nomod(s: string): string { return sys->sprint("can't load %s: %r", s); } init2(nil: ref Draw->Context, args: list of string, iscmd: int): string { sys = load Sys Sys->PATH; pipefd := array[2] of ref Sys->FD; srvfile := "/n/dos"; deffile = ""; # no default, for safety sectors := 0; stdin := 0; arg := load Arg Arg->PATH; if(arg == nil) return nomod(Arg->PATH); arg->init(args); arg0 = arg->progname(); while((o := arg->opt()) != 0) { case o { 'v' => if(debug & STYX_MESS) debug |= VERBOSE; debug |= STYX_MESS; 'F' => debug |= FAT_INFO; 'c' => debug |= CLUSTER_INFO; iodebug = 1; 'S' => s := arg->arg(); if(s != nil && s[0]>='0' && s[0]<='9') sectors = int s; else return usage(iscmd); 's' => stdin = 1; 'f' => deffile = arg->arg(); if(deffile == nil) return usage(iscmd); 'm' => srvfile = arg->arg(); if(srvfile == nil) return usage(iscmd); 'p' => pflag++; * => return usage(iscmd); } } args = arg->argv(); arg = nil; if(deffile == "" || !stdin && srvfile == "") return usage(iscmd); styx = load Styx Styx->PATH; if(styx == nil) return nomod(Styx->PATH); styx->init(); daytime = load Daytime Daytime->PATH; if(daytime == nil) return nomod(Daytime->PATH); iotrackinit(sectors); if(!stdin) { if(sys->pipe(pipefd) < 0) return sys->sprint("can't create pipe: %r"); }else{ pipefd[0] = nil; pipefd[1] = sys->fildes(1); } dossetup(); spawn dossrv(pipefd[1]); if(!stdin) { if(sys->mount(pipefd[0], nil, srvfile, sys->MREPL|sys->MCREATE, deffile) < 0) return sys->sprint("mount %s: %r", srvfile); } return nil; } # # Styx server # Enevermind, Eformat, Eio, Enomem, Enonexist, Enotdir, Enofid, Efidopen, Efidinuse, Eexist, Eperm, Enofilsys, Eauth, Econtig, Efull, Eopen, Ephase: con iota; errmsg := array[] of { Enevermind => "never mind", Eformat => "unknown format", Eio => "I/O error", Enomem => "server out of memory", Enonexist => "file does not exist", Enotdir => "not a directory", Enofid => "no such fid", Efidopen => "fid already open", Efidinuse => "fid in use", Eexist => "file exists", Eperm => "permission denied", Enofilsys => "no file system device specified", Eauth => "authentication failed", Econtig => "out of contiguous disk space", Efull => "file system full", Eopen => "invalid open mode", Ephase => "phase error -- directory entry not found", }; e(n: int): ref Rmsg.Error { if(n < 0 || n >= len errmsg) return ref Rmsg.Error(0, "it's thermal problems"); return ref Rmsg.Error(0, errmsg[n]); } dossrv(rfd: ref Sys->FD) { sys->pctl(Sys->NEWFD, rfd.fd :: 2 :: nil); rfd = sys->fildes(rfd.fd); while((t := Tmsg.read(rfd, 0)) != nil){ if(debug & STYX_MESS) chat(sys->sprint("%s...", t.text())); r: ref Rmsg; pick m := t { Readerror => panic(sys->sprint("mount read error: %s", m.error)); Version => r = rversion(m); Auth => r = rauth(m); Flush => r = rflush(m); Attach => r = rattach(m); Walk => r = rwalk(m); Open => r = ropen(m); Create => r = rcreate(m); Read => r = rread(m); Write => r = rwrite(m); Clunk => r = rclunk(m); Remove => r = rremove(m); Stat => r = rstat(m); Wstat => r = rwstat(m); * => panic("Styx mtype"); } pick m := r { Error => r.tag = t.tag; } rbuf := r.pack(); if(rbuf == nil) panic("Rmsg.pack"); if(debug & STYX_MESS) chat(sys->sprint("%s\n", r.text())); if(sys->write(rfd, rbuf, len rbuf) != len rbuf) panic("mount write"); } if(debug & STYX_MESS) chat("server EOF\n"); } rversion(t: ref Tmsg.Version): ref Rmsg { (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION); return ref Rmsg.Version(t.tag, msize, version); } rauth(t: ref Tmsg.Auth): ref Rmsg { return ref Rmsg.Error(t.tag, "authentication not required"); } rflush(t: ref Tmsg.Flush): ref Rmsg { return ref Rmsg.Flush(t.tag); } rattach(t: ref Tmsg.Attach): ref Rmsg { root := xfile(t.fid, Clean); if(root == nil) return e(Eio); if(t.aname == nil) t.aname = deffile; (xf, ec) := getxfs(t.aname); root.xf = xf; if(xf == nil) { if(root!=nil) xfile(t.fid, Clunk); return ref Rmsg.Error(t.tag, ec); } if(xf.fmt == 0 && dosfs(xf) < 0){ if(root!=nil) xfile(t.fid, Clunk); return e(Eformat); } root.qid = Sys->Qid(big 0, 0, Sys->QTDIR); root.xf.rootqid = root.qid; return ref Rmsg.Attach(t.tag, root.qid); } clone(ofl: ref Xfile, newfid: int): ref Xfile { nfl := xfile(newfid, Clean); next := nfl.next; *nfl = *ofl; nfl.ptr = nil; nfl.next = next; nfl.fid = newfid; refxfs(nfl.xf, 1); if(ofl.ptr != nil){ dp := ref *ofl.ptr; dp.p = nil; dp.d = nil; nfl.ptr = dp; } return nfl; } walk1(f: ref Xfile, name: string): ref Rmsg.Error { if((f.qid.qtype & Sys->QTDIR) == 0){ if(debug) chat(sys->sprint("qid.path=0x%bx...", f.qid.path)); return e(Enotdir); } if(name == ".") # can't happen return nil; if(name== "..") { if(f.qid.path == f.xf.rootqid.path) { if (debug) chat("walkup from root..."); return nil; } (r,dp) := walkup(f); if(r < 0) return e(Enonexist); f.ptr = dp; if(dp.addr == 0) { f.qid.path = f.xf.rootqid.path; f.qid.qtype = Sys->QTFILE; } else { f.qid.path = QIDPATH(dp); f.qid.qtype = Sys->QTDIR; } } else { if(getfile(f) < 0) return e(Enonexist); (r,dp) := searchdir(f, name, 0,1); putfile(f); if(r < 0) return e(Enonexist); f.ptr = dp; f.qid.path = QIDPATH(dp); f.qid.qtype = Sys->QTFILE; if(dp.addr == 0) f.qid.path = f.xf.rootqid.path; else { d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); if((int d.attr & DDIR) != 0) f.qid.qtype = Sys->QTDIR; } putfile(f); } return nil; } rwalk(t: ref Tmsg.Walk): ref Rmsg { f := xfile(t.fid, Asis); if(f==nil) { if(debug) chat("no xfile..."); return e(Enofid); } nf: ref Xfile; if(t.newfid != t.fid) f = nf = clone(f, t.newfid); qids: array of Sys->Qid; if(len t.names > 0){ savedqid := f.qid; savedptr := f.ptr; qids = array[len t.names] of Sys->Qid; for(i := 0; i < len t.names; i++){ e := walk1(f, t.names[i]); if(e != nil){ f.qid = savedqid; f.ptr = savedptr; if(nf != nil) xfile(t.newfid, Clunk); if(i == 0) return e; return ref Rmsg.Walk(t.tag, qids[0:i]); } qids[i] = f.qid; } } return ref Rmsg.Walk(t.tag, qids); } ropen(t: ref Tmsg.Open): ref Rmsg { attr: int; omode := 0; f := xfile(t.fid, Asis); if(f == nil) return e(Enofid); if((f.flags&Omodes) != 0) return e(Efidopen); dp := f.ptr; if(dp.paddr && (t.mode & Styx->ORCLOSE) != 0) { # check on parent directory of file to be deleted p := getsect(f.xf, dp.paddr); if(p == nil) return e(Eio); # 11 is the attr byte offset in a FAT directory entry attr = int p.iobuf[dp.poffset+11]; putsect(p); if((attr & int DRONLY) != 0) return e(Eperm); omode |= Orclose; } else if(t.mode & Styx->ORCLOSE) omode |= Orclose; if(getfile(f) < 0) return e(Enonexist); if(dp.addr != 0) { d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); attr = int d.attr; } else attr = int DDIR; case t.mode & 7 { Styx->OREAD or Styx->OEXEC => omode |= Oread; Styx->ORDWR => omode |= Oread; omode |= Owrite; if(attr & int (DRONLY|DDIR)) { putfile(f); return e(Eperm); } Styx->OWRITE => omode |= Owrite; if(attr & int (DRONLY|DDIR)) { putfile(f); return e(Eperm); } * => putfile(f); return e(Eopen); } if(t.mode & Styx->OTRUNC) { if((attr & int DDIR)!=0 || (attr & int DRONLY) != 0) { putfile(f); return e(Eperm); } if(truncfile(f) < 0) { putfile(f); return e(Eio); } } f.flags |= omode; putfile(f); return ref Rmsg.Open(t.tag, f.qid, Styx->MAXFDATA); } mkdentry(xf: ref Xfs, ndp: ref Dosptr, name: string, sname: string, islong: int, nattr: byte, start: array of byte, length: array of byte): int { ndp.p = getsect(xf, ndp.addr); if(ndp.p == nil) return Eio; if(islong && (r := putlongname(xf, ndp, name, sname)) < 0){ putsect(ndp.p); if(r == -2) return Efull; return Eio; } nd := ref Dosdir(". "," ",byte 0,array[10] of { * => byte 0}, array[2] of { * => byte 0}, array[2] of { * => byte 0}, array[2] of { * => byte 0},array[4] of { * => byte 0}); nd.attr = nattr; puttime(nd); nd.start[0: ] = start[0: 2]; nd.length[0: ] = length[0: 4]; if(islong) putname(sname[0:8]+"."+sname[8:11], nd); else putname(name, nd); ndp.p.iobuf[ndp.offset: ] = Dosdir.Dd2arr(nd); ndp.p.flags |= BMOD; return 0; } rcreate(t: ref Tmsg.Create): ref Rmsg { bp: ref Dosbpb; omode:=0; start:=0; sname := ""; islong :=0; f := xfile(t.fid, Asis); if(f == nil) return e(Enofid); if((f.flags&Omodes) != 0) return e(Efidopen); if(getfile(f)<0) return e(Eio); pdp := f.ptr; if(pdp.addr != 0) pd := Dosdir.arr2Dd(pdp.p.iobuf[pdp.offset:pdp.offset+DOSDIRSIZE]); else pd = nil; if(pd != nil) attr := int pd.attr; else attr = DDIR; if(!(attr & DDIR) || (attr & DRONLY)) { putfile(f); return e(Eperm); } if(t.mode & Styx->ORCLOSE) omode |= Orclose; case (t.mode & 7) { Styx->OREAD or Styx->OEXEC => omode |= Oread; Styx->OWRITE or Styx->ORDWR => if ((t.mode & 7) == Styx->ORDWR) omode |= Oread; omode |= Owrite; if(t.perm & Sys->DMDIR){ putfile(f); return e(Eperm); } * => putfile(f); return e(Eopen); } if(t.name=="." || t.name=="..") { putfile(f); return e(Eperm); } (r,ndp) := searchdir(f, t.name, 1, 1); if(r < 0) { putfile(f); if(r == -2) return e(Efull); return e(Eexist); } nds := name2de(t.name); if(nds > 0) { # long file name, find "new" short name i := 1; for(;;) { sname = long2short(t.name, i); (r1, tmpdp) := searchdir(f, sname, 0, 0); if(r1 < 0) break; putsect(tmpdp.p); i++; } islong = 1; } # allocate first cluster, if making directory if(t.perm & Sys->DMDIR) { bp = f.xf.ptr; start = falloc(f.xf); if(start <= 0) { putfile(f); return e(Efull); } } # now we're committed if(pd != nil) { puttime(pd); pdp.p.flags |= BMOD; } f.ptr = ndp; ndp.p = getsect(f.xf, ndp.addr); if(ndp.p == nil || islong && putlongname(f.xf, ndp, t.name, sname) < 0){ putsect(pdp.p); if(ndp.p != nil) putsect(ndp.p); return e(Eio); } nd := ref Dosdir(". "," ",byte 0,array[10] of { * => byte 0}, array[2] of { * => byte 0}, array[2] of { * => byte 0}, array[2] of { * => byte 0},array[4] of { * => byte 0}); if((t.perm & 8r222) == 0) nd.attr |= byte DRONLY; puttime(nd); nd.start[0] = byte start; nd.start[1] = byte (start>>8); if(islong) putname(sname[0:8]+"."+sname[8:11], nd); else putname(t.name, nd); f.qid.path = QIDPATH(ndp); if(t.perm & Sys->DMDIR) { nd.attr |= byte DDIR; f.qid.qtype |= Sys->QTDIR; xp := getsect(f.xf, bp.dataaddr+(start-2)*bp.clustsize); if(xp == nil) { if(ndp.p!=nil) putfile(f); putsect(pdp.p); return e(Eio); } xd := ref *nd; xd.name = ". "; xd.ext = " "; xp.iobuf[0:] = Dosdir.Dd2arr(xd); if(pd!=nil) xd = ref *pd; else{ xd = ref Dosdir(".. "," ",byte 0, array[10] of { * => byte 0}, array[2] of { * => byte 0}, array[2] of { * => byte 0}, array[2] of { * => byte 0}, array[4] of { * => byte 0}); puttime(xd); xd.attr = byte DDIR; } xd.name=".. "; xd.ext=" "; xp.iobuf[DOSDIRSIZE:] = Dosdir.Dd2arr(xd); xp.flags |= BMOD; putsect(xp); }else f.qid.qtype = Sys->QTFILE; ndp.p.flags |= BMOD; tmp := Dosdir.Dd2arr(nd); ndp.p.iobuf[ndp.offset:]= tmp; putfile(f); putsect(pdp.p); f.flags |= omode; return ref Rmsg.Create(t.tag, f.qid, Styx->MAXFDATA); } rread(t: ref Tmsg.Read): ref Rmsg { r: int; data: array of byte; if(((f:=xfile(t.fid, Asis))==nil) || (f.flags&Oread == 0)) return e(Eio); if((f.qid.qtype & Sys->QTDIR) != 0) { if(getfile(f) < 0) return e(Eio); (r, data) = readdir(f, int t.offset, t.count); } else { if(getfile(f) < 0) return e(Eio); (r,data) = readfile(f, int t.offset, t.count); } putfile(f); if(r < 0) return e(Eio); return ref Rmsg.Read(t.tag, data[0:r]); } rwrite(t: ref Tmsg.Write): ref Rmsg { if(((f:=xfile(t.fid, Asis))==nil) || !(f.flags&Owrite)) return e(Eio); if(getfile(f) < 0) return e(Eio); r := writefile(f, t.data, int t.offset, len t.data); putfile(f); if(r < 0){ if(r == -2) return e(Efull); return e(Eio); } return ref Rmsg.Write(t.tag, r); } rclunk(t: ref Tmsg.Clunk): ref Rmsg { xfile(t.fid, Clunk); sync(); return ref Rmsg.Clunk(t.tag); } doremove(f: ref Xfs, dp: ref Dosptr) { dp.p.iobuf[dp.offset] = byte DOSEMPTY; dp.p.flags |= BMOD; for(prevdo := dp.offset-DOSDIRSIZE; prevdo >= 0; prevdo-=DOSDIRSIZE){ if (dp.p.iobuf[prevdo+11] != byte DLONG) break; dp.p.iobuf[prevdo] = byte DOSEMPTY; } if (prevdo <= 0 && dp.prevaddr != -1){ p := getsect(f,dp.prevaddr); for(prevdo = f.ptr.sectsize-DOSDIRSIZE; prevdo >= 0; prevdo-=DOSDIRSIZE) { if(p.iobuf[prevdo+11] != byte DLONG) break; p.iobuf[prevdo] = byte DOSEMPTY; p.flags |= BMOD; } putsect(p); } } rremove(t: ref Tmsg.Remove): ref Rmsg { f := xfile(t.fid, Asis); if(f == nil) return e(Enofid); if(!f.ptr.addr) { if(debug) chat("root..."); xfile(t.fid, Clunk); sync(); return e(Eperm); } # check on parent directory of file to be deleted parp := getsect(f.xf, f.ptr.paddr); if(parp == nil) { xfile(t.fid, Clunk); sync(); return e(Eio); } pard := Dosdir.arr2Dd(parp.iobuf[f.ptr.poffset:f.ptr.poffset+DOSDIRSIZE]); if(f.ptr.paddr && (int pard.attr & DRONLY)) { if(debug) chat("parent read-only..."); putsect(parp); xfile(t.fid, Clunk); sync(); return e(Eperm); } if(getfile(f) < 0){ if(debug) chat("getfile failed..."); putsect(parp); xfile(t.fid, Clunk); sync(); return e(Eio); } dattr := int f.ptr.p.iobuf[f.ptr.offset+11]; if(dattr & DDIR && emptydir(f) < 0){ if(debug) chat("non-empty dir..."); putfile(f); putsect(parp); xfile(t.fid, Clunk); sync(); return e(Eperm); } if(f.ptr.paddr == 0 && dattr&DRONLY) { if(debug) chat("read-only file in root directory..."); putfile(f); putsect(parp); xfile(t.fid, Clunk); sync(); return e(Eperm); } doremove(f.xf, f.ptr); if(f.ptr.paddr) { puttime(pard); parp.flags |= BMOD; } parp.iobuf[f.ptr.poffset:] = Dosdir.Dd2arr(pard); putsect(parp); err := 0; if(truncfile(f) < 0) err = Eio; putfile(f); xfile(t.fid, Clunk); sync(); if(err) return e(err); return ref Rmsg.Remove(t.tag); } rstat(t: ref Tmsg.Stat): ref Rmsg { f := xfile(t.fid, Asis); if(f == nil) return e(Enofid); if(getfile(f) < 0) return e(Eio); dir := dostat(f); putfile(f); return ref Rmsg.Stat(t.tag, *dir); } dostat(f: ref Xfile): ref Sys->Dir { islong :=0; prevdo: int; longnamebuf:=""; # get file info. dir := getdir(f.ptr.p.iobuf[f.ptr.offset:f.ptr.offset+DOSDIRSIZE], f.ptr.addr, f.ptr.offset); # get previous entry if(f.ptr.prevaddr == -1) { # maybe extended, but will never cross sector boundary... # short filename at beginning of sector.. if(f.ptr.offset!=0) { for(prevdo = f.ptr.offset-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE) { prevdattr := f.ptr.p.iobuf[prevdo+11]; if(prevdattr != byte DLONG) break; islong = 1; longnamebuf += getnamesect(f.ptr.p.iobuf[prevdo:prevdo+DOSDIRSIZE]); } } } else { # extended and will cross sector boundary. for(prevdo = f.ptr.offset-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE) { prevdattr := f.ptr.p.iobuf[prevdo+11]; if(prevdattr != byte DLONG) break; islong = 1; longnamebuf += getnamesect(f.ptr.p.iobuf[prevdo:prevdo+DOSDIRSIZE]); } if (prevdo < 0) { p := getsect(f.xf,f.ptr.prevaddr); for(prevdo = f.xf.ptr.sectsize-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE){ prevdattr := p.iobuf[prevdo+11]; if(prevdattr != byte DLONG) break; islong = 1; longnamebuf += getnamesect(p.iobuf[prevdo:prevdo+DOSDIRSIZE]); } putsect(p); } } if(islong) dir.name = longnamebuf; return dir; } nameok(elem: string): int { isfrog := array[256] of { # 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, '/' => 1, 16r7f => 1, * => 0 }; for(i:=0; i < len elem; i++) { if(isfrog[elem[i]]) return -1; } return 0; } rwstat(t: ref Tmsg.Wstat): ref Rmsg { f := xfile(t.fid, Asis); if(f == nil) return e(Enofid); if(getfile(f) < 0) return e(Eio); dp := f.ptr; if(dp.addr == 0){ # root putfile(f); return e(Eperm); } changes := 0; dir := dostat(f); wdir := ref t.stat; if(dir.uid != wdir.uid || dir.gid != wdir.gid){ putfile(f); return e(Eperm); } if(dir.mtime != wdir.mtime || ((dir.mode^wdir.mode) & 8r777)) changes = 1; if((wdir.mode & 7) != ((wdir.mode >> 3) & 7) || (wdir.mode & 7) != ((wdir.mode >> 6) & 7)){ putfile(f); return e(Eperm); } if(dir.name != wdir.name){ # temporarily disable this # g.errno = Eperm; # putfile(f); # return; # # 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){ putfile(f); return e(Eio); } # pard := Dosdir.arr2Dd(parp.iobuf[dp.poffset: dp.poffset+DOSDIRSIZE]); pardattr := int parp.iobuf[dp.poffset+11]; dpd := Dosdir.arr2Dd(dp.p.iobuf[dp.offset: dp.offset+DOSDIRSIZE]); if(dp.paddr != 0 && int pardattr & DRONLY || dp.paddr == 0 && int dpd.attr & DRONLY){ putsect(parp); putfile(f); return e(Eperm); } # # retrieve info from old entry # oaddr := dp.addr; ooffset := dp.offset; d := dpd; # od := *d; # start := getstart(f.xf, d); start := d.start; length := 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 := ref *f; pdp := ref Dosptr(dp.paddr, dp.poffset, 0, 0, 0, 0, -1, -1, parp, nil); # if(pdp.addr != 0) # pdpd := Dosdir.arr2Dd(parp.iobuf[pdp.offset: pdp.offset+DOSDIRSIZE]); # else # pdpd = nil; pf.ptr = pdp; if(wdir.name == "." || wdir.name == ".."){ putsect(parp); return e(Eperm); } islong := 0; sname := ""; nds := name2de(wdir.name); if(nds > 0) { # long file name, find "new" short name i := 1; for(;;) { sname = long2short(wdir.name, i); (r1, tmpdp) := searchdir(f, sname, 0, 0); if(r1 < 0) break; putsect(tmpdp.p); i++; } islong = 1; }else{ (b, e) := dosname(wdir.name); sname = b+e; } # (r, ndp) := searchdir(pf, wdir.name, 1, 1); # if(r < 0){ # putsect(parp); # g.errno = Eperm; # return; # } if(getfile(f) < 0){ putsect(parp); return e(Eio); } 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. # r := 0; (r, dp) = searchdir(pf, sname, 1, islong); if(r < 0){ putsect(parp); return e(Ephase); } if((r = mkdentry(pf.xf, dp, wdir.name, sname, islong, attr, start, length)) != 0){ putsect(parp); return e(r); } putsect(parp); # # relocate up other fids to the same file, if it moved # f.ptr = dp; f.qid.path = QIDPATH(dp); if(oaddr != dp.addr || ooffset != dp.offset) dosptrreloc(f, dp, oaddr, ooffset); changes = 1; # f = nil; } if(changes){ d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); putdir(d, wdir); dp.p.iobuf[dp.offset: ] = Dosdir.Dd2arr(d); dp.p.flags |= BMOD; } if(f != nil) putfile(f); sync(); return ref Rmsg.Wstat(t.tag); } # # FAT file system format # Dospart: adt { active: byte; hstart: byte; cylstart: array of byte; typ: byte; hend: byte; cylend: array of byte; start: array of byte; length: array of byte; }; Dosboot: adt { arr2Db: fn(arr: array of byte): ref Dosboot; magic: array of byte; version: array of byte; sectsize: array of byte; clustsize: byte; nresrv: array of byte; nfats: byte; rootsize: array of byte; volsize: array of byte; mediadesc: byte; fatsize: array of byte; trksize: array of byte; nheads: array of byte; nhidden: array of byte; bigvolsize: array of byte; driveno: byte; bootsig: byte; volid: array of byte; label: array of byte; }; Dosbpb: adt { sectsize: int; # in bytes clustsize: int; # in sectors nresrv: int; # sectors nfats: int; # usually 2 rootsize: int; # number of entries volsize: int; # in sectors mediadesc: int; fatsize: int; # in sectors fatclusters: int; fatbits: int; # 12 or 16 fataddr: int; #big; # sector number rootaddr: int; #big; dataaddr: int; #big; freeptr: int; #big; # next free cluster candidate }; Dosdir: adt { Dd2arr: fn(d: ref Dosdir): array of byte; arr2Dd: fn(arr: array of byte): ref Dosdir; name: string; ext: string; attr: byte; reserved: array of byte; time: array of byte; date: array of byte; start: array of byte; length: array of byte; }; Dosptr: adt { addr: int; # of file's directory entry offset: int; paddr: int; # of parent's directory entry poffset: int; iclust: int; # ordinal within file clust: int; prevaddr: int; naddr: int; p: ref Iosect; d: ref Dosdir; }; Asis, Clean, Clunk: con iota; FAT12: con byte 16r01; FAT16: con byte 16r04; FATHUGE: con byte 16r06; DMDDO: con 16r54; DRONLY: con 16r01; DHIDDEN: con 16r02; DSYSTEM: con 16r04; DVLABEL: con 16r08; DDIR: con 16r10; DARCH: con 16r20; DLONG: con DRONLY | DHIDDEN | DSYSTEM | DVLABEL; DMLONG: con DLONG | DDIR | DARCH; DOSDIRSIZE: con 32; DOSEMPTY: con 16rE5; DOSRUNES: con 13; FATRESRV: con 2; Oread: con 1; Owrite: con 2; Orclose: con 4; Omodes: con 3; VERBOSE, STYX_MESS, FAT_INFO, CLUSTER_INFO: con (1 << iota); nowt, nowt1: int; tzoff: int; # # because we map all incoming short names from all upper to all lower case, # and FAT cannot store mixed case names in short name form, # we'll declare upper case as unacceptable to decide whether a long name # is needed on output. thus, long names are always written in the case # in the system call, and are always read back as written; short names # are produced by the common case of writing all lower case letters # isdos := array[256] of { 'a' to 'z' => 1, 'A' to 'Z' => 0, '0' to '9' => 1, ' ' => 1, '$' => 1, '%' => 1, '"' => 1, '-' => 1, '_' => 1, '@' => 1, '~' => 1, '`' => 1, '!' => 1, '(' => 1, ')' => 1, '{' => 1, '}' => 1, '^' => 1, '#' => 1, '&' => 1, * => 0 }; dossetup() { nowt = daytime->now(); nowt1 = sys->millisec(); tzoff = daytime->local(0).tzoff; } # make xf into a Dos file system... or die trying to. dosfs(xf: ref Xfs): int { mbroffset := 0; i: int; p: ref Iosect; Dmddo: for(;;) { for(i=2; i>0; i--) { p = getsect(xf, 0); if(p == nil) return -1; if((mbroffset == 0) && (p.iobuf[0] == byte 16re9)) break; # Check if the jump displacement (magic[1]) is too # short for a FAT. DOS 4.0 MBR has a displacement of 8. if(p.iobuf[0] == byte 16reb && p.iobuf[2] == byte 16r90 && p.iobuf[1] != byte 16r08) break; if(i < 2 || p.iobuf[16r1fe] != byte 16r55 || p.iobuf[16r1ff] != byte 16raa) { i = 0; break; } dp := 16r1be; for(j:=4; j>0; j--) { if(debug) { chat(sys->sprint("16r%2.2ux (%d,%d) 16r%2.2ux (%d,%d) %d %d...", int p.iobuf[dp], int p.iobuf[dp+1], bytes2short(p.iobuf[dp+2: dp+4]), int p.iobuf[dp+4], int p.iobuf[dp+5], bytes2short(p.iobuf[dp+6: dp+8]), bytes2int(p.iobuf[dp+8: dp+12]), bytes2int(p.iobuf[dp+12:dp+16]))); } # Check for a disc-manager partition in the MBR. # Real MBR is at lba 63. Unfortunately it starts # with 16rE9, hence the check above against magic. if(int p.iobuf[dp+4] == DMDDO) { mbroffset = 63*Sectorsize; putsect(p); purgebuf(xf); xf.offset += mbroffset; break Dmddo; } # Make sure it really is the right type, other # filesystems can look like a FAT # (e.g. OS/2 BOOT MANAGER). if(p.iobuf[dp+4] == FAT12 || p.iobuf[dp+4] == FAT16 || p.iobuf[dp+4] == FATHUGE) break; dp+=16; } if(j <= 0) { if(debug) chat("no active partition..."); putsect(p); return -1; } offset := bytes2int(p.iobuf[dp+8:dp+12])* Sectorsize; putsect(p); purgebuf(xf); xf.offset = mbroffset+offset; } break; } if(i <= 0) { if(debug) chat("bad magic..."); putsect(p); return -1; } b := Dosboot.arr2Db(p.iobuf); if(debug & FAT_INFO) bootdump(b); bp := ref Dosbpb; xf.ptr = bp; xf.fmt = 1; bp.sectsize = bytes2short(b.sectsize); bp.clustsize = int b.clustsize; bp.nresrv = bytes2short(b.nresrv); bp.nfats = int b.nfats; bp.rootsize = bytes2short(b.rootsize); bp.volsize = bytes2short(b.volsize); if(bp.volsize == 0) bp.volsize = bytes2int(b.bigvolsize); bp.mediadesc = int b.mediadesc; bp.fatsize = bytes2short(b.fatsize); bp.fataddr = int bp.nresrv; bp.rootaddr = bp.fataddr + bp.nfats*bp.fatsize; i = bp.rootsize*DOSDIRSIZE + bp.sectsize-1; i /= bp.sectsize; bp.dataaddr = bp.rootaddr + i; bp.fatclusters = FATRESRV+(bp.volsize - bp.dataaddr)/bp.clustsize; if(bp.fatclusters < 4087) bp.fatbits = 12; else bp.fatbits = 16; bp.freeptr = 2; if(debug & FAT_INFO){ chat(sys->sprint("fatbits=%d (%d clusters)...", bp.fatbits, bp.fatclusters)); for(i=0; i< int b.nfats; i++) chat(sys->sprint("fat %d: %d...", i, bp.fataddr+i*bp.fatsize)); chat(sys->sprint("root: %d...", bp.rootaddr)); chat(sys->sprint("data: %d...", bp.dataaddr)); } putsect(p); return 0; } QIDPATH(dp: ref Dosptr): big { return big (dp.addr*(Sectorsize/DOSDIRSIZE) + dp.offset/DOSDIRSIZE); } isroot(addr: int): int { return addr == 0; } getfile(f: ref Xfile): int { dp := f.ptr; if(dp.p!=nil) panic("getfile"); if(dp.addr < 0) panic("getfile address"); p := getsect(f.xf, dp.addr); if(p == nil) return -1; dp.d = nil; if(!isroot(dp.addr)) { if(f.qid.path != QIDPATH(dp)){ if(debug) { chat(sys->sprint("qid mismatch f=0x%x d=0x%x...", int f.qid.path, int QIDPATH(dp))); } putsect(p); return -1; } # dp.d = Dosdir.arr2Dd(p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); } dp.p = p; return 0; } putfile(f: ref Xfile) { dp := f.ptr; if(dp.p==nil) panic("putfile"); putsect(dp.p); dp.p = nil; dp.d = nil; } getstart(nil: ref Xfs, d: ref Dosdir): int { start := bytes2short(d.start); # if(xf.isfat32) # start |= bytes2short(d.hstart)<<16; return start; } putstart(nil: ref Xfs, d: ref Dosdir, start: int) { d.start[0] = byte start; d.start[1] = byte (start>>8); # if(xf.isfat32){ # d.hstart[0] = start>>16; # d.hstart[1] = start>>24; # } } # # return the disk cluster for the iclust cluster in f # fileclust(f: ref Xfile, iclust: int, cflag: int): int { # bp := f.xf.ptr; dp := f.ptr; if(isroot(dp.addr)) return -1; # root directory for old FAT format does not start on a cluster boundary d := dp.d; if(d == nil){ if(dp.p == nil) panic("fileclust"); d = Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); } next := 0; start := getstart(f.xf, d); if(start == 0) { if(!cflag) return -1; start = falloc(f.xf); if(start <= 0) return -1; puttime(d); putstart(f.xf, d, start); dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d); dp.p.flags |= BMOD; dp.clust = 0; } clust, nskip: int; if(dp.clust == 0 || iclust < dp.iclust) { clust = start; nskip = iclust; } else { clust = dp.clust; nskip = iclust - dp.iclust; } if(debug & CLUSTER_INFO && nskip > 0) chat(sys->sprint("clust %d, skip %d...", clust, nskip)); if(clust <= 0) return -1; if(nskip > 0) { while(--nskip >= 0) { next = getfat(f.xf, clust); if(debug & CLUSTER_INFO) chat(sys->sprint(".%d", next)); if(next <= 0){ if(!cflag) break; next = falloc(f.xf); if(next <= 0) return -1; putfat(f.xf, clust, next); } clust = next; } if(next <= 0) return -1; dp.clust = clust; dp.iclust = iclust; } if(debug & CLUSTER_INFO) chat(sys->sprint(" clust(%d)=0x%x...", iclust, clust)); return clust; } # # return the disk sector for the isect disk sector in f, # allocating space if necessary and cflag is set # fileaddr(f: ref Xfile, isect: int, cflag: int): int { bp := f.xf.ptr; dp := f.ptr; if(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; } # # look for a directory entry matching name # always searches for long names which match a short name # # if creating (cflag is set), set address of available slot and allocate next cluster if necessary # searchdir(f: ref Xfile, name: string, cflag: int, lflag: int): (int, ref Dosptr) { xf := f.xf; bp := xf.ptr; addr1 := -1; addr2 := -1; prevaddr1 := -1; o1 := 0; dp := ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil); # prevaddr and naddr are -1 dp.paddr = f.ptr.addr; dp.poffset = f.ptr.offset; islong :=0; buf := ""; need := 1; if(lflag && cflag) need += name2de(name); if(!lflag) { name = name[0:8]+"."+name[8:11]; i := len name -1; while(i >= 0 && (name[i]==' ' || name[i] == '.')) i--; name = name[0:i+1]; } addr := -1; prevaddr: int; have := 0; for(isect:=0;; isect++) { prevaddr = addr; addr = fileaddr(f, isect, cflag); if(addr < 0) break; p := getsect(xf, addr); if(p == nil) break; for(o:=0; osprint("allocate addr1=%d,%d addr2=%d for %s nleft=%d have=%d need=%d", addr1, o1, addr2, name, nleft, have, need)); dp.addr = addr1; dp.offset = o1; dp.prevaddr = prevaddr1; dp.naddr = addr2; return (0, dp); } if(dname0 == byte DOSEMPTY) { if(debug) chat("empty..."); 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; if(0 && lflag && debug) dirdump(p.iobuf[o:o+DOSDIRSIZE],addr,o); if((dattr & DMLONG) == DLONG) { if(!islong) buf = ""; islong = 1; buf = getnamesect(p.iobuf[o:o+DOSDIRSIZE]) + buf; # getnamesect should return sum continue; } if(dattr & DVLABEL) { islong = 0; continue; } if(!islong || !lflag) buf = getname(p.iobuf[o:o+DOSDIRSIZE]); islong = 0; if(debug) chat(sys->sprint("cmp: [%s] [%s]", buf, name)); if(mystrcmp(buf, name) != 0) { buf=""; continue; } if(debug) chat("found\n"); if(cflag) { putsect(p); return (-1,nil); } dp.addr = addr; dp.prevaddr = prevaddr; dp.offset = o; dp.p = p; #dp.d = Dosdir.arr2Dd(p.iobuf[o:o+DOSDIRSIZE]); return (0, dp); } putsect(p); } if(debug) chat("end dir(1)..."); if(!cflag) return (-1, nil); # # end of root directory or end of non-root directory on cluster boundary # if(addr1 < 0){ addr1 = fileaddr(f, isect, 1); if(addr1 < 0) return (-2, nil); prevaddr1 = prevaddr; o1 = 0; }else{ if(addr2 < 0 && have < need){ addr2 = fileaddr(f, isect, 1); if(addr2 < 0) return (-2, nil); } } if(addr2 == addr1) addr2 = -1; dp.addr = addr1; dp.offset = o1; dp.prevaddr = prevaddr1; dp.naddr = addr2; return (0, dp); } emptydir(f: ref Xfile): int { for(isect:=0;; isect++) { addr := fileaddr(f, isect, 0); if(addr < 0) break; p := getsect(f.xf, addr); if(p == nil) return -1; for(o:=0; oMAXFDATA] of byte; islong :=0; longnamebuf:=""; if(count <= 0) return (0, nil); Read: for(isect:=0;; isect++) { addr := fileaddr(f, isect, 0); if(addr < 0) break; p := getsect(xf, addr); if(p == nil) return (-1,nil); for(o:=0; opackdir(*dir); if(offset > 0) { offset -= len d; islong = 0; continue; } if(rcnt+len d > count){ putsect(p); break Read; } buf[rcnt:] = d; rcnt += len d; if(rcnt >= count) { putsect(p); break Read; } } putsect(p); } return (rcnt, buf[0:rcnt]); } walkup(f: ref Xfile): (int, ref Dosptr) { bp := f.xf.ptr; dp := f.ptr; o: int; ndp:= ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil); ndp.addr = dp.paddr; ndp.offset = dp.poffset; if(debug) chat(sys->sprint("walkup: paddr=0x%x...", dp.paddr)); if(dp.paddr == 0) return (0,ndp); p := getsect(f.xf, dp.paddr); if(p == nil) return (-1,nil); if(debug) dirdump(p.iobuf[dp.poffset:dp.poffset+DOSDIRSIZE],dp.paddr,dp.poffset); xd := Dosdir.arr2Dd(p.iobuf[dp.poffset:dp.poffset+DOSDIRSIZE]); start := getstart(f.xf, xd); if(debug & CLUSTER_INFO) if(debug) chat(sys->sprint("start=0x%x...", start)); putsect(p); if(start == 0) return (-1,nil); # # check that parent's . points to itself # p = getsect(f.xf, bp.dataaddr + (start-2)*bp.clustsize); if(p == nil) return (-1,nil); if(debug) dirdump(p.iobuf,0,0); xd = Dosdir.arr2Dd(p.iobuf); if(p.iobuf[0]!= byte '.' || p.iobuf[1]!= byte ' ' || start != getstart(f.xf, xd)) { if(p!=nil) putsect(p); return (-1,nil); } if(debug) dirdump(p.iobuf[DOSDIRSIZE:],0,0); # # parent's .. is the next entry, and has start of parent's parent # xd = Dosdir.arr2Dd(p.iobuf[DOSDIRSIZE:]); if(p.iobuf[32] != byte '.' || p.iobuf[33] != byte '.') { if(p != nil) putsect(p); return (-1,nil); } # # we're done if parent is root # pstart := getstart(f.xf, xd); putsect(p); if(pstart == 0) return (0, ndp); # # check that parent's . points to itself # p = getsect(f.xf, clust2sect(bp, pstart)); if(p == nil) { if(debug) chat(sys->sprint("getsect %d failed\n", pstart)); return (-1,nil); } if(debug) dirdump(p.iobuf,0,0); xd = Dosdir.arr2Dd(p.iobuf); if(p.iobuf[0]!= byte '.' || p.iobuf[1]!=byte ' ' || pstart!=getstart(f.xf, xd)) { if(p != nil) putsect(p); return (-1,nil); } # # parent's parent's .. is the next entry, and has start of parent's parent's parent # if(debug) dirdump(p.iobuf[DOSDIRSIZE:],0,0); xd = Dosdir.arr2Dd(p.iobuf[DOSDIRSIZE:]); if(xd.name[0] != '.' || xd.name[1] != '.') { if(p != nil) putsect(p); return (-1,nil); } ppstart :=getstart(f.xf, xd); putsect(p); # # open parent's parent's parent, and walk through it until parent's paretn is found # need this to find parent's parent's addr and offset # ppclust := ppstart; # TO DO: FAT32 if(ppclust != 0) k := clust2sect(bp, ppclust); else k = bp.rootaddr; p = getsect(f.xf, k); if(p == nil) { if(debug) chat(sys->sprint("getsect %d failed\n", k)); return (-1,nil); } if(debug) dirdump(p.iobuf,0,0); if(ppstart) { xd = Dosdir.arr2Dd(p.iobuf); if(p.iobuf[0]!= byte '.' || p.iobuf[1]!= byte ' ' || ppstart!=getstart(f.xf, xd)) { if(p!=nil) putsect(p); return (-1,nil); } } for(so:=1; ;so++) { for(o=0; osprint("getfat %d fail\n", ppstart)); if(p != nil) putsect(p); return (-1,nil); } } k = clust2sect(bp, ppclust) + so%bp.clustsize; } else { if(so*bp.sectsize >= bp.rootsize*DOSDIRSIZE) { if(p != nil) putsect(p); return (-1,nil); } k = bp.rootaddr + so; } putsect(p); p = getsect(f.xf, k); if(p == nil) { if(debug) chat(sys->sprint("getsect %d failed\n", k)); return (-1,nil); } } putsect(p); ndp.paddr = k; ndp.poffset = o; return (0,ndp); } readfile(f: ref Xfile, offset: int, count: int): (int, array of byte) { xf := f.xf; bp := xf.ptr; dp := f.ptr; length := bytes2int(dp.p.iobuf[dp.offset+28:dp.offset+32]); rcnt := 0; if(offset >= length) return (0,nil); buf := array[Styx->MAXFDATA] of byte; 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 == nil) return (-1, nil); buf[rcnt:] = p.iobuf[o:o+c]; putsect(p); count -= c; rcnt += c; o = 0; } return (rcnt, buf[0:rcnt]); } writefile(f: ref Xfile, buf: array of byte, offset,count: int): int { xf := f.xf; bp := xf.ptr; dp := f.ptr; addr := 0; c: int; rcnt := 0; p: ref Iosect; d := dp.d; if(d == nil) d = Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); 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); if(p == nil) return -1; p.flags = 0; }else{ p = getsect(xf, addr); if(p == nil) return -1; } p.iobuf[o:] = buf[rcnt:rcnt+c]; p.flags |= BMOD; putsect(p); count -= c; rcnt += c; o = 0; } if(rcnt <= 0 && addr < 0) return -2; length := 0; dlen := bytes2int(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) { d.length[0] = byte length; d.length[1] = byte (length>>8); d.length[2] = byte (length>>16); d.length[3] = byte (length>>24); } puttime(d); dp.p.flags |= BMOD; dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d); return rcnt; } truncfile(f: ref Xfile): int { xf := f.xf; # bp := xf.ptr; dp := f.ptr; d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); clust := getstart(f.xf, d); putstart(f.xf, d, 0); while(clust > 0) { next := getfat(xf, clust); putfat(xf, clust, 0); clust = next; } d.length[0] = byte 0; d.length[1] = byte 0; d.length[2] = byte 0; d.length[3] = byte 0; dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d); dp.iclust = 0; dp.clust = 0; dp.p.flags |= BMOD; return 0; } getdir(arr: array of byte, addr,offset: int) :ref Sys->Dir { dp := ref Sys->Dir; if(arr == nil || addr == 0) { dp.name = ""; dp.qid.path = big 0; dp.qid.qtype = Sys->QTDIR; dp.length = big 0; dp.mode = Sys->DMDIR|8r777; } else { dp.name = getname(arr); for(i:=0; i < len dp.name; i++) if(dp.name[i]>='A' && dp.name[i]<='Z') dp.name[i] = dp.name[i]-'A'+'a'; # dp.qid.path = bytes2short(d.start); dp.qid.path = big (addr*(Sectorsize/DOSDIRSIZE) + offset/DOSDIRSIZE); dattr := int arr[11]; if(dattr & DRONLY) dp.mode = 8r444; else dp.mode = 8r666; dp.atime = gtime(arr); dp.mtime = dp.atime; if(dattr & DDIR) { dp.length = big 0; dp.qid.qtype |= Styx->QTDIR; dp.mode |= Sys->DMDIR|8r111; } else dp.length = big bytes2int(arr[28:32]); if(dattr & DSYSTEM){ dp.mode |= Styx->DMEXCL; dp.qid.qtype |= Styx->QTEXCL; } } dp.qid.vers = 0; dp.dtype = 0; dp.dev = 0; dp.uid = "dos"; dp.gid = "srv"; return dp; } putdir(d: ref Dosdir, dp: ref Sys->Dir) { if(dp.mode & 2) d.attr &= byte ~DRONLY; else d.attr |= byte DRONLY; if(dp.mode & Styx->DMEXCL) d.attr |= byte DSYSTEM; else d.attr &= byte ~DSYSTEM; xputtime(d, dp.mtime); } getname(arr: array of byte): string { p: string; for(i:=0; i<8; i++) { c := int arr[i]; if(c == 0 || c == ' ') break; if(i == 0 && c == 16r05) c = 16re5; p[len p] = c; } for(i=8; i<11; i++) { c := int arr[i]; if(c == 0 || c == ' ') break; if(i == 8) p[len p] = '.'; p[len p] = c; } return p; } dosname(p: string): (string, string) { name := " "; for(i := 0; i < len p && i < 8; i++) { c := p[i]; if(c >= 'a' && c <= 'z') c += 'A'-'a'; else if(c == '.') break; name[i] = c; } ext := " "; for(j := len p - 1; j >= i; j--) { if(p[j] == '.') { q := 0; for(j++; j < len p && q < 3; j++) { c := p[j]; if(c >= 'a' && c <= 'z') c += 'A'-'a'; ext[q++] = c; } break; } } return (name, ext); } putname(p: string, d: ref Dosdir) { if ((int d.attr & DLONG) == DLONG) panic("putname of long name"); (d.name, d.ext) = dosname(p); } mystrcmp(s1, s2: string): int { n := len s1; if(n != len s2) return 1; for(i := 0; i < n; i++) { c := s1[i]; if(c >= 'A' && c <= 'Z') c -= 'A'-'a'; d := s2[i]; if(d >= 'A' && d <= 'Z') d -= 'A'-'a'; if(c != d) return 1; } return 0; } # # return the length of a long name in directory # entries or zero if it's normal dos # name2de(p: string): int { ext := 0; name := 0; for(end := len p; --end >= 0 && p[end] != '.';) ext++; if(end > 0) { name = end; for(i := 0; i < end; i++) { if(p[i] == '.') return (len p+DOSRUNES-1)/DOSRUNES; } } else { name = ext; ext = 0; } if(name <= 8 && ext <= 3 && isvalidname(p)) return 0; return (len p+DOSRUNES-1)/DOSRUNES; } isvalidname(s: string): int { dot := 0; for(i := 0; i < len s; i++) if(s[i] == '.') { if(++dot > 1 || i == len s-1) return 0; } else if(s[i] > len isdos || isdos[s[i]] == 0) return 0; return 1; } getnamesect(arr: array of byte): string { s: string; c: int; for(i := 1; i < 11; i += 2) { c = int arr[i] | (int arr[i+1] << 8); if(c == 0) return s; s[len s] = c; } for(i = 14; i < 26; i += 2) { c = int arr[i] | (int arr[i+1] << 8); if(c == 0) return s; s[len s] = c; } for(i = 28; i < 32; i += 2) { c = int arr[i] | (int arr[i+1] << 8); if(c == 0) return s; s[len s] = c; } return s; } # takes a long filename and converts to a short dos name, with a tag number. long2short(src: string,val: int): string { dst :=" "; skip:=0; xskip:=0; ext:=len src-1; while(ext>=0 && src[ext]!='.') ext--; if (ext < 0) ext=len src -1; # convert name eliding periods j:=0; for(name := 0; name < ext && j<8; name++){ c := src[name]; if(c!='.' && c!=' ' && c!='\t') { if(c>='a' && c<='z') dst[j++] = c-'a'+'A'; else dst[j++] = c; } else skip++; } # convert extension j=8; for(xname := ext+1; xname < len src && j<11; xname++) { c := src[xname]; if(c!=' ' && c!='\t'){ if (c>='a' && c<='z') dst[j++] = c-'a'+'A'; else dst[j++] = c; }else xskip++; } # add tag number j =1; for(i:=val; i > 0; i/=10) j++; if (8-j 0; val /= 10) dst[name+ --j] = (val%10)+'0'; if(debug) chat(sys->sprint("returning dst [%s] src [%s]\n",dst,src)); return dst; } getfat(xf: ref Xfs, n: int): int { bp := xf.ptr; k := 0; if(n < 2 || n >= bp.fatclusters) return -1; fb := bp.fatbits; k = (fb*n) >> 3; if(k < 0 || 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 = int p.iobuf[o++]; if(o >= bp.sectsize) { putsect(p); p = getsect(xf, sect+1); if(p == nil) return -1; o = 0; } k |= int p.iobuf[o++]<<8; if(fb == 32){ # fat32 is really fat28 k |= int p.iobuf[o++] << 16; k |= (int p.iobuf[o] & 16r0F) << 24; fb = 28; } putsect(p); if(fb == 12) { if(n&1) k >>= 4; else k &= 16rfff; } if(debug & FAT_INFO) chat(sys->sprint("fat(0x%x)=0x%x...", n, k)); # # check for out of range # if(k >= (1<= bp.fatclusters) panic(sys->sprint("putfat n=%d", n)); k := (bp.fatbits*n) >> 3; if(k >= bp.fatsize*bp.sectsize) panic("putfat"); sect := k/bp.sectsize + bp.fataddr; for(; sect if(n&1) { p.iobuf[o] &= byte 16r0f; p.iobuf[o++] |= byte (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] = byte (val>>4); } else { p.iobuf[o++] = byte val; if(o >= bp.sectsize) { p.flags |= BMOD; putsect(p); p = getsect(xf, sect+1); if(p == nil) continue; o = 0; } p.iobuf[o] &= byte 16rf0; p.iobuf[o] |= byte ((val>>8)&16r0f); } 16 => p.iobuf[o++] = byte val; p.iobuf[o] = byte (val>>8); 32 => # fat32 is really fat28 p.iobuf[o++] = byte val; p.iobuf[o++] = byte (val>>8); p.iobuf[o++] = byte (val>>16); p.iobuf[o] = byte ((int p.iobuf[o] & 16rF0) | ((val>>24) & 16r0F)); * => panic("putfat fatbits"); } p.flags |= BMOD; putsect(p); } } falloc(xf: ref Xfs): int { bp := xf.ptr; n := bp.freeptr; for(;;) { if(getfat(xf, n) == 0) break; if(++n >= bp.fatclusters) n = FATRESRV; if(n == bp.freeptr) return 0; } bp.freeptr = n+1; if(bp.freeptr >= bp.fatclusters) bp.freeptr = FATRESRV; putfat(xf, n, int 16rffffffff); k := clust2sect(bp, n); for(i:=0; isprint("magic: 0x%2.2x 0x%2.2x 0x%2.2x\n", int b.magic[0], int b.magic[1], int b.magic[2])); chat(sys->sprint("version: \"%8.8s\"\n", string b.version)); chat(sys->sprint("sectsize: %d\n", bytes2short(b.sectsize))); chat(sys->sprint("allocsize: %d\n", int b.clustsize)); chat(sys->sprint("nresrv: %d\n", bytes2short(b.nresrv))); chat(sys->sprint("nfats: %d\n", int b.nfats)); chat(sys->sprint("rootsize: %d\n", bytes2short(b.rootsize))); chat(sys->sprint("volsize: %d\n", bytes2short(b.volsize))); chat(sys->sprint("mediadesc: 0x%2.2x\n", int b.mediadesc)); chat(sys->sprint("fatsize: %d\n", bytes2short(b.fatsize))); chat(sys->sprint("trksize: %d\n", bytes2short(b.trksize))); chat(sys->sprint("nheads: %d\n", bytes2short(b.nheads))); chat(sys->sprint("nhidden: %d\n", bytes2int(b.nhidden))); chat(sys->sprint("bigvolsize: %d\n", bytes2int(b.bigvolsize))); chat(sys->sprint("driveno: %d\n", int b.driveno)); chat(sys->sprint("bootsig: 0x%2.2x\n", int b.bootsig)); chat(sys->sprint("volid: 0x%8.8x\n", bytes2int(b.volid))); chat(sys->sprint("label: \"%11.11s\"\n", string b.label)); } xputtime(d: ref Dosdir, s: int) { if(s == 0) t := daytime->local((sys->millisec() - nowt1)/1000 + nowt); else t = daytime->local(s); x := (t.hour<<11) | (t.min<<5) | (t.sec>>1); d.time[0] = byte x; d.time[1] = byte (x>>8); x = ((t.year-80)<<9) | ((t.mon+1)<<5) | t.mday; d.date[0] = byte x; d.date[1] = byte (x>>8); } puttime(d: ref Dosdir) { xputtime(d, 0); } gtime(a: array of byte): int { tm := ref Daytime->Tm; i := bytes2short(a[22:24]); # dos time tm.hour = i >> 11; tm.min = (i>>5) & 63; tm.sec = (i & 31) << 1; i = bytes2short(a[24:26]); # dos date tm.year = 80 + (i>>9); tm.mon = ((i>>5) & 15) - 1; tm.mday = i & 31; tm.tzoff = tzoff; # DOS time is local time return daytime->tm2epoch(tm); } dirdump(arr: array of byte, addr, offset: int) { if(!debug) return; attrchar:= "rhsvda67"; d := Dosdir.arr2Dd(arr); buf := sys->sprint("\"%.8s.%.3s\" ", d.name, d.ext); p_i:=7; for(i := 16r80; i != 0; i >>= 1) { if((d.attr & byte i) == byte i) ch := attrchar[p_i]; else ch = '-'; buf += sys->sprint("%c", ch); p_i--; } i = bytes2short(d.time); buf += sys->sprint(" %2.2d:%2.2d:%2.2d", i>>11, (i>>5)&63, (i&31)<<1); i = bytes2short(d.date); buf += sys->sprint(" %2.2d.%2.2d.%2.2d", 80+(i>>9), (i>>5)&15, i&31); buf += sys->sprint(" %d %d", bytes2short(d.start), bytes2short(d.length)); buf += sys->sprint(" %d %d\n",addr,offset); chat(buf); } putnamesect(longname: string, curslot: int, first: int, sum: int, a: array of byte) { for(i := 0; i < DOSDIRSIZE; i++) a[i] = byte 16rFF; if(first) a[0] = byte (16r40 | curslot); else a[0] = byte curslot; a[11] = byte DLONG; a[12] = byte 0; a[13] = byte sum; a[26] = byte 0; a[27] = byte 0; # a[1:1+10] = characters 1 to 5 n := len longname; j := (curslot-1)*DOSRUNES; for(i = 1; i < 1+10; i += 2){ c := 0; if(j < n) c = longname[j++]; a[i] = byte c; a[i+1] = byte (c >> 8); if(c == 0) return; } # a[14:14+12] = characters 6 to 11 for(i = 14; i < 14+12; i += 2){ c := 0; if(j < n) c = longname[j++]; a[i] = byte c; a[i+1] = byte (c >> 8); if(c == 0) return; } # a[28:28+4] characters 12 to 13 for(i = 28; i < 28+4; i += 2){ c := 0; if(j < n) c = longname[j++]; a[i] = byte c; a[i+1] = byte (c>>8); if(c == 0) return; } } putlongname(xf: ref Xfs, ndp: ref Dosptr, name: string, sname: string): int { bp := xf.ptr; first := 1; sum := aliassum(sname); for(nds := (len name+DOSRUNES-1)/DOSRUNES; nds > 0; nds--) { putnamesect(name, nds, first, sum, ndp.p.iobuf[ndp.offset:]); first = 0; ndp.offset += DOSDIRSIZE; if(ndp.offset == bp.sectsize) { if(debug) chat(sys->sprint("long name %s entry %d/%d crossing sector, addr=%d, naddr=%d", name, nds, (len name+DOSRUNES-1)/DOSRUNES, ndp.addr, ndp.naddr)); ndp.p.flags |= BMOD; putsect(ndp.p); ndp.p = nil; ndp.d = nil; # switch to the next cluster for the next long entry or the subsequent normal dir. entry # naddr must be set up correctly by searchdir because we'll need one or the other ndp.prevaddr = ndp.addr; ndp.addr = ndp.naddr; ndp.naddr = -1; if(ndp.addr < 0) return -1; ndp.p = getsect(xf, ndp.addr); if(ndp.p == nil) return -1; ndp.offset = 0; } } return 0; } bytes2int(a: array of byte): int { return (((((int a[3] << 8) | int a[2]) << 8) | int a[1]) << 8) | int a[0]; } bytes2short(a: array of byte): int { return (int a[1] << 8) | int a[0]; } chat(s: string) { if(debug) sys->fprint(sys->fildes(2), "%s", s); } panic(s: string) { sys->fprint(sys->fildes(2), "dosfs: panic: %s\n", s); if(pflag) <-chan of int; # hang here raise "fail:panic"; } Dosboot.arr2Db(arr: array of byte): ref Dosboot { db := ref Dosboot; db.magic = arr[0:3]; db.version = arr[3:11]; db.sectsize = arr[11:13]; db.clustsize = arr[13]; db.nresrv = arr[14:16]; db.nfats = arr[16]; db.rootsize = arr[17:19]; db.volsize = arr[19:21]; db.mediadesc = arr[21]; db.fatsize = arr[22:24]; db.trksize = arr[24:26]; db.nheads = arr[26:28]; db.nhidden = arr[28:32]; db.bigvolsize = arr[32:36]; db.driveno = arr[36]; db.bootsig = arr[38]; db.volid = arr[39:43]; db.label = arr[43:54]; return db; } Dosdir.arr2Dd(arr: array of byte): ref Dosdir { dir := ref Dosdir; for(i := 0; i < 8; i++) dir.name[len dir.name] = int arr[i]; for(; i < 11; i++) dir.ext[len dir.ext] = int arr[i]; dir.attr = arr[11]; dir.reserved = arr[12:22]; dir.time = arr[22:24]; dir.date = arr[24:26]; dir.start = arr[26:28]; dir.length = arr[28:32]; return dir; } Dosdir.Dd2arr(d: ref Dosdir): array of byte { a := array[32] of byte; i:=0; for(j := 0; j < len d.name; j++) a[i++] = byte d.name[j]; for(; j<8; j++) a[i++]= byte 0; for(j=0; j>1))+sname[i]; return sum; } # # track i/o # # An Xfs represents the root of an external file system, anchored # to the server and the client Xfs: adt { next:cyclic ref Xfs; name: string; # of file containing external f.s. qid: Sys->Qid; # of file containing external f.s. refn: int; # attach count rootqid: Sys->Qid; # of inferno constructed root directory dev: ref Sys->FD; # FD of the file containing external f.s. fmt: int; # successfully read format offset: int; # offset in sectors to file system ptr: ref Dosbpb; }; # An Xfile represents the mapping of fid's & qid's to the server. Xfile: adt { next: cyclic ref Xfile; # in hash bucket client: int; fid: int; flags: int; qid: Sys->Qid; xf: ref Xfs; ptr: ref Dosptr; }; Iosect: adt { next: cyclic ref Iosect; flags: int; t: cyclic ref Iotrack; iobuf: array of byte; }; Iotrack: adt { flags: int; xf: ref Xfs; addr: int; next: cyclic ref Iotrack; # in lru list prev: cyclic ref Iotrack; hnext: cyclic ref Iotrack; # in hash list hprev: cyclic ref Iotrack; refn: int; tp: cyclic ref Track; }; Track: adt { create: fn(): ref Track; p: cyclic array of ref Iosect; buf: array of byte; }; BMOD: con 1<<0; BIMM: con 1<<1; BSTALE: con 1<<2; HIOB: con 31; # a prime NIOBUF: con 20; Sectorsize: con 512; Sect2trk: con 9; # default hiob := array[HIOB+1] of ref Iotrack; # hash buckets + lru list iobuf := array[NIOBUF] of ref Iotrack; # the real ones freelist: ref Iosect; sect2trk := Sect2trk; trksize := Sect2trk*Sectorsize; FIDMOD: con 127; # prime xhead: ref Xfs; client: int; xfiles := array[FIDMOD] of ref Xfile; iodebug := 0; iotrackinit(sectors: int) { if(sectors <= 0) sectors = 9; sect2trk = sectors; trksize = sect2trk*Sectorsize; freelist = nil; for(i := 0;i < FIDMOD; i++) xfiles[i] = ref Xfile(nil,0,0,0,Sys->Qid(big 0,0,0),nil,nil); for(i = 0; i <= HIOB; i++) hiob[i] = ref Iotrack; for(i = 0; i < HIOB; i++) { hiob[i].hprev = hiob[i]; hiob[i].hnext = hiob[i]; hiob[i].refn = 0; hiob[i].addr = 0; } hiob[i].prev = hiob[i]; hiob[i].next = hiob[i]; hiob[i].refn = 0; hiob[i].addr = 0; for(i=0;isprint("iotrack %d,%d...", xf.dev.fd, addr)); # find bucket in hash table. h := (xf.dev.fd<<24) ^ addr; if(h < 0) h = ~h; h %= HIOB; hp := hiob[h]; out: for(;;){ loop: for(;;) { # look for it in the active list for(p = hp.hnext; p != hp; p=p.hnext) { if(p.addr != addr || p.xf != xf) continue; if(p.addr == addr && p.xf == xf) { break out; } continue loop; } # not found # take oldest unref'd entry for(p = mp.prev; p != mp; p=p.prev) if(p.refn == 0 ) break; if(p == mp) { if(iodebug) chat("iotrack all ref'd\n"); continue loop; } if((p.flags & BMOD)!= 0) { twrite(p); p.flags &= ~(BMOD|BIMM); continue loop; } purgetrack(p); p.addr = addr; p.xf = xf; p.flags = BSTALE; break out; } } if(hp.hnext != p) { p.hprev.hnext = p.hnext; p.hnext.hprev = p.hprev; p.hnext = hp.hnext; p.hprev = hp; hp.hnext.hprev = p; hp.hnext = p; } if(mp.next != p) { p.prev.next = p.next; p.next.prev = p.prev; p.next = mp.next; p.prev = mp; mp.next.prev = p; mp.next = p; } return p; } purgetrack(t: ref Iotrack) { refn := sect2trk; for(i := 0; i < sect2trk; i++) { if(t.tp.p[i] == nil) { --refn; continue; } freesect(t.tp.p[i]); --refn; t.tp.p[i]=nil; } if(t.refn != refn) panic("purgetrack"); if(refn!=0) panic("refn not 0"); } twrite(t: ref Iotrack): int { if(iodebug) chat(sys->sprint("[twrite %d...", t.addr)); if((t.flags & BSTALE)!= 0) { refn:=0; for(i:=0; isprint("[tread %d...", t.addr)); tbuf := t.tp.buf; if(refn != 0) tbuf = array[trksize] of byte; rval = devread(t.xf, t.addr, tbuf); if(rval < 0) { if(iodebug) chat("error]"); return -1; } if(refn != 0) { for(i=0; i < sect2trk; i++) { if(t.tp.p[i] == nil) { t.tp.buf[i*Sectorsize:]=tbuf[i*Sectorsize:(i+1)*Sectorsize]; if(iodebug) chat(sys->sprint("%d ", i)); } } } if(iodebug) chat("done]"); t.flags &= ~BSTALE; return 0; } purgebuf(xf: ref Xfs) { for(p := 0; p < NIOBUF; p++) { if(iobuf[p].xf != xf) continue; if(iobuf[p].xf == xf) { if((iobuf[p].flags & BMOD) != 0) twrite(iobuf[p]); iobuf[p].flags = BSTALE; purgetrack(iobuf[p]); } } } sync() { for(p := 0; p < NIOBUF; p++) { if(!(iobuf[p].flags & BMOD)) continue; if(iobuf[p].flags & BMOD){ twrite(iobuf[p]); iobuf[p].flags &= ~(BMOD|BIMM); } } } newsect(): ref Iosect { if((p := freelist)!=nil) { freelist = p.next; p.next = nil; } else p = ref Iosect(nil, 0, nil,nil); return p; } freesect(p: ref Iosect) { p.next = freelist; freelist = p; } # devio from here deverror(name: string, xf: ref Xfs, addr,n,nret: int): int { if(nret < 0) { if(iodebug) chat(sys->sprint("%s errstr=\"%r\"...", name)); xf.dev = nil; return -1; } if(iodebug) chat(sys->sprint("dev %d sector %d, %s: %d, should be %d\n", xf.dev.fd, addr, name, nret, n)); panic(name); return -1; } devread(xf: ref Xfs, addr: int, buf: array of byte): int { if(xf.dev==nil) return -1; sys->seek(xf.dev, big (xf.offset+addr*Sectorsize), sys->SEEKSTART); nread := sys->read(xf.dev, buf, trksize); if(nread != trksize) return deverror("read", xf, addr, trksize, nread); return 0; } devwrite(xf: ref Xfs, addr: int, buf: array of byte): int { if(xf.dev == nil) return -1; sys->seek(xf.dev, big (xf.offset+addr*Sectorsize), 0); nwrite := sys->write(xf.dev, buf, trksize); if(nwrite != trksize) return deverror("write", xf, addr, trksize , nwrite); return 0; } devcheck(xf: ref Xfs): int { buf := array[Sectorsize] of byte; if(xf.dev == nil) return -1; sys->seek(xf.dev, big 0, sys->SEEKSTART); if(sys->read(xf.dev, buf, Sectorsize) != Sectorsize){ xf.dev = nil; return -1; } return 0; } # setup and return the Xfs associated with "name" getxfs(name: string): (ref Xfs, string) { if(name == nil) return (nil, "no file system device specified"); # 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 programm at the beginning of the disc. offset := 0; for(i := 0;i < len name; i++) if(name[i]==':') break; if(i < len name) { offset = int name[i+1:]; if(offset < 0) return (nil, "invalid device offset to file system"); offset *= Sectorsize; name = name[0:i]; } fd := sys->open(name, Sys->ORDWR); if(fd == nil) { if(iodebug) chat(sys->sprint("getxfs: open(%s) failed: %r\n", name)); return (nil, sys->sprint("can't open %s: %r", name)); } (rval,dir) := sys->fstat(fd); if(rval < 0) return (nil, sys->sprint("can't stat %s: %r", name)); # lock down the list of xf's. fxf: ref Xfs; for(xf := xhead; xf != nil; xf = xf.next) { if(xf.refn == 0) { if(fxf == nil) fxf = xf; continue; } if(xf.qid.path != dir.qid.path || xf.qid.vers != dir.qid.vers) continue; if(xf.name!= name || xf.dev == nil) continue; if(devcheck(xf) < 0) # look for media change continue; if(offset && xf.offset != offset) continue; if(iodebug) chat(sys->sprint("incref \"%s\", dev=%d...", xf.name, xf.dev.fd)); ++xf.refn; return (xf, nil); } # this xf doesn't exist, make a new one and stick it on the list. if(fxf == nil){ fxf = ref Xfs; fxf.next = xhead; xhead = fxf; } if(iodebug) chat(sys->sprint("alloc \"%s\", dev=%d...", name, fd.fd)); fxf.name = name; fxf.refn = 1; fxf.qid = dir.qid; fxf.dev = fd; fxf.fmt = 0; fxf.offset = offset; return (fxf, nil); } refxfs(xf: ref Xfs, delta: int) { xf.refn += delta; if(xf.refn == 0) { if (iodebug) chat(sys->sprint("free \"%s\", dev=%d...", xf.name, xf.dev.fd)); purgebuf(xf); if(xf.dev !=nil) xf.dev = nil; } } xfile(fid, flag: int): ref Xfile { pf: ref Xfile; # find hashed file list in LRU? table. k := (fid^client)%FIDMOD; # find if this fid is in the hashed file list. f:=xfiles[k]; for(pf = nil; f != nil; f = f.next) { if(f.fid == fid && f.client == client) break; pf=f; } # move this fid to the front of the list if it was further down. if(f != nil && pf != nil){ pf.next = f.next; f.next = xfiles[k]; xfiles[k] = f; } case flag { * => panic("xfile"); Asis => if(f != nil && f.xf != nil && f.xf.dev == nil) return nil; return f; Clean => break; Clunk => if(f != nil) { xfiles[k] = f.next; clean(f); } return nil; } # clean it up .. if(f != nil) return clean(f); # f wasn't found in the hashtable, make a new one and add it f = ref Xfile; f.next = xfiles[k]; xfiles[k] = f; # sort out the fid, etc. f.fid = fid; f.client = client; f.flags = 0; f.qid = Sys->Qid(big 0, 0, Styx->QTFILE); f.xf = nil; f.ptr = ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil); return f; } clean(f: ref Xfile): ref Xfile { f.ptr = nil; if(f.xf != nil) { refxfs(f.xf, -1); f.xf = nil; } f.flags = 0; f.qid = Sys->Qid(big 0, 0, 0); return f; } # # the file at has moved # relocate the dos entries of all fids in the same file # dosptrreloc(f: ref Xfile, dp: ref Dosptr, addr: int, offset: int) { i: int; p: ref Xfile; xdp: ref Dosptr; 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){ *xdp = *dp; xdp.p = nil; # xdp.d = nil; p.qid.path = big QIDPATH(xdp); } } } }