diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/disk | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/cmd/disk')
| -rw-r--r-- | appl/cmd/disk/format.b | 755 | ||||
| -rw-r--r-- | appl/cmd/disk/ftl.b | 911 | ||||
| -rw-r--r-- | appl/cmd/disk/kfs.b | 3842 | ||||
| -rw-r--r-- | appl/cmd/disk/kfscmd.b | 53 | ||||
| -rw-r--r-- | appl/cmd/disk/mbr.b | 134 | ||||
| -rw-r--r-- | appl/cmd/disk/mkext.b | 377 | ||||
| -rw-r--r-- | appl/cmd/disk/mkfile | 25 | ||||
| -rw-r--r-- | appl/cmd/disk/mkfs.b | 778 | ||||
| -rw-r--r-- | appl/cmd/disk/prep/calc.tab.b | 454 | ||||
| -rw-r--r-- | appl/cmd/disk/prep/calc.tab.m | 7 | ||||
| -rw-r--r-- | appl/cmd/disk/prep/calc.y | 174 | ||||
| -rw-r--r-- | appl/cmd/disk/prep/fdisk.b | 925 | ||||
| -rw-r--r-- | appl/cmd/disk/prep/mkfile | 26 | ||||
| -rw-r--r-- | appl/cmd/disk/prep/pedit.b | 504 | ||||
| -rw-r--r-- | appl/cmd/disk/prep/pedit.m | 53 | ||||
| -rw-r--r-- | appl/cmd/disk/prep/prep.b | 509 |
16 files changed, 9527 insertions, 0 deletions
diff --git a/appl/cmd/disk/format.b b/appl/cmd/disk/format.b new file mode 100644 index 00000000..80fee62c --- /dev/null +++ b/appl/cmd/disk/format.b @@ -0,0 +1,755 @@ +implement Format; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "daytime.m"; + daytime: Daytime; + +include "disks.m"; + disks: Disks; + Disk: import disks; + +include "arg.m"; + +Format: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +# +# floppy types (all MFM encoding) +# +Type: adt { + name: string; + bytes: int; # bytes/sector + sectors: int; # sectors/track + heads: int; # number of heads + tracks: int; # tracks/disk + media: int; # media descriptor byte + cluster: int; # default cluster size +}; + +floppytype := array[] of { + Type ( "3½HD", 512, 18, 2, 80, 16rf0, 1 ), + Type ( "3½DD", 512, 9, 2, 80, 16rf9, 2 ), + Type ( "3½QD", 512, 36, 2, 80, 16rf9, 2 ), # invented + Type ( "5¼HD", 512, 15, 2, 80, 16rf9, 1 ), + Type ( "5¼DD", 512, 9, 2, 40, 16rfd, 2 ), + Type ( "hard", 512, 0, 0, 0, 16rf8, 4 ), +}; + +# offsets in DOS boot area +DB_MAGIC : con 0; +DB_VERSION : con 3; +DB_SECTSIZE : con 11; +DB_CLUSTSIZE : con 13; +DB_NRESRV : con 14; +DB_NFATS : con 16; +DB_ROOTSIZE : con 17; +DB_VOLSIZE : con 19; +DB_MEDIADESC: con 21; +DB_FATSIZE : con 22; +DB_TRKSIZE : con 24; +DB_NHEADS : con 26; +DB_NHIDDEN : con 28; +DB_BIGVOLSIZE: con 32; +DB_DRIVENO : con 36; +DB_RESERVED0: con 37; +DB_BOOTSIG : con 38; +DB_VOLID : con 39; +DB_LABEL : con 43; +DB_TYPE : con 54; + +DB_VERSIONSIZE: con 8; +DB_LABELSIZE : con 11; +DB_TYPESIZE : con 8; +DB_SIZE : con 62; + +# offsets in DOS directory +DD_NAME : con 0; +DD_EXT : con 8; +DD_ATTR : con 11; +DD_RESERVED : con 12; +DD_TIME : con 22; +DD_DATE : con 24; +DD_START : con 26; +DD_LENGTH : con 28; + +DD_NAMESIZE : con 8; +DD_EXTSIZE : con 3; +DD_SIZE : con 32; + +DRONLY : con 16r01; +DHIDDEN : con 16r02; +DSYSTEM : con byte 16r04; +DVLABEL : con byte 16r08; +DDIR : con byte 16r10; +DARCH : con byte 16r20; + +# the boot program for the boot sector. +bootprog := array[512] of { +16r000 => + byte 16rEB, byte 16r3C, byte 16r90, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, + byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, +16r03E => + byte 16rFA, byte 16rFC, byte 16r8C, byte 16rC8, byte 16r8E, byte 16rD8, byte 16r8E, byte 16rD0, + byte 16rBC, byte 16r00, byte 16r7C, byte 16rBE, byte 16r77, byte 16r7C, byte 16rE8, byte 16r19, + byte 16r00, byte 16r33, byte 16rC0, byte 16rCD, byte 16r16, byte 16rBB, byte 16r40, byte 16r00, + byte 16r8E, byte 16rC3, byte 16rBB, byte 16r72, byte 16r00, byte 16rB8, byte 16r34, byte 16r12, + byte 16r26, byte 16r89, byte 16r07, byte 16rEA, byte 16r00, byte 16r00, byte 16rFF, byte 16rFF, + byte 16rEB, byte 16rD6, byte 16rAC, byte 16r0A, byte 16rC0, byte 16r74, byte 16r09, byte 16rB4, + byte 16r0E, byte 16rBB, byte 16r07, byte 16r00, byte 16rCD, byte 16r10, byte 16rEB, byte 16rF2, + byte 16rC3, byte 'N', byte 'o', byte 't', byte ' ', byte 'a', byte ' ', byte 'b', + byte 'o', byte 'o', byte 't', byte 'a', byte 'b', byte 'l', byte 'e', byte ' ', + byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'o', byte 'r', byte ' ', + byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'e', byte 'r', byte 'r', + byte 'o', byte 'r', byte '\r', byte '\n', byte 'P', byte 'r', byte 'e', byte 's', + byte 's', byte ' ', byte 'a', byte 'l', byte 'm', byte 'o', byte 's', byte 't', + byte ' ', byte 'a', byte 'n', byte 'y', byte ' ', byte 'k', byte 'e', byte 'y', + byte ' ', byte 't', byte 'o', byte ' ', byte 'r', byte 'e', byte 'b', byte 'o', + byte 'o', byte 't', byte '.', byte '.', byte '.', byte 16r00, byte 16r00, byte 16r00, +16r1F0 => + byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, + byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r55, byte 16rAA, +* => + byte 16r00, +}; + +dev: string; +clustersize := 0; +fat: array of byte; # the fat +fatbits: int; +fatsecs: int; +fatlast: int; # last cluster allocated +clusters: int; +volsecs: int; +root: array of byte; # first block of root +rootsecs: int; +rootfiles: int; +rootnext: int; +chatty := 0; +xflag := 0; +nresrv := 1; +dos := 0; +fflag := 0; +file: string; # output file name +pbs: string; +typ: string; + +Sof: con 1; # start of file +Eof: con 2; # end of file + +stdin, stdout, stderr: ref Sys->FD; + +fatal(str: string) +{ + sys->fprint(stderr, "format: %s\n", str); + if(fflag && file != nil) + sys->remove(file); + raise "fail:error"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + daytime = load Daytime Daytime->PATH; + disks = load Disks Disks->PATH; + arg := load Arg Arg->PATH; + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + disks->init(); + + fflag = 0; + typ = nil; + clustersize = 0; + writepbs := 0; + label := array[DB_LABELSIZE] of {* => byte ' '}; + label[0:] = array of byte "CYLINDRICAL"; + arg->init(args); + arg->setusage("disk/format [-df] [-b bootblock] [-c csize] [-l label] [-r nresrv] [-t type] disk [files ...]"); + while((o := arg->opt()) != 0) + case o { + 'b' => + pbs = arg->earg(); + writepbs = 1; + 'd' => + dos = 1; + writepbs = 1; + 'c' => + clustersize = int arg->earg(); + 'f' => + fflag = 1; + 'l' => + a := array of byte arg->earg(); + if(len a > len label) + a = a[0:len label]; + label[0:] = a; + for(i := len a; i < len label; i++) + label[i] = byte ' '; + 'r' => + nresrv = int arg->earg(); + 't' => + typ = arg->earg(); + 'v' => + chatty = 1; + 'x' => + xflag = 1; + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + dev = hd args; + disk := Disk.open(dev, Sys->ORDWR, 0); + if(disk == nil){ + if(fflag){ + fd := sys->create(dev, Sys->ORDWR, 8r666); + if(fd != nil){ + fd = nil; + disk = Disk.open(dev, Sys->ORDWR, 0); + } + } + if(disk == nil) + fatal(sys->sprint("opendisk %q: %r", dev)); + } + + if(disk.dtype == "file") + fflag = 1; + + if(typ == nil){ + case disk.dtype { + "file" => + typ = "3½HD"; + "floppy" => + sys->seek(disk.ctlfd, big 0, 0); + buf := array[10] of byte; + n := sys->read(disk.ctlfd, buf, len buf); + if(n <= 0 || n >= 10) + fatal("reading floppy type"); + typ = string buf[0:n]; + "sd" => + typ = "hard"; + * => + typ = "unknown"; + } + } + + if(!fflag && disk.dtype == "floppy") + if(sys->fprint(disk.ctlfd, "format %s", typ) < 0) + fatal(sys->sprint("formatting floppy as %s: %r", typ)); + + if(disk.dtype != "floppy" && !xflag) + sanitycheck(disk); + + # check that everything will succeed + dosfs(dos, writepbs, disk, label, tl args, 0); + + # commit + dosfs(dos, writepbs, disk, label, tl args, 1); + + sys->print("used %bd bytes\n", big fatlast*big clustersize*big disk.secsize); + exit; +} + +# +# look for a partition table on sector 1, as would be the +# case if we were erroneously formatting 9fat without -r 2. +# if it's there and nresrv is not big enough, complain and exit. +# i've blown away my partition table too many times. +# +sanitycheck(disk: ref Disk) +{ + buf := array[512] of byte; + bad := 0; + if(dos && nresrv < 2 && sys->seek(disk.fd, big disk.secsize, 0) == big disk.secsize && + sys->read(disk.fd, buf, len buf) >= 5 && string buf[0:5] == "part "){ + sys->fprint(sys->fildes(2), "there's a plan9 partition on the disk\n"+ + "and you didn't specify -r 2 (or greater).\n" + + "either specify -r 2 or -x to disable this check.\n"); + bad = 1; + } + + if(disk.dtype == "sd" && disk.offset == big 0){ + sys->fprint(sys->fildes(2), "you're attempting to format your disk (/dev/sdXX/data)\n"+ + "rather than a partition such as /dev/sdXX/9fat;\n" + + "this is probably a mistake. specify -x to disable this check.\n"); + bad = 1; + } + + if(bad) + raise "fail:failed disk sanity check"; +} + +# +# return the BIOS driver number for the disk. +# 16r80 is the first fixed disk, 16r81 the next, etc. +# We map sdC0=16r80, sdC1=16r81, sdD0=16r82, sdD1=16r83 +# +getdriveno(disk: ref Disk): int +{ + if(disk.dtype != "sd") + return 16r80; # first hard disk + + name := sys->fd2path(disk.fd); + if(len name < 3) + return 16r80; + + # + # The name is of the format #SsdC0/foo + # or /dev/sdC0/foo. + # So that we can just look for /sdC0, turn + # #SsdC0/foo into #/sdC0/foo. + # + if(name[0:1] == "#S") + name[1] = '/'; + + for(p := name; len p >= 4; p = p[1:]) + if(p[0:2] == "sd" && (p[2]=='C' || p[2]=='D') && (p[3]=='0' || p[3]=='1')) + return 16r80 + (p[2]-'c')*2 + (p[3]-'0'); + + return 16r80; +} + +writen(fd: ref Sys->FD, buf: array of byte, n: int): int +{ + # write 8k at a time, to be nice to the disk subsystem + m: int; + for(tot:=0; tot<n; tot+=m){ + m = n - tot; + if(m > 8192) + m = 8192; + if(sys->write(fd, buf[tot:], m) != m) + break; + } + return tot; +} + +dosfs(dofat: int, dopbs: int, disk: ref Disk, label: array of byte, arg: list of string, commit: int) +{ + if(dofat == 0 && dopbs == 0) + return; + + for(i := 0; i < len floppytype; i++) + if(typ == floppytype[i].name) + break; + if(i == len floppytype) + fatal(sys->sprint("unknown floppy type %q", typ)); + + t := floppytype[i]; + if(t.sectors == 0 && typ == "hard"){ + t.sectors = disk.s; + t.heads = disk.h; + t.tracks = disk.c; + } + + if(t.sectors == 0 && dofat) + fatal(sys->sprint("cannot format fat with type %s: geometry unknown", typ)); + + if(fflag){ + disk.size = big (t.bytes*t.sectors*t.heads*t.tracks); + disk.secsize = t.bytes; + disk.secs = disk.size / big disk.secsize; + } + + secsize := disk.secsize; + length := disk.size; + + # + # make disk full size if a file + # + if(fflag && disk.dtype == "file"){ + (ok, d) := sys->fstat(disk.wfd); + if(ok < 0) + fatal(sys->sprint("fstat disk: %r")); + if(commit && d.length < disk.size){ + if(sys->seek(disk.wfd, disk.size-big 1, 0) < big 0) + fatal(sys->sprint("seek to 9: %r")); + if(sys->write(disk.wfd, array[] of {0 => byte '9'}, 1) < 0) + fatal(sys->sprint("writing 9: @%bd %r", sys->seek(disk.wfd, big 0, 1))); + } + } + + buf := array[secsize] of byte; + + # + # start with initial sector from disk + # + if(sys->seek(disk.fd, big 0, 0) < big 0) + fatal(sys->sprint("seek to boot sector: %r")); + if(commit && sys->read(disk.fd, buf, secsize) != secsize) + fatal(sys->sprint("reading boot sector: %r")); + + if(dofat) + memset(buf, 0, DB_SIZE); + + # + # Jump instruction and OEM name + # + b := buf; # hmm. + b[DB_MAGIC+0] = byte 16rEB; + b[DB_MAGIC+1] = byte 16r3C; + b[DB_MAGIC+2] = byte 16r90; + memmove(b[DB_VERSION: ], array of byte "Plan9.00", DB_VERSIONSIZE); + + # + # Add bootstrapping code; assume it starts + # at 16r3E (the destination of the jump we just + # wrote to b[DB_MAGIC] + # + if(dopbs){ + pbsbuf := array[secsize] of byte; + npbs: int; + if(pbs != nil){ + if((sysfd := sys->open(pbs, Sys->OREAD)) == nil) + fatal(sys->sprint("open %s: %r", pbs)); + npbs = sys->read(sysfd, pbsbuf, len pbsbuf); + if(npbs < 0) + fatal(sys->sprint("read %s: %r", pbs)); + if(npbs > secsize-2) + fatal("boot block too large"); + }else{ + pbsbuf[0:] = bootprog; + npbs = len bootprog; + } + if(npbs <= 16r3E) + sys->fprint(sys->fildes(2), "warning: pbs too small\n"); + else + buf[16r3E:] = pbsbuf[16r3E:npbs]; + } + + # + # Add FAT BIOS parameter block + # + if(dofat){ + if(commit){ + sys->print("Initializing FAT file system\n"); + sys->print("type %s, %d tracks, %d heads, %d sectors/track, %d bytes/sec\n", + t.name, t.tracks, t.heads, t.sectors, secsize); + } + + if(clustersize == 0) + clustersize = t.cluster; + # + # the number of fat bits depends on how much disk is left + # over after you subtract out the space taken up by the fat tables. + # try both. what a crock. + # + for(fatbits = 12;;){ + volsecs = int (length/big secsize); + # + # here's a crock inside a crock. even having fixed fatbits, + # the number of fat sectors depends on the number of clusters, + # but of course we don't know yet. maybe iterating will get us there. + # or maybe it will cycle. + # + clusters = 0; + for(i=0;; i++){ + fatsecs = (fatbits*clusters + 8*secsize - 1)/(8*secsize); + rootsecs = volsecs/200; + rootfiles = rootsecs * (secsize/DD_SIZE); + if(rootfiles > 512){ + rootfiles = 512; + rootsecs = rootfiles/(secsize/DD_SIZE); + } + data := nresrv + 2*fatsecs + (rootfiles*DD_SIZE + secsize-1)/secsize; + newclusters := 2 + (volsecs - data)/clustersize; + if(newclusters == clusters) + break; + clusters = newclusters; + if(i > 10) + fatal(sys->sprint("can't decide how many clusters to use (%d? %d?)", clusters, newclusters)); +if(chatty) sys->print("clusters %d\n", clusters); +if(clusters <= 1) raise "trap"; + } + +if(chatty) sys->print("try %d fatbits => %d clusters of %d\n", fatbits, clusters, clustersize); + if(clusters < 4087 || fatbits > 12) + break; + fatbits = 16; + } + if(clusters >= 65527) + fatal("disk too big; implement fat32"); + + putshort(b[DB_SECTSIZE: ], secsize); + b[DB_CLUSTSIZE] = byte clustersize; + putshort(b[DB_NRESRV: ], nresrv); + b[DB_NFATS] = byte 2; + putshort(b[DB_ROOTSIZE: ], rootfiles); + if(volsecs < (1<<16)) + putshort(b[DB_VOLSIZE: ], volsecs); + b[DB_MEDIADESC] = byte t.media; + putshort(b[DB_FATSIZE: ], fatsecs); + putshort(b[DB_TRKSIZE: ], t.sectors); + putshort(b[DB_NHEADS: ], t.heads); + putlong(b[DB_NHIDDEN: ], int disk.offset); + putlong(b[DB_BIGVOLSIZE: ], volsecs); + + # + # Extended BIOS Parameter Block + # + if(t.media == 16rF8) + dno := getdriveno(disk); + else + dno = 0; +if(chatty) sys->print("driveno = %ux\n", dno); + b[DB_DRIVENO] = byte dno; + b[DB_BOOTSIG] = byte 16r29; + x := int (disk.offset + big b[DB_NFATS]*big fatsecs + big nresrv); + putlong(b[DB_VOLID:], x); +if(chatty) sys->print("volid = %ux\n", x); + b[DB_LABEL:] = label; + r := sys->aprint("FAT%d ", fatbits); + if(len r > DB_TYPESIZE) + r = r[0:DB_TYPESIZE]; + b[DB_TYPE:] = r; + } + + b[secsize-2] = byte Disks->Magic0; + b[secsize-1] = byte Disks->Magic1; + + if(commit){ + if(sys->seek(disk.wfd, big 0, 0) < big 0) + fatal(sys->sprint("seek to boot sector: %r\n")); + if(sys->write(disk.wfd, b, secsize) != secsize) + fatal(sys->sprint("writing to boot sector: %r")); + } + + # + # if we were only called to write the PBS, leave now + # + if(dofat == 0) + return; + + # + # allocate an in memory fat + # + if(sys->seek(disk.wfd, big (nresrv*secsize), 0) < big 0) + fatal(sys->sprint("seek to fat: %r")); +if(chatty) sys->print("fat @%buX\n", sys->seek(disk.wfd, big 0, 1)); + fat = array[fatsecs*secsize] of {* => byte 0}; + if(fat == nil) + fatal("out of memory"); + fat[0] = byte t.media; + fat[1] = byte 16rff; + fat[2] = byte 16rff; + if(fatbits == 16) + fat[3] = byte 16rff; + fatlast = 1; + if(sys->seek(disk.wfd, big (2*fatsecs*secsize), 1) < big 0) # 2 fats + fatal(sys->sprint("seek to root: %r")); +if(chatty) sys->print("root @%buX\n", sys->seek(disk.wfd, big 0, 1)); + + # + # allocate an in memory root + # + root = array[rootsecs*secsize] of {* => byte 0}; + if(sys->seek(disk.wfd, big (rootsecs*secsize), 1) < big 0) # rootsecs + fatal(sys->sprint("seek to files: %r")); +if(chatty) sys->print("files @%buX\n", sys->seek(disk.wfd, big 0, 1)); + + # + # Now positioned at the Files Area. + # If we have any arguments, process + # them and write out. + # + for(p := 0; arg != nil; arg = tl arg){ + if(p >= rootsecs*secsize) + fatal("too many files in root"); + # + # Open the file and get its length. + # + if((sysfd := sys->open(hd arg, Sys->OREAD)) == nil) + fatal(sys->sprint("open %s: %r", hd arg)); + (ok, d) := sys->fstat(sysfd); + if(ok < 0) + fatal(sys->sprint("stat %s: %r", hd arg)); + if(d.length >= big 16r7FFFFFFF) + fatal(sys->sprint("file %s too big (%bd bytes)", hd arg, d.length)); + if(commit) + sys->print("Adding file %s, length %bd\n", hd arg, d.length); + + x: int; + length = d.length; + if(length > big 0){ + # + # Allocate a buffer to read the entire file into. + # This must be rounded up to a cluster boundary. + # + # Read the file and write it out to the Files Area. + # + length += big (secsize*clustersize - 1); + length /= big (secsize*clustersize); + length *= big (secsize*clustersize); + fbuf := array[int length] of byte; + if((nr := sys->read(sysfd, fbuf, int d.length)) != int d.length){ + if(nr >= 0) + sys->werrstr("short read"); + fatal(sys->sprint("read %s: %r", hd arg)); + } + for(; nr < len fbuf; nr++) + fbuf[nr] = byte 0; +if(chatty) sys->print("%q @%buX\n", d.name, sys->seek(disk.wfd, big 0, 1)); + if(commit && writen(disk.wfd, fbuf, len fbuf) != len fbuf) + fatal(sys->sprint("write %s: %r", hd arg)); + fbuf = nil; + + # + # Allocate the FAT clusters. + # We're assuming here that where we + # wrote the file is in sync with + # the cluster allocation. + # Save the starting cluster. + # + length /= big (secsize*clustersize); + x = clustalloc(Sof); + for(n := 0; n < int length-1; n++) + clustalloc(0); + clustalloc(Eof); + } + else + x = 0; + + # + # Add the filename to the root. + # +sys->fprint(sys->fildes(2), "add %s at clust %ux\n", d.name, x); + addrname(root[p:], d, hd arg, x); + p += DD_SIZE; + } + + # + # write the fats and root + # + if(commit){ + if(sys->seek(disk.wfd, big (nresrv*secsize), 0) < big 0) + fatal(sys->sprint("seek to fat #1: %r")); + if(sys->write(disk.wfd, fat, fatsecs*secsize) < 0) + fatal(sys->sprint("writing fat #1: %r")); + if(sys->write(disk.wfd, fat, fatsecs*secsize) < 0) + fatal(sys->sprint("writing fat #2: %r")); + if(sys->write(disk.wfd, root, rootsecs*secsize) < 0) + fatal(sys->sprint("writing root: %r")); + } +} + +# +# allocate a cluster +# +clustalloc(flag: int): int +{ + o, x: int; + + if(flag != Sof){ + if (flag == Eof) + x =16rffff; + else + x = fatlast+1; + if(fatbits == 12){ + x &= 16rfff; + o = (3*fatlast)/2; + if(fatlast & 1){ + fat[o] = byte ((int fat[o] & 16r0f) | (x<<4)); + fat[o+1] = byte (x>>4); + } else { + fat[o] = byte x; + fat[o+1] = byte ((int fat[o+1] & 16rf0) | ((x>>8) & 16r0F)); + } + } else { + o = 2*fatlast; + fat[o] = byte x; + fat[o+1] = byte (x>>8); + } + } + + if(flag == Eof) + return 0; + if(++fatlast >= clusters) + fatal(sys->sprint("data does not fit on disk (%d %d)", fatlast, clusters)); + return fatlast; +} + +putname(p: string, buf: array of byte) +{ + memset(buf[DD_NAME: ], ' ', DD_NAMESIZE+DD_EXTSIZE); + for(i := 0; i < DD_NAMESIZE && i < len p && p[i] != '.'; i++){ + c := p[i]; + if(c >= 'a' && c <= 'z') + c += 'A'-'a'; + buf[DD_NAME+i] = byte c; + } + for(i = 0; i < len p; i++) + if(p[i] == '.'){ + p = p[i+1:]; + for(i = 0; i < DD_EXTSIZE && i < len p; i++){ + c := p[i]; + if(c >= 'a' && c <= 'z') + c += 'A'-'a'; + buf[DD_EXT+i] = byte c; + } + break; + } +} + +puttime(buf: array of byte) +{ + t := daytime->local(daytime->now()); + x := (t.hour<<11) | (t.min<<5) | (t.sec>>1); + buf[DD_TIME+0] = byte x; + buf[DD_TIME+1] = byte (x>>8); + x = ((t.year-80)<<9) | ((t.mon+1)<<5) | t.mday; + buf[DD_DATE+0] = byte x; + buf[DD_DATE+1] = byte (x>>8); +} + +addrname(buf: array of byte, dir: Sys->Dir, name: string, start: int) +{ + s := name; + for(i := len s; --i >= 0;) + if(s[i] == '/'){ + s = s[i+1:]; + break; + } + putname(s, buf); + if(s == "9load") + buf[DD_ATTR] = byte DSYSTEM; + else + buf[DD_ATTR] = byte 0; + puttime(buf); + buf[DD_START+0] = byte start; + buf[DD_START+1] = byte (start>>8); + buf[DD_LENGTH+0] = byte dir.length; + buf[DD_LENGTH+1] = byte (dir.length>>8); + buf[DD_LENGTH+2] = byte (dir.length>>16); + buf[DD_LENGTH+3] = byte (dir.length>>24); +} + +memset(d: array of byte, v: int, n: int) +{ + for (i := 0; i < n; i++) + d[i] = byte v; +} + +memmove(d: array of byte, s: array of byte, n: int) +{ + d[0:] = s[0:n]; +} + +putshort(b: array of byte, v: int) +{ + b[1] = byte (v>>8); + b[0] = byte v; +} + +putlong(b: array of byte, v: int) +{ + putshort(b, v); + putshort(b[2: ], v>>16); +} diff --git a/appl/cmd/disk/ftl.b b/appl/cmd/disk/ftl.b new file mode 100644 index 00000000..750defb7 --- /dev/null +++ b/appl/cmd/disk/ftl.b @@ -0,0 +1,911 @@ +# +# basic Flash Translation Layer driver +# see for instance the Intel technical paper +# ``Understanding the Flash Translation Layer (FTL) Specification'' +# Order number 297816-001 (online at www.intel.com) +# +# a public driver by David Hinds, dhinds@allegro.stanford.edu +# further helps with some details. +# +# this driver uses the common simplification of never storing +# the VBM on the medium (a waste of precious flash!) but +# rather building it on the fly as the block maps are read. +# +# Plan 9 driver (c) 1997 by C H Forsyth (forsyth@caldo.demon.co.uk) +# This driver may be used or adapted by anyone for any non-commercial purpose. +# +# adapted for Inferno 1998 by C H Forsyth, Vita Nuova Limited, York, England (byteles@vitanuova.com) +# +# C H Forsyth and Vita Nuova Limited expressly allow Lucent Technologies +# to use this driver freely for any Inferno-related purposes whatever, +# including commercial applications. +# +# TO DO: +# check error handling details for get/put flash +# bad block handling +# reserved space in formatted size +# possibly block size as parameter +# fetch parameters from header on init +# +# Adapted to a ftl formatter for Inferno 2000 by J R Firth, Vita Nuova Limited +# usage : ftl flashsize secsize inputfile outputfile +# outputfile will then be a ftl image of inputfile +# nb assumes the base address is zero +# +# Converted to limbo for Inferno 2000 by JR Firth, Vita Nuova Holdings Limited +# + +implement Ftlimage; + +include "sys.m"; +include "draw.m"; + +sys : Sys; + OREAD, OWRITE, FD, open, create, read, write, print, fprint : import sys; + +Ftlimage : module +{ + init : fn(nil : ref Draw->Context, argv : list of string); +}; + +stderr : ref FD; + +flashsize, secsize : int; +flashm : array of byte; +trace : int = 0; + +Eshift : con 18; # 2^18=256k; log2(eraseunit) +Flashseg : con 1<<Eshift; +Bshift : con 9; # 2^9=512 +Bsize : con 1<<Bshift; +BAMoffset : con 16r100; +Nolimit : con ~0; +USABLEPCT : con 95; # release only this % to client + +FTLDEBUG : con 0; + +# erase unit header (defined by FTL specification) +# offsets into Merase +O_LINKTUPLE : con 0; +O_ORGTUPLE : con 5; +O_NXFER : con 15; +O_NERASE : con 16; +O_ID : con 20; +O_BSHIFT : con 22; +O_ESHIFT : con 23; +O_PSTART : con 24; +O_NUNITS : con 26; +O_PSIZE : con 28; +O_VBMBASE : con 32; +O_NVBM : con 36; +O_FLAGS : con 38; +O_CODE : con 39; +O_SERIAL : con 40; +O_ALTOFFSET : con 44; +O_BAMOFFSET : con 48; +O_RSV2 : con 52; + +ERASEHDRLEN : con 64; + +# special unit IDs +XferID : con 16rffff; +XferBusy : con 16r7fff; + +# special BAM addresses +Bfree : con -1; #16rffffffff +Bwriting : con -2; #16rfffffffe +Bdeleted : con 0; + +# block types +TypeShift : con 7; +BlockType : con (1<<TypeShift)-1; +ControlBlock : con 16r30; +DataBlock : con 16r40; +ReplacePage : con 16r60; +BadBlock : con 16r70; + +BNO(va : int) : int +{ + return va>>Bshift; +} +MKBAM(b : int,t : int) : int +{ + return (b<<Bshift)|t; +} + +Terase : adt { + x : int; + id : int; + offset : int; + bamoffset : int; + nbam : int; + bam : array of byte; + bamx : int; + nfree : int; + nused : int; + ndead : int; + nbad : int; + nerase : int; +}; + +Ftl : adt { + base : int; # base of flash region + size : int; # size of flash region + segsize : int; # size of flash segment (erase unit) + eshift : int; # log2(erase-unit-size) + bshift : int; # log2(bsize) + bsize : int; + nunit : int; # number of segments (erase units) + unit : array of ref Terase; + lastx : int; # index in unit of last allocation + xfer : int; # index in unit of current transfer unit (-1 if none) + nfree : int; # total free space in blocks + nblock : int; # total space in blocks + rwlimit : int; # user-visible block limit (`formatted size') + vbm : array of int; # virtual block map + fstart : int; # address of first block of data in a segment + trace : int; # (debugging) trace of read/write actions + detach : int; # free Ftl on last close + + # scavenging variables + needspace : int; + hasproc : int; +}; + +# Ftl.detach +Detached : con 1; # detach on close +Deferred : con 2; # scavenger must free it + +ftls : ref Ftl; + +ftlstat(sz : int) +{ + print("16r%x:16r%x:16r%x\n", ftls.rwlimit*Bsize, sz, flashsize); + print("%d:%d:%d in 512b blocks\n", ftls.rwlimit, sz>>Bshift, flashsize>>Bshift); +} + +ftlread(buf : array of byte, n : int, offset : int) : int +{ + ftl : ref Ftl; + e : ref Terase; + nb : int; + a : int; + pb : int; + mapb : int; + + if(n <= 0 || n%Bsize || offset%Bsize) { + fprint(stderr, "ftl: bad read\n"); + exit; + } + ftl = ftls; + nb = n/Bsize; + offset /= Bsize; + if(offset >= ftl.rwlimit) + return 0; + if(offset+nb > ftl.rwlimit) + nb = ftl.rwlimit - offset; + a = 0; + for(n = 0; n < nb; n++){ + (mapb, e, pb) = mapblk(ftl, offset+n); + if(mapb) + getflash(ftl, buf[a:], e.offset + pb*Bsize, Bsize); + else + memset(buf[a:], 0, Bsize); + a += Bsize; + } + return a; +} + +ftlwrite(buf : array of byte, n : int, offset : int) : int +{ + ns, nb : int; + a : int; + e, oe : ref Terase; + ob, v : int; + ftl : ref Ftl; + mapb : int; + + if(n <= 0) + return 0; + ftl = ftls; + if(n <= 0 || n%Bsize || offset%Bsize) { + fprint(stderr, "ftl: bad write\n"); + exit; + } + nb = n/Bsize; + offset /= Bsize; + if(offset >= ftl.rwlimit) + return 0; + if(offset+nb > ftl.rwlimit) + nb = ftl.rwlimit - offset; + a = 0; + for(n = 0; n < nb; n++){ + ns = 0; + while((v = allocblk(ftl)) == 0) + if(!scavenge(ftl) || ++ns > 3){ + fprint(stderr, "ftl: flash memory full\n"); + } + (mapb, oe, ob) = mapblk(ftl, offset+n); + if(!mapb) + oe = nil; + e = ftl.unit[v>>16]; + v &= 16rffff; + putflash(ftl, e.offset + v*Bsize, buf[a:], Bsize); + putbam(ftl, e, v, MKBAM(offset+n, DataBlock)); + # both old and new block references exist in this window (can't be closed?) + ftl.vbm[offset+n] = (e.x<<16) | v; + if(oe != nil){ + putbam(ftl, oe, ob, Bdeleted); + oe.ndead++; + } + a += Bsize; + } + return a; +} + +mkftl(fname : string, base : int, size : int, eshift : int, op : string) : ref Ftl +{ + i, j, nov, segblocks : int; + limit : int; + e : ref Terase; + + ftl := ref Ftl; + ftl.lastx = 0; + ftl.detach = 0; + ftl.needspace = 0; + ftl.hasproc = 0; + ftl.trace = 0; + limit = flashsize; + if(size == Nolimit) + size = limit-base; + if(base >= limit || size > limit || base+size > limit || eshift < 8 || (1<<eshift) > size) { + fprint(stderr, "bad flash space parameters"); + exit; + } + if(FTLDEBUG || ftl.trace || trace) + print("%s flash %s #%x:#%x limit #%x\n", op, fname, base, size, limit); + ftl.base = base; + ftl.size = size; + ftl.bshift = Bshift; + ftl.bsize = Bsize; + ftl.eshift = eshift; + ftl.segsize = 1<<eshift; + ftl.nunit = size>>eshift; + nov = ((ftl.segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize; # number of overhead blocks per segment (header, and BAM itself) + ftl.fstart = nov; + segblocks = ftl.segsize/Bsize - nov; + ftl.nblock = ftl.nunit*segblocks; + if(ftl.nblock >= 16r10000) + ftl.nblock = 16r10000; + ftl.vbm = array[ftl.nblock] of int; + ftl.unit = array[ftl.nunit] of ref Terase; + if(ftl.vbm == nil || ftl.unit == nil) { + fprint(stderr, "out of mem"); + exit; + } + for(i=0; i<ftl.nblock; i++) + ftl.vbm[i] = 0; + if(op == "format"){ + for(i=0; i<ftl.nunit-1; i++) + eraseinit(ftl, i*ftl.segsize, i, 1); + eraseinit(ftl, i*ftl.segsize, XferID, 1); + } + ftl.xfer = -1; + for(i=0; i<ftl.nunit; i++){ + e = eraseload(ftl, i, i*ftl.segsize); + if(e == nil){ + fprint(stderr, "ftl: logical segment %d: bad format\n", i); + continue; + } + if(e.id == XferBusy){ + e.nerase++; + eraseinit(ftl, e.offset, XferID, e.nerase); + e.id = XferID; + } + for(j=0; j<ftl.nunit; j++) + if(ftl.unit[j] != nil && ftl.unit[j].id == e.id){ + fprint(stderr, "ftl: duplicate erase unit #%x\n", e.id); + erasefree(e); + e = nil; + break; + } + if(e != nil){ + ftl.unit[e.x] = e; + if(e.id == XferID) + ftl.xfer = e.x; + if (FTLDEBUG || ftl.trace || trace) + fprint(stderr, "ftl: unit %d:#%x used %d free %d dead %d bad %d nerase %d\n", + e.x, e.id, e.nused, e.nfree, e.ndead, e.nbad, e.nerase); + } + } + if(ftl.xfer < 0 && ftl.nunit <= 0 || ftl.xfer >= 0 && ftl.nunit <= 1) { + fprint(stderr, "ftl: no valid flash data units"); + exit; + } + if(ftl.xfer < 0) + fprint(stderr, "ftl: no transfer unit: device is WORM\n"); + else + ftl.nblock -= segblocks; # discount transfer segment + if(ftl.nblock >= 1000) + ftl.rwlimit = ftl.nblock-100; # TO DO: variable reserve + else + ftl.rwlimit = ftl.nblock*USABLEPCT/100; + return ftl; +} + +ftlfree(ftl : ref Ftl) +{ + if(ftl != nil){ + ftl.unit = nil; + ftl.vbm = nil; + ftl = nil; + } +} + +# +# this simple greedy algorithm weighted by nerase does seem to lead +# to even wear of erase units (cf. the eNVy file system) +# + +bestcopy(ftl : ref Ftl) : ref Terase +{ + e, be : ref Terase; + i : int; + + be = nil; + for(i=0; i<ftl.nunit; i++) + if((e = ftl.unit[i]) != nil && e.id != XferID && e.id != XferBusy && e.ndead+e.nbad && + (be == nil || e.nerase <= be.nerase && e.ndead >= be.ndead)) + be = e; + return be; +} + +copyunit(ftl : ref Ftl, from : ref Terase, too : ref Terase) : int +{ + i, nb : int; + id := array[2] of byte; + bam : array of byte; + buf : array of byte; + v, bno : int; + + if(FTLDEBUG || ftl.trace || trace) + print("ftl: copying %d (#%x) to #%x\n", from.id, from.offset, too.offset); + too.nbam = 0; + too.bam = nil; + bam = nil; + buf = array[Bsize] of byte; + if(buf == nil) + return 0; + PUT2(id, XferBusy); + putflash(ftl, too.offset+O_ID, id, 2); + # make new BAM + nb = from.nbam*4; + bam = array[nb] of byte; + memmove(bam, from.bam, nb); + too.nused = 0; + too.nbad = 0; + too.nfree = 0; + too.ndead = 0; + for(i = 0; i < from.nbam; i++) + bv := GET4(bam[4*i:]); + case(bv){ + Bwriting or + Bdeleted or + Bfree => + PUT4(bam[4*i:], Bfree); + too.nfree++; + break; + * => + case(bv&BlockType){ + DataBlock or + ReplacePage => + v = bv; + bno = BNO(v & ~BlockType); + if(i < ftl.fstart || bno >= ftl.nblock){ + print("ftl: unit %d:#%x bad bam[%d]=#%x\n", from.x, from.id, i, v); + too.nfree++; + PUT4(bam[4*i:], Bfree); + break; + } + getflash(ftl, buf, from.offset+i*Bsize, Bsize); + putflash(ftl, too.offset+i*Bsize, buf, Bsize); + too.nused++; + break; + ControlBlock => + too.nused++; + break; + * => + # case BadBlock: # it isn't necessarily bad in this unit + too.nfree++; + PUT4(bam[4*i:], Bfree); + break; + } + } + # for(i=0; i<from.nbam; i++){ + # v = GET4(bam[4*i:]); + # if(v != Bfree && ftl.trace > 1) + # print("to[%d]=#%x\n", i, v); + # PUT4(bam[4*i:], v); + # } + putflash(ftl, too.bamoffset, bam, nb); # BUG: PUT4 ? IS IT ? + # for(i=0; i<from.nbam; i++){ + # v = GET4(bam[4*i:]); + # PUT4(bam[4*i:], v); + # } + too.id = from.id; + PUT2(id, too.id); + putflash(ftl, too.offset+O_ID, id, 2); + too.nbam = from.nbam; + too.bam = bam; + ftl.nfree += too.nfree - from.nfree; + buf = nil; + return 1; +} + +mustscavenge(a : ref Ftl) : int +{ + return a.needspace || a.detach == Deferred; +} + +donescavenge(a : ref Ftl) : int +{ + return a.needspace == 0; +} + +scavengeproc(arg : ref Ftl) +{ + ftl : ref Ftl; + i : int; + e, ne : ref Terase; + + ftl = arg; + if(mustscavenge(ftl)){ + if(ftl.detach == Deferred){ + ftlfree(ftl); + fprint(stderr, "scavenge out of memory\n"); + exit; + } + if(FTLDEBUG || ftl.trace || trace) + print("ftl: scavenge %d\n", ftl.nfree); + e = bestcopy(ftl); + if(e == nil || ftl.xfer < 0 || (ne = ftl.unit[ftl.xfer]) == nil || ne.id != XferID || e == ne) + ; + else if(copyunit(ftl, e, ne)){ + i = ne.x; ne.x = e.x; e.x = i; + ftl.unit[ne.x] = ne; + ftl.unit[e.x] = e; + ftl.xfer = e.x; + e.id = XferID; + e.nbam = 0; + e.bam = nil; + e.bamx = 0; + e.nerase++; + eraseinit(ftl, e.offset, XferID, e.nerase); + } + if(FTLDEBUG || ftl.trace || trace) + print("ftl: end scavenge %d\n", ftl.nfree); + ftl.needspace = 0; + } +} + +scavenge(ftl : ref Ftl) : int +{ + if(ftl.xfer < 0 || bestcopy(ftl) == nil) + return 0; # you worm! + + if(!ftl.hasproc){ + ftl.hasproc = 1; + } + ftl.needspace = 1; + + scavengeproc(ftls); + + return ftl.nfree; +} + +putbam(ftl : ref Ftl, e : ref Terase, n : int, entry : int) +{ + b := array[4] of byte; + + PUT4(e.bam[4*n:], entry); + PUT4(b, entry); + putflash(ftl, e.bamoffset + n*4, b, 4); +} + +allocblk(ftl : ref Ftl) : int +{ + e : ref Terase; + i, j : int; + + i = ftl.lastx; + do{ + e = ftl.unit[i]; + if(e != nil && e.id != XferID && e.nfree){ + ftl.lastx = i; + for(j=e.bamx; j<e.nbam; j++) + if(GET4(e.bam[4*j:])== Bfree){ + putbam(ftl, e, j, Bwriting); + ftl.nfree--; + e.nfree--; + e.bamx = j+1; + return (e.x<<16) | j; + } + e.nfree = 0; + print("ftl: unit %d:#%x nfree %d but not free in BAM\n", e.x, e.id, e.nfree); + } + if(++i >= ftl.nunit) + i = 0; + }while(i != ftl.lastx); + return 0; +} + +mapblk(ftl : ref Ftl, bno : int) : (int, ref Terase, int) +{ + v : int; + x : int; + + if(bno < ftl.nblock){ + v = ftl.vbm[bno]; + if(v == 0 || v == ~0) + return (0, nil, 0); + x = v>>16; + if(x >= ftl.nunit || x == ftl.xfer || ftl.unit[x] == nil){ + print("ftl: corrupt format: bad block mapping %d . unit #%x\n", bno, x); + return (0, nil, 0); + } + return (1, ftl.unit[x], v & 16rFFFF); + } + return (0, nil, 0); +} + +eraseinit(ftl : ref Ftl, offset : int, id : int, nerase : int) +{ + m : array of byte; + bam : array of byte; + i, nov : int; + + nov = ((ftl.segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize; # number of overhead blocks (header, and BAM itself) + if(nov*Bsize >= ftl.segsize) { + fprint(stderr, "ftl -- too small for files"); + exit; + } + eraseflash(ftl, offset); + m = array[ERASEHDRLEN] of byte; + if(m == nil) { + fprint(stderr, "nomem\n"); + exit; + } + memset(m, 16rFF, len m); + m[O_LINKTUPLE+0] = byte 16r13; + m[O_LINKTUPLE+1] = byte 16r3; + memmove(m[O_LINKTUPLE+2:], array of byte "CIS", 3); + m[O_ORGTUPLE+0] = byte 16r46; + m[O_ORGTUPLE+1] = byte 16r57; + m[O_ORGTUPLE+2] = byte 16r00; + memmove(m[O_ORGTUPLE+3:], array of byte "FTL100\0", 7); + m[O_NXFER] = byte 1; + PUT4(m[O_NERASE:], nerase); + PUT2(m[O_ID:], id); + m[O_BSHIFT] = byte ftl.bshift; + m[O_ESHIFT] = byte ftl.eshift; + PUT2(m[O_PSTART:], 0); + PUT2(m[O_NUNITS:], ftl.nunit); + PUT4(m[O_PSIZE:], ftl.size - nov*Bsize); + PUT4(m[O_VBMBASE:], -1); # we always calculate the VBM (16rffffffff) + PUT2(m[O_NVBM:], 0); + m[O_FLAGS] = byte 0; + m[O_CODE] = byte 16rFF; + memmove(m[O_SERIAL:], array of byte "Inf1", 4); + PUT4(m[O_ALTOFFSET:], 0); + PUT4(m[O_BAMOFFSET:], BAMoffset); + putflash(ftl, offset, m, ERASEHDRLEN); + m = nil; + if(id == XferID) + return; + nov *= 4; # now bytes of BAM + bam = array[nov] of byte; + if(bam == nil) { + fprint(stderr, "nomem"); + exit; + } + for(i=0; i<nov; i += 4) + PUT4(bam[i:], ControlBlock); # reserve them + putflash(ftl, offset+BAMoffset, bam, nov); + bam = nil; +} + +eraseload(ftl : ref Ftl, x : int, offset : int) : ref Terase +{ + m : array of byte; + e : ref Terase; + i, nbam : int; + bno, v : int; + + m = array[ERASEHDRLEN] of byte; + if(m == nil) { + fprint(stderr, "nomem"); + exit; + } + getflash(ftl, m, offset, ERASEHDRLEN); + if(memcmp(m[O_ORGTUPLE+3:], array of byte "FTL100\0", 7) != 0 || + memcmp(m[O_SERIAL:], array of byte "Inf1", 4) != 0){ + m = nil; + return nil; + } + e = ref Terase; + if(e == nil){ + m = nil; + fprint(stderr, "nomem"); + exit; + } + e.x = x; + e.id = GET2(m[O_ID:]); + e.offset = offset; + e.bamoffset = GET4(m[O_BAMOFFSET:]); + e.nerase = GET4(m[O_NERASE:]); + e.bamx = 0; + e.nfree = 0; + e.nused = 0; + e.ndead = 0; + e.nbad = 0; + m = nil; + if(e.bamoffset != BAMoffset){ + e = nil; + return nil; + } + e.bamoffset += offset; + if(e.id == XferID || e.id == XferBusy){ + e.bam = nil; + e.nbam = 0; + return e; + } + nbam = ftl.segsize/Bsize; + e.bam = array[4*nbam] of byte; + e.nbam = nbam; + getflash(ftl, e.bam, e.bamoffset, nbam*4); + # scan BAM to build VBM + e.bamx = 0; + for(i=0; i<nbam; i++){ + v = GET4(e.bam[4*i:]); + if(v == Bwriting || v == Bdeleted) + e.ndead++; + else if(v == Bfree){ + if(e.bamx == 0) + e.bamx = i; + e.nfree++; + ftl.nfree++; + }else{ + case(v & BlockType){ + ControlBlock => + break; + DataBlock => + # add to VBM + if(v & (1<<31)) + break; # negative => VBM page, ignored + bno = BNO(v & ~BlockType); + if(i < ftl.fstart || bno >= ftl.nblock){ + print("ftl: unit %d:#%x bad bam[%d]=#%x\n", e.x, e.id, i, v); + e.nbad++; + break; + } + ftl.vbm[bno] = (e.x<<16) | i; + e.nused++; + break; + ReplacePage => + # replacement VBM page; ignored + break; + BadBlock => + e.nbad++; + break; + * => + print("ftl: unit %d:#%x bad bam[%d]=%x\n", e.x, e.id, i, v); + } + } + } + return e; +} + +erasefree(e : ref Terase) +{ + e.bam = nil; + e = nil; +} + +eraseflash(ftl : ref Ftl, offset : int) +{ + offset += ftl.base; + if(FTLDEBUG || ftl.trace || trace) + print("ftl: erase seg @#%x\n", offset); + memset(flashm[offset:], 16rff, secsize); +} + +putflash(ftl : ref Ftl, offset : int, buf : array of byte, n : int) +{ + offset += ftl.base; + if(ftl.trace || trace) + print("ftl: write(#%x, %d)\n", offset, n); + memmove(flashm[offset:], buf, n); +} + +getflash(ftl : ref Ftl, buf : array of byte, offset : int, n : int) +{ + offset += ftl.base; + if(ftl.trace || trace) + print("ftl: read(#%x, %d)\n", offset, n); + memmove(buf, flashm[offset:], n); +} + +BUFSIZE : con 8192; + +main(argv : list of string) +{ + k, r, sz, offset : int = 0; + buf, buf1 : array of byte; + fd1, fd2 : ref FD; + + if (len argv != 5) { + fprint(stderr, "usage: %s flashsize secsize kfsfile flashfile\n", hd argv); + exit; + } + flashsize = atoi(hd tl argv); + secsize = atoi(hd tl tl argv); + fd1 = open(hd tl tl tl argv, OREAD); + fd2 = create(hd tl tl tl tl argv, OWRITE, 8r644); + if (fd1 == nil || fd2 == nil) { + fprint(stderr, "bad io files\n"); + exit; + } + if(secsize == 0 || secsize > flashsize || secsize&(secsize-1) || 0&(secsize-1) || flashsize == 0 || flashsize != Nolimit && flashsize&(secsize-1)) { + fprint(stderr, "ftl: bad sizes\n"); + exit; + } + for(k=0; k<32 && (1<<k) != secsize; k++) + ; + flashm = array[flashsize] of byte; + buf = array[BUFSIZE] of byte; + if (flashm == nil) { + fprint(stderr, "ftl: no mem for flash\n"); + exit; + } + ftls = mkftl("FLASH", 0, Nolimit, k, "format"); + for (;;) { + r = read(fd1, buf, BUFSIZE); + if (r <= 0) + break; + if (ftlwrite(buf, r, offset) != r) { + fprint(stderr, "ftl: ftlwrite failed - input file too big\n"); + exit; + } + offset += r; + } + write(fd2, flashm, flashsize); + fd1 = fd2 = nil; + ftlstat(offset); + # ftls = mkftl("FLASH", 0, Nolimit, k, "init"); + sz = offset; + offset = 0; + buf1 = array[BUFSIZE] of byte; + fd1 = open(hd tl tl tl argv, OREAD); + for (;;) { + r = read(fd1, buf1, BUFSIZE); + if (r <= 0) + break; + if (ftlread(buf, r, offset) != r) { + fprint(stderr, "ftl: ftlread failed\n"); + exit; + } + if (memcmp(buf, buf1, r) != 0) { + fprint(stderr, "ftl: bad read\n"); + exit; + } + offset += r; + } + fd1 = nil; + if (offset != sz) { + fprint(stderr, "ftl: bad final offset\n"); + exit; + } + exit; +} + +init(nil : ref Draw->Context, argl : list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + main(argl); +} + +memset(d : array of byte, v : int, n : int) +{ + for (i := 0; i < n; i++) + d[i] = byte v; +} + +memmove(d : array of byte, s : array of byte, n : int) +{ + d[0:] = s[0:n]; +} + +memcmp(s1 : array of byte, s2 : array of byte, n : int) : int +{ + for (i := 0; i < n; i++) { + if (s1[i] < s2[i]) + return -1; + if (s1[i] > s2[i]) + return 1; + } + return 0; +} + +atoi(s : string) : int +{ + v : int; + base := 10; + n := len s; + neg := 0; + + for (i := 0; i < n && (s[i] == ' ' || s[i] == '\t'); i++) + ; + if (s[i] == '+' || s[i] == '-') { + if (s[i] == '-') + neg = 1; + i++; + } + if (n-i >= 2 && s[i] == '0' && s[i+1] == 'x') { + base = 16; + i += 2; + } + else if (n-i >= 1 && s[i] == '0') { + base = 8; + i++; + } + m := 0; + for(; i < n; i++) { + c := s[i]; + case c { + 'a' to 'z' => + v = c - 'a' + 10; + 'A' to 'Z' => + v = c - 'A' + 10; + '0' to '9' => + v = c - '0'; + * => + fprint(stderr, "ftl: bad character in number %s\n", s); + exit; + } + if(v >= base) { + fprint(stderr, "ftl: character too big for base in %s\n", s); + exit; + } + m = m * base + v; + } + if(neg) + m = -m; + return m; +} + +# little endian + +GET2(b : array of byte) : int +{ + return ((int b[1]) << 8) | (int b[0]); +} + +GET4(b : array of byte) : int +{ + return ((int b[3]) << 24) | ((int b[2]) << 16) | ((int b[1]) << 8) | (int b[0]); +} + +PUT2(b : array of byte, v : int) +{ + b[1] = byte (v>>8); + b[0] = byte v; +} + +PUT4(b : array of byte, v : int) +{ + b[3] = byte (v>>24); + b[2] = byte (v>>16); + b[1] = byte (v>>8); + b[0] = byte v; +} diff --git a/appl/cmd/disk/kfs.b b/appl/cmd/disk/kfs.b new file mode 100644 index 00000000..56440205 --- /dev/null +++ b/appl/cmd/disk/kfs.b @@ -0,0 +1,3842 @@ +implement Kfs; + +# +# Copyright © 1991-2003 Lucent Technologies Inc. +# Limbo version Copyright © 2004 Vita Nuova Holdings Limited +# + +# +# TO DO: +# - sync proc; Bmod; process structure +# - swiz? + +include "sys.m"; + sys: Sys; + Qid, Dir: import Sys; + DMEXCL, DMAPPEND, DMDIR: import Sys; + QTEXCL, QTAPPEND, QTDIR: import Sys; + +include "draw.m"; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + NOFID, OEXEC, ORCLOSE, OREAD, OWRITE, ORDWR, OTRUNC: import Styx; + IOHDRSZ: import Styx; + +include "daytime.m"; + daytime: Daytime; + now: import daytime; + +include "arg.m"; + +Kfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +MAXBUFSIZE: con 16*1024; + +# +# fundamental constants +# +NAMELEN: con 28; # size of names, including null byte +NDBLOCK: con 6; # number of direct blocks in Dentry +MAXFILESIZE: con big 16r7FFFFFFF; # Plan 9's limit (kfs's size is signed) + +SUPERADDR: con 1; +ROOTADDR: con 2; + +QPDIR: con int (1<<31); +QPNONE: con 0; +QPROOT: con 1; +QPSUPER: con 2; + +# +# don't change, these are the mode bits on disc +# +DALLOC: con 16r8000; +DDIR: con 16r4000; +DAPND: con 16r2000; +DLOCK: con 16r1000; +DREAD: con 4; +DWRITE: con 2; +DEXEC: con 1; + +# +# other constants +# + +MINUTE: con 60; +TLOCK: con 5*MINUTE; +NTLOCK: con 200; # number of active file locks + +Buffering: con 1; + +FID1, FID2, FID3: con 1+iota; + +None: con 0; # user ID for "none" +Noworld: con 9999; # conventional id for "noworld" group + +Lock: adt +{ + c: chan of int; + new: fn(): ref Lock; + lock: fn(c: self ref Lock); + canlock: fn(c: self ref Lock): int; + unlock: fn(c: self ref Lock); +}; + +Dentry: adt +{ + name: string; + uid: int; + gid: int; + muid: int; # not set by plan 9's kfs + mode: int; # mode bits on disc: DALLOC etc + qid: Qid; # 9p1 format on disc + size: big; # only 32-bits on disc, and Plan 9 limits it to signed + atime: int; + mtime: int; + + iob: ref Iobuf; # locked block containing directory entry, when in memory + buf: array of byte; # pointer into block to packed directory entry, when in memory + mod: int; # bits of buf that need updating + + unpack: fn(a: array of byte): ref Dentry; + get: fn(p: ref Iobuf, slot: int): ref Dentry; + geta: fn(d: ref Device, addr: int, slot: int, qpath: int, mode: int): (ref Dentry, string); + getd: fn(f: ref File, mode: int): (ref Dentry, string); + put: fn(d: self ref Dentry); + access: fn(d: self ref Dentry, f: int, uid: int); + change: fn(d: self ref Dentry, f: int); + release: fn(d: self ref Dentry); + getblk: fn(d: self ref Dentry, a: int, tag: int): ref Iobuf; + getblk1: fn(d: self ref Dentry, a: int, tag: int): ref Iobuf; + rel2abs: fn(d: self ref Dentry, a: int, tag: int, putb: int): int; + trunc: fn(d: self ref Dentry, uid: int); + update: fn(d: self ref Dentry); + print: fn(d: self ref Dentry); +}; + +Uname, Uids, Umode, Uqid, Usize, Utime: con 1<<iota; # Dentry.mod + +# +# disc structure: +# Tag: pad[2] tag[2] path[4] +Tagsize: con 2+2+4; + +Tag: adt +{ + tag: int; + path: int; + + unpack: fn(a: array of byte): Tag; + pack: fn(t: self Tag, a: array of byte); +}; + +Superb: adt +{ + iob: ref Iobuf; + + fstart: int; + fsize: int; + tfree: int; + qidgen: int; # generator for unique ids + + fsok: int; + + fbuf: array of byte; # nfree[4] free[FEPERBLK*4]; aliased into containing block + + get: fn(dev: ref Device, flags: int): ref Superb; + touched: fn(s: self ref Superb); + put: fn(s: self ref Superb); + print: fn(s: self ref Superb); + + pack: fn(s: self ref Superb, a: array of byte); + unpack: fn(a: array of byte): ref Superb; +}; + +Device: adt +{ + fd: ref Sys->FD; + ronly: int; + # could put locks here if necessary + # partitioning by ds(3) +}; + +# +# one for each locked qid +# +Tlock: adt +{ + dev: ref Device; + time: int; + qpath: int; + file: cyclic ref File; # TO DO: probably not needed +}; + +File: adt +{ + qlock: chan of int; + qid: Qid; + wpath: ref Wpath; + tlock: cyclic ref Tlock; # if file is locked + fs: ref Device; + addr: int; + slot: int; + lastra: int; # read ahead address + fid: int; + uid: int; + open: int; + cons: int; # if opened by console + doffset: big; # directory reading + dvers: int; + dslot: int; + + new: fn(fid: int): ref File; + access: fn(f: self ref File, d: ref Dentry, mode: int): int; + lock: fn(f: self ref File); + unlock: fn(f: self ref File); +}; + +FREAD, FWRITE, FREMOV, FWSTAT: con 1<<iota; # File.open + +Chan: adt +{ + fd: ref Sys->FD; # fd request came in on +# rlock, wlock: QLock; # lock for reading/writing messages on cp + flags: int; + flist: list of ref File; # active files + fqlock: chan of int; +# reflock: RWLock; # lock for Tflush + msize: int; # version + + new: fn(fd: ref Sys->FD): ref Chan; + getfid: fn(c: self ref Chan, fid: int, flag: int): ref File; + putfid: fn(c: self ref Chan, f: ref File); + flock: fn(nil: self ref Chan); + funlock: fn(nil: self ref Chan); +}; + +Hiob: adt +{ + link: ref Iobuf; # TO DO: eliminate circular list + lk: ref Lock; + niob: int; + + newbuf: fn(h: self ref Hiob): ref Iobuf; +}; + +Iobuf: adt +{ + qlock: chan of int; + dev: ref Device; + fore: cyclic ref Iobuf; # lru hash chain + back: cyclic ref Iobuf; # for lru + iobuf: array of byte; # only active while locked + xiobuf: array of byte; # "real" buffer pointer + addr: int; + flags: int; + + get: fn(dev: ref Device, addr: int, flags: int):ref Iobuf; + put: fn(iob: self ref Iobuf); + lock: fn(iob: self ref Iobuf); + canlock: fn(iob: self ref Iobuf): int; + unlock: fn(iob: self ref Iobuf); + + checktag: fn(iob: self ref Iobuf, tag: int, qpath: int): int; + settag: fn(iob: self ref Iobuf, tag: int, qpath: int); +}; + +Wpath: adt +{ + up: cyclic ref Wpath; # pointer upwards in path + addr: int; # directory entry addr + slot: int; # directory entry slot +}; + +# +# error codes generated from the file server +# +Eaccess: con "access permission denied"; +Ealloc: con "phase error -- directory entry not allocated"; +Eauth: con "authentication failed"; +Eauthmsg: con "kfs: authentication not required"; +Ebadspc: con "attach -- bad specifier"; +Ebadu: con "attach -- privileged user"; +Ebroken: con "close/read/write -- lock is broken"; +Echar: con "bad character in directory name"; +Econvert: con "protocol botch"; +Ecount: con "read/write -- count too big"; +Edir1: con "walk -- in a non-directory"; +Edir2: con "create -- in a non-directory"; +Edot: con "create -- . and .. illegal names"; +Eempty: con "remove -- directory not empty"; +Eentry: con "directory entry not found"; +Eexist: con "create -- file exists"; +Efid: con "unknown fid"; +Efidinuse: con "fid already in use"; +Efull: con "file system full"; +Elocked: con "open/create -- file is locked"; +Emode: con "open/create -- unknown mode"; +Ename: con "create/wstat -- bad character in file name"; +Enotd: con "wstat -- attempt to change directory"; +Enotg: con "wstat -- not in group"; +Enotl: con "wstat -- attempt to change length"; +Enotm: con "wstat -- unknown type/mode"; +Enotu: con "wstat -- not owner"; +Eoffset: con "read/write -- offset negative"; +Eopen: con "read/write -- on non open fid"; +Ephase: con "phase error -- cannot happen"; +Eqid: con "phase error -- qid does not match"; +Eqidmode: con "wstat -- qid.qtype/dir.mode mismatch"; +Eronly: con "file system read only"; +Ersc: con "it's russ's fault. bug him."; +Esystem: con "kfs system error"; +Etoolong: con "name too long"; +Etoobig: con "write -- file size limit"; +Ewalk: con "walk -- too many (system wide)"; + +# +# tags on block +# +Tnone, +Tsuper, # the super block +Tdir, # directory contents +Tind1, # points to blocks +Tind2, # points to Tind1 +Tfile, # file contents +Tfree, # in free list +Tbuck, # cache fs bucket +Tvirgo, # fake worm virgin bits +Tcache, # cw cache things +MAXTAG: con iota; + +# +# flags to Iobuf.get +# + Bread, # read the block if miss + Bprobe, # return null if miss + Bmod, # set modified bit in buffer + Bimm, # set immediate bit in buffer + Bres: # never renamed + con 1<<iota; + +# +# check flags +# + Crdall, # read all files + Ctag, # rebuild tags + Cpfile, # print files + Cpdir, # print directories + Cfree, # rebuild free list + Cream, # clear all bad tags + Cbad, # clear all bad blocks + Ctouch, # touch old dir and indir + Cquiet: # report just nasty things + con 1<<iota; + +# +# buffer size variables, determined by RBUFSIZE +# +RBUFSIZE: int; +BUFSIZE: int; +DIRPERBUF: int; +INDPERBUF: int; +INDPERBUF2: int; +FEPERBUF: int; + +emptyblock: array of byte; + +wrenfd: ref Sys->FD; +thedevice: ref Device; +devnone: ref Device; +wstatallow := 0; +writeallow := 0; +writegroup := 0; + +ream := 0; +readonly := 0; +noatime := 0; +localfs: con 1; +conschan: ref Chan; +consuid := -1; +consgid := -1; +debug := 0; +kfsname: string; +consoleout: chan of string; +mainlock: ref Lock; +pids: list of int; + +noqid: Qid; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + styx = load Styx Styx->PATH; + daytime = load Daytime Daytime->PATH; + + styx->init(); + + + arg := load Arg Arg->PATH; + if(arg == nil) + error(sys->sprint("can't load %s: %r", Arg->PATH)); + arg->init(args); + arg->setusage("disk/kfs [-r [-b bufsize]] [-cADPRW] [-n name] kfsfile"); + bufsize := 1024; + nocheck := 0; + while((o := arg->opt()) != 0) + case o { + 'c' => nocheck = 1; + 'r' => ream = 1; + 'b' => bufsize = int arg->earg(); + 'D' => debug = !debug; + 'P' => writeallow = 1; + 'W' => wstatallow = 1; + 'R' => readonly = 1; + 'A' => noatime = 1; # mainly useful for flash + 'n' => kfsname = arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + devnone = ref Device(nil, 1); + mainlock = Lock.new(); + + conschan = Chan.new(nil); + conschan.msize = Styx->MAXRPC; + + mode := Sys->ORDWR; + if(readonly) + mode = Sys->OREAD; + wrenfd = sys->open(hd args, mode); + if(wrenfd == nil) + error(sys->sprint("can't open %s: %r", hd args)); + thedevice = ref Device(wrenfd, readonly); + if(ream){ + if(bufsize <= 0 || bufsize % 512 || bufsize > MAXBUFSIZE) + error(sys->sprint("invalid block size %d", bufsize)); + RBUFSIZE = bufsize; + wrenream(thedevice); + }else{ + if(!wreninit(thedevice)) + error("kfs magic in trouble"); + } + BUFSIZE = RBUFSIZE - Tagsize; + DIRPERBUF = BUFSIZE / Dentrysize; + INDPERBUF = BUFSIZE / 4; + INDPERBUF2 = INDPERBUF * INDPERBUF; + FEPERBUF = (BUFSIZE - Super1size - 4) / 4; + emptyblock = array[RBUFSIZE] of {* => byte 0}; + + iobufinit(30); + + if(ream){ + superream(thedevice, SUPERADDR); + rootream(thedevice, ROOTADDR); + wstatallow = writeallow = 1; + } + if(wrencheck(wrenfd)) + error("kfs super/root in trouble"); + + if(!ream && !superok(0)){ + sys->print("kfs needs check\n"); + if(!nocheck) + check(thedevice, Cquiet|Cfree); + } + + (d, e) := Dentry.geta(thedevice, ROOTADDR, 0, QPROOT, Bread); + if(d != nil && !(d.mode & DDIR)) + e = "not a directory"; + if(e != nil) + error("bad root: "+e); + if(debug) + d.print(); + d.put(); + + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + + sys->pctl(Sys->NEWFD, wrenfd.fd :: 0 :: 1 :: 2 :: nil); + wrenfd = sys->fildes(wrenfd.fd); + thedevice.fd = wrenfd; + + c := chan of int; + + if(Buffering){ + spawn syncproc(c); + pid := <-c; + if(pid) + pids = pid :: pids; + } + spawn consinit(c); + pid := <- c; + if(pid) + pids = pid :: pids; + + spawn kfs(sys->fildes(0)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "kfs: %s\n", s); + for(; pids != nil; pids = tl pids) + kill(hd pids); + raise "fail:error"; +} + +panic(s: string) +{ + sys->fprint(sys->fildes(2), "kfs: panic: %s\n", s); + for(; pids != nil; pids = tl pids) + kill(hd pids); + raise "panic"; +} + +syncproc(c: chan of int) +{ + c <-= 0; +} + +shutdown() +{ + for(; pids != nil; pids = tl pids) + kill(hd pids); + # TO DO: when Bmod deferred, must sync + # sync super block + if(superok(1)){ + # ; + } + iobufclear(); +} + +kill(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +# +# limited file system support for console +# +kattach(fid: int): string +{ + return applycons(ref Tmsg.Attach(1, fid, NOFID, "adm", "")).t1; +} + +kopen(oldfid: int, newfid: int, names: array of string, mode: int): string +{ + (r1, e1) := applycons(ref Tmsg.Walk(1, oldfid, newfid, names)); + if(r1 != nil){ + pick m := r1 { + Walk => + if(len m.qids != len names){ + kclose(newfid); + cprint(Eexist); + return Eexist; + } + * => + return "unexpected reply"; + } + (r1, e1) = applycons(ref Tmsg.Open(1, newfid, mode)); + if(e1 != nil){ + kclose(newfid); + cprint(sys->sprint("open: %s", e1)); + } + } + return e1; +} + +kread(fid: int, offset: int, nbytes: int): (array of byte, string) +{ + (r, e) := applycons(ref Tmsg.Read(1, fid, big offset, nbytes)); + if(r != nil){ + pick m := r { + Read => + return (m.data, nil); + * => + return (nil, "unexpected reply"); + } + } + cprint(sys->sprint("read error: %s", e)); + return (nil, e); +} + +kclose(fid: int) +{ + applycons(ref Tmsg.Clunk(1, fid)); +} + +applycons(t: ref Tmsg): (ref Rmsg, string) +{ + r := apply(conschan, t); + pick m := r { + Error => + if(debug) + cprint(sys->sprint("%s: %s\n", t.text(), m.ename)); + return (nil, m.ename); + } + return (r, nil); +} + +# +# always reads /adm/users in userinit(), then +# optionally serves the command file, if used. +# +Req: adt { + nbytes: int; + rc: chan of (array of byte, string); +}; + +consinit(c: chan of int) +{ + kattach(FID1); + userinit(); + if(kfsname == nil){ + c <-= 0; + exit; + } + cfname := "kfs."+kfsname+".cmd"; + sys->bind("#s", "/chan", Sys->MBEFORE); + file := sys->file2chan("/chan", cfname); + if(file == nil) + error(sys->sprint("can't create /chan/%s: %r", cfname)); + c <-= sys->pctl(0, nil); + consc := chan of string; + checkend := chan of int; + cdata: array of byte; + pending: ref Req; + cfid := -1; + for(;;) alt{ + (nil, nbytes, fid, rc) := <-file.read => + if(rc == nil) + break; + if(cfid == -1) + cfid = fid; + if(fid != cfid || pending != nil){ + rc <-= (nil, "kfs.cmd is busy"); + break; + } + if(cdata != nil){ + cdata = reply(rc, nbytes, cdata); + break; + } + if(nbytes <= 0 || consoleout == nil){ + rc <-= (nil, nil); + break; + } + pending = ref Req(nbytes, rc); + consc = consoleout; + (nil, data, fid, wc) := <-file.write => + if(cfid == -1) + cfid = fid; + if(wc == nil){ + if(fid == cfid){ + cfid = -1; + pending = nil; + cdata = nil; # discard unread data from last command + if((consc = consoleout) == nil) + consc = chan of string; + } + break; + } + if(fid != cfid){ + wc <-= (0, "kfs.cmd is busy"); + break; + } + (nf, fld) := sys->tokenize(string data, " \t\n\r"); + if(nf < 1){ + wc <-= (0, "illegal kfs request"); + break; + } + case hd fld { + "check" => + if(consoleout != nil){ + wc <-= (0, "check in progress"); + break; + } + f := 0; + if(nf > 1){ + f = checkflags(hd tl fld); + if(f < 0){ + wc <-= (0, "illegal check flag: "+hd tl fld); + break; + } + } + consoleout = chan of string; + spawn checkproc(checkend, f); + wc <-= (len data, nil); + consc = consoleout; + "users" or "user" => + cmd_users(); + wc <-= (len data, nil); + "sync" => + # nothing TO DO until writes are buffered + wc <-= (len data, nil); + "allow" => + wstatallow = writeallow = 1; + wc <-= (len data, nil); + "allowoff" or "disallow" => + wstatallow = writeallow = 0; + wc <-= (len data, nil); + * => + wc <-= (0, "unknown kfs request"); + continue; + } + <-checkend => + consoleout = nil; + consc = chan of string; + s := <-consc => + #sys->print("<-%s\n", s); + req := pending; + pending = nil; + if(req != nil) + cdata = reply(req.rc, req.nbytes, array of byte s); + else + cdata = array of byte s; + if(cdata != nil && cfid != -1) + consc = chan of string; + } +} + +reply(rc: chan of (array of byte, string), nbytes: int, a: array of byte): array of byte +{ + if(len a < nbytes) + nbytes = len a; + rc <-= (a[0:nbytes], nil); + if(nbytes == len a) + return nil; + return a[nbytes:]; +} + +checkproc(c: chan of int, flags: int) +{ + mainlock.lock(); + check(thedevice, flags); + mainlock.unlock(); + c <-= 1; +} + +# +# normal kfs service +# +kfs(rfd: ref Sys->FD) +{ + cp := Chan.new(rfd); + while((t := Tmsg.read(rfd, cp.msize)) != nil){ + if(debug) + sys->print("<- %s\n", t.text()); + r := apply(cp, t); + pick m := r { + Error => + r.tag = t.tag; + } + if(debug) + sys->print("-> %s\n", r.text()); + rbuf := r.pack(); + if(rbuf == nil) + panic("Rmsg.pack"); + if(sys->write(rfd, rbuf, len rbuf) != len rbuf) + panic("mount write"); + } + shutdown(); +} + +apply(cp: ref Chan, t: ref Tmsg): ref Rmsg +{ + mainlock.lock(); # TO DO: this is just to keep console and kfs from colliding + r: ref Rmsg; + pick m := t { + Readerror => + error(sys->sprint("mount read error: %s", m.error)); + Version => + r = rversion(cp, m); + Auth => + r = rauth(cp, m); + Flush => + r = rflush(cp, m); + Attach => + r = rattach(cp, m); + Walk => + r = rwalk(cp, m); + Open => + r = ropen(cp, m); + Create => + r = rcreate(cp, m); + Read => + r = rread(cp, m); + Write => + r = rwrite(cp, m); + Clunk => + r = rclunk(cp, m); + Remove => + r = rremove(cp, m); + Stat => + r = rstat(cp, m); + Wstat => + r = rwstat(cp, m); + * => + panic("Styx mtype"); + return nil; + } + mainlock.unlock(); + return r; +} + +rversion(cp: ref Chan, t: ref Tmsg.Version): ref Rmsg +{ + cp.msize = RBUFSIZE+IOHDRSZ; + if(cp.msize < Styx->MAXRPC) + cp.msize = Styx->MAXRPC; + (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION); + if(msize < 256) + return ref Rmsg.Error(t.tag, "message size too small"); + return ref Rmsg.Version(t.tag, msize, version); +} + +rauth(nil: ref Chan, t: ref Tmsg.Auth): ref Rmsg +{ + return ref Rmsg.Error(t.tag, Eauthmsg); +} + +rflush(nil: ref Chan, t: ref Tmsg.Flush): ref Rmsg +{ + # runlock(cp.reflock); + # wlock(cp.reflock); + # wunlock(cp.reflock); + # rlock(cp.reflock); + return ref Rmsg.Flush(t.tag); +} + +err(t: ref Tmsg, s: string): ref Rmsg.Error +{ + return ref Rmsg.Error(t.tag, s); +} + +ferr(t: ref Tmsg, s: string, file: ref File, p: ref Iobuf): ref Rmsg.Error +{ + if(p != nil) + p.put(); + if(file != nil) + file.unlock(); + return ref Rmsg.Error(t.tag, s); +} + +File.new(fid: int): ref File +{ + f := ref File; + f.qlock = chan[1] of int; + f.fid = fid; + f.cons = 0; + f.tlock = nil; + f.wpath = nil; + f.doffset = big 0; + f.dvers = 0; + f.dslot = 0; + f.uid = None; + f.cons = 0; +# f.cuid = None; + return f; +} + +# +# returns a locked file structure +# + +Chan.getfid(cp: self ref Chan, fid: int, flag: int): ref File +{ + if(fid == NOFID) + return nil; + cp.flock(); + for(l := cp.flist; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid){ + cp.funlock(); + if(flag) + return nil; # fid in use + f.lock(); + if(f.fid == fid) + return f; + f.unlock(); + cp.flock(); + } + } + if(flag == 0){ + sys->print("kfs: cannot find %H.%ud", cp, fid); + cp.funlock(); + return nil; + } + f := File.new(fid); + f.lock(); + cp.flist = f :: cp.flist; + cp.funlock(); + return f; +} + +Chan.putfid(cp: self ref Chan, f: ref File) +{ + cp.flock(); + nl: list of ref File; + for(x := cp.flist; x != nil; x = tl x) + if(hd x != f) + nl = hd x :: nl; + cp.flist = nl; + cp.funlock(); + f.unlock(); +} + +File.lock(f: self ref File) +{ + f.qlock <-= 1; +} + +File.unlock(f: self ref File) +{ + <-f.qlock; +} + +Chan.new(fd: ref Sys->FD): ref Chan +{ + c := ref Chan; + c.fd = fd; + c.fqlock = chan[1] of int; +# rlock, wlock: QLock; # lock for reading/writing messages on cp + c.flags = 0; +# reflock: RWLock; # lock for Tflush + c.msize = 0; # set by rversion + return c; +} + +Chan.flock(c: self ref Chan) +{ + c.fqlock <-= 1; +} + +Chan.funlock(c: self ref Chan) +{ + <-c.fqlock; +} + +rattach(cp: ref Chan, t: ref Tmsg.Attach): ref Rmsg +{ + if(t.aname != "" && t.aname != "main") + return err(t, Ebadspc); + file := cp.getfid(t.fid, 1); + if(file == nil) + return err(t, Efidinuse); + p := Iobuf.get(thedevice, ROOTADDR, Bread); + if(p == nil){ + cp.putfid(file); + return err(t, "can't access root block"); + } + d := Dentry.get(p, 0); + if(d == nil || p.checktag(Tdir, QPROOT) || (d.mode & DALLOC) == 0 || (d.mode & DDIR) == 0){ + p.put(); + cp.putfid(file); + return err(t, Ealloc); + } + if(file.access(d, DEXEC)){ + p.put(); + cp.putfid(file); + return err(t, Eaccess); + } + d.access(FREAD, file.uid); + file.fs = thedevice; + file.qid = d.qid; + file.addr = p.addr; + file.slot = 0; + file.open = 0; + file.uid = strtouid(t.uname); + file.wpath = nil; + p.put(); + qid := file.qid; + file.unlock(); + return ref Rmsg.Attach(t.tag, qid); +} + +clone(nfile: ref File, file: ref File) +{ + nfile.qid = file.qid; + nfile.wpath = file.wpath; + nfile.fs = file.fs; + nfile.addr = file.addr; + nfile.slot = file.slot; + nfile.uid = file.uid; +# nfile.cuid = None; + nfile.open = file.open & ~FREMOV; +} + +walkname(file: ref File, wname: string): (string, Qid) +{ + # + # File must not have been opened for I/O by an open + # or create message and must represent a directory. + # + if(file.open != 0) + return (Emode, noqid); + + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return (e, noqid); + if(!(d.mode & DDIR)){ + d.put(); + return (Edir1, noqid); + } + + # + # For walked elements the implied user must + # have permission to search the directory. + # + if(file.access(d, DEXEC)){ + d.put(); + return (Eaccess, noqid); + } + d.access(FREAD, file.uid); + + if(wname == "." || wname == ".." && file.wpath == nil){ + d.put(); + return (nil, file.qid); + } + + d1: ref Dentry; # entry for wname, if found + slot: int; + + if(wname == ".."){ + d.put(); + addr := file.wpath.addr; + slot = file.wpath.slot; + (d1, e) = Dentry.geta(file.fs, addr, slot, QPNONE, Bread); + if(d1 == nil) + return (e, noqid); + file.wpath = file.wpath.up; + }else{ + + Search: + for(addr := 0; ; addr++){ + if(d.iob == nil){ + (d, e) = Dentry.getd(file, Bread); + if(d == nil) + return (e, noqid); + } + p1 := d.getblk1(addr, 0); + if(p1 == nil || p1.checktag(Tdir, int d.qid.path)){ + if(p1 != nil) + p1.put(); + return (Eentry, noqid); + } + for(slot = 0; slot < DIRPERBUF; slot++){ + d1 = Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)) + continue; + if(wname != d1.name) + continue; + # + # update walk path + # + file.wpath = ref Wpath(file.wpath, file.addr, file.slot); + slot += DIRPERBUF*addr; + break Search; + } + p1.put(); + } + d.put(); + } + + file.addr = d1.iob.addr; + file.slot = slot; + file.qid = d1.qid; + d1.put(); + return (nil, file.qid); +} + +rwalk(cp: ref Chan, t: ref Tmsg.Walk): ref Rmsg +{ + nfile, tfile: ref File; + q: Qid; + + # The file identified by t.fid must be valid in the + # current session and must not have been opened for I/O + # by an open or create message. + + if((file := cp.getfid(t.fid, 0)) == nil) + return err(t, Efid); + if(file.open != 0) + return ferr(t, Emode, file, nil); + + # If newfid is not the same as fid, allocate a new file; + # a side effect is checking newfid is not already in use (error); + # if there are no names to walk this will be equivalent to a + # simple 'clone' operation. + # Otherwise, fid and newfid are the same and if there are names + # to walk make a copy of 'file' to be used during the walk as + # 'file' must only be updated on success. + # Finally, it's a no-op if newfid is the same as fid and t.nwname + # is 0. + + nwqid := 0; + if(t.newfid != t.fid){ + if((nfile = cp.getfid(t.newfid, 1)) == nil) + return ferr(t, Efidinuse, file, nil); + } + else if(len t.names != 0) + nfile = tfile = File.new(NOFID); + else{ + file.unlock(); + return ref Rmsg.Walk(t.tag, nil); + } + clone(nfile, file); + + r := ref Rmsg.Walk(t.tag, array[len t.names] of Qid); + error: string; + for(nwname := 0; nwname < len t.names; nwname++){ + (error, q) = walkname(nfile, t.names[nwname]); + if(error != nil) + break; + r.qids[nwqid++] = q; + } + + if(len t.names == 0){ + + # Newfid must be different to fid (see above) + # so this is a simple 'clone' operation - there's + # nothing to do except unlock unless there's + # an error. + + nfile.unlock(); + if(error != nil) + cp.putfid(nfile); + }else if(nwqid < len t.names){ + # + # Didn't walk all elements, 'clunk' nfile + # and leave 'file' alone. + # Clear error if some of the elements were + # walked OK. + # + if(nfile != tfile) + cp.putfid(nfile); + if(nwqid != 0) + error = nil; + r.qids = r.qids[0:nwqid]; + }else{ + # + # Walked all elements. If newfid is the same + # as fid must update 'file' from the temporary + # copy used during the walk. + # Otherwise just unlock (when using tfile there's + # no need to unlock as it's a local). + # + if(nfile == tfile){ + file.qid = nfile.qid; + file.wpath = nfile.wpath; + file.addr = nfile.addr; + file.slot = nfile.slot; + }else + nfile.unlock(); + } + file.unlock(); + + if(error != nil) + return err(t, error); + return r; +} + +ropen(cp: ref Chan, f: ref Tmsg.Open): ref Rmsg +{ + wok := cp == conschan || writeallow; + + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + + # + # if remove on close, check access here + # + ro := isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup)); + if(f.mode & ORCLOSE){ + if(ro) + return ferr(f, Eronly, file, nil); + # + # check on parent directory of file to be deleted + # + if(file.wpath == nil || file.wpath.addr == file.addr) + return ferr(f, Ephase, file, nil); + p := Iobuf.get(file.fs, file.wpath.addr, Bread); + if(p == nil || p.checktag(Tdir, QPNONE)) + return ferr(f, Ephase, file, p); + if((d := Dentry.get(p, file.wpath.slot)) == nil || !(d.mode & DALLOC)) + return ferr(f, Ephase, file, p); + if(file.access(d, DWRITE)) + return ferr(f, Eaccess, file, p); + p.put(); + } + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + p := d.iob; + qid := d.qid; + fmod: int; + case f.mode & 7 { + + OREAD => + if(file.access(d, DREAD) && !wok) + return ferr(f, Eaccess, file, p); + fmod = FREAD; + + OWRITE => + if((d.mode & DDIR) || (file.access(d, DWRITE) && !wok)) + return ferr(f, Eaccess, file, p); + if(ro) + return ferr(f, Eronly, file, p); + fmod = FWRITE; + + ORDWR => + if((d.mode & DDIR) + || (file.access(d, DREAD) && !wok) + || (file.access(d, DWRITE) && !wok)) + return ferr(f, Eaccess, file, p); + if(ro) + return ferr(f, Eronly, file, p); + fmod = FREAD+FWRITE; + + OEXEC => + if((d.mode & DDIR) || (file.access(d, DEXEC) && !wok)) + return ferr(f, Eaccess, file, p); + fmod = FREAD; + + * => + return ferr(f, Emode, file, p); + } + if(f.mode & OTRUNC){ + if((d.mode & DDIR) || (file.access(d, DWRITE) && !wok)) + return ferr(f, Eaccess, file, p); + if(ro) + return ferr(f, Eronly, file, p); + } + if(d.mode & DLOCK){ + if((t := tlocked(file, d)) == nil) + return ferr(f, Elocked, file, p); + file.tlock = t; + t.file = file; + } + if(f.mode & ORCLOSE) + fmod |= FREMOV; + file.open = fmod; + if((f.mode & OTRUNC) && !(d.mode & DAPND)){ + d.trunc(file.uid); + qid.vers = d.qid.vers; + } + file.lastra = 1; + p.put(); + file.unlock(); + return ref Rmsg.Open(f.tag, qid, cp.msize-IOHDRSZ); +} + +rcreate(cp: ref Chan, f: ref Tmsg.Create): ref Rmsg +{ + wok := cp == conschan || writeallow; + + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup))) + return ferr(f, Eronly, file, nil); + + (d, e) := Dentry.getd(file, Bread); + if(e != nil) + return ferr(f, e, file, nil); + p := d.iob; + if(!(d.mode & DDIR)) + return ferr(f, Edir2, file, p); + if(file.access(d, DWRITE) && !wok) + return ferr(f, Eaccess, file, p); + d.access(FREAD, file.uid); + + # + # Check the name is valid and will fit in an old + # directory entry. + # + if((l := checkname9p2(f.name)) == 0) + return ferr(f, Ename, file, p); + if(l+1 > NAMELEN) + return ferr(f, Etoolong, file, p); + if(f.name == "." || f.name == "..") + return ferr(f, Edot, file, p); + + addr1 := 0; # block with first empty slot, if any + slot1 := 0; + for(addr := 0; ; addr++){ + if((p1 := d.getblk(addr, 0)) == nil){ + if(addr1 != 0) + break; + p1 = d.getblk(addr, Tdir); + } + if(p1 == nil) + return ferr(f, Efull, file, p); + if(p1.checktag(Tdir, int d.qid.path)){ + p1.put(); + return ferr(f, Ephase, file, p); + } + for(slot := 0; slot < DIRPERBUF; slot++){ + d1 := Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)){ + if(addr1 == 0){ + addr1 = p1.addr; + slot1 = slot + addr*DIRPERBUF; + } + continue; + } + if(f.name == d1.name){ + p1.put(); + return ferr(f, Eexist, file, p); + } + } + p1.put(); + } + + fmod: int; + + case f.mode & 7 { + OEXEC or + OREAD => # seems only useful to make directories + fmod = FREAD; + + OWRITE => + fmod = FWRITE; + + ORDWR => + fmod = FREAD+FWRITE; + + * => + return ferr(f, Emode, file, p); + } + if(f.perm & DMDIR) + if((f.mode & OTRUNC) || (f.perm & DMAPPEND) || (fmod & FWRITE)) + return ferr(f, Eaccess, file, p); + + # do it + + path := qidpathgen(file.fs); + if((p1 := Iobuf.get(file.fs, addr1, Bread|Bimm|Bmod)) == nil) + return ferr(f, Ephase, file, p); + d1 := Dentry.get(p1, slot1); + if(d1 == nil || p1.checktag(Tdir, int d.qid.path)){ + p.put(); + return ferr(f, Ephase, file, p1); + } + if(d1.mode & DALLOC){ + p.put(); + return ferr(f, Ephase, file, p1); + } + + d1.name = f.name; + if(cp == conschan){ + d1.uid = consuid; + d1.gid = consgid; + } + else{ + d1.uid = file.uid; + d1.gid = d.gid; + f.perm &= d.mode | ~8r666; + if(f.perm & DMDIR) + f.perm &= d.mode | ~8r777; + } + d1.qid.path = big path; + d1.qid.vers = 0; + d1.mode = DALLOC | (f.perm & 8r777); + if(f.perm & DMDIR) + d1.mode |= DDIR; + if(f.perm & DMAPPEND) + d1.mode |= DAPND; + t: ref Tlock; + if(f.perm & DMEXCL){ + d1.mode |= DLOCK; + t = tlocked(file, d1); + # if nil, out of tlock structures + } + d1.access(FWRITE, file.uid); + d1.change(~0); + d1.update(); + qid := mkqid(path, 0, d1.mode); + p1.put(); + d.change(~0); + d.access(FWRITE, file.uid); + d.update(); + p.put(); + + # + # do a walk to new directory entry + # + file.wpath = ref Wpath(file.wpath, file.addr, file.slot); + file.qid = qid; + file.tlock = t; + if(t != nil) + t.file = file; + file.lastra = 1; + if(f.mode & ORCLOSE) + fmod |= FREMOV; + file.open = fmod; + file.addr = addr1; + file.slot = slot1; + file.unlock(); + return ref Rmsg.Create(f.tag, qid, cp.msize-IOHDRSZ); +} + +dirread(cp: ref Chan, f: ref Tmsg.Read, file: ref File, d: ref Dentry): ref Rmsg +{ + p1: ref Iobuf; + d1: ref Dentry; + + count := f.count; + data := array[count] of byte; + offset := f.offset; + iounit := cp.msize-IOHDRSZ; + + # Pick up where we left off last time if nothing has changed, + # otherwise must scan from the beginning. + + addr, slot: int; + start: big; + + if(offset == file.doffset){ # && file.qid.vers == file.dvers + addr = file.dslot/DIRPERBUF; + slot = file.dslot%DIRPERBUF; + start = offset; + } + else{ + addr = 0; + slot = 0; + start = big 0; + } + + nread := 0; +Dread: + for(;;){ + if(d.iob == nil){ + # + # This is just a check to ensure the entry hasn't + # gone away during the read of each directory block. + # + e: string; + (d, e) = Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + } + p1 = d.getblk1(addr, 0); + if(p1 == nil) + break; + if(p1.checktag(Tdir, QPNONE)) + return ferr(f, Ephase, file, p1); + + for(; slot < DIRPERBUF; slot++){ + d1 = Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)) + continue; + dir := dir9p2(d1); + n := styx->packdirsize(dir); + if(n > count-nread){ + p1.put(); + break Dread; + } + data[nread:] = styx->packdir(dir); + start += big n; + if(start < offset) + continue; + if(count < n){ + p1.put(); + break Dread; + } + count -= n; + nread += n; + offset += big n; + } + p1.put(); + slot = 0; + addr++; + } + + file.doffset = offset; + file.dvers = file.qid.vers; + file.dslot = slot+DIRPERBUF*addr; + + d.put(); + file.unlock(); + return ref Rmsg.Read(f.tag, data[0:nread]); +} + +rread(cp: ref Chan, f: ref Tmsg.Read): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + if(!(file.open & FREAD)) + return ferr(f, Eopen, file, nil); + count := f.count; + iounit := cp.msize-IOHDRSZ; + if(count < 0 || count > iounit) + return ferr(f, Ecount, file, nil); + offset := f.offset; + if(offset < big 0) + return ferr(f, Eoffset, file, nil); + + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + if((t := file.tlock) != nil){ + tim := now(); + if(t.time < tim || t.file != file){ + d.put(); + return ferr(f, Ebroken, file, nil); + } + # renew the lock + t.time = tim + TLOCK; + } + d.access(FREAD, file.uid); + if(d.mode & DDIR) + return dirread(cp, f, file, d); + + if(offset+big count > d.size) + count = int (d.size - offset); + if(count < 0) + count = 0; + data := array[count] of byte; + nread := 0; + while(count > 0){ + if(d.iob == nil){ + # must check and reacquire entry + (d, e) = Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + } + addr := int (offset / big BUFSIZE); + if(addr == file.lastra+1) + ; # dbufread(p, d, addr+1); + file.lastra = addr; + o := int (offset % big BUFSIZE); + n := BUFSIZE - o; + if(n > count) + n = count; + p1 := d.getblk1(addr, 0); + if(p1 != nil){ + if(p1.checktag(Tfile, QPNONE)){ + p1.put(); + return ferr(f, Ephase, file, nil); + } + data[nread:] = p1.iobuf[o:o+n]; + p1.put(); + }else + data[nread:] = emptyblock[0:n]; + count -= n; + nread += n; + offset += big n; + } + d.put(); + file.unlock(); + return ref Rmsg.Read(f.tag, data[0:nread]); +} + +rwrite(cp: ref Chan, f: ref Tmsg.Write): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + if(!(file.open & FWRITE)) + return ferr(f, Eopen, file, nil); + if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup))) + return ferr(f, Eronly, file, nil); + count := len f.data; + if(count < 0 || count > cp.msize-IOHDRSZ) + return ferr(f, Ecount, file, nil); + offset := f.offset; + if(offset < big 0) + return ferr(f, Eoffset, file, nil); + + (d, e) := Dentry.getd(file, Bread|Bmod); + if(d == nil) + return ferr(f, e, file, nil); + if((t := file.tlock) != nil){ + tim := now(); + if(t.time < tim || t.file != file){ + d.put(); + return ferr(f, Ebroken, file, nil); + } + # renew the lock + t.time = tim + TLOCK; + } + d.access(FWRITE, file.uid); + if(d.mode & DAPND) + offset = d.size; + end := offset + big count; + if(end > d.size){ + if(end > MAXFILESIZE) + return ferr(f, Etoobig, file, nil); + d.size = end; + d.change(Usize); + } + d.update(); + + nwrite := 0; + while(count > 0){ + if(d.iob == nil){ + # must check and reacquire entry + (d, e) = Dentry.getd(file, Bread|Bmod); + if(d == nil) + return ferr(f, e, file, nil); + } + addr := int (offset / big BUFSIZE); + o := int (offset % big BUFSIZE); + n := BUFSIZE - o; + if(n > count) + n = count; + qpath := int d.qid.path; + p1 := d.getblk1(addr, Tfile); + if(p1 == nil) + return ferr(f, Efull, file, nil); + if(p1.checktag(Tfile, qpath)){ + p1.put(); + return ferr(f, Ealloc, file, nil); + } + p1.iobuf[o:] = f.data[nwrite:nwrite+n]; + p1.flags |= Bmod; + p1.put(); + count -= n; + nwrite += n; + offset += big n; + } + d.put(); + file.unlock(); + return ref Rmsg.Write(f.tag, nwrite); +} + +doremove(f: ref File, iscon: int): string +{ + if(isro(f.fs) || f.cons == 0 && (writegroup && !ingroup(f.uid, writegroup))) + return Eronly; + # + # check permission on parent directory of file to be deleted + # + if(f.wpath == nil || f.wpath.addr == f.addr) + return Ephase; + (d1, e1) := Dentry.geta(f.fs, f.wpath.addr, f.wpath.slot, QPNONE, Bread); + if(e1 != nil) + return e1; + if(!iscon && f.access(d1, DWRITE)){ + d1.put(); + return Eaccess; + } + d1.access(FWRITE, f.uid); + d1.put(); + + # + # check on file to be deleted + # + (d, e) := Dentry.getd(f, Bread); + if(e != nil) + return e; + + # + # if deleting a directory, make sure it is empty + # + if(d.mode & DDIR) + for(addr:=0; (p1 := d.getblk(addr, 0)) != nil; addr++){ + if(p1.checktag(Tdir, int d.qid.path)){ + p1.put(); + d.put(); + return Ephase; + } + for(slot:=0; slot<DIRPERBUF; slot++){ + d1 = Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)) + continue; + p1.put(); + d.put(); + return Eempty; + } + p1.put(); + } + + # + # do it + # + d.trunc(f.uid); + d.buf[0:] = emptyblock[0:Dentrysize]; + d.put(); + return nil; +} + +clunk(cp: ref Chan, file: ref File, remove: int, wok: int): string +{ + if((t := file.tlock) != nil){ + if(t.file == file) + t.time = 0; # free the lock + file.tlock = nil; + } + if(remove) + error := doremove(file, wok); + file.open = 0; + file.wpath = nil; + cp.putfid(file); + + return error; +} + +rclunk(cp: ref Chan, t: ref Tmsg.Clunk): ref Rmsg +{ + if((file := cp.getfid(t.fid, 0)) == nil) + return err(t, Efid); + clunk(cp, file, file.open & FREMOV, 0); + return ref Rmsg.Clunk(t.tag); +} + +rremove(cp: ref Chan, t: ref Tmsg.Remove): ref Rmsg +{ + if((file := cp.getfid(t.fid, 0)) == nil) + return err(t, Efid); + e := clunk(cp, file, 1, cp == conschan); + if(e != nil) + return err(t, e); + return ref Rmsg.Remove(t.tag); +} + +rstat(cp: ref Chan, f: ref Tmsg.Stat): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + dir := dir9p2(d); + if(d.qid.path == big QPROOT) # stat of root gives time + dir.atime = now(); + d.put(); + if(styx->packdirsize(dir) > cp.msize-IOHDRSZ) + return ferr(f, Ersc, file, nil); + file.unlock(); + + return ref Rmsg.Stat(f.tag, dir); +} + +rwstat(cp: ref Chan, f: ref Tmsg.Wstat): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + + # if user none, can't do anything unless in allow mode + + if(file.uid == None && !wstatallow) + return ferr(f, Eaccess, file, nil); + + if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup))) + return ferr(f, Eronly, file, nil); + + # + # first get parent + # + p1: ref Iobuf; + d1: ref Dentry; + if(file.wpath != nil){ + p1 = Iobuf.get(file.fs, file.wpath.addr, Bread); + if(p1 == nil) + return ferr(f, Ephase, file, p1); + d1 = Dentry.get(p1, file.wpath.slot); + if(d1 == nil || p1.checktag(Tdir, QPNONE) || !(d1.mode & DALLOC)) + return ferr(f, Ephase, file, p1); + } + + # + # now the file + # + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, p1); + + # + # Convert the message and fix up + # fields not to be changed. + # + dir := f.stat; + if(dir.uid == nil) + uid := d.uid; + else + uid = strtouid(dir.uid); + if(dir.gid == nil) + gid := d.gid; + else + gid = strtouid(dir.gid); + if(dir.name == nil) + dir.name = d.name; + else{ + if((l := checkname9p2(dir.name)) == 0){ + d.put(); + return ferr(f, Ename, file, p1); + } + if(l+1 > NAMELEN){ + d.put(); + return ferr(f, Etoolong, file, p1); + } + } + + # Before doing sanity checks, find out what the + # new 'mode' should be: + # if 'type' and 'mode' are both defaults, take the + # new mode from the old directory entry; + # else if 'type' is the default, use the new mode entry; + # else if 'mode' is the default, create the new mode from + # 'type' or'ed with the old directory mode; + # else neither are defaults, use the new mode but check + # it agrees with 'type'. + + if(dir.qid.qtype == 16rFF && dir.mode == ~0){ + dir.mode = d.mode & 8r777; + if(d.mode & DLOCK) + dir.mode |= DMEXCL; + if(d.mode & DAPND) + dir.mode |= DMAPPEND; + if(d.mode & DDIR) + dir.mode |= DMDIR; + } + else if(dir.qid.qtype == 16rFF){ + # nothing to do + } + else if(dir.mode == ~0) + dir.mode = (dir.qid.qtype<<24)|(d.mode & 8r777); + else if(dir.qid.qtype != ((dir.mode>>24) & 16rFF)){ + d.put(); + return ferr(f, Eqidmode, file, p1); + } + + # Check for unknown type/mode bits + # and an attempt to change the directory bit. + + if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|8r777)){ + d.put(); + return ferr(f, Enotm, file, p1); + } + if(d.mode & DDIR) + mode := DMDIR; + else + mode = 0; + if((dir.mode^mode) & DMDIR){ + d.put(); + return ferr(f, Enotd, file, p1); + } + + if(dir.mtime == ~0) + dir.mtime = d.mtime; + if(dir.length == ~big 0) + dir.length = big d.size; + + + # Currently, can't change length. + + if(dir.length != big d.size){ + d.put(); + return ferr(f, Enotl, file, p1); + } + + + # if chown, + # must be god + # wstatallow set to allow chown during boot + + if(uid != d.uid && !wstatallow){ + d.put(); + return ferr(f, Enotu, file, p1); + } + + # if chgroup, + # must be either + # a) owner and in new group + # b) leader of both groups + # wstatallow and writeallow are set to allow chgrp during boot + + while(gid != d.gid){ + if(wstatallow || writeallow) + break; + if(d.uid == file.uid && ingroup(file.uid, gid)) + break; + if(leadgroup(file.uid, gid)) + if(leadgroup(file.uid, d.gid)) + break; + d.put(); + return ferr(f, Enotg, file, p1); + } + + # if rename, + # must have write permission in parent + + while(d.name != dir.name){ + + # drop entry to prevent deadlock, then + # check that destination name is valid and unique + + d.put(); + if(checkname9p2(dir.name) == 0 || d1 == nil) + return ferr(f, Ename, file, p1); + if(dir.name == "." || dir.name == "..") + return ferr(f, Edot, file, p1); + + + for(addr := 0; ; addr++){ + if((p := d1.getblk(addr, 0)) == nil) + break; + if(p.checktag(Tdir, int d1.qid.path)){ + p.put(); + continue; + } + for(slot := 0; slot < DIRPERBUF; slot++){ + d = Dentry.get(p, slot); + if(!(d.mode & DALLOC)) + continue; + if(dir.name == d.name){ + p.put(); + return ferr(f, Eexist, file, p1); + } + } + p.put(); + } + + # reacquire entry + + (d, nil) = Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, Ephase, file, p1); + + if(wstatallow || writeallow) # set to allow rename during boot + break; + if(d1 == nil || file.access(d1, DWRITE)){ + d.put(); + return ferr(f, Eaccess, file, p1); + } + break; + } + + # if mode/time, either + # a) owner + # b) leader of either group + + mode = dir.mode & 8r777; + if(dir.mode & DMAPPEND) + mode |= DAPND; + if(dir.mode & DMEXCL) + mode |= DLOCK; + while(d.mtime != dir.mtime || ((d.mode^mode) & (DAPND|DLOCK|8r777))){ + if(wstatallow) # set to allow chmod during boot + break; + if(d.uid == file.uid) + break; + if(leadgroup(file.uid, gid)) + break; + if(leadgroup(file.uid, d.gid)) + break; + d.put(); + return ferr(f, Enotu, file, p1); + } + d.mtime = dir.mtime; + d.uid = uid; + d.gid = gid; + d.mode = (mode & (DAPND|DLOCK|8r777)) | (d.mode & (DALLOC|DDIR)); + + d.name = dir.name; + d.access(FWSTAT, file.uid); + d.change(~0); + d.put(); + + if(p1 != nil) + p1.put(); + file.unlock(); + + return ref Rmsg.Wstat(f.tag); +} + +superok(set: int): int +{ + sb := Superb.get(thedevice, Bread|Bmod|Bimm); + ok := sb.fsok; + sb.fsok = set; + if(debug) + sb.print(); + sb.touched(); + sb.put(); + return ok; +} + +# little-endian +get2(a: array of byte, o: int): int +{ + return (int a[o+1]<<8) | int a[o]; +} + +get2s(a: array of byte, o: int): int +{ + v := (int a[o+1]<<8) | int a[o]; + if(v & 16r8000) + v |= ~0 << 8; + return v; +} + +get4(a: array of byte, o: int): int +{ + return (int a[o+3]<<24) | (int a[o+2] << 16) | (int a[o+1]<<8) | int a[o]; +} + +put2(a: array of byte, o: int, v: int) +{ + a[o] = byte v; + a[o+1] = byte (v>>8); +} + +put4(a: array of byte, o: int, v: int) +{ + a[o] = byte v; + a[o+1] = byte (v>>8); + a[o+2] = byte (v>>16); + a[o+3] = byte (v>>24); +} + +Tag.unpack(a: array of byte): Tag +{ + return Tag(get2(a,2), get4(a,4)); +} + +Tag.pack(t: self Tag, a: array of byte) +{ + put2(a, 0, 0); + put2(a, 2, t.tag); + if(t.path != QPNONE) + put4(a, 4, t.path & ~QPDIR); +} + +Superb.get(dev: ref Device, flags: int): ref Superb +{ + p := Iobuf.get(dev, SUPERADDR, flags); + if(p == nil) + return nil; + if(p.checktag(Tsuper, QPSUPER)){ + p.put(); + return nil; + } + sb := Superb.unpack(p.iobuf); + sb.iob = p; + return sb; +} + +Superb.touched(s: self ref Superb) +{ + s.iob.flags |= Bmod; +} + +Superb.put(sb: self ref Superb) +{ + if(sb.iob == nil) + return; + if(sb.iob.flags & Bmod) + sb.pack(sb.iob.iobuf); + sb.iob.put(); + sb.iob = nil; +} + +# this is the disk structure +# Superb: +# Super1; +# Fbuf fbuf; +# Fbuf: +# nfree[4] +# free[] # based on BUFSIZE +# Super1: +# long fstart; +# long fsize; +# long tfree; +# long qidgen; # generator for unique ids +# long fsok; # file system ok +# long roraddr; # dump root addr +# long last; # last super block addr +# long next; # next super block addr + +Ofstart: con 0; +Ofsize: con Ofstart+4; +Otfree: con Ofsize+4; +Oqidgen: con Otfree+4; +Ofsok: con Oqidgen+4; +Ororaddr: con Ofsok+4; +Olast: con Ororaddr+4; +Onext: con Olast+4; +Super1size: con Onext+4; + +Superb.unpack(a: array of byte): ref Superb +{ + s := ref Superb; + s.fstart = get4(a, Ofstart); + s.fsize = get4(a, Ofsize); + s.tfree = get4(a, Otfree); + s.qidgen = get4(a, Oqidgen); + s.fsok = get4(a, Ofsok); + s.fbuf = a[Super1size:]; + return s; +} + +Superb.pack(s: self ref Superb, a: array of byte) +{ + put4(a, Ofstart, s.fstart); + put4(a, Ofsize, s.fsize); + put4(a, Otfree, s.tfree); + put4(a, Oqidgen, s.qidgen); + put4(a, Ofsok, s.fsok); +} + +Superb.print(sb: self ref Superb) +{ + sys->print("fstart=%ud fsize=%ud tfree=%ud qidgen=%ud fsok=%d\n", + sb.fstart, sb.fsize, sb.tfree, sb.qidgen, sb.fsok); +} + +Dentry.get(p: ref Iobuf, slot: int): ref Dentry +{ + if(p == nil) + return nil; + buf := p.iobuf[(slot%DIRPERBUF)*Dentrysize:]; + d := Dentry.unpack(buf); + d.iob = p; + d.buf = buf; + return d; +} + +Dentry.geta(fs: ref Device, addr: int, slot: int, qpath: int, mode: int): (ref Dentry, string) +{ + p := Iobuf.get(fs, addr, mode); + if(p == nil || p.checktag(Tdir, qpath)){ + if(p != nil) + p.put(); + return (nil, Ealloc); + } + d := Dentry.get(p, slot); + if(d == nil || !(d.mode & DALLOC)){ + p.put(); + return (nil, Ealloc); + } + return (d, nil); +} + +Dentry.getd(file: ref File, mode: int): (ref Dentry, string) +{ + (d, e) := Dentry.geta(file.fs, file.addr, file.slot, QPNONE, mode); # QPNONE should be file.wpath's path + if(e != nil) + return (nil, e); + if(file.qid.path != d.qid.path || (file.qid.qtype&QTDIR) != (d.qid.qtype&QTDIR)){ + d.put(); + return (nil, Eqid); + } + return (d, nil); +} + +# this is the disk structure: +# char name[NAMELEN]; +# short uid; +# short gid; [2*2] +# ushort mode; +# #define DALLOC 0x8000 +# #define DDIR 0x4000 +# #define DAPND 0x2000 +# #define DLOCK 0x1000 +# #define DREAD 0x4 +# #define DWRITE 0x2 +# #define DEXEC 0x1 +# [ushort muid] [2*2] +# Qid.path; [4] +# Qid.version; [4] +# long size; [4] +# long dblock[NDBLOCK]; +# long iblock; +# long diblock; +# long atime; +# long mtime; + +Oname: con 0; +Ouid: con Oname+NAMELEN; +Ogid: con Ouid+2; +Omode: con Ogid+2; +Omuid: con Omode+2; +Opath: con Omuid+2; +Overs: con Opath+4; +Osize: con Overs+4; +Odblock: con Osize+4; +Oiblock: con Odblock+NDBLOCK*4; +Odiblock: con Oiblock+4; +Oatime: con Odiblock+4; +Omtime: con Oatime+4; +Dentrysize: con Omtime+4; + +Dentry.unpack(a: array of byte): ref Dentry +{ + d := ref Dentry; + for(i:=0; i<NAMELEN; i++) + if(int a[i] == 0) + break; + d.name = string a[0:i]; + d.uid = get2s(a, Ouid); + d.gid = get2s(a, Ogid); + d.mode = get2(a, Omode); + d.muid = get2(a, Omuid); # note: not set by Plan 9's kfs + d.qid = mkqid(get4(a, Opath), get4(a, Overs), d.mode); + d.size = big get4(a, Osize) & big 16rFFFFFFFF; + d.atime = get4(a, Oatime); + d.mtime = get4(a, Omtime); + d.mod = 0; + return d; +} + +Dentry.change(d: self ref Dentry, f: int) +{ + d.mod |= f; +} + +Dentry.update(d: self ref Dentry) +{ + f := d.mod; + d.mod = 0; + if(d.iob == nil || (d.iob.flags & Bmod) == 0){ + if(f != 0) + panic("Dentry.update"); + return; + } + a := d.buf; + if(f & Uname){ + b := array of byte d.name; + for(i := 0; i < NAMELEN; i++) + if(i < len b) + a[i] = b[i]; + else + a[i] = byte 0; + } + if(f & Uids){ + put2(a, Ouid, d.uid); + put2(a, Ogid, d.gid); + } + if(f & Umode) + put2(a, Omode, d.mode); + if(f & Uqid){ + path := int d.qid.path; + if(d.mode & DDIR) + path |= QPDIR; + put4(a, Opath, path); + put4(a, Overs, d.qid.vers); + } + if(f & Usize) + put4(a, Osize, int d.size); + if(f & Utime){ + put4(a, Omtime, d.mtime); + put4(a, Oatime, d.atime); + } + d.iob.flags |= Bmod; +} + +Dentry.access(d: self ref Dentry, f: int, uid: int) +{ + if((p := d.iob) != nil && !readonly){ + if((f & (FWRITE|FWSTAT)) == 0 && noatime) + return; + if(f & (FREAD|FWRITE|FWSTAT)){ + d.atime = now(); + put4(d.buf, Oatime, d.atime); + p.flags |= Bmod; + } + if(f & FWRITE){ + d.mtime = now(); + put4(d.buf, Omtime, d.mtime); + d.muid = uid; + put2(d.buf, Omuid, uid); + d.qid.vers++; + put4(d.buf, Overs, d.qid.vers); + p.flags |= Bmod; + } + } +} + +# +# release the directory entry buffer and thus the +# lock on both buffer and entry, typically during i/o, +# to be reacquired later if needed +# +Dentry.release(d: self ref Dentry) +{ + if(d.iob != nil){ + d.update(); + d.iob.put(); + d.iob = nil; + d.buf = nil; + } +} + +Dentry.getblk(d: self ref Dentry, a: int, tag: int): ref Iobuf +{ + addr := d.rel2abs(a, tag, 0); + if(addr == 0) + return nil; + return Iobuf.get(thedevice, addr, Bread); +} + +# +# same as Dentry.buf but calls d.release +# to reduce interference. +# +Dentry.getblk1(d: self ref Dentry, a: int, tag: int): ref Iobuf +{ + addr := d.rel2abs(a, tag, 1); + if(addr == 0) + return nil; + return Iobuf.get(thedevice, addr, Bread); +} + +Dentry.rel2abs(d: self ref Dentry, a: int, tag: int, putb: int): int +{ + if(a < 0){ + sys->print("Dentry.rel2abs: neg\n"); + return 0; + } + p := d.iob; + if(p == nil || d.buf == nil) + panic("nil iob"); + data := d.buf; + qpath := int d.qid.path; + dev := p.dev; + if(a < NDBLOCK){ + addr := get4(data, Odblock+a*4); + if(addr == 0 && tag){ + addr = balloc(dev, tag, qpath); + put4(data, Odblock+a*4, addr); + p.flags |= Bmod|Bimm; + } + if(putb) + d.release(); + return addr; + } + a -= NDBLOCK; + if(a < INDPERBUF){ + addr := get4(data, Oiblock); + if(addr == 0 && tag){ + addr = balloc(dev, Tind1, qpath); + put4(data, Oiblock, addr); + p.flags |= Bmod|Bimm; + } + if(putb) + d.release(); + return indfetch(dev, qpath, addr, a, Tind1, tag); + } + a -= INDPERBUF; + if(a < INDPERBUF2){ + addr := get4(data, Odiblock); + if(addr == 0 && tag){ + addr = balloc(dev, Tind2, qpath); + put4(data, Odiblock, addr); + p.flags |= Bmod|Bimm; + } + if(putb) + d.release(); + addr = indfetch(dev, qpath, addr, a/INDPERBUF, Tind2, Tind1); + return indfetch(dev, qpath, addr, a%INDPERBUF, Tind1, tag); + } + if(putb) + d.release(); + sys->print("Dentry.buf: trip indirect\n"); + return 0; +} + +indfetch(dev: ref Device, path: int, addr: int, a: int, itag: int, tag: int): int +{ + if(addr == 0) + return 0; + bp := Iobuf.get(dev, addr, Bread); + if(bp == nil){ + sys->print("ind fetch bp = nil\n"); + return 0; + } + if(bp.checktag(itag, path)){ + sys->print("ind fetch tag\n"); + bp.put(); + return 0; + } + addr = get4(bp.iobuf, a*4); + if(addr == 0 && tag){ + addr = balloc(dev, tag, path); + if(addr != 0){ + put4(bp.iobuf, a*4, addr); + bp.flags |= Bmod; + if(localfs || tag == Tdir) + bp.flags |= Bimm; + bp.settag(itag, path); + } + } + bp.put(); + return addr; +} + +balloc(dev: ref Device, tag: int, qpath: int): int +{ + # TO DO: cache superblock to reduce pack/unpack + sb := Superb.get(dev, Bread|Bmod); + if(sb == nil) + panic("balloc: super block"); + n := get4(sb.fbuf, 0); + n--; + sb.tfree--; + if(n < 0 || n >= FEPERBUF) + panic("balloc: bad freelist"); + a := get4(sb.fbuf, 4+n*4); + if(n == 0){ + if(a == 0){ + sb.tfree = 0; + sb.touched(); + sb.put(); + return 0; + } + bp := Iobuf.get(dev, a, Bread); + if(bp == nil || bp.checktag(Tfree, QPNONE)){ + if(bp != nil) + bp.put(); + sb.put(); + return 0; + } + sb.fbuf[0:] = bp.iobuf[0:(FEPERBUF+1)*4]; + sb.touched(); + bp.put(); + }else{ + put4(sb.fbuf, 0, n); + sb.touched(); + } + bp := Iobuf.get(dev, a, Bmod); + bp.iobuf[0:] = emptyblock; + bp.settag(tag, qpath); + if(tag == Tind1 || tag == Tind2 || tag == Tdir) + bp.flags |= Bimm; + bp.put(); + sb.put(); + return a; +} + +bfree(dev: ref Device, addr: int, d: int) +{ + if(addr == 0) + return; + if(d > 0){ + d--; + p := Iobuf.get(dev, addr, Bread); + if(p != nil){ + for(i:=INDPERBUF-1; i>=0; i--){ + a := get4(p.iobuf, i*4); + bfree(dev, a, d); + } + p.put(); + } + } + + # stop outstanding i/o + p := Iobuf.get(dev, addr, Bprobe); + if(p != nil){ + p.flags &= ~(Bmod|Bimm); + p.put(); + } + + s := Superb.get(dev, Bread|Bmod); + if(s == nil) + panic("bfree: super block"); + addfree(dev, addr, s); + s.put(); +} + +addfree(dev: ref Device, addr: int, sb: ref Superb) +{ + if(addr >= sb.fsize){ + sys->print("addfree: bad addr %ud\n", addr); + return; + } + n := get4(sb.fbuf, 0); + if(n < 0 || n > FEPERBUF) + panic("addfree: bad freelist"); + if(n >= FEPERBUF){ + p := Iobuf.get(dev, addr, Bmod); + if(p == nil) + panic("addfree: Iobuf.get"); + p.iobuf[0:] = sb.fbuf[0:(1+FEPERBUF)*4]; + sb.fbuf[0:] = emptyblock[0:(1+FEPERBUF)*4]; # clear it for debugging + p.settag(Tfree, QPNONE); + p.put(); + n = 0; + } + put4(sb.fbuf, 4+n*4, addr); + put4(sb.fbuf, 0, n+1); + sb.tfree++; + if(addr >= sb.fsize) + sb.fsize = addr+1; + sb.touched(); +} + +qidpathgen(dev: ref Device): int +{ + sb := Superb.get(dev, Bread|Bmod); + if(sb == nil) + panic("qidpathgen: super block"); + sb.qidgen++; + path := sb.qidgen; + sb.touched(); + sb.put(); + return path; +} + +Dentry.trunc(d: self ref Dentry, uid: int) +{ + p := d.iob; + data := d.buf; + bfree(p.dev, get4(data, Odiblock), 2); + put4(data, Odiblock, 0); + bfree(p.dev, get4(data, Oiblock), 1); + put4(data, Oiblock, 0); + for(i:=NDBLOCK-1; i>=0; i--){ + bfree(p.dev, get4(data, Odblock+i*4), 0); + put4(data, Odblock+i*4, 0); + } + d.size = big 0; + d.change(Usize); + p.flags |= Bmod|Bimm; + d.access(FWRITE, uid); + d.update(); +} + +Dentry.put(d: self ref Dentry) +{ + p := d.iob; + if(p == nil || d.buf == nil) + return; + d.update(); + p.put(); + d.iob = nil; + d.buf = nil; +} + +Dentry.print(d: self ref Dentry) +{ + sys->print("name=%#q uid=%d gid=%d mode=#%8.8ux qid.path=#%bux qid.vers=%ud size=%bud\n", + d.name, d.uid, d.gid, d.mode, d.qid.path, d.qid.vers, d.size); + p := d.iob; + if(p != nil && (data := p.iobuf) != nil){ + sys->print("\tdblock="); + for(i := 0; i < NDBLOCK; i++) + sys->print(" %d", get4(data, Odblock+i*4)); + sys->print(" iblock=%ud diblock=%ud\n", get4(data, Oiblock), get4(data, Odiblock)); + } +} + +HWidth: con 5; # buffers per line + +hiob: array of ref Hiob; + +iobufinit(niob: int) +{ + nhiob := niob/HWidth; + while(!prime(nhiob)) + nhiob++; + hiob = array[nhiob] of {* => ref Hiob(nil, Lock.new(), 0)}; + # allocate the buffers now + for(i := 0; i < len hiob; i++){ + h := hiob[i]; + while(h.niob < HWidth) + h.newbuf(); + } +} + +iobufclear() +{ + # eliminate the cyclic references + for(i := 0; i < len hiob; i++){ + h := hiob[i]; + while(--h.niob >= 0){ + p := hiob[i].link; + hiob[i].link = p.fore; + p.fore = p.back = nil; + p = nil; + } + } +} + +prime(n: int): int +{ + if((n%2) == 0) + return 0; + for(i:=3;; i+=2) { + if((n%i) == 0) + return 0; + if(i*i >= n) + return 1; + } +} + +Hiob.newbuf(hb: self ref Hiob): ref Iobuf +{ + # hb must be locked + p := ref Iobuf; + p.qlock = chan[1] of int; + q := hb.link; + if(q != nil){ + p.fore = q; + p.back = q.back; + q.back = p; + p.back.fore = p; + }else{ + hb.link = p; + p.fore = p; + p.back = p; + } + p.dev = devnone; + p.addr = -1; + p.flags = 0; + p.xiobuf = array[RBUFSIZE] of byte; + hb.niob++; + return p; +} + +Iobuf.get(dev: ref Device, addr: int, flags: int): ref Iobuf +{ + hb := hiob[addr%len hiob]; + p: ref Iobuf; +Search: + for(;;){ + hb.lk.lock(); + s := hb.link; + + # see if it's active + p = s; + do{ + if(p.addr == addr && p.dev == dev){ + if(p != s){ + p.back.fore = p.fore; + p.fore.back = p.back; + p.fore = s; + p.back = s.back; + s.back = p; + p.back.fore = p; + hb.link = p; + } + hb.lk.unlock(); + p.lock(); + if(p.addr != addr || p.dev != dev){ + # lost race + p.unlock(); + continue Search; + } + p.flags |= flags; + p.iobuf = p.xiobuf; + return p; + } + }while((p = p.fore) != s); + if(flags == Bprobe){ + hb.lk.unlock(); + return nil; + } + + # steal the oldest unlocked buffer + do{ + p = s.back; + if(p.canlock()){ + # TO DO: if Bmod, write it out and restart Hashed + # for now we needn't because Iobuf.put is synchronous + if(p.flags & Bmod) + sys->print("Bmod unexpected (%ud)\n", p.addr); + hb.link = p; + p.dev = dev; + p.addr = addr; + p.flags = flags; + break Search; + } + s = p; + }while(p != hb.link); + + # no unlocked blocks available; add a new one + p = hb.newbuf(); + p.lock(); # return it locked + break; + } + + p.dev = dev; + p.addr = addr; + p.flags = flags; + hb.lk.unlock(); + p.iobuf = p.xiobuf; + if(flags & Bread){ + if(wrenread(dev.fd, addr, p.iobuf)){ + eprint(sys->sprint("error reading block %ud: %r", addr)); + p.flags = 0; + p.dev = devnone; + p.addr = -1; + p.iobuf = nil; + p.unlock(); + return nil; + } + } + return p; +} + +Iobuf.put(p: self ref Iobuf) +{ + if(p.flags & Bmod) + p.flags |= Bimm; # temporary; see comment in Iobuf.get + if(p.flags & Bimm){ + if(!(p.flags & Bmod)) + eprint(sys->sprint("imm and no mod (%d)", p.addr)); + if(!wrenwrite(p.dev.fd, p.addr, p.iobuf)) + p.flags &= ~(Bmod|Bimm); + else + panic(sys->sprint("error writing block %ud: %r", p.addr)); + } + p.iobuf = nil; + p.unlock(); +} + +Iobuf.lock(p: self ref Iobuf) +{ + p.qlock <-= 1; +} + +Iobuf.canlock(p: self ref Iobuf): int +{ + alt{ + p.qlock <-= 1 => + return 1; + * => + return 0; + } +} + +Iobuf.unlock(p: self ref Iobuf) +{ + <-p.qlock; +} + +File.access(f: self ref File, d: ref Dentry, m: int): int +{ + if(wstatallow) + return 0; + + # none gets only other permissions + + if(f.uid != None){ + if(f.uid == d.uid) # owner + if((m<<6) & d.mode) + return 0; + if(ingroup(f.uid, d.gid)) # group membership + if((m<<3) & d.mode) + return 0; + } + + # + # other access for everyone except members of group "noworld" + # + if(m & d.mode){ + # + # walk directories regardless. + # otherwise it's impossible to get + # from the root to noworld's directories. + # + if((d.mode & DDIR) && (m == DEXEC)) + return 0; + if(!ingroup(f.uid, Noworld)) + return 0; + } + return 1; +} + +tagname(t: int): string +{ + case t { + Tnone => return "Tnone"; + Tsuper => return "Tsuper"; + Tdir => return "Tdir"; + Tind1 => return "Tind1"; + Tind2 => return "Tind2"; + Tfile => return "Tfile"; + Tfree => return "Tfree"; + Tbuck => return "Tbuck"; + Tvirgo => return "Tvirgo"; + Tcache => return "Tcache"; + * => return sys->sprint("%d", t); + } +} + +Iobuf.checktag(p: self ref Iobuf, tag: int, qpath: int): int +{ + t := Tag.unpack(p.iobuf[BUFSIZE:]); + if(t.tag != tag){ + if(1) + eprint(sys->sprint(" tag = %s; expected %s; addr = %ud\n", + tagname(t.tag), tagname(tag), p.addr)); + return 2; + } + if(qpath != QPNONE){ + qpath &= ~QPDIR; + if(qpath != t.path){ + if(qpath == (t.path&~QPDIR)) # old bug + return 0; + if(1) + eprint(sys->sprint(" tag/path = %ux; expected %s/%ux\n", + t.path, tagname(tag), qpath)); + return 1; + } + } + return 0; +} + +Iobuf.settag(p: self ref Iobuf, tag: int, qpath: int) +{ + Tag(tag, qpath).pack(p.iobuf[BUFSIZE:]); + p.flags |= Bmod; +} + +badmagic := 0; +wmagic := "kfs wren device\n"; + +wrenream(dev: ref Device) +{ + if(RBUFSIZE % 512) + panic(sys->sprint("kfs: bad buffersize(%d): restart a multiple of 512", RBUFSIZE)); + if(RBUFSIZE > MAXBUFSIZE) + panic(sys->sprint("kfs: bad buffersize(%d): must be at most %d", RBUFSIZE, MAXBUFSIZE)); + sys->print("kfs: reaming the file system using %d byte blocks\n", RBUFSIZE); + buf := array[RBUFSIZE] of {* => byte 0}; + buf[256:] = sys->aprint("%s%d\n", wmagic, RBUFSIZE); + if(sys->seek(dev.fd, big 0, 0) < big 0 || sys->write(dev.fd, buf, len buf) != len buf) + panic("can't ream disk"); +} + +wreninit(dev: ref Device): int +{ + (ok, nil) := sys->fstat(dev.fd); + if(ok < 0) + return 0; + buf := array[MAXBUFSIZE] of byte; + sys->seek(dev.fd, big 0, 0); + n := sys->read(dev.fd, buf, len buf); + if(n < len buf) + return 0; + badmagic = 0; + RBUFSIZE = 1024; + if(string buf[256:256+len wmagic] != wmagic){ + badmagic = 1; + return 0; + } + RBUFSIZE = int string buf[256+len wmagic:256+len wmagic+12]; + if(RBUFSIZE % 512) + error("bad block size"); + return 1; +} + +wrenread(fd: ref Sys->FD, addr: int, a: array of byte): int +{ + return sys->pread(fd, a, len a, big addr * big RBUFSIZE) != len a; +} + +wrenwrite(fd: ref Sys->FD, addr: int, a: array of byte): int +{ + return sys->pwrite(fd, a, len a, big addr * big RBUFSIZE) != len a; +} + +wrentag(buf: array of byte, tag: int, qpath: int): int +{ + t := Tag.unpack(buf[BUFSIZE:]); + return t.tag != tag || (qpath&~QPDIR) != t.path; +} + +wrencheck(fd: ref Sys->FD): int +{ + if(badmagic) + return 1; + buf := array[RBUFSIZE] of byte; + if(wrenread(fd, SUPERADDR, buf) || wrentag(buf, Tsuper, QPSUPER) || + wrenread(fd, ROOTADDR, buf) || wrentag(buf, Tdir, QPROOT)) + return 1; + d0 := Dentry.unpack(buf); + if(d0.mode & DALLOC) + return 0; + return 1; +} + +wrensize(dev: ref Device): int +{ + (ok, d) := sys->fstat(dev.fd); + if(ok < 0) + return -1; + return int (d.length / big RBUFSIZE); +} + +checkname9p2(s: string): int +{ + for(i := 0; i < len s; i++) + if(s[i] <= 8r40) + return 0; + return styx->utflen(s); +} + +isro(d: ref Device): int +{ + return d == nil || d.ronly; +} + +tlocks: list of ref Tlock; + +tlocked(f: ref File, d: ref Dentry): ref Tlock +{ + tim := now(); + path := int d.qid.path; + t1: ref Tlock; + for(l := tlocks; l != nil; l = tl l){ + t := hd l; + if(t.qpath == path && t.time >= tim && t.dev == f.fs) + return nil; # it's locked + if(t.file == nil || t1 == nil && t.time < tim) + t1 = t; + } + t := t1; + if(t == nil) + t = ref Tlock; + t.dev = f.fs; + t.qpath = path; + t.time = tim + TLOCK; + tlocks = t :: tlocks; + return t; +} + +mkqid(path: int, vers: int, mode: int): Qid +{ + qid: Qid; + + qid.path = big (path & ~QPDIR); + qid.vers = vers; + qid.qtype = 0; + if(mode & DDIR) + qid.qtype |= QTDIR; + if(mode & DAPND) + qid.qtype |= QTAPPEND; + if(mode & DLOCK) + qid.qtype |= QTEXCL; + return qid; +} + +dir9p2(d: ref Dentry): Sys->Dir +{ + dir: Sys->Dir; + + dir.name = d.name; + dir.uid = uidtostr(d.uid); + dir.gid = uidtostr(d.gid); + dir.muid = uidtostr(d.muid); + dir.qid = d.qid; + dir.mode = d.mode & 8r777; + if(d.mode & DDIR) + dir.mode |= DMDIR; + if(d.mode & DAPND) + dir.mode |= DMAPPEND; + if(d.mode & DLOCK) + dir.mode |= DMEXCL; + dir.atime = d.atime; + dir.mtime = d.mtime; + dir.length = big d.size; + dir.dtype = 0; + dir.dev = 0; + return dir; +} + +rootream(dev: ref Device, addr: int) +{ + p := Iobuf.get(dev, addr, Bmod|Bimm); + p.iobuf[0:] = emptyblock; + p.settag(Tdir, QPROOT); + d := Dentry.get(p, 0); + d.name = "/"; + d.uid = -1; + d.gid = -1; + d.mode = DALLOC | DDIR | + ((DREAD|DWRITE|DEXEC) << 6) | + ((DREAD|DWRITE|DEXEC) << 3) | + ((DREAD|DWRITE|DEXEC) << 0); + d.qid.path = big QPROOT; + d.qid.vers = 0; + d.qid.qtype = QTDIR; + d.atime = now(); + d.mtime = d.atime; + d.change(~0); + d.access(FREAD|FWRITE, -1); + d.update(); + p.put(); +} + +superream(dev: ref Device, addr: int) +{ + fsize := wrensize(dev); + if(fsize <= 0) + panic("file system device size"); + p := Iobuf.get(dev, addr, Bmod|Bimm); + p.iobuf[0:] = emptyblock; + p.settag(Tsuper, QPSUPER); + sb := ref Superb; + sb.iob = p; + sb.fstart = 1; + sb.fsize = fsize; + sb.qidgen = 10; + sb.tfree = 0; + sb.fsok = 0; + sb.fbuf = p.iobuf[Super1size:]; + put4(sb.fbuf, 0, 1); # nfree = 1 + for(i := fsize-1; i>=addr+2; i--) + addfree(dev, i, sb); + sb.put(); +} + +eprint(s: string) +{ + sys->print("kfs: %s\n", s); +} + +# +# /adm/users +# +# uid:user:leader:members[,...] + +User: adt { + uid: int; + name: string; + leader: int; + mem: list of int; +}; + +users: list of ref User; + +admusers := array[] of { + (-1, "adm", "adm"), + (None, "none", "adm"), + (Noworld, "noworld", nil), + (10000, "sys", nil), + (10001, "upas", "upas"), + (10002, "bootes", "bootes"), + (10006, "inferno", nil), +}; + +userinit() +{ + if(!cmd_users() && users == nil){ + cprint("initializing minimal user table"); + defaultusers(); + } + writegroup = strtouid("write"); +} + +cmd_users(): int +{ + if(kopen(FID1, FID2, array[] of {"adm", "users"}, OREAD) != nil) + return 0; + buf: array of byte; + for(off := 0;;){ + (a, e) := kread(FID2, off, Styx->MAXFDATA); + if(e != nil){ + cprint("/adm/users read error: "+e); + return 0; + } + if(len a == 0) + break; + off += len a; + if(buf != nil){ + c := array[len buf + len a] of byte; + if(buf != nil) + c[0:] = buf; + c[len buf:] = a; + buf = c; + }else + buf = a; + } + kclose(FID2); + + # (uid:name:lead:mem,...\n)+ + (nl, lines) := sys->tokenize(string buf, "\n"); + if(nl == 0){ + cprint("empty /adm/users"); + return 0; + } + oldusers := users; + users = nil; + + # first pass: enter id:name + for(l := lines; l != nil; l = tl l){ + uid, name, r: string; + s := hd l; + if(s == "" || s[0] == '#') + continue; + (uid, r) = field(s, ':'); + (name, r) = field(r, ':'); + if(uid == nil || name == nil || string int uid != uid){ + cprint("invalid /adm/users line: "+hd l); + users = oldusers; + return 0; + } + adduser(int uid, name, nil, nil); + } + + # second pass: groups and leaders + for(l = lines; l != nil; l = tl l){ + s := hd l; + if(s == "" || s[0] == '#') + continue; + name, lead, mem, r: string; + (nil, r) = field(s, ':'); # skip id + (name, r) = field(r, ':'); + (lead, mem) = field(r, ':'); + (nil, mems) := sys->tokenize(mem, ",\n"); + if(name == nil || lead == nil && mems == nil) + continue; + u := finduname(name); + if(lead != nil){ + lu := strtouid(lead); + if(lu != None) + u.leader = lu; + else if(lead != nil) + u.leader = u.uid; # mimic kfs not fs + } + mids: list of int = nil; + for(; mems != nil; mems = tl mems){ + lu := strtouid(hd mems); + if(lu != None) + mids = lu :: mids; + } + u.mem = mids; + } + + if(debug) + for(x := users; x != nil; x = tl x){ + u := hd x; + sys->print("%d : %q : %d :", u.uid, u.name, u.leader); + for(y := u.mem; y != nil; y = tl y) + sys->print(" %d", hd y); + sys->print("\n"); + } + return 1; +} + +field(s: string, c: int): (string, string) +{ + for(i := 0; i < len s; i++) + if(s[i] == c) + return (s[0:i], s[i+1:]); + return (s, nil); +} + +defaultusers() +{ + for(i := 0; i < len admusers; i++){ + (id, name, leader) := admusers[i]; + adduser(id, name, leader, nil); + } +} + +finduname(s: string): ref User +{ + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.name == s) + return u; + } + return nil; +} + +uidtostr(id: int): string +{ + if(id == None) + return "none"; + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == id) + return u.name; + } + return sys->sprint("#%d", id); +} + +leadgroup(ui: int, gi: int): int +{ + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == gi){ + if(u.leader == ui) + return 1; + if(u.leader == 0) + return ingroup(ui, gi); + return 0; + } + } + return 0; +} + +strtouid(s: string): int +{ + if(s == "none") + return None; + u := finduname(s); + if(u != nil) + return u.uid; + return 0; +} + +ingroup(uid: int, gid: int): int +{ + if(uid == gid) + return 1; + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == gid){ + for(m := u.mem; m != nil; m = tl m) + if(hd m == uid) + return 1; + return 0; + } + } + return 0; +} + +baduname(s: string): int +{ + n := checkname9p2(s); + if(n == 0 || n+1 > NAMELEN || s == "." || s == ".."){ + sys->print("kfs: illegal user name %q\n", s); + return 1; + } + return 0; +} + +adduser(id: int, name: string, leader: string, mem: list of string) +{ + if(baduname(name)) + return; + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == id){ + sys->print("kfs: duplicate user ID %d (name %q)\n", id, u.name); + return; + }else if(u.name == name){ + sys->print("kfs: duplicate user name %q (id %d)\n", name, u.uid); + return; + } + } + if(name == leader) + lid := id; + else if(leader == nil) + lid = 0; + else if(!baduname(leader)) + lid = strtouid(leader); + else + return; + memid: list of int; + for(; mem != nil; mem = tl mem){ + if(baduname(hd mem)) + return; + x := strtouid(hd mem); + if(x != 0) + memid = x :: memid; + } + u := ref User(id, name, lid, memid); + users = u :: users; +} + +Lock.new(): ref Lock +{ + return ref Lock(chan[1] of int); +} + +Lock.lock(l: self ref Lock) +{ + l.c <-= 1; +} + +Lock.canlock(l: self ref Lock): int +{ + alt{ + l.c <-= 1 => + return 1; + * => + return 0; + } +} + +Lock.unlock(l: self ref Lock) +{ + <-l.c; +} + +# +# kfs check, could be a separate module if that seemed important +# + +MAXDEPTH: con 100; +MAXNAME: con 4000; + +Map: adt { + lo, hi: int; + bits: array of byte; + nbad: int; + ndup: int; + nmark: int; + + new: fn(lo, hi: int): ref Map; + isset: fn(b: self ref Map, a: int): int; + mark: fn(b: self ref Map, a: int): string; +}; + +Check: adt { + dev: ref Device; + + amap: ref Map; + qmap: ref Map; + + name: string; + nfiles: int; + maxq: int; + + mod: int; + flags: int; + oldblock: int; + + depth: int; + maxdepth: int; + + check: fn(c: self ref Check); + touch: fn(c: self ref Check, a: int): int; + checkdir: fn(c: self ref Check, a: int, qpath: int): int; + checkindir: fn(c: self ref Check, a: int, d: ref Dentry, qpath: int): int; + maked: fn(c: self ref Check, a: int, s: int, qpath: int): ref Dentry; + modd: fn(c: self ref Check, a: int, s: int, d: ref Dentry); + fsck: fn(c: self ref Check, d: ref Dentry): int; + xread: fn(c: self ref Check, a: int, qpath: int); + xtag: fn(c: self ref Check, a: int, tag: int, qpath: int): ref Iobuf; + ckfreelist: fn(c: self ref Check, sb: ref Superb); + mkfreelist: fn(c: self ref Check, sb: ref Superb); + amark: fn(c: self ref Check, a: int): int; + fmark: fn(c: self ref Check, a: int): int; + missing: fn(c: self ref Check, sb: ref Superb); + qmark: fn(c: self ref Check, q: int); +}; + +check(dev: ref Device, flag: int) +{ + #mainlock.wlock(); + #mainlock.wunlock(); + c := ref Check; + c.dev = dev; + c.nfiles = 0; + c.maxq = 0; + c.mod = 0; + c.flags = flag; + c.oldblock = 0; + c.depth = 0; + c.maxdepth = 0; + c.check(); +} + +checkflags(s: string): int +{ + f := 0; + for(i := 0; i < len s; i++) + case s[i] { + 'r' => f |= Crdall; + 't' => f |= Ctag; + 'P' => f |= Cpfile; + 'p' => f |= Cpdir; + 'f' => f |= Cfree; + 'c' => f |= Cream; + 'd' => f |= Cbad; + 'w' => f |= Ctouch; + 'q' => f |= Cquiet; + 'v' => ; # old verbose flag; ignored + * => return -1; + } + return f; +} + +Check.check(c: self ref Check) +{ + sbaddr := SUPERADDR; + p := c.xtag(sbaddr, Tsuper, QPSUPER); + if(p == nil){ + cprint(sys->sprint("bad superblock")); + return; + } + sb := Superb.unpack(p.iobuf); + sb.iob = p; + + fstart := sb.fstart; + if(fstart != 1){ + cprint(sys->sprint("invalid superblock")); + return; + } + fsize := sb.fsize; + if(fsize < fstart || fsize > wrensize(c.dev)){ + cprint(sys->sprint("invalid size in superblock")); + return; + } + c.amap = Map.new(fstart, fsize); + + nqid := sb.qidgen+100; # not as much of a botch + if(nqid > 1024*1024*8) + nqid = 1024*1024*8; + if(nqid < 64*1024) + nqid = 64*1024; + c.qmap = Map.new(0, nqid); + + c.mod = 0; + c.depth = 0; + c.maxdepth = 0; + + if(c.amark(sbaddr)) + {} + + if(!(c.flags & Cquiet)) + cprint(sys->sprint("checking file system: %s", "main")); + c.nfiles = 0; + c.maxq = 0; + + d := c.maked(ROOTADDR, 0, QPROOT); + if(d != nil){ + if(c.amark(ROOTADDR)) + {} + if(c.fsck(d)) + c.modd(ROOTADDR, 0, d); + if(--c.depth != 0) + cprint("depth not zero on return"); + } + if(sb.qidgen < c.maxq) + cprint(sys->sprint("qid generator low path=%d maxq=%d", sb.qidgen, c.maxq)); + + nqbad := c.qmap.nbad + c.qmap.ndup; + c.qmap = nil; # could use to implement resequence + + ndup := c.amap.ndup; + nused := c.amap.nmark; + + c.amap.ndup = c.amap.nmark = 0; # reset for free list counts + if(c.flags & Cfree){ + c.name = "free list"; + c.mkfreelist(sb); + sb.qidgen = c.maxq; + p.settag(Tsuper, QPNONE); + }else + c.ckfreelist(sb); + + nbad := c.amap.nbad; + nfdup := c.amap.ndup; + nfree := c.amap.nmark; + # leave amap for missing, below + + if(c.mod){ + cprint("file system was modified"); + p.settag(Tsuper, QPNONE); + } + + if(!(c.flags & Cquiet)){ + cprint(sys->sprint("%8d files", c.nfiles)); + cprint(sys->sprint("%8d blocks in the file system", fsize-fstart)); + cprint(sys->sprint("%8d used blocks", nused)); + cprint(sys->sprint("%8d free blocks", sb.tfree)); + } + if(!(c.flags & Cfree)){ + if(nfree != sb.tfree) + cprint(sys->sprint("%8d free blocks found", nfree)); + if(nfdup) + cprint(sys->sprint("%8d blocks duplicated in the free list", nfdup)); + if(fsize-fstart-nused-nfree) + cprint(sys->sprint("%8d missing blocks", fsize-fstart-nused-nfree)); + } + if(ndup) + cprint(sys->sprint("%8d address duplications", ndup)); + if(nbad) + cprint(sys->sprint("%8d bad block addresses", nbad)); + if(nqbad) + cprint(sys->sprint("%8d bad qids", nqbad)); + if(!(c.flags & Cquiet)) + cprint(sys->sprint("%8d maximum qid path", c.maxq)); + c.missing(sb); + + sb.put(); +} + +Check.touch(c: self ref Check, a: int): int +{ + if((c.flags&Ctouch) && a){ + p := Iobuf.get(c.dev, a, Bread|Bmod); + if(p != nil) + p.put(); + return 1; + } + return 0; +} + +Check.checkdir(c: self ref Check, a: int, qpath: int): int +{ + ns := len c.name; + dmod := c.touch(a); + for(i:=0; i<DIRPERBUF; i++){ + nd := c.maked(a, i, qpath); + if(nd == nil) + break; + if(c.fsck(nd)){ + c.modd(a, i, nd); + dmod++; + } + c.depth--; + c.name = c.name[0:ns]; + } + c.name = c.name[0:ns]; + return dmod; +} + +Check.checkindir(c: self ref Check, a: int, d: ref Dentry, qpath: int): int +{ + dmod := c.touch(a); + p := c.xtag(a, Tind1, qpath); + if(p == nil) + return dmod; + for(i:=0; i<INDPERBUF; i++){ + a = get4(p.iobuf, i*4); + if(a == 0) + continue; + if(c.amark(a)){ + if(c.flags & Cbad){ + put4(p.iobuf, i*4, 0); + p.flags |= Bmod; + } + continue; + } + if(d.mode & DDIR) + dmod += c.checkdir(a, qpath); + else if(c.flags & Crdall) + c.xread(a, qpath); + } + p.put(); + return dmod; +} + +Check.fsck(c: self ref Check, d: ref Dentry): int +{ + p: ref Iobuf; + i: int; + a, qpath: int; + + if(++c.depth >= c.maxdepth){ + c.maxdepth = c.depth; + if(c.maxdepth >= MAXDEPTH){ + cprint(sys->sprint("max depth exceeded: %s", c.name)); + return 0; + } + } + dmod := 0; + if(!(d.mode & DALLOC)) + return 0; + c.nfiles++; + + ns := len c.name; + i = styx->utflen(d.name); + if(i >= NAMELEN){ + d.name[NAMELEN-1] = 0; # TO DO: not quite right + cprint(sys->sprint("%q.name (%q) not terminated", c.name, d.name)); + return 0; + } + ns += i; + if(ns >= MAXNAME){ + cprint(sys->sprint("%q.name (%q) name too large", c.name, d.name)); + return 0; + } + c.name += d.name; + + if(d.mode & DDIR){ + if(ns > 1) + c.name += "/"; + if(c.flags & Cpdir) + cprint(sys->sprint("%s", c.name)); + } else if(c.flags & Cpfile) + cprint(sys->sprint("%s", c.name)); + + qpath = int d.qid.path & ~QPDIR; + c.qmark(qpath); + if(qpath > c.maxq) + c.maxq = qpath; + for(i=0; i<NDBLOCK; i++){ + a = get4(d.buf, Odblock+i*4); + if(a == 0) + continue; + if(c.amark(a)){ + put4(d.buf, Odblock+i*4, 0); + dmod++; + continue; + } + if(d.mode & DDIR) + dmod += c.checkdir(a, qpath); + else if(c.flags & Crdall) + c.xread(a, qpath); + } + a = get4(d.buf, Oiblock); + if(a){ + if(c.amark(a)){ + put4(d.buf, Oiblock, 0); + dmod++; + } + else + dmod += c.checkindir(a, d, qpath); + } + + a = get4(d.buf, Odiblock); + if(a && c.amark(a)){ + put4(d.buf, Odiblock, 0); + return dmod + 1; + } + dmod += c.touch(a); + p = c.xtag(a, Tind2, qpath); + if(p != nil){ + for(i=0; i<INDPERBUF; i++){ + a = get4(p.iobuf, i*4); + if(a == 0) + continue; + if(c.amark(a)){ + if(c.flags & Cbad){ + put4(p.iobuf, i*4, 0); + p.flags |= Bmod; + } + continue; + } + dmod += c.checkindir(a, d, qpath); + } + p.put(); + } + return dmod; +} + +Check.ckfreelist(c: self ref Check, sb: ref Superb) +{ + c.name = "free list"; + cprint(sys->sprint("check %s", c.name)); + fb := sb.fbuf; + a := SUPERADDR; + p: ref Iobuf; + lo := 0; + hi := 0; + for(;;){ + n := get4(fb, 0); # nfree + if(n < 0 || n > FEPERBUF){ + cprint(sys->sprint("check: nfree bad %d", a)); + break; + } + for(i:=1; i<n; i++){ + a = get4(fb, 4+i*4); # free[i] + if(a && !c.fmark(a)){ + if(!lo || lo > a) + lo = a; + if(!hi || hi < a) + hi = a; + } + } + a = get4(fb, 4); # free[0] + if(a == 0) + break; + if(c.fmark(a)) + break; + if(!lo || lo > a) + lo = a; + if(!hi || hi < a) + hi = a; + if(p != nil) + p.put(); + p = c.xtag(a, Tfree, QPNONE); + if(p == nil) + break; + fb = p.iobuf; + } + if(p != nil) + p.put(); + cprint(sys->sprint("lo = %d; hi = %d", lo, hi)); +} + +# +# make freelist from scratch +# +Check.mkfreelist(c: self ref Check, sb: ref Superb) +{ + sb.fbuf[0:] = emptyblock[0:(FEPERBUF+1)*4]; + sb.tfree = 0; + put4(sb.fbuf, 0, 1); # nfree = 1 + for(a:=sb.fsize-sb.fstart-1; a >= 0; a--){ + i := a>>3; + if(i < 0 || i >= len c.amap.bits) + continue; + b := byte (1 << (a&7)); + if((c.amap.bits[i] & b) != byte 0) + continue; + addfree(c.dev, sb.fstart+a, sb); + c.amap.bits[i] |= b; + } + sb.iob.flags |= Bmod; +} + +# +# makes a copy of a Dentry's representation on disc so that +# the rest of the much larger iobuf can be freed. +# +Check.maked(c: self ref Check, a: int, s: int, qpath: int): ref Dentry +{ + p := c.xtag(a, Tdir, qpath); + if(p == nil) + return nil; + d := Dentry.get(p, s); + if(d == nil) + return nil; + copy := array[len d.buf] of byte; + copy[0:] = d.buf; + d.put(); + d.buf = copy; + return d; +} + +Check.modd(c: self ref Check, a: int, s: int, d1: ref Dentry) +{ + if(!(c.flags & Cbad)) + return; + p := Iobuf.get(c.dev, a, Bread); + d := Dentry.get(p, s); + if(d == nil){ + if(p != nil) + p.put(); + return; + } + d.buf[0:] = d1.buf; + p.flags |= Bmod; + p.put(); +} + +Check.xread(c: self ref Check, a: int, qpath: int) +{ + p := c.xtag(a, Tfile, qpath); + if(p != nil) + p.put(); +} + +Check.xtag(c: self ref Check, a: int, tag: int, qpath: int): ref Iobuf +{ + if(a == 0) + return nil; + p := Iobuf.get(c.dev, a, Bread); + if(p == nil){ + cprint(sys->sprint("check: \"%s\": xtag: p null", c.name)); + if(c.flags & (Cream|Ctag)){ + p = Iobuf.get(c.dev, a, Bmod); + if(p != nil){ + p.iobuf[0:] = emptyblock; + p.settag(tag, qpath); + c.mod++; + return p; + } + } + return nil; + } + if(p.checktag(tag, qpath)){ + cprint(sys->sprint("check: \"%s\": xtag: checktag", c.name)); + if(c.flags & Cream) + p.iobuf[0:] = emptyblock; + if(c.flags & (Cream|Ctag)){ + p.settag(tag, qpath); + c.mod++; + } + return p; + } + return p; +} + +Check.amark(c: self ref Check, a: int): int +{ + e := c.amap.mark(a); + if(e != nil){ + cprint(sys->sprint("check: \"%s\": %s %d", c.name, e, a)); + return e != "dup"; # don't clear dup blocks because rm might repair + } + return 0; +} + +Check.fmark(c: self ref Check,a: int): int +{ + e := c.amap.mark(a); + if(e != nil){ + cprint(sys->sprint("check: \"%s\": %s %d", c.name, e, a)); + return 1; + } + return 0; +} + +Check.missing(c: self ref Check, sb: ref Superb) +{ + n := 0; + for(a:=sb.fsize-sb.fstart-1; a>=0; a--){ + i := a>>3; + b := byte (1 << (a&7)); + if((c.amap.bits[i] & b) == byte 0){ + cprint(sys->sprint("missing: %d", sb.fstart+a)); + n++; + } + if(n > 10){ + cprint(sys->sprint(" ...")); + break; + } + } +} + +Check.qmark(c: self ref Check, qpath: int) +{ + e := c.qmap.mark(qpath); + if(e != nil){ + if(c.qmap.nbad+c.qmap.ndup < 20) + cprint(sys->sprint("check: \"%s\": qid %s 0x%ux", c.name, e, qpath)); + } +} + +Map.new(lo, hi: int): ref Map +{ + m := ref Map; + n := (hi-lo+7)>>3; + m.bits = array[n] of {* => byte 0}; + m.lo = lo; + m.hi = hi; + m.nbad = 0; + m.ndup = 0; + m.nmark = 0; + return m; +} + +Map.isset(m: self ref Map, i: int): int +{ + if(i < m.lo || i >= m.hi) + return -1; # hard to say + i -= m.lo; + return (m.bits[i>>3] & byte (1<<(i&7))) != byte 0; +} + +Map.mark(m: self ref Map, i: int): string +{ + if(i < m.lo || i >= m.hi){ + m.nbad++; + return "out of range"; + } + i -= m.lo; + b := byte (1 << (i&7)); + i >>= 3; + if((m.bits[i] & b) != byte 0){ + m.ndup++; + return "dup"; + } + m.bits[i] |= b; + m.nmark++; + return nil; +} + +cprint(s: string) +{ + if(consoleout != nil) + consoleout <-= s+"\n"; + else + eprint(s); +} diff --git a/appl/cmd/disk/kfscmd.b b/appl/cmd/disk/kfscmd.b new file mode 100644 index 00000000..e1b023a9 --- /dev/null +++ b/appl/cmd/disk/kfscmd.b @@ -0,0 +1,53 @@ +implement Kfscmd; + +include "sys.m"; + sys: Sys; + +include "draw.m"; +include "arg.m"; + +Kfscmd: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + arg := load Arg Arg->PATH; + if (arg == nil) + err(sys->sprint("can't load %s: %r", Arg->PATH)); + + cfs := "main"; + arg->init(args); + arg->setusage("disk/kfscmd [-n fsname] cmd ..."); + while((c := arg->opt()) != 0) + case c { + 'n' => + cfs = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + arg = nil; + + ctlf := "/chan/kfs."+cfs+".cmd"; + ctl := sys->open(ctlf, Sys->ORDWR); + if(ctl == nil) + err(sys->sprint("can't open %s: %r", ctlf)); + for(; args != nil; args = tl args){ + if(sys->fprint(ctl, "%s", hd args) > 0){ + buf := array[1024] of byte; + while((n := sys->read(ctl, buf, len buf)) > 0) + sys->write(sys->fildes(1), buf, n); + }else + err(sys->sprint("%q: %r", hd args)); + } +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "kfscmd: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/disk/mbr.b b/appl/cmd/disk/mbr.b new file mode 100644 index 00000000..9d51c945 --- /dev/null +++ b/appl/cmd/disk/mbr.b @@ -0,0 +1,134 @@ +implement Mbr; + +# +# install new master boot record boot code on PC disk. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "disks.m"; + disks: Disks; + Disk, PCpart, Toffset: import disks; + +include "arg.m"; + +Mbr: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + + + +# +# Default boot block prints an error message and reboots. +# +ndefmbr := Toffset; +defmbr := array[512] of { + byte 16rEB, byte 16r3C, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, + byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, +16r03E => byte 16rFA, byte 16rFC, byte 16r8C, byte 16rC8, byte 16r8E, byte 16rD8, byte 16r8E, byte 16rD0, + byte 16rBC, byte 16r00, byte 16r7C, byte 16rBE, byte 16r77, byte 16r7C, byte 16rE8, byte 16r19, + byte 16r00, byte 16r33, byte 16rC0, byte 16rCD, byte 16r16, byte 16rBB, byte 16r40, byte 16r00, + byte 16r8E, byte 16rC3, byte 16rBB, byte 16r72, byte 16r00, byte 16rB8, byte 16r34, byte 16r12, + byte 16r26, byte 16r89, byte 16r07, byte 16rEA, byte 16r00, byte 16r00, byte 16rFF, byte 16rFF, + byte 16rEB, byte 16rD6, byte 16rAC, byte 16r0A, byte 16rC0, byte 16r74, byte 16r09, byte 16rB4, + byte 16r0E, byte 16rBB, byte 16r07, byte 16r00, byte 16rCD, byte 16r10, byte 16rEB, byte 16rF2, + byte 16rC3, byte 'N', byte 'o', byte 't', byte ' ', byte 'a', byte ' ', byte 'b', + byte 'o', byte 'o', byte 't', byte 'a', byte 'b', byte 'l', byte 'e', byte ' ', + byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'o', byte 'r', byte ' ', + byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'e', byte 'r', byte 'r', + byte 'o', byte 'r', byte '\r', byte '\n', byte 'P', byte 'r', byte 'e', byte 's', + byte 's', byte ' ', byte 'a', byte 'l', byte 'm', byte 'o', byte 's', byte 't', + byte ' ', byte 'a', byte 'n', byte 'y', byte ' ', byte 'k', byte 'e', byte 'y', + byte ' ', byte 't', byte 'o', byte ' ', byte 'r', byte 'e', byte 'b', byte 'o', + byte 'o', byte 't', byte '.', byte '.', byte '.', byte 16r00, byte 16r00, byte 16r00, +}; + +init(nil: ref Draw->Context, args: list of string) +{ + flag9 := 0; + mbrfile: string; + sys = load Sys Sys->PATH; + disks = load Disks Disks->PATH; + + sys->pctl(Sys->FORKFD, nil); + disks->init(); + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("disk/mbr [-m mbrfile] disk"); + while((o := arg->opt()) != 0) + case o { + '9' => + flag9 = 1; + 'm' => + mbrfile = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + if(len args != 1) + arg->usage(); + arg = nil; + + disk := Disk.open(hd args, Sys->ORDWR, 0); + if(disk == nil) + fatal(sys->sprint("opendisk %s: %r", hd args)); + + if(disk.dtype == "floppy") + fatal(sys->sprint("will not install mbr on floppy")); + if(disk.secsize != 512) + fatal(sys->sprint("secsize %d invalid: must be 512", disk.secsize)); + + secsize := disk.secsize; + mbr := array[secsize*disk.s] of {* => byte 0}; + + # + # Start with initial sector from disk. + # + if(sys->seek(disk.fd, big 0, 0) < big 0) + fatal(sys->sprint("seek to boot sector: %r\n")); + if(sys->read(disk.fd, mbr, secsize) != secsize) + fatal(sys->sprint("reading boot sector: %r")); + + nmbr: int; + if(mbrfile == nil){ + nmbr = ndefmbr; + mbr[0:] = defmbr; + } else { + buf := array[secsize*(disk.s+1)] of {* => byte 0}; + if((sysfd := sys->open(mbrfile, Sys->OREAD)) == nil) + fatal(sys->sprint("open %s: %r", mbrfile)); + if((nmbr = sys->read(sysfd, buf, secsize*(disk.s+1))) < 0) + fatal(sys->sprint("read %s: %r", mbrfile)); + if(nmbr > secsize*disk.s) + fatal(sys->sprint("master boot record too large %d > %d", nmbr, secsize*disk.s)); + if(nmbr < secsize) + nmbr = secsize; + sysfd = nil; + buf[Toffset:] = mbr[Toffset:secsize]; + mbr[0:] = buf[0:nmbr]; + } + + if(flag9){ + for(i := Toffset; i < secsize; i++) + mbr[i] = byte 0; + mbr[Toffset:] = PCpart(0, Disks->Type9, big 0, big disk.s, disk.secs-big disk.s).bytes(disk); + } + mbr[secsize-2] = byte Disks->Magic0; + mbr[secsize-1] = byte Disks->Magic1; + nmbr = (nmbr+secsize-1)&~(secsize-1); + if(sys->seek(disk.wfd, big 0, 0) < big 0) + fatal(sys->sprint("seek to MBR sector: %r\n")); + if(sys->write(disk.wfd, mbr, nmbr) != nmbr) + fatal(sys->sprint("writing MBR: %r")); +} + +fatal(s: string) +{ + sys->fprint(sys->fildes(2), "disk/mbr: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/disk/mkext.b b/appl/cmd/disk/mkext.b new file mode 100644 index 00000000..fc13f2fe --- /dev/null +++ b/appl/cmd/disk/mkext.b @@ -0,0 +1,377 @@ +implement Mkext; + +include "sys.m"; + sys: Sys; + Dir, sprint, fprint: import sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "arg.m"; + arg: Arg; + +Mkext: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +LEN: con Sys->ATOMICIO; +NFLDS: con 6; # filename, modes, uid, gid, mtime, bytes + +bin: ref Iobuf; +uflag := 0; +tflag := 0; +hflag := 0; +vflag := 0; +fflag := 0; +qflag := 1; +stderr: ref Sys->FD; +bout: ref Iobuf; +argv0 := "mkext"; + +usage() +{ + fprint(stderr, "Usage: mkext [-h] [-u] [-v] [-f] [-t] [-q] [-d dest-fs] [file ...]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + error(sys->sprint("cannot load %s: %r\n", Bufio->PATH)); + + str = load String String->PATH; + if(str == nil) + error(sys->sprint("cannot load %s: %r\n", String->PATH)); + + arg = load Arg Arg->PATH; + if(arg == nil) + error(sys->sprint("cannot load %s: %r\n", Arg->PATH)); + + destdir := ""; + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'd' => + destdir = arg->arg(); + if(destdir == nil) + error("destination directory name missing"); + 'f' => + fflag = 1; + + 'h' => + hflag = 1; + bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + if(bout == nil) + error(sys->sprint("can't access standard output: %r")); + 'u' => + uflag = 1; + 't' => + tflag = 1; + 'v' => + vflag = 1; + 'q' => + qflag = 0; + * => + usage(); + } + args = arg->argv(); + + bin = bufio->fopen(sys->fildes(0), Sys->OREAD); + if(bin == nil) + error(sys->sprint("can't access standard input: %r")); + while((p := bin.gets('\n')) != nil){ + if(p == "end of archive\n"){ + fprint(stderr, "done\n"); + quit(nil); + } + fields: list of string; + nf: int; + if(qflag){ + fields = str->unquoted(p); + nf = len fields; + }else + (nf, fields) = sys->tokenize(p, " \t\n"); + if(nf != NFLDS){ + warn("too few fields in file header"); + continue; + } + name := hd fields; + fields = tl fields; + (mode, nil) := str->toint(hd fields, 8); + fields = tl fields; + uid := hd fields; + fields = tl fields; + gid := hd fields; + fields = tl fields; + (mtime, nil) := str->toint(hd fields, 10); + fields = tl fields; + (bytes, nil) := str->tobig(hd fields, 10); + if(args != nil){ + if(!selected(name, args)){ + if(bytes != big 0) + seekpast(bytes); + continue; + } + mkdirs(destdir, name); + } + name = destdir+name; + if(hflag){ + bout.puts(sys->sprint("%s %s %s %s %ud %bd\n", + quoted(name), octal(mode), uid, gid, mtime, bytes)); + if(bytes != big 0) + seekpast(bytes); + continue; + } + if(mode & Sys->DMDIR) + mkdir(name, mode, mtime, uid, gid); + else + extract(name, mode, mtime, uid, gid, bytes); + } + fprint(stderr, "premature end of archive\n"); + quit("eof"); +} + +quit(s: string) +{ + if(bout != nil) + bout.flush(); + if(s != nil) + raise "fail: "+s; + exit; +} + +fileprefix(prefix, s: string): int +{ + n := len prefix; + m := len s; + if(n > m || !str->prefix(prefix, s)) + return 0; + if(m > n && s[n] != '/') + return 0; + return 1; +} + +selected(s: string, args: list of string): int +{ + for(; args != nil; args = tl args) + if(fileprefix(hd args, s)) + return 1; + return 0; +} + +mkdirs(basedir, name: string) +{ + (nil, names) := sys->tokenize(name, "/"); + while(names != nil) { + #sys->print("mkdir %s\n", basedir); + create(basedir, Sys->OREAD, 8r775|Sys->DMDIR); + + if(tl names == nil) + break; + basedir = basedir + "/" + hd names; + names = tl names; + } +} + +mkdir(name: string, mode: int, mtime: int, uid: string, gid: string) +{ + d: Dir; + i: int; + + fd := create(name, Sys->OREAD, mode); + if(fd == nil){ + (i, d) = sys->stat(name); + if(i < 0 || !(d.mode & Sys->DMDIR)){ + warn(sys->sprint("can't make directory %s: %r", name)); + return; + } + }else{ + (i, d) = sys->fstat(fd); + if(i < 0) + warn(sys->sprint("can't stat %s: %r", name)); + fd = nil; + } + + d = sys->nulldir; + (nil, p) := str->splitr(name, "/"); + if(p == nil) + p = name; + d.name = p; + if(tflag) + d.mtime = mtime; + if(uflag){ + d.uid = uid; + d.gid = gid; + d.mtime = mtime; + } + d.mode = mode; + if(sys->wstat(name, d) < 0) + warn(sys->sprint("can't set modes for %s: %r", name)); + if(uflag){ + (i, d) = sys->stat(name); + if(i < 0) + warn(sys->sprint("can't reread modes for %s: %r", name)); + if(d.mtime != mtime) + warn(sys->sprint("%s: time mismatch %ud %ud\n", name, mtime, d.mtime)); + if(uid != d.uid) + warn(sys->sprint("%s: uid mismatch %s %s", name, uid, d.uid)); + if(gid != d.gid) + warn(sys->sprint("%s: gid mismatch %s %s", name, gid, d.gid)); + } +} + +extract(name: string, mode: int, mtime: int, uid: string, gid: string, bytes: big) +{ + n: int; + + if(vflag) + sys->print("x %s %bd bytes\n", name, bytes); + + sfd := create(name, Sys->OWRITE, mode); + if(sfd == nil) { + if(!fflag || sys->remove(name) == -1 || + (sfd = create(name, Sys->OWRITE, mode)) == nil) { + warn(sys->sprint("can't make file %s: %r", name)); + seekpast(bytes); + return; + } + } + b := bufio->fopen(sfd, Bufio->OWRITE); + if (b == nil) { + warn(sys->sprint("can't open file %s for bufio : %r", name)); + seekpast(bytes); + return; + } + buf := array [LEN] of byte; + for(tot := big 0; tot < bytes; tot += big n){ + n = len buf; + if(tot + big n > bytes) + n = int(bytes - tot); + n = bin.read(buf, n); + if(n <= 0) + error(sys->sprint("premature eof reading %s", name)); + if(b.write(buf, n) != n) + warn(sys->sprint("error writing %s: %r", name)); + } + + (i, nil) := sys->fstat(b.fd); + if(i < 0) + warn(sys->sprint("can't stat %s: %r", name)); + d := sys->nulldir; + (nil, p) := str->splitr(name, "/"); + if(p == nil) + p = name; + d.name = p; + if(tflag || uflag) + d.mtime = mtime; + if(uflag){ + d.uid = uid; + d.gid = gid; + } + d.mode = mode; + if(b.flush() == Bufio->ERROR) + warn(sys->sprint("error writing %s: %r", name)); + if(sys->fwstat(b.fd, d) < 0) + warn(sys->sprint("can't set modes for %s: %r", name)); + if(uflag){ + (i, d) = sys->fstat(b.fd); + if(i < 0) + warn(sys->sprint("can't reread modes for %s: %r", name)); + if(d.mtime != mtime) + warn(sys->sprint("%s: time mismatch %ud %ud\n", name, mtime, d.mtime)); + if(d.uid != uid) + warn(sys->sprint("%s: uid mismatch %s %s", name, uid, d.uid)); + if(d.gid != gid) + warn(sys->sprint("%s: gid mismatch %s %s", name, gid, d.gid)); + } + b.close(); +} + +seekpast(bytes: big) +{ + n: int; + + buf := array [LEN] of byte; + for(tot := big 0; tot < bytes; tot += big n){ + n = len buf; + if(tot + big n > bytes) + n = int(bytes - tot); + n = bin.read(buf, n); + if(n <= 0) + error("premature eof"); + } +} + +error(s: string) +{ + fprint(stderr, "%s: %s\n", argv0, s); + quit("error"); +} + +warn(s: string) +{ + fprint(stderr, "%s: %s\n", argv0, s); +} + +octal(i: int): string +{ + s := ""; + do { + t: string; + t[0] = '0' + (i&7); + s = t+s; + } while((i = (i>>3)&~(7<<29)) != 0); + return s; +} + +parent(name : string) : string +{ + slash := -1; + for (i := 0; i < len name; i++) + if (name[i] == '/') + slash = i; + if (slash > 0) + return name[0:slash]; + return "/"; +} + +create(name : string, rw : int, mode : int) : ref Sys->FD +{ + fd := sys->create(name, rw, mode); + if (fd == nil) { + p := parent(name); + (ok, d) := sys->stat(p); + if (ok < 0) + return nil; + omode := d.mode; + d = sys->nulldir; + d.mode = omode | 8r222; # ensure parent is writable + if(sys->wstat(p, d) < 0) { + warn(sys->sprint("can't set modes for %s: %r", p)); + return nil; + } + fd = sys->create(name, rw, mode); + d.mode = omode; + sys->wstat(p, d); + } + return fd; +} + +quoted(s: string): string +{ + if(qflag) + for(i:=0; i<len s; i++) + if((c := s[i]) == ' ' || c == '\t' || c == '\n' || c == '\'') + return str->quoted(s :: nil); + return s; +} diff --git a/appl/cmd/disk/mkfile b/appl/cmd/disk/mkfile new file mode 100644 index 00000000..46b7f067 --- /dev/null +++ b/appl/cmd/disk/mkfile @@ -0,0 +1,25 @@ +<../../../mkconfig + +DIRS=\ + prep\ + +TARG=\ + kfs.dis\ + mbr.dis\ + mkext.dis\ + mkfs.dis\ + kfscmd.dis\ + format.dis\ + ftl.dis\ + +SYSMODULES=\ + arg.m\ + sys.m\ + draw.m\ + bufio.m\ + string.m\ + +DISBIN=$ROOT/dis/disk + +<$ROOT/mkfiles/mkdis +<$ROOT/mkfiles/mksubdirs diff --git a/appl/cmd/disk/mkfs.b b/appl/cmd/disk/mkfs.b new file mode 100644 index 00000000..8b07aa8f --- /dev/null +++ b/appl/cmd/disk/mkfs.b @@ -0,0 +1,778 @@ +implement Mkfs; + +include "sys.m"; + sys: Sys; + Dir, sprint, fprint: import sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "arg.m"; + arg: Arg; + +Mkfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +LEN: con Sys->ATOMICIO; +HUNKS: con 128; + +Kfs, Fs, Archive: con iota; # types of destination file sytems + +File: adt { + new: string; + elem: string; + old: string; + uid: string; + gid: string; + mode: int; +}; + +b: ref Iobuf; +bout: ref Iobuf; # stdout when writing archive +newfile: string; +oldfile: string; +proto: string; +cputype: string; +users: string; +oldroot: string; +newroot: string; +prog := "mkfs"; +lineno := 0; +buf: array of byte; +zbuf: array of byte; +buflen := 1024-8; +indent: int; +verb: int; +modes: int; +ream: int; +debug: int; +xflag: int; +qflag := 1; +sfd: ref Sys->FD; +fskind: int; # Kfs, Fs, Archive +user: string; +stderr: ref Sys->FD; +usrid, grpid : string; +setuid: int; + +usage() +{ + fprint(stderr, "usage: %s [-apqrvx] [-d root] [-n kfsname] [-s src-fs] [-u userfile] [-z n] proto ...\n", prog); + quit("usage"); +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + str = load String String->PATH; + arg = load Arg Arg->PATH; + + sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil); + + stderr = sys->fildes(2); + if(arg == nil) + error(sys->sprint("can't load %s: %r", Arg->PATH)); + + user = getuser(); + if(user == nil) + user = "none"; + name := ""; + file := ref File; + file.new = ""; + file.old = nil; + file.mode = 0; + oldroot = ""; + newroot = "/n/kfs"; + users = nil; + fskind = Kfs; # i suspect Inferno default should be different + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'a' => + fskind = Archive; + newroot = ""; + bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + if(bout == nil) + error(sys->sprint("can't open standard output for archive: %r")); + 'd' => + fskind = Fs; + newroot = reqarg("destination directory (-d)"); + 'D' => + debug = 1; + 'n' => + name = reqarg("kfs instance name (-n)"); + 'p' => + modes = 1; + 'q' => + qflag = 0; + 'r' => + ream = 1; + 's' => + oldroot = reqarg("source directory (-d)"); + 'u' => + users = reqarg("/adm/users file (-u)"); + 'v' => + verb = 1; + 'x' => + xflag = 1; + 'z' => + (buflen, nil) = str->toint(reqarg("buffer length (-z)"), 10); + buflen -= 8; # qid.path and tag at end of each kfs block + 'U' => + usrid = reqarg("user name (-U)"); + 'G' => + grpid = reqarg("group name (-G)"); + 'S' => + setuid = 1; + * => + usage(); + } + + args = arg->argv(); + if(args == nil) + usage(); + + buf = array [buflen] of byte; + zbuf = array [buflen] of { * => byte 0 }; + + mountkfs(name); + kfscmd("allow"); + proto = "users"; + setusers(); + cputype = getenv("cputype"); + if(cputype == nil) + cputype = "dis"; + + errs := 0; + for(; args != nil; args = tl args){ + proto = hd args; + fprint(stderr, "processing %s\n", proto); + + b = bufio->open(proto, Sys->OREAD); + if(b == nil){ + fprint(stderr, "%s: can't open %s: %r: skipping\n", prog, proto); + errs++; + continue; + } + + lineno = 0; + indent = 0; + mkfs(file, -1); + b.close(); + } + fprint(stderr, "file system made\n"); + kfscmd("disallow"); + kfscmd("sync"); + if(errs) + quit("skipped protos"); + if(fskind == Archive){ + bout.puts("end of archive\n"); + if(bout.flush() == Bufio->ERROR) + error(sys->sprint("write error: %r")); + } +} + +quit(why: string) +{ + if(bout != nil) + bout.flush(); + if(why != nil) + raise "fail:"+why; + exit; +} + +reqarg(what: string): string +{ + if((o := arg->arg()) == nil){ + sys->fprint(stderr, "%s: missing %s\n", prog, what); + quit("usage"); + } + return o; +} + +mkfs(me: ref File, level: int) +{ + (child, fp) := getfile(me); + if(child == nil) + return; + if(child.elem == "+" || child.elem == "*" || child.elem == "%"){ + rec := child.elem[0] == '+'; + filesonly := child.elem[0] == '%'; + child.new = me.new; + setnames(child); + mktree(child, rec, filesonly); + (child, fp) = getfile(me); + } + while(child != nil && indent > level){ + if(mkfile(child)) + mkfs(child, indent); + (child, fp) = getfile(me); + } + if(child != nil){ + b.seek(fp, 0); + lineno--; + } +} + +mktree(me: ref File, rec: int, filesonly: int) +{ + fd := sys->open(oldfile, Sys->OREAD); + if(fd == nil){ + warn(sys->sprint("can't open %s: %r", oldfile)); + return; + } + + child := ref *me; + r := ref Rec(nil, 0); + for(;;){ + (n, d) := sys->dirread(fd); + if(n <= 0) + break; + for(i := 0; i < n; i++) + if (!recall(d[i].name, r)) { + if(filesonly && d[i].mode & Sys->DMDIR) + continue; + child.new = mkpath(me.new, d[i].name); + if(me.old != nil) + child.old = mkpath(me.old, d[i].name); + child.elem = d[i].name; + setnames(child); + if(copyfile(child, ref d[i], 1) && rec) + mktree(child, rec, filesonly); + } + } +} + +# Recall namespace fix +# -- remove duplicates (could use Readdir->init(,Readdir->COMPACT)) +# obc + +Rec: adt +{ + ad: array of string; + l: int; +}; + +AL : con HUNKS; +recall(e : string, r : ref Rec) : int +{ + if (r.ad == nil) r.ad = array[AL] of string; + # double array + if (r.l >= len r.ad) { + nar := array[2*(len r.ad)] of string; + nar[0:] = r.ad; + r.ad = nar; + } + for(i := 0; i < r.l; i++) + if (r.ad[i] == e) return 1; + r.ad[r.l++] = e; + return 0; +} + +mkfile(f: ref File): int +{ + (i, dir) := sys->stat(oldfile); + if(i < 0){ + warn(sys->sprint("can't stat file %s: %r", oldfile)); + skipdir(); + return 0; + } + return copyfile(f, ref dir, 0); +} + +copyfile(f: ref File, d: ref Dir, permonly: int): int +{ + mode: int; + + if(xflag && bout != nil){ + bout.puts(sys->sprint("%s\t%d\t%bd\n", quoted(f.new), d.mtime, d.length)); + return (d.mode & Sys->DMDIR) != 0; + } + d.name = f.elem; + if(d.dtype != 'M' && d.dtype != 'U'){ # hmm... Indeed! + d.uid = "inferno"; + d.gid = "inferno"; + mode = (d.mode >> 6) & 7; + d.mode |= mode | (mode << 3); + } + if(f.uid != "-") + d.uid = f.uid; + if(f.gid != "-") + d.gid = f.gid; + if(fskind == Fs && !setuid){ # new system: set to nil + d.uid = user; + d.gid = user; + } + if (usrid != nil) + d.uid = usrid; + if (grpid != nil) + d.gid = grpid; + if(f.mode != ~0){ + if(permonly) + d.mode = (d.mode & ~8r666) | (f.mode & 8r666); + else if((d.mode&Sys->DMDIR) != (f.mode&Sys->DMDIR)) + warn(sys->sprint("inconsistent mode for %s", f.new)); + else + d.mode = f.mode; + } + if(!uptodate(d, newfile)){ + if(d.mode & Sys->DMDIR) + mkdir(d); + else { + if(verb) + fprint(stderr, "%s\n", f.new); + copy(d); + } + }else if(modes){ + nd := sys->nulldir; + nd.mode = d.mode; + nd.mtime = d.mtime; + nd.gid = d.gid; + if(sys->wstat(newfile, nd) < 0) + warn(sys->sprint("can't set modes for %s: %r", f.new)); + # do the uid separately since different file systems object + nd = sys->nulldir; + nd.uid = d.uid; + sys->wstat(newfile, nd); + } + return (d.mode & Sys->DMDIR) != 0; +} + + +# check if file to is up to date with +# respect to the file represented by df + +uptodate(df: ref Dir, newf: string): int +{ + if(fskind == Archive || ream) + return 0; + (i, dt) := sys->stat(newf); + if(i < 0) + return 0; + return dt.mtime >= df.mtime; +} + +copy(d: ref Dir) +{ + t: ref Sys->FD; + n: int; + + f := sys->open(oldfile, Sys->OREAD); + if(f == nil){ + warn(sys->sprint("can't open %s: %r", oldfile)); + return; + } + t = nil; + if(fskind == Archive) + arch(d); + else{ + (dname, fname) := str->splitr(newfile, "/"); + if(fname == nil) + error(sys->sprint("internal temporary file error (%s)", dname)); + cptmp := dname+"__mkfstmp"; + t = sys->create(cptmp, Sys->OWRITE, 8r666); + if(t == nil){ + warn(sys->sprint("can't create %s: %r", newfile)); + return; + } + } + + for(tot := big 0;; tot += big n){ + n = sys->read(f, buf, buflen); + if(n < 0){ + warn(sys->sprint("can't read %s: %r", oldfile)); + break; + } + if(n == 0) + break; + if(fskind == Archive){ + if(bout.write(buf, n) != n) + error(sys->sprint("write error: %r")); + }else if(buf[0:buflen] == zbuf[0:buflen]){ + if(sys->seek(t, big buflen, 1) < big 0) + error(sys->sprint("can't write zeros to %s: %r", newfile)); + }else if(sys->write(t, buf, n) < n) + error(sys->sprint("can't write %s: %r", newfile)); + } + f = nil; + if(tot != d.length){ + warn(sys->sprint("wrong number bytes written to %s (was %bd should be %bd)", + newfile, tot, d.length)); + if(fskind == Archive){ + warn("seeking to proper position"); + bout.seek(d.length - tot, 1); + } + } + if(fskind == Archive) + return; + sys->remove(newfile); + nd := sys->nulldir; + nd.name = d.name; + nd.mode = d.mode; + nd.mtime = d.mtime; + if(sys->fwstat(t, nd) < 0) + error(sys->sprint("can't move tmp file to %s: %r", newfile)); + nd = sys->nulldir; + nd.gid = d.gid; + if(sys->fwstat(t, nd) < 0) + warn(sys->sprint("can't set group id of %s to %s: %r", newfile, d.gid)); + nd.gid = nil; + nd.uid = d.uid; + sys->fwstat(t, nd); +} + +mkdir(d: ref Dir) +{ + if(fskind == Archive){ + arch(d); + return; + } + fd := sys->create(newfile, Sys->OREAD, d.mode); + nd := sys->nulldir; + nd.mode = d.mode; + nd.gid = d.gid; + nd.mtime = d.mtime; + if(fd == nil){ + (i, d1) := sys->stat(newfile); + if(i < 0 || !(d1.mode & Sys->DMDIR)) + error(sys->sprint("can't create %s", newfile)); + if(sys->wstat(newfile, nd) < 0) + warn(sys->sprint("can't set modes for %s: %r", newfile)); + nd = sys->nulldir; + nd.uid = d.uid; + sys->wstat(newfile, nd); + return; + } + if(sys->fwstat(fd, nd) < 0) + warn(sys->sprint("can't set modes for %s: %r", newfile)); + nd = sys->nulldir; + nd.uid = d.uid; + sys->fwstat(fd, nd); +} + +arch(d: ref Dir) +{ + bout.puts(sys->sprint("%s %s %s %s %ud %bd\n", + quoted(newfile), octal(d.mode), d.uid, d.gid, d.mtime, d.length)); +} + +mkpath(prefix, elem: string): string +{ + return sys->sprint("%s/%s", prefix, elem); +} + +setnames(f: ref File) +{ + newfile = newroot+f.new; + if(f.old != nil){ + if(f.old[0] == '/') + oldfile = oldroot+f.old; + else + oldfile = f.old; + }else + oldfile = oldroot+f.new; +} + +# +# skip all files in the proto that +# could be in the current dir +# +skipdir() +{ + if(indent < 0) + return; + level := indent; + for(;;){ + indent = 0; + fp := b.offset(); + p := b.gets('\n'); + lineno++; + if(p == nil){ + indent = -1; + return; + } + for(j := 0; (c := p[j++]) != '\n';) + if(c == ' ') + indent++; + else if(c == '\t') + indent += 8; + else + break; + if(indent <= level){ + b.seek(fp, 0); + lineno--; + return; + } + } +} + +getfile(old: ref File): (ref File, big) +{ + f: ref File; + p, elem: string; + c: int; + + if(indent < 0) + return (nil, big 0); + fp := b.offset(); + do { + indent = 0; + p = b.gets('\n'); + lineno++; + if(p == nil){ + indent = -1; + return (nil, big 0); + } + for(; (c = p[0]) != '\n'; p = p[1:]) + if(c == ' ') + indent++; + else if(c == '\t') + indent += 8; + else + break; + } while(c == '\n' || c == '#'); + f = ref File; + (elem, p) = getname(p); + if(debug) + fprint(stderr, "getfile: %s root %s\n", elem, old.new); + f.new = mkpath(old.new, elem); + (nil, f.elem) = str->splitr(f.new, "/"); + if(f.elem == nil) + error(sys->sprint("can't find file name component of %s", f.new)); + (f.mode, p) = getmode(p); + (f.uid, p) = getname(p); + if(f.uid == nil) + f.uid = "-"; + (f.gid, p) = getname(p); + if(f.gid == nil) + f.gid = "-"; + f.old = getpath(p); + if(f.old == "-") + f.old = nil; + setnames(f); + + if(debug) + printfile(f); + + return (f, fp); +} + +getpath(p: string): string +{ + for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:]) + ; + for(n := 0; (c = p[n]) != '\n' && c != ' ' && c != '\t'; n++) + ; + return p[0:n]; +} + +getname(p: string): (string, string) +{ + for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:]) + ; + i := 0; + s := ""; + quoted := 0; + for(; (c = p[0]) != '\n' && (c != ' ' && c != '\t' || quoted); p = p[1:]){ + if(quoted && c == '\'' && p[1] == '\'') + p = p[1:]; + else if(c == '\'' && qflag){ + quoted = !quoted; + continue; + } + s[i++] = c; + } + if(len s > 0 && s[0] == '$'){ + s = getenv(s[1:]); + if(s == nil) + error(sys->sprint("can't read environment variable %s", s)); + } + return (s, p); +} + +getenv(s: string): string +{ + if(s == "user") + return getuser(); + return readfile("/env/"+s); +} + +getuser(): string +{ + return readfile("/dev/user"); +} + +readfile(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd != nil){ + a := array[256] of byte; + n := sys->read(fd, a, len a); + if(n > 0) + return string a[0:n]; + } + return nil; +} + +getmode(p: string): (int, string) +{ + s: string; + + (s, p) = getname(p); + if(s == nil || s == "-") + return (~0, p); + os := s; + m := 0; + if(s[0] == 'd'){ + m |= Sys->DMDIR; + s = s[1:]; + } + if(s[0] == 'a'){ + m |= Sys->DMAPPEND; + s = s[1:]; + } + if(s[0] == 'l'){ + m |= Sys->DMEXCL; + s = s[1:]; + } + + for(i:=0; i<len s || i < 3; i++) + if(i >= len s || !(s[i]>='0' && s[i]<='7')){ + warn(sys->sprint("bad mode specification %s", os)); + return (~0, p); + } + (v, nil) := str->toint(s, 8); + return (m|v, p); +} + +quoted(s: string): string +{ + if(qflag) + return sys->sprint("%q", s); + return s; +} + +setusers() +{ + if(fskind != Kfs) + return; + file := ref File; + m := modes; + modes = 1; + file.uid = "adm"; + file.gid = "adm"; + file.mode = Sys->DMDIR|8r775; + file.new = "/adm"; + file.elem = "adm"; + file.old = nil; + setnames(file); + mkfile(file); + file.new = "/adm/users"; + file.old = users; + file.elem = "users"; + file.mode = 8r664; + setnames(file); + mkfile(file); + kfscmd("user"); + mkfile(file); + file.mode = Sys->DMDIR|8r775; + file.new = "/adm"; + file.old = "/adm"; + file.elem = "adm"; + setnames(file); + mkfile(file); + modes = m; +} + +# this isn't right for the current #K +mountkfs(name: string) +{ + kname: string; + + if(fskind != Kfs) + return; + if(name != nil) + kname = sys->sprint("/srv/kfs.%s", name); + else + kname = "/srv/kfs"; + fd := sys->open(kname, Sys->ORDWR); + if(fd == nil){ + fprint(stderr, "%s: can't open %s: %r\n", prog, kname); + quit("open kfs"); + } + if(sys->mount(fd, nil, "/n/kfs", Sys->MREPL|Sys->MCREATE, "") < 0){ + fprint(stderr, "%s: can't mount kfs on /n/kfs: %r\n", prog); + quit("mount kfs"); + } + kname += ".cmd"; + sfd = sys->open(kname, Sys->ORDWR); + if(sfd == nil){ + fprint(stderr, "%s: can't open %s: %r\n", prog, kname); + quit("open kfscmd"); + } +} + +kfscmd(cmd: string) +{ + if(fskind != Kfs || sfd == nil) + return; + a := array of byte cmd; + if(sys->write(sfd, a, len a) != len a){ + fprint(stderr, "%s: error writing %s: %r", prog, cmd); + return; + } + for(;;){ + reply := array[4*1024] of byte; + n := sys->read(sfd, reply, len reply); + if(n <= 0) + return; + s := string reply[0:n]; + if(s == "done" || s == "success") + return; + if(s == "unknown command"){ + fprint(stderr, "%s: command %s not recognized\n", prog, cmd); + return; + } + } +} + +error(s: string) +{ + fprint(stderr, "%s: %s: %d: %s\n", prog, proto, lineno, s); + kfscmd("disallow"); + kfscmd("sync"); + quit("error"); +} + +warn(s: string) +{ + fprint(stderr, "%s: %s: %d: %s\n", prog, proto, lineno, s); +} + +printfile(f: ref File) +{ + if(f.old != nil) + fprint(stderr, "%s from %s %s %s %s\n", f.new, f.old, f.uid, f.gid, octal(f.mode)); + else + fprint(stderr, "%s %s %s %s\n", f.new, f.uid, f.gid, octal(f.mode)); +} + +octal(i: int): string +{ + s := ""; + do { + t: string; + t[0] = '0' + (i&7); + s = t+s; + } while((i = (i>>3)&~(7<<29)) != 0); + return s; +} diff --git a/appl/cmd/disk/prep/calc.tab.b b/appl/cmd/disk/prep/calc.tab.b new file mode 100644 index 00000000..25f81487 --- /dev/null +++ b/appl/cmd/disk/prep/calc.tab.b @@ -0,0 +1,454 @@ +implement Calc; + +#line 2 "calc.y" +# +# from Plan 9. subject to the Lucent Public License 1.02 +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + + NUM, + DOT, + DOLLAR, + ADD, + SUB, + MUL, + DIV, + FRAC, + NEG: con iota; + +Exp: adt { + ty: int; + n: big; + e1, e2: cyclic ref Exp; +}; + +YYSTYPE: adt { + e: ref Exp; +}; +yyexp: ref Exp; + +YYLEX: adt { + s: string; + n: int; + lval: YYSTYPE; + lex: fn(l: self ref YYLEX): int; + error: fn(l: self ref YYLEX, msg: string); +}; +Calc: module { + + parseexpr: fn(s: string, a, b, c: big): (big, string); + init: fn(nil: ref Draw->Context, nil: list of string); +NUMBER: con 57346; +UNARYMINUS: con 57347; + +}; +YYEOFCODE: con 1; +YYERRCODE: con 2; +YYMAXDEPTH: con 200; + +#line 68 "calc.y" + + +mkNUM(x: big): ref Exp +{ + return ref Exp(NUM, x, nil, nil); +} + +mkOP(ty: int, e1: ref Exp, e2: ref Exp): ref Exp +{ + return ref Exp(ty, big 0, e1, e2); +} + +dot, size, dollar: big; + +YYLEX.lex(l: self ref YYLEX): int +{ + while(l.n < len l.s && isspace(l.s[l.n])) + l.n++; + + if(l.n == len l.s) + return -1; + + if(isdigit(l.s[l.n])){ + for(o := l.n; o < len l.s && isdigit(l.s[o]); o++) + ; + l.lval.e = mkNUM(big l.s[l.n:o]); + l.n = o; + return NUMBER; + } + + return l.s[l.n++]; +} + +isdigit(c: int): int +{ + return c >= '0' && c <= '9'; +} + +isspace(c: int): int +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'; +} + +YYLEX.error(nil: self ref YYLEX, s: string) +{ + raise s; +} + +eval(e: ref Exp): big +{ + case e.ty { + NUM => + return e.n; + DOT => + return dot; + DOLLAR => + return dollar; + ADD => + return eval(e.e1)+eval(e.e2); + SUB => + return eval(e.e1)-eval(e.e2); + MUL => + return eval(e.e1)*eval(e.e2); + DIV => + i := eval(e.e2); + if(i == big 0) + raise "division by zero"; + return eval(e.e1)/i; + FRAC => + return (size*eval(e.e1))/big 100; + NEG => + return -eval(e.e1); + * => + raise "invalid operator"; + } +} + +parseexpr(s: string, xdot: big, xdollar: big, xsize: big): (big, string) +{ + dot = xdot; + size = xsize; + dollar = xdollar; + l := ref YYLEX(s, 0, YYSTYPE(nil)); + { + yyparse(l); + if(yyexp == nil) + return (big 0, "nil yylval?"); + return (eval(yyexp), nil); + }exception e{ + "*" => + return (big 0, e); + } +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + while((args = tl args) != nil){ + (r, e) := parseexpr(hd args, big 1000, big 1000000, big 1000000); + if(e != nil) + sys->print("%s\n", e); + else + sys->print("%bd\n", r); + } +} + +yyexca := array[] of {-1, 1, + 1, -1, + -2, 0, +}; +YYNPROD: con 12; +YYPRIVATE: con 57344; +yytoknames: array of string; +yystates: array of string; +yydebug: con 0; +YYLAST: con 30; +yyact := array[] of { + 8, 9, 10, 11, 3, 12, 7, 2, 12, 19, + 1, 4, 5, 6, 13, 14, 15, 16, 17, 18, + 8, 9, 10, 11, 0, 12, 10, 11, 0, 12, +}; +yypact := array[] of { + 0,-1000, 15,-1000,-1000,-1000, 0, 0, 0, 0, + 0, 0,-1000, -5,-1000, 19, 19, -2, -2,-1000, +}; +yypgo := array[] of { + 0, 7, 10, +}; +yyr1 := array[] of { + 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, +}; +yyr2 := array[] of { + 0, 1, 1, 1, 1, 3, 3, 3, 3, 3, + 2, 2, +}; +yychk := array[] of { +-1000, -2, -1, 4, 11, 12, 13, 6, 5, 6, + 7, 8, 10, -1, -1, -1, -1, -1, -1, 14, +}; +yydef := array[] of { + 0, -2, 1, 2, 3, 4, 0, 0, 0, 0, + 0, 0, 10, 0, 11, 6, 7, 8, 9, 5, +}; +yytok1 := array[] of { + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 12, 10, 3, 3, + 13, 14, 7, 5, 3, 6, 11, 8, +}; +yytok2 := array[] of { + 2, 3, 4, 9, +}; +yytok3 := array[] of { + 0 +}; + +YYSys: module +{ + FD: adt + { + fd: int; + }; + fildes: fn(fd: int): ref FD; + fprint: fn(fd: ref FD, s: string, *): int; +}; + +yysys: YYSys; +yystderr: ref YYSys->FD; + +YYFLAG: con -1000; + +# parser for yacc output + +yytokname(yyc: int): string +{ + if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil) + return yytoknames[yyc-1]; + return "<"+string yyc+">"; +} + +yystatname(yys: int): string +{ + if(yys >= 0 && yys < len yystates && yystates[yys] != nil) + return yystates[yys]; + return "<"+string yys+">\n"; +} + +yylex1(yylex: ref YYLEX): int +{ + c : int; + yychar := yylex.lex(); + if(yychar <= 0) + c = yytok1[0]; + else if(yychar < len yytok1) + c = yytok1[yychar]; + else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2) + c = yytok2[yychar-YYPRIVATE]; + else{ + n := len yytok3; + c = 0; + for(i := 0; i < n; i+=2) { + if(yytok3[i+0] == yychar) { + c = yytok3[i+1]; + break; + } + } + if(c == 0) + c = yytok2[1]; # unknown char + } + if(yydebug >= 3) + yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c)); + return c; +} + +YYS: adt +{ + yyv: YYSTYPE; + yys: int; +}; + +yyparse(yylex: ref YYLEX): int +{ + if(yydebug >= 1 && yysys == nil) { + yysys = load YYSys "$Sys"; + yystderr = yysys->fildes(2); + } + + yys := array[YYMAXDEPTH] of YYS; + + yyval: YYSTYPE; + yystate := 0; + yychar := -1; + yynerrs := 0; # number of errors + yyerrflag := 0; # error recovery flag + yyp := -1; + yyn := 0; + +yystack: + for(;;){ + # put a state and value onto the stack + if(yydebug >= 4) + yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + + yyp++; + if(yyp >= len yys) + yys = (array[len yys * 2] of YYS)[0:] = yys; + yys[yyp].yys = yystate; + yys[yyp].yyv = yyval; + + for(;;){ + yyn = yypact[yystate]; + if(yyn > YYFLAG) { # simple state + if(yychar < 0) + yychar = yylex1(yylex); + yyn += yychar; + if(yyn >= 0 && yyn < YYLAST) { + yyn = yyact[yyn]; + if(yychk[yyn] == yychar) { # valid shift + yychar = -1; + yyp++; + if(yyp >= len yys) + yys = (array[len yys * 2] of YYS)[0:] = yys; + yystate = yyn; + yys[yyp].yys = yystate; + yys[yyp].yyv = yylex.lval; + if(yyerrflag > 0) + yyerrflag--; + if(yydebug >= 4) + yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + continue; + } + } + } + + # default state action + yyn = yydef[yystate]; + if(yyn == -2) { + if(yychar < 0) + yychar = yylex1(yylex); + + # look through exception table + for(yyxi:=0;; yyxi+=2) + if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate) + break; + for(yyxi += 2;; yyxi += 2) { + yyn = yyexca[yyxi]; + if(yyn < 0 || yyn == yychar) + break; + } + yyn = yyexca[yyxi+1]; + if(yyn < 0){ + yyn = 0; + break yystack; + } + } + + if(yyn != 0) + break; + + # error ... attempt to resume parsing + if(yyerrflag == 0) { # brand new error + yylex.error("syntax error"); + yynerrs++; + if(yydebug >= 1) { + yysys->fprint(yystderr, "%s", yystatname(yystate)); + yysys->fprint(yystderr, "saw %s\n", yytokname(yychar)); + } + } + + if(yyerrflag != 3) { # incompletely recovered error ... try again + yyerrflag = 3; + + # find a state where "error" is a legal shift action + while(yyp >= 0) { + yyn = yypact[yys[yyp].yys] + YYERRCODE; + if(yyn >= 0 && yyn < YYLAST) { + yystate = yyact[yyn]; # simulate a shift of "error" + if(yychk[yystate] == YYERRCODE) + continue yystack; + } + + # the current yyp has no shift onn "error", pop stack + if(yydebug >= 2) + yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n", + yys[yyp].yys, yys[yyp-1].yys ); + yyp--; + } + # there is no state on the stack with an error shift ... abort + yyn = 1; + break yystack; + } + + # no shift yet; clobber input char + if(yydebug >= 2) + yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar)); + if(yychar == YYEOFCODE) { + yyn = 1; + break yystack; + } + yychar = -1; + # try again in the same state + } + + # reduction by production yyn + if(yydebug >= 2) + yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); + + yypt := yyp; + yyp -= yyr2[yyn]; +# yyval = yys[yyp+1].yyv; + yym := yyn; + + # consult goto table to find next state + yyn = yyr1[yyn]; + yyg := yypgo[yyn]; + yyj := yyg + yys[yyp].yys + 1; + + if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) + yystate = yyact[yyg]; + case yym { + +1=> +#line 54 "calc.y" +{ yyexp = yys[yypt-0].yyv.e; return 0; } +2=> +yyval.e = yys[yyp+1].yyv.e; +3=> +#line 57 "calc.y" +{ yyval.e = mkOP(DOT, nil, nil); } +4=> +#line 58 "calc.y" +{ yyval.e = mkOP(DOLLAR, nil, nil); } +5=> +#line 59 "calc.y" +{ yyval.e = yys[yypt-1].yyv.e; } +6=> +#line 60 "calc.y" +{ yyval.e = mkOP(ADD, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); } +7=> +#line 61 "calc.y" +{ yyval.e = mkOP(SUB, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); } +8=> +#line 62 "calc.y" +{ yyval.e = mkOP(MUL, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); } +9=> +#line 63 "calc.y" +{ yyval.e = mkOP(DIV, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); } +10=> +#line 64 "calc.y" +{ yyval.e = mkOP(FRAC, yys[yypt-1].yyv.e, nil); } +11=> +#line 65 "calc.y" +{ yyval.e = mkOP(NEG, yys[yypt-0].yyv.e, nil); } + } + } + + return yyn; +} diff --git a/appl/cmd/disk/prep/calc.tab.m b/appl/cmd/disk/prep/calc.tab.m new file mode 100644 index 00000000..fa531c74 --- /dev/null +++ b/appl/cmd/disk/prep/calc.tab.m @@ -0,0 +1,7 @@ +Calc: module { + + parseexpr: fn(s: string, a, b, c: big): (big, string); + init: fn(nil: ref Draw->Context, nil: list of string); +NUMBER: con 57346; +UNARYMINUS: con 57347; +}; diff --git a/appl/cmd/disk/prep/calc.y b/appl/cmd/disk/prep/calc.y new file mode 100644 index 00000000..7ce56049 --- /dev/null +++ b/appl/cmd/disk/prep/calc.y @@ -0,0 +1,174 @@ +%{ +# +# from Plan 9. subject to the Lucent Public License 1.02 +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + + NUM, + DOT, + DOLLAR, + ADD, + SUB, + MUL, + DIV, + FRAC, + NEG: con iota; + +Exp: adt { + ty: int; + n: big; + e1, e2: cyclic ref Exp; +}; + +YYSTYPE: adt { + e: ref Exp; +}; +yyexp: ref Exp; + +YYLEX: adt { + s: string; + n: int; + lval: YYSTYPE; + lex: fn(l: self ref YYLEX): int; + error: fn(l: self ref YYLEX, msg: string); +}; +%} +%module Calc +{ + parseexpr: fn(s: string, a, b, c: big): (big, string); + init: fn(nil: ref Draw->Context, nil: list of string); +} + +%token <e> NUMBER + +%type <e> expr + +%left '+' '-' +%left '*' '/' +%left UNARYMINUS '%' +%% +top: expr { yyexp = $1; return 0; } + +expr: NUMBER + | '.' { $$ = mkOP(DOT, nil, nil); } + | '$' { $$ = mkOP(DOLLAR, nil, nil); } + | '(' expr ')' { $$ = $2; } + | expr '+' expr { $$ = mkOP(ADD, $1, $3); } + | expr '-' expr { $$ = mkOP(SUB, $1, $3); } + | expr '*' expr { $$ = mkOP(MUL, $1, $3); } + | expr '/' expr { $$ = mkOP(DIV, $1, $3); } + | expr '%' { $$ = mkOP(FRAC, $1, nil); } + | '-' expr %prec UNARYMINUS { $$ = mkOP(NEG, $2, nil); } + ; + +%% + +mkNUM(x: big): ref Exp +{ + return ref Exp(NUM, x, nil, nil); +} + +mkOP(ty: int, e1: ref Exp, e2: ref Exp): ref Exp +{ + return ref Exp(ty, big 0, e1, e2); +} + +dot, size, dollar: big; + +YYLEX.lex(l: self ref YYLEX): int +{ + while(l.n < len l.s && isspace(l.s[l.n])) + l.n++; + + if(l.n == len l.s) + return -1; + + if(isdigit(l.s[l.n])){ + for(o := l.n; o < len l.s && isdigit(l.s[o]); o++) + ; + l.lval.e = mkNUM(big l.s[l.n:o]); + l.n = o; + return NUMBER; + } + + return l.s[l.n++]; +} + +isdigit(c: int): int +{ + return c >= '0' && c <= '9'; +} + +isspace(c: int): int +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'; +} + +YYLEX.error(nil: self ref YYLEX, s: string) +{ + raise s; +} + +eval(e: ref Exp): big +{ + case e.ty { + NUM => + return e.n; + DOT => + return dot; + DOLLAR => + return dollar; + ADD => + return eval(e.e1)+eval(e.e2); + SUB => + return eval(e.e1)-eval(e.e2); + MUL => + return eval(e.e1)*eval(e.e2); + DIV => + i := eval(e.e2); + if(i == big 0) + raise "division by zero"; + return eval(e.e1)/i; + FRAC => + return (size*eval(e.e1))/big 100; + NEG => + return -eval(e.e1); + * => + raise "invalid operator"; + } +} + +parseexpr(s: string, xdot: big, xdollar: big, xsize: big): (big, string) +{ + dot = xdot; + size = xsize; + dollar = xdollar; + l := ref YYLEX(s, 0, YYSTYPE(nil)); + { + yyparse(l); + if(yyexp == nil) + return (big 0, "nil yylval?"); + return (eval(yyexp), nil); + }exception e{ + "*" => + return (big 0, e); + } +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + while((args = tl args) != nil){ + (r, e) := parseexpr(hd args, big 1000, big 1000000, big 1000000); + if(e != nil) + sys->print("%s\n", e); + else + sys->print("%bd\n", r); + } +} + diff --git a/appl/cmd/disk/prep/fdisk.b b/appl/cmd/disk/prep/fdisk.b new file mode 100644 index 00000000..00ecbb36 --- /dev/null +++ b/appl/cmd/disk/prep/fdisk.b @@ -0,0 +1,925 @@ +implement Fdisk; + +# +# fdisk - edit dos disk partition table +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "disks.m"; + disks: Disks; + Disk, PCpart: import disks; + NTentry, Toffset, TentrySize: import Disks; + Magic0, Magic1: import Disks; + readn: import disks; + +include "pedit.m"; + pedit: Pedit; + Edit, Part: import pedit; + +include "arg.m"; + +Fdisk: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Mpart: con 64; + +blank := 0; +dowrite := 0; +file := 0; +rdonly := 0; +doauto := 0; +mbroffset := big 0; +printflag := 0; +printchs := 0; +sec2cyl := big 0; +written := 0; + +edit: ref Edit; +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + disks = load Disks Disks->PATH; + pedit = load Pedit Pedit->PATH; + + sys->pctl(Sys->FORKFD, nil); + disks->init(); + pedit->init(); + + edit = Edit.mk("cylinder"); + + edit.add = cmdadd; + edit.del = cmddel; + edit.okname = cmdokname; + edit.ext = cmdext; + edit.help = cmdhelp; + edit.sum = cmdsum; + edit.write = cmdwrite; + edit.printctl = cmdprintctl; + + stderr = sys->fildes(2); + + secsize := 0; + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("disk/fdisk [-abfprvw] [-s sectorsize] /dev/sdC0/data"); + while((o := arg->opt()) != 0) + case o { + 'a' => + doauto++; + 'b' => + blank++; + 'f' => + file++; + 'p' => + printflag++; + 'r' => + rdonly++; + 's' => + secsize = int arg->earg(); + 'v' => + printchs++; + 'w' => + dowrite++; + * => + arg->usage(); + } + args = arg->argv(); + if(len args != 1) + arg->usage(); + arg = nil; + + mode := Sys->ORDWR; + if(rdonly) + mode = Sys->OREAD; + edit.disk = Disk.open(hd args, mode, file); + if(edit.disk == nil) { + sys->fprint(stderr, "cannot open disk: %r\n"); + exits("opendisk"); + } + + if(secsize != 0) { + edit.disk.secsize = secsize; + edit.disk.secs = edit.disk.size / big secsize; + } + + sec2cyl = big (edit.disk.h * edit.disk.s); + edit.end = edit.disk.secs / sec2cyl; + + findmbr(edit); + + if(blank) + blankpart(edit); + else + rdpart(edit, big 0, big 0); + + if(doauto) + autopart(edit); + + { + if(dowrite) + edit.runcmd("w"); + + if(printflag) + edit.runcmd("P"); + + if(dowrite || printflag) + exits(nil); + + sys->fprint(stderr, "cylinder = %bd bytes\n", sec2cyl*big edit.disk.secsize); + edit.runcmd("p"); + for(;;) { + sys->fprint(stderr, ">>> "); + edit.runcmd(edit.getline()); + } + }exception e{ + "*" => + sys->fprint(stderr, "fdisk: exception %q\n", e); + if(written) + recover(edit); + } +} + +Active: con 16r80; # partition is active +Primary: con 16r01; # internal flag + +TypeBB: con 16rFF; + +TypeEMPTY: con 16r00; +TypeFAT12: con 16r01; +TypeXENIX: con 16r02; # root +TypeXENIXUSR: con 16r03; # usr +TypeFAT16: con 16r04; +TypeEXTENDED: con 16r05; +TypeFATHUGE: con 16r06; +TypeHPFS: con 16r07; +TypeAIXBOOT: con 16r08; +TypeAIXDATA: con 16r09; +TypeOS2BOOT: con 16r0A; # OS/2 Boot Manager +TypeFAT32: con 16r0B; # FAT 32 +TypeFAT32LBA: con 16r0C; # FAT 32 needing LBA support +TypeEXTHUGE: con 16r0F; # FAT 32 extended partition +TypeUNFORMATTED: con 16r16; # unformatted primary partition (OS/2 FDISK)? +TypeHPFS2: con 16r17; +TypeIBMRecovery: con 16r1C; # really hidden fat +TypeCPM0: con 16r52; +TypeDMDDO: con 16r54; # Disk Manager Dynamic Disk Overlay +TypeGB: con 16r56; # ???? +TypeSPEEDSTOR: con 16r61; +TypeSYSV386: con 16r63; # also HURD? +TypeNETWARE: con 16r64; +TypePCIX: con 16r75; +TypeMINIX13: con 16r80; # Minix v1.3 and below +TypeMINIX: con 16r81; # Minix v1.5+ +TypeLINUXSWAP: con 16r82; +TypeLINUX: con 16r83; +TypeLINUXEXT: con 16r85; +TypeAMOEBA: con 16r93; +TypeAMOEBABB: con 16r94; +TypeBSD386: con 16rA5; +TypeBSDI: con 16rB7; +TypeBSDISWAP: con 16rB8; +TypeOTHER: con 16rDA; +TypeCPM: con 16rDB; +TypeDellRecovery: con 16rDE; +TypeSPEEDSTOR12: con 16rE1; +TypeSPEEDSTOR16: con 16rE4; +TypeLANSTEP: con 16rFE; + +Type9: con Disks->Type9; + +TableSize: con TentrySize*NTentry; +Omagic: con TableSize; + +Type: adt { + desc: string; + name: string; +}; + +Dospart: adt { + p: ref Part; + pc: ref PCpart; + primary: int; + lba: big; # absolute address + size: big; +}; + +Recover: adt { + table: array of byte; # [TableSize+2] copy of table and magic + lba: big; # where it came from +}; + +types: array of Type = array[256] of { + TypeEMPTY => ( "EMPTY", "" ), + TypeFAT12 => ( "FAT12", "dos" ), + TypeFAT16 => ( "FAT16", "dos" ), + TypeFAT32 => ( "FAT32", "dos" ), + TypeFAT32LBA => ( "FAT32LBA", "dos" ), + TypeEXTHUGE => ( "EXTHUGE", "" ), + TypeIBMRecovery => ( "IBMRECOVERY", "ibm" ), + TypeEXTENDED => ( "EXTENDED", "" ), + TypeFATHUGE => ( "FATHUGE", "dos" ), + TypeBB => ( "BB", "bb" ), + + TypeXENIX => ( "XENIX", "xenix" ), + TypeXENIXUSR => ( "XENIX USR", "xenixusr" ), + TypeHPFS => ( "HPFS", "ntfs" ), + TypeAIXBOOT => ( "AIXBOOT", "aixboot" ), + TypeAIXDATA => ( "AIXDATA", "aixdata" ), + TypeOS2BOOT => ( "OS/2BOOT", "os2boot" ), + TypeUNFORMATTED => ( "UNFORMATTED", "" ), + TypeHPFS2 => ( "HPFS2", "hpfs2" ), + TypeCPM0 => ( "CPM0", "cpm0" ), + TypeDMDDO => ( "DMDDO", "dmdd0" ), + TypeGB => ( "GB", "gb" ), + TypeSPEEDSTOR => ( "SPEEDSTOR", "speedstor" ), + TypeSYSV386 => ( "SYSV386", "sysv386" ), + TypeNETWARE => ( "NETWARE", "netware" ), + TypePCIX => ( "PCIX", "pcix" ), + TypeMINIX13 => ( "MINIXV1.3", "minix13" ), + TypeMINIX => ( "MINIXV1.5", "minix15" ), + TypeLINUXSWAP => ( "LINUXSWAP", "linuxswap" ), + TypeLINUX => ( "LINUX", "linux" ), + TypeLINUXEXT => ( "LINUXEXTENDED", "" ), + TypeAMOEBA => ( "AMOEBA", "amoeba" ), + TypeAMOEBABB => ( "AMOEBABB", "amoebaboot" ), + TypeBSD386 => ( "BSD386", "bsd386" ), + TypeBSDI => ( "BSDI", "bsdi" ), + TypeBSDISWAP => ( "BSDISWAP", "bsdiswap" ), + TypeOTHER => ( "OTHER", "other" ), + TypeCPM => ( "CPM", "cpm" ), + TypeDellRecovery => ( "DELLRECOVERY", "dell" ), + TypeSPEEDSTOR12 => ( "SPEEDSTOR12", "speedstor" ), + TypeSPEEDSTOR16 => ( "SPEEDSTOR16", "speedstor" ), + TypeLANSTEP => ( "LANSTEP", "lanstep" ), + + Type9 => ( "PLAN9", "plan9" ), + + * => (nil, nil), +}; + +dosparts: list of ref Dospart; + +tag2part(p: ref Part): ref Dospart +{ + for(l := dosparts; l != nil; l = tl l) + if((hd l).p.tag == p.tag) + return hd l; + raise "tag2part: cannot happen"; +} + +typestr0(ptype: int): string +{ + if(ptype < 0 || ptype >= len types || types[ptype].desc == nil) + return sys->sprint("type %d", ptype); + return types[ptype].desc; +} + +gettable(disk: ref Disk, addr: big, mbr: int): array of byte +{ + table := array[TableSize+2] of {* => byte 0}; + diskread(disk, table, len table, addr, Toffset); + if(mbr){ + # the informal specs say all must have this but apparently not, only mbr + if(int table[Omagic] != Magic0 || int table[Omagic+1] != Magic1) + sysfatal("did not find master boot record"); + } + return table; +} + +diskread(disk: ref Disk, data: array of byte, ndata: int, sec: big, off: int) +{ + a := sec*big disk.secsize + big off; + if(sys->seek(disk.fd, a, 0) != a) + sysfatal(sys->sprint("diskread seek %bud.%ud: %r", sec, off)); + if(readn(disk.fd, data, ndata) != ndata) + sysfatal(sys->sprint("diskread %ud at %bud.%ud: %r", ndata, sec, off)); +} + +puttable(disk: ref Disk, table: array of byte, sec: big): int +{ + return diskwrite(disk, table, len table, sec, Toffset); +} + +diskwrite(disk: ref Disk, data: array of byte, ndata: int, sec: big, off: int): int +{ + written = 1; + a := sec*big disk.secsize + big off; + if(sys->seek(disk.wfd, a, 0) != a || + sys->write(disk.wfd, data, ndata) != ndata){ + sys->fprint(stderr, "write %d bytes at %bud.%ud failed: %r\n", ndata, sec, off); + return -1; + } + return 0; +} + +partgen := 0; +parttag := 0; + +mkpart(name: string, primary: int, lba: big, size: big, pcpart: ref PCpart): ref Dospart +{ + p := ref Dospart; + if(name == nil){ + if(primary) + c := 'p'; + else + c = 's'; + name = sys->sprint("%c%d", c, ++partgen); + } + + if(pcpart != nil) + p.pc = pcpart; + else + p.pc = ref PCpart(0, 0, big 0, big 0, big 0); + + p.primary = primary; + p.p = ref Part; # TO DO + p.p.name = name; + p.p.start = lba/sec2cyl; + p.p.end = (lba+size)/sec2cyl; + p.p.ctlstart = lba; + p.p.ctlend = lba+size; + p.p.tag = ++parttag; + p.lba = lba; # absolute lba + p.size = size; + dosparts = p :: dosparts; + return p; +} + +# +# Recovery takes care of remembering what the various tables +# looked like when we started, attempting to restore them when +# we are finished. +# +rtabs: list of ref Recover; + +addrecover(t: array of byte, lba: big) +{ + tc := array[TableSize+2] of byte; + tc[0:] = t[0:len tc]; + rtabs = ref Recover(tc, lba) :: rtabs; +} + +recover(edit: ref Edit) +{ + err := 0; + for(rl := rtabs; rl != nil; rl = tl rl){ + r := hd rl; + if(puttable(edit.disk, r.table, r.lba) < 0) + err = 1; + } + if(err) { + sys->fprint(stderr, "warning: some writes failed during restoration of old partition tables\n"); + exits("inconsistent"); + } else + sys->fprint(stderr, "restored old partition tables\n"); + + ctlfd := edit.disk.ctlfd; + if(ctlfd != nil){ + offset := edit.disk.offset; + for(i:=0; i<len edit.part; i++) + if(edit.part[i].ctlname != nil && sys->fprint(ctlfd, "delpart %s", edit.part[i].ctlname)<0) + sys->fprint(stderr, "delpart failed: %s: %r", edit.part[i].ctlname); + for(i=0; i<len edit.ctlpart; i++) + if(edit.part[i].name != nil && sys->fprint(ctlfd, "delpart %s", edit.ctlpart[i].name)<0) + sys->fprint(stderr, "delpart failed: %s: %r", edit.ctlpart[i].name); + for(i=0; i<len edit.ctlpart; i++){ + if(sys->fprint(ctlfd, "part %s %bd %bd", edit.ctlpart[i].name, + edit.ctlpart[i].start+offset, edit.ctlpart[i].end+offset) < 0){ + sys->fprint(stderr, "restored disk partition table but not kernel; reboot\n"); + exits("inconsistent"); + } + } + } + exits("restored"); +} + +# +# Read the partition table (including extended partition tables) +# from the disk into the part array. +# +rdpart(edit: ref Edit, lba: big, xbase: big) +{ + if(xbase == big 0) + xbase = lba; # extended partition in mbr sets the base + + table := gettable(edit.disk, mbroffset+lba, lba == big 0); + addrecover(table, mbroffset+lba); + + for(tp := 0; tp<TableSize; tp += TentrySize){ + dp := PCpart.extract(table[tp:], edit.disk); + case dp.ptype { + TypeEMPTY => + ; + TypeEXTENDED or + TypeEXTHUGE or + TypeLINUXEXT => + rdpart(edit, xbase+dp.offset, xbase); + * => + p := mkpart(nil, lba==big 0, lba+dp.offset, dp.size, ref dp); + if((err := edit.addpart(p.p)) != nil) + sys->fprint(stderr, "error adding partition: %s\n", err); + } + } +} + +blankpart(edit: ref Edit) +{ + edit.changed = 1; +} + +findmbr(edit: ref Edit) +{ + table := gettable(edit.disk, big 0, 1); + for(tp := 0; tp < TableSize; tp += TentrySize){ + p := PCpart.extract(table[tp:], edit.disk); + if(p.ptype == TypeDMDDO) + mbroffset = big edit.disk.s; + } +} + +haveroom(edit: ref Edit, primary: int, start: big): int +{ + if(primary) { + # + # must be open primary slot. + # primary slots are taken by primary partitions + # and runs of secondary partitions. + # + n := 0; + lastsec := 0; + for(i:=0; i<len edit.part; i++) { + p := tag2part(edit.part[i]); + if(p.primary){ + n++; + lastsec = 0; + }else if(!lastsec){ + n++; + lastsec = 1; + } + } + return n<4; + } + + # + # secondary partitions can be inserted between two primary + # partitions only if there is an empty primary slot. + # otherwise, we can put a new secondary partition next + # to a secondary partition no problem. + # + n := 0; + for(i:=0; i<len edit.part; i++){ + p := tag2part(edit.part[i]); + if(p.primary) + n++; + pend := p.p.end; + q: ref Dospart; + qstart: big; + if(i+1<len edit.part){ + q = tag2part(edit.part[i+1]); + qstart = q.p.start; + }else{ + qstart = edit.end; + q = nil; + } + if(start < pend || start >= qstart) + continue; + # we go between these two + if(p.primary==0 || (q != nil && q.primary==0)) + return 1; + } + # not next to a secondary, need a new primary + return n<4; +} + +autopart(edit: ref Edit) +{ + for(i:=0; i<len edit.part; i++) + if(tag2part(edit.part[i]).pc.ptype == Type9) + return; + + # look for the biggest gap in which we can put a primary partition + start := big 0; + bigsize := big 0; + bigstart := big 0; + for(i=0; i<len edit.part; i++) { + p := tag2part(edit.part[i]); + if(p.p.start > start && p.p.start - start > bigsize && haveroom(edit, 1, start)) { + bigsize = p.p.start - start; + bigstart = start; + } + start = p.p.end; + } + + if(edit.end - start > bigsize && haveroom(edit, 1, start)) { + bigsize = edit.end - start; + bigstart = start; + } + if(bigsize < big 1) { + sys->fprint(stderr, "couldn't find space or partition slot for plan 9 partition\n"); + return; + } + + # set new partition active only if no others are + active := Active; + for(i=0; i<len edit.part; i++){ + p := tag2part(edit.part[i]); + if(p.primary && p.pc.active & Active) + active = 0; + } + + # add new plan 9 partition + bigsize *= sec2cyl; + bigstart *= sec2cyl; + if(bigstart == big 0) { + bigstart += big edit.disk.s; + bigsize -= big edit.disk.s; + } + p := mkpart(nil, 1, bigstart, bigsize, nil); + p.p.changed = 1; + p.pc.active = active; + p.pc.ptype = Type9; + edit.changed = 1; + if((err := edit.addpart(p.p)) != nil){ + sys->fprint(stderr, "error adding plan9 partition: %s\n", err); + return; + } +} + +namelist: list of string; + +plan9print(part: ref Dospart, fd: ref Sys->FD) +{ + vname := types[part.pc.ptype].name; + if(vname==nil) { + part.p.ctlname = ""; + return; + } + + start := mbroffset+part.lba; + end := start+part.size; + + # avoid names like plan90 + i := len vname - 1; + if(isdigit(vname[i])) + sep := "."; + else + sep = ""; + + i = 0; + name := sys->sprint("%s", vname); + ok: int; + do { + ok = 1; + for(nl := namelist; nl != nil; nl = tl nl) + if(name == hd nl) { + i++; + name = sys->sprint("%s%s%d", vname, sep, i); + ok = 0; + } + } while(ok == 0); + + namelist = name :: namelist; + part.p.ctlname = name; + + if(fd != nil) + sys->print("part %s %bd %bd\n", name, start, end); +} + +cmdprintctl(edit: ref Edit, ctlfd: ref Sys->FD) +{ + namelist = nil; + for(i:=0; i<len edit.part; i++) + plan9print(tag2part(edit.part[i]), nil); + edit.ctldiff(ctlfd); +} + +cmdokname(nil: ref Edit, name: string): string +{ + if(name[0] != 'p' && name[0] != 's' || len name < 2) + return "name must be pN or sN"; + for(i := 1; i < len name; i++) + if(!isdigit(name[i])) + return "name must be pN or sN"; + + return nil; +} + +KB: con big 1024; +MB: con KB*KB; +GB: con KB*MB; + +cmdsum(edit: ref Edit, vp: ref Part, a, b: big) +{ + if(vp != nil) + p := tag2part(vp); + + qual: string; + if(p != nil && p.p.changed) + qual += "'"; + else + qual += " "; + if(p != nil && p.pc.active&Active) + qual += "*"; + else + qual += " "; + + if(p != nil) + name := p.p.name; + else + name = "empty"; + if(p != nil) + ty := " "+typestr0(p.pc.ptype); + else + ty = ""; + + sz := (b-a)*big edit.disk.secsize*sec2cyl; + suf := "B"; + div := big 1; + if(sz >= big 1*GB){ + suf = "GB"; + div = GB; + }else if(sz >= big 1*MB){ + suf = "MB"; + div = MB; + }else if(sz >= big 1*KB){ + suf = "KB"; + div = KB; + } + + if(div == big 1) + sys->print("%s %-12s %*bd %-*bd (%bd cylinders, %bd %s)%s\n", qual, name, + edit.disk.width, a, edit.disk.width, b, b-a, sz, suf, ty); + else + sys->print("%s %-12s %*bd %-*bd (%bd cylinders, %bd.%.2d %s)%s\n", qual, name, + edit.disk.width, a, edit.disk.width, b, b-a, + sz/div, int(((sz%div)*big 100)/div), suf, ty); +} + +cmdadd(edit: ref Edit, name: string, start: big, end: big): string +{ + if(!haveroom(edit, name[0]=='p', start)) + return "no room for partition"; + start *= sec2cyl; + end *= sec2cyl; + if(start == big 0 || name[0] != 'p') + start += big edit.disk.s; + p := mkpart(name, name[0]=='p', start, end-start, nil); + p.p.changed = 1; + p.pc.ptype = Type9; + return edit.addpart(p.p); +} + +cmddel(edit: ref Edit, p: ref Part): string +{ + return edit.delpart(p); +} + +cmdwrite(edit: ref Edit): string +{ + wrpart(edit); + return nil; +} + +help: con + "A name - set partition active\n"+ + "P - sys->print table in ctl format\n"+ + "R - restore disk back to initial configuration and exit\n"+ + "e - show empty dos partitions\n"+ + "t name [type] - set partition type\n"; + +cmdhelp(nil: ref Edit): string +{ + sys->print("%s\n", help); + return nil; +} + +cmdactive(edit: ref Edit, f: array of string): string +{ + if(len f != 2) + return "args"; + + if(f[1][0] != 'p') + return "cannot set secondary partition active"; + + if((p := tag2part(edit.findpart(f[1]))) == nil) + return "unknown partition"; + + for(i:=0; i<len edit.part; i++) { + ip := tag2part(edit.part[i]); + if(ip.pc.active & Active) { + ip.pc.active &= ~Active; + ip.p.changed = 1; + edit.changed = 1; + } + } + + if((p.pc.active & Active) == 0) { + p.pc.active |= Active; + p.p.changed = 1; + edit.changed = 1; + } + + return nil; +} + +strupr(s: string): string +{ + for(i := 0; i < len s; i++) + if(s[i] >= 'a' && s[i] <= 'z') + s[i] += 'A' - 'a'; + return s; +} + +dumplist() +{ + n := 0; + for(i:=0; i<len types; i++) { + if(types[i].desc != nil) { + sys->print("%-16s", types[i].desc); + if(n++%4 == 3) + sys->print("\n"); + } + } + if(n%4) + sys->print("\n"); +} + +cmdtype(edit: ref Edit, f: array of string): string +{ + if(len f < 2) + return "args"; + + if((p := tag2part(edit.findpart(f[1]))) == nil) + return "unknown partition"; + + q: string; + if(len f == 2) { + for(;;) { + sys->fprint(stderr, "new partition type [? for list]: "); + q = edit.getline(); + if(q[0] == '?') + dumplist(); + else + break; + } + } else + q = f[2]; + + q = strupr(q); + for(i:=0; i<len types; i++) + if(types[i].desc != nil && types[i].desc == q) + break; + if(i < len types && p.pc.ptype != i) { + p.pc.ptype = i; + p.p.changed = 1; + edit.changed = 1; + } + return nil; +} + +cmdext(edit: ref Edit, f: array of string): string +{ + case f[0][0] { + 'A' => + return cmdactive(edit, f); + 't' => + return cmdtype(edit, f); + 'R' => + recover(edit); + return nil; + * => + return "unknown command"; + } +} + +wrextend(edit: ref Edit, i: int, xbase: big, startlba: big): (int, big) +{ + if(i == len edit.part){ + endlba := edit.disk.secs; + if(startlba < endlba) + wrzerotab(edit.disk, mbroffset+startlba); + return (i, endlba); + } + + p := tag2part(edit.part[i]); + if(p.primary){ + endlba := p.p.start*sec2cyl; + if(startlba < endlba) + wrzerotab(edit.disk, mbroffset+startlba); + return (i, endlba); + } + + disk := edit.disk; + table := gettable(disk, mbroffset+startlba, 0); + + (ni, endlba) := wrextend(edit, i+1, xbase, p.p.end*sec2cyl); + + tp := wrtentry(disk, table[0:], p.pc.active, p.pc.ptype, startlba, startlba+big disk.s, p.p.end*sec2cyl); + if(p.p.end*sec2cyl != endlba) + tp += wrtentry(disk, table[tp:], 0, TypeEXTENDED, xbase, p.p.end*sec2cyl, endlba); + + for(; tp<TableSize; tp++) + table[tp] = byte 0; + + table[Omagic] = byte Magic0; + table[Omagic+1] = byte Magic1; + + if(puttable(edit.disk, table, mbroffset+startlba) < 0) + recover(edit); + return (ni, endlba); +} + +wrzerotab(disk: ref Disk, addr: big) +{ + table := array[TableSize+2] of {Omagic => byte Magic0, Omagic+1 => byte Magic1, * => byte 0}; + if(puttable(disk, table, addr) < 0) + recover(edit); +} + +wrpart(edit: ref Edit) +{ + disk := edit.disk; + + table := gettable(disk, mbroffset, 0); + + tp := 0; + for(i:=0; i<len edit.part && tp<TableSize; ) { + p := tag2part(edit.part[i]); + if(p.p.start == big 0) + s := big disk.s; + else + s = p.p.start*sec2cyl; + if(p.primary) { + tp += wrtentry(disk, table[tp:], p.pc.active, p.pc.ptype, big 0, s, p.p.end*sec2cyl); + i++; + }else{ + (ni, endlba) := wrextend(edit, i, p.p.start*sec2cyl, p.p.start*sec2cyl); + if(endlba >= big 1024*sec2cyl) + t := TypeEXTHUGE; + else + t = TypeEXTENDED; + tp += wrtentry(disk, table[tp:], 0, t, big 0, s, endlba); + i = ni; + } + } + for(; tp<TableSize; tp++) + table[tp] = byte 0; + + if(i != len edit.part) + raise "wrpart: cannot happen #1"; + + if(puttable(disk, table, mbroffset) < 0) + recover(edit); + + # bring parts up to date + namelist = nil; + for(i=0; i<len edit.part; i++) + plan9print(tag2part(edit.part[i]), nil); + + if(edit.ctldiff(disk.ctlfd) < 0) + sys->fprint(stderr, "?warning: partitions could not be updated in devsd\n"); +} + +isdigit(c: int): int +{ + return c >= '0' && c <= '9'; +} + +sysfatal(s: string) +{ + sys->fprint(stderr, "fdisk: %s\n", s); + raise "fail:error"; +} + +exits(s: string) +{ + if(s != nil) + raise "fail:"+s; + exit; +} + +assert(i: int) +{ + if(!i) + raise "assertion failed"; +} + +wrtentry(disk: ref Disk, entry: array of byte, active: int, ptype: int, xbase: big, lba: big, end: big): int +{ + pc: PCpart; + pc.active = active; + pc.ptype = ptype; + pc.base = xbase; + pc.offset = lba-xbase; + pc.size = end-lba; + entry[0:] = pc.bytes(disk); + return TentrySize; +} diff --git a/appl/cmd/disk/prep/mkfile b/appl/cmd/disk/prep/mkfile new file mode 100644 index 00000000..714c26f8 --- /dev/null +++ b/appl/cmd/disk/prep/mkfile @@ -0,0 +1,26 @@ +<../../../../mkconfig + +TARG=\ + fdisk.dis\ + pedit.dis\ + prep.dis\ + calc.tab.dis\ + +MODULES=\ + pedit.m\ + +SYSMODULES=\ + arg.m\ + sys.m\ + draw.m\ + disks.m\ + bufio.m\ + string.m\ + +DISBIN=$ROOT/dis/disk + +<$ROOT/mkfiles/mkdis + +# calc +calc.tab.b: + yacc -s calc -d calc.y diff --git a/appl/cmd/disk/prep/pedit.b b/appl/cmd/disk/prep/pedit.b new file mode 100644 index 00000000..f55bcaff --- /dev/null +++ b/appl/cmd/disk/prep/pedit.b @@ -0,0 +1,504 @@ +implement Pedit; + +# +# disk partition editor +# + +include "sys.m"; + sys: Sys; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "disks.m"; + disks: Disks; + Disk: import disks; + readn: import disks; + +include "draw.m"; +include "calc.tab.m"; + calc: Calc; + +include "pedit.m"; + +Cmd: adt { + c: int; + f: ref fn(e: ref Edit, a: array of string): string; +}; + +cmds: array of Cmd; + +bin: ref Iobuf; + +init() +{ + sys = load Sys Sys->PATH; + calc = load Calc "/dis/disk/calc.tab.dis"; + bufio = load Bufio Bufio->PATH; + disks = load Disks Disks->PATH; + disks->init(); + + bin = bufio->fopen(sys->fildes(0), Bufio->OREAD); + cmds = array[] of { + ('.', editdot), + ('a', editadd), + ('d', editdel), + ('?', edithelp), + ('h', edithelp), + ('P', editctlprint), + ('p', editprint), + ('w', editwrite), + ('q', editquit), + }; +} + +Edit.mk(unit: string): ref Edit +{ + e := ref Edit; + e.unit = unit; + e.dot = big 0; + e.end = big 0; + e.changed = 0; + e.warned = 0; + e.lastcmd = 0; + return e; +} + +Edit.getline(edit: self ref Edit): string +{ + p := bin.gets('\n'); + if(p == nil){ + if(edit.changed) + sys->fprint(sys->fildes(2), "?warning: changes not written\n"); + exit; + } + for(i := 0; i < len p; i++) + if(!isspace(p[i])) + break; + if(i) + return p[i:]; + return p; +} + +Edit.findpart(edit: self ref Edit, name: string): ref Part +{ + for(i:=0; i<len edit.part; i++) + if(edit.part[i].name == name) + return edit.part[i]; + return nil; +} + +okname(edit: ref Edit, name: string): string +{ + if(name[0] == '\0') + return "partition has no name"; + + for(i:=0; i<len edit.part; i++) { + if(name == edit.part[i].name) + return sys->sprint("already have partition with name '%s'", name); + } + return nil; +} + +Edit.addpart(edit: self ref Edit, p: ref Part): string +{ + if((err := okname(edit, p.name)) != nil) + return err; + + for(i:=0; i<len edit.part; i++) { + if(p.start < edit.part[i].end && edit.part[i].start < p.end) { + msg := sys->sprint("\"%s\" %bd-%bd overlaps with \"%s\" %bd-%bd", + p.name, p.start, p.end, + edit.part[i].name, edit.part[i].start, edit.part[i].end); + # return msg; + } + } + + if(len edit.part >= Maxpart) + return "too many partitions"; + + pa := array[i+1] of ref Part; + pa[0:] = edit.part; + edit.part = pa; + + edit.part[i] = p; + for(; i > 0 && p.start < edit.part[i-1].start; i--) { + edit.part[i] = edit.part[i-1]; + edit.part[i-1] = p; + } + + if(p.changed) + edit.changed = 1; + return nil; +} + +Edit.delpart(edit: self ref Edit, p: ref Part): string +{ + n := len edit.part; + for(i:=0; i<n; i++) + if(edit.part[i] == p) + break; + if(i >= n) + raise "internal error: Part not found"; + n--; + pa := array[n] of ref Part; + if(n){ + pa[0:] = edit.part[0:i]; + if(i != n) + pa[i:] = edit.part[i+1:]; + } + edit.part = pa; + edit.changed = 1; + return nil; +} + +editdot(edit: ref Edit, argv: array of string): string +{ + if(len argv == 1) { + sys->print("\t. %bd\n", edit.dot); + return nil; + } + + if(len argv > 2) + return "args"; + + (ndot, err) := calc->parseexpr(argv[1], edit.dot, edit.end, edit.end); + if(err != nil) + return err; + + edit.dot = ndot; + return nil; +} + +editadd(edit: ref Edit, argv: array of string): string +{ + if(len argv < 2) + return "args"; + + name := argv[1]; + if((err := okname(edit, name)) != nil || edit.okname != nil && (err = edit.okname(edit, name)) != nil) + return err; + + if(len argv >= 3) + q := argv[2]; + else { + sys->fprint(sys->fildes(2), "start %s: ", edit.unit); + q = edit.getline(); + } + start: big; + (start, err) = calc->parseexpr(q, edit.dot, edit.end, edit.end); + if(err != nil) + return err; + + if(start < big 0 || start >= edit.end) + return "start out of range"; + + for(i:=0; i < len edit.part; i++) { + if(edit.part[i].start <= start && start < edit.part[i].end) + return sys->sprint("start %s in partition '%s'", edit.unit, edit.part[i].name); + } + + maxend := edit.end; + for(i=0; i < len edit.part; i++) + if(start < edit.part[i].start && edit.part[i].start < maxend) + maxend = edit.part[i].start; + + if(len argv >= 4) + q = argv[3]; + else { + sys->fprint(sys->fildes(2), "end [%bd..%bd] ", start, maxend); + q = edit.getline(); + } + end: big; + (end, err) = calc->parseexpr(q, edit.dot, maxend, edit.end); + if(err != nil) + return err; + + if(start == end) + return "size zero partition"; + + if(end <= start || end > maxend) + return "end out of range"; + + if(len argv > 4) + return "args"; + + if((err = edit.add(edit, name, start, end)) != nil) + return err; + + edit.dot = end; + return nil; +} + +editdel(edit: ref Edit, argv: array of string): string +{ + if(len argv != 2) + return "args"; + + if((p := edit.findpart(argv[1])) == nil) + return "no such partition"; + + return edit.del(edit, p); +} + +helptext := + ". [newdot] - display or set value of dot\n"+ + "a name [start [end]] - add partition\n"+ + "d name - delete partition\n"+ + "h - sys->print help message\n"+ + "p - sys->print partition table\n"+ + "P - sys->print commands to update sd(3) device\n"+ + "w - write partition table\n"+ + "q - quit\n"; + +edithelp(edit: ref Edit, nil: array of string): string +{ + sys->print("%s", helptext); + if(edit.help != nil) + return edit.help(edit); + return nil; +} + +editprint(edit: ref Edit, argv: array of string): string +{ + if(len argv != 1) + return "args"; + + lastend := big 0; + part := edit.part; + for(i:=0; i<len edit.part; i++) { + if(lastend < part[i].start) + edit.sum(edit, nil, lastend, part[i].start); + edit.sum(edit, part[i], part[i].start, part[i].end); + lastend = part[i].end; + } + if(lastend < edit.end) + edit.sum(edit, nil, lastend, edit.end); + return nil; +} + +editwrite(edit: ref Edit, argv: array of string): string +{ + if(len argv != 1) + return "args"; + + if(edit.disk.rdonly) + return "read only"; + + err := edit.write(edit); + if(err != nil) + return err; + for(i:=0; i<len edit.part; i++) + edit.part[i].changed = 0; + edit.changed = 0; + return nil; +} + +editquit(edit: ref Edit, argv: array of string): string +{ + if(len argv != 1) { + edit.warned = 0; + return "args"; + } + + if(edit.changed && (!edit.warned || edit.lastcmd != 'q')) { + edit.warned = 1; + return "changes unwritten"; + } + + exit; +} + +editctlprint(edit: ref Edit, argv: array of string): string +{ + if(len argv != 1) + return "args"; + + if(edit.printctl != nil) + edit.printctl(edit, sys->fildes(1)); + else + edit.ctldiff(sys->fildes(1)); + return nil; +} + +Edit.runcmd(edit: self ref Edit, cmd: string) +{ + (nf, fl) := sys->tokenize(cmd, " \t\n\r"); + if(nf < 1) + return; + f := array[nf] of string; + for(nf = 0; fl != nil; fl = tl fl) + f[nf++] = hd fl; + if(len f[0] != 1) { + sys->fprint(sys->fildes(2), "?\n"); + return; + } + + err := ""; + for(i:=0; i<len cmds; i++) { + if(cmds[i].c == f[0][0]) { + op := cmds[i].f; + err = op(edit, f); + break; + } + } + if(i == len cmds){ + if(edit.ext != nil) + err = edit.ext(edit, f); + else + err = "unknown command"; + } + if(err != nil) + sys->fprint(sys->fildes(2), "?%s\n", err); + edit.lastcmd = f[0][0]; +} + +isspace(c: int): int +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +ctlmkpart(name: string, start: big, end: big, changed: int): ref Part +{ + p := ref Part; + p.name = name; + p.ctlname = name; + p.start = start; + p.end = end; + p.ctlstart = big 0; + p.ctlend = big 0; + p.changed = changed; + return p; +} + +rdctlpart(edit: ref Edit) +{ + disk := edit.disk; + edit.ctlpart = array[0] of ref Part; + sys->seek(disk.ctlfd, big 0, 0); + buf := array[4096] of byte; + if(readn(disk.ctlfd, buf, len buf) <= 0) + return; + for(i := 0; i < len buf; i++) + if(buf[i] == byte 0) + break; + + (nline, lines) := sys->tokenize(string buf[0:i], "\n\r"); + edit.ctlpart = array[nline] of ref Part; # upper bound + npart := 0; + for(i=0; i<nline; i++){ + line := hd lines; + lines = tl lines; + if(len line < 5 || line[0:5] != "part ") + continue; + + (nf, f) := sys->tokenize(line, " \t"); + if(nf != 4 || hd f != "part") + break; + + a := big hd tl tl f; + b := big hd tl tl tl f; + + if(a >= b) + break; + + # only gather partitions contained in the disk partition we are editing + if(a < disk.offset || disk.offset+disk.secs < b) + continue; + + a -= disk.offset; + b -= disk.offset; + + # the partition we are editing does not count + if(hd tl f == disk.part) + continue; + + edit.ctlpart[npart++] = ctlmkpart(hd tl f, a, b, 0); + } + if(npart != len edit.ctlpart) + edit.ctlpart = edit.ctlpart[0:npart]; +} + +ctlstart(p: ref Part): big +{ + if(p.ctlstart != big 0) + return p.ctlstart; + return p.start; +} + +ctlend(p: ref Part): big +{ + if(p.ctlend != big 0) + return p.ctlend; + return p.end; +} + +areequiv(p: ref Part, q: ref Part): int +{ + if(p.ctlname == nil || q.ctlname == nil) + return 0; + return p.ctlname == q.ctlname && + ctlstart(p) == ctlstart(q) && ctlend(p) == ctlend(q); +} + +unchange(edit: ref Edit, p: ref Part) +{ + for(i:=0; i<len edit.ctlpart; i++) { + q := edit.ctlpart[i]; + if(p.start <= q.start && q.end <= p.end) + q.changed = 0; + } + if(p.changed) + raise "internal error: Part unchanged"; +} + +Edit.ctldiff(edit: self ref Edit, ctlfd: ref Sys->FD): int +{ + rdctlpart(edit); + + # everything is bogus until we prove otherwise + for(i:=0; i<len edit.ctlpart; i++) + edit.ctlpart[i].changed = 1; + + # + # partitions with same info have not changed, + # and neither have partitions inside them. + # + for(i=0; i<len edit.ctlpart; i++) + for(j:=0; j<len edit.part; j++) + if(areequiv(edit.ctlpart[i], edit.part[j])) { + unchange(edit, edit.ctlpart[i]); + break; + } + + waserr := 0; + # + # delete all the changed partitions except data (we'll add them back if necessary) + # + for(i=0; i<len edit.ctlpart; i++) { + p := edit.ctlpart[i]; + if(p.changed) + if(sys->fprint(ctlfd, "delpart %s\n", p.ctlname)<0) { + sys->fprint(sys->fildes(2), "delpart failed: %s: %r\n", p.ctlname); + waserr = -1; + } + } + + # + # add all the partitions from the real list; + # this is okay since adding a partition with + # information identical to what is there is a no-op. + # + offset := edit.disk.offset; + for(i=0; i<len edit.part; i++) { + p := edit.part[i]; + if(p.ctlname != nil) { + if(sys->fprint(ctlfd, "part %s %bd %bd\n", p.ctlname, offset+ctlstart(p), offset+ctlend(p)) < 0) { + sys->fprint(sys->fildes(2), "adding part failed: %s: %r\n", p.ctlname); + waserr = -1; + } + } + } + return waserr; +} diff --git a/appl/cmd/disk/prep/pedit.m b/appl/cmd/disk/prep/pedit.m new file mode 100644 index 00000000..2b0d142d --- /dev/null +++ b/appl/cmd/disk/prep/pedit.m @@ -0,0 +1,53 @@ +Pedit: module +{ + PATH: con "/dis/disk/pedit.dis"; + + Part: adt { + name: string; + ctlname: string; + start: big; + end: big; + ctlstart: big; + ctlend: big; + changed: int; + tag: int; + }; + + Maxpart: con 32; + + Edit: adt { + disk: ref Disks->Disk; + + ctlpart: array of ref Part; + part: array of ref Part; + + # to do: replace by channels + add: ref fn(e: ref Edit, s: string, a, b: big): string; + del: ref fn(e: ref Edit, p: ref Part): string; + ext: ref fn(e: ref Edit, f: array of string): string; + help: ref fn(e: ref Edit): string; + okname: ref fn(e: ref Edit, s: string): string; + sum: ref fn(e: ref Edit, p: ref Part, a, b: big); + write: ref fn(e: ref Edit): string; + printctl: ref fn(e: ref Edit, x: ref Sys->FD); + + unit: string; + dot: big; + end: big; + + # do not use fields below this line + changed: int; + warned: int; + lastcmd: int; + + mk: fn(unit: string): ref Edit; + getline: fn(e: self ref Edit): string; + runcmd: fn(e: self ref Edit, c: string); + findpart: fn(e: self ref Edit, n: string): ref Part; + addpart: fn(e: self ref Edit, p: ref Part): string; + delpart: fn(e: self ref Edit, p: ref Part): string; + ctldiff: fn(e: self ref Edit, ctlfd: ref Sys->FD): int; + }; + + init: fn(); +}; diff --git a/appl/cmd/disk/prep/prep.b b/appl/cmd/disk/prep/prep.b new file mode 100644 index 00000000..fa4c60a1 --- /dev/null +++ b/appl/cmd/disk/prep/prep.b @@ -0,0 +1,509 @@ +implement Prep; + +# +# prepare plan 9/inferno disk partition +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "disks.m"; + disks: Disks; + Disk: import disks; + readn: import disks; + +include "pedit.m"; + pedit: Pedit; + Edit, Part: import pedit; + +include "arg.m"; + +Prep: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +blank := 0; +file := 0; +doauto := 0; +printflag := 0; +opart: array of ref Part; +secbuf: array of byte; +osecbuf: array of byte; +zeroes: array of byte; +rdonly := 0; +dowrite := 0; + +Prepedit: type Edit[string]; + +edit: ref Edit; + +Auto: adt +{ + name: string; + min: big; + max: big; + weight: int; + alloc: int; + size: big; +}; + +KB: con big 1024; +MB: con KB*KB; +GB: con KB*MB; + +# +# Order matters -- this is the layout order on disk. +# +auto: array of Auto = array[] of { + ("9fat", big 10*MB, big 100*MB, 10, 0, big 0), + ("nvram", big 512, big 512, 1, 0, big 0), + ("fscfg", big 512, big 512, 1, 0, big 0), + ("fs", big 200*MB, big 0, 10, 0, big 0), + ("fossil", big 200*MB, big 0, 4, 0, big 0), + ("arenas", big 500*MB, big 0, 20, 0, big 0), + ("isect", big 25*MB, big 0, 1, 0, big 0), + ("other", big 200*MB, big 0, 4, 0, big 0), + ("swap", big 100*MB, big 512*MB, 1, 0, big 0), + ("cache", big 50*MB, big 1*GB, 2, 0, big 0), +}; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + disks = load Disks Disks->PATH; + pedit = load Pedit Pedit->PATH; + + sys->pctl(Sys->FORKFD, nil); + disks->init(); + pedit->init(); + + edit = Edit.mk("sector"); + + edit.add = cmdadd; + edit.del = cmddel; + edit.okname = cmdokname; + edit.sum = cmdsum; + edit.write = cmdwrite; + + stderr = sys->fildes(2); + secsize := 0; + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("disk/prep [-bfprw] [-a partname]... [-s sectorsize] /dev/sdC0/plan9"); + while((o := arg->opt()) != 0) + case o { + 'a' => + p := arg->earg(); + for(i:=0; i<len auto; i++){ + if(p == auto[i].name){ + if(auto[i].alloc){ + sys->fprint(stderr, "you said -a %s more than once.\n", p); + arg->usage(); + } + auto[i].alloc = 1; + break; + } + } + if(i == len auto){ + sys->fprint(stderr, "don't know how to create automatic partition %s\n", p); + arg->usage(); + } + doauto = 1; + 'b' => + blank++; + 'f' => + file++; + 'p' => + printflag++; + rdonly++; + 'r' => + rdonly++; + 's' => + secsize = int arg->earg(); + 'w' => + dowrite++; + * => + arg->usage(); + } + args = arg->argv(); + if(len args != 1) + arg->usage(); + arg = nil; + + mode := Sys->ORDWR; + if(rdonly) + mode = Sys->OREAD; + disk := Disk.open(hd args, mode, file); + if(disk == nil) { + sys->fprint(stderr, "cannot open disk: %r\n"); + exits("opendisk"); + } + + if(secsize != 0) { + disk.secsize = secsize; + disk.secs = disk.size / big secsize; + } + edit.end = disk.secs; + + checkfat(disk); + + secbuf = array[disk.secsize+1] of byte; + osecbuf = array[disk.secsize+1] of byte; + zeroes = array[disk.secsize+1] of {* => byte 0}; + edit.disk = disk; + + if(blank == 0) + rdpart(edit); + + # save old partition table + opart = array[len edit.part] of ref Part; + opart[0:] = edit.part; + + if(printflag) { + edit.runcmd("P"); + exits(nil); + } + + if(doauto) + autopart(edit); + + if(dowrite) { + edit.runcmd("w"); + exits(nil); + } + + edit.runcmd("p"); + for(;;) { + sys->fprint(stderr, ">>> "); + edit.runcmd(edit.getline()); + } +} + +cmdsum(edit: ref Edit, p: ref Part, a: big, b: big) +{ + c := ' '; + name := "empty"; + if(p != nil){ + if(p.changed) + c = '\''; + name = p.name; + } + + sz := (b-a)*big edit.disk.secsize; + suf := "B "; + div := big 1; + if(sz >= big 1*GB){ + suf = "GB"; + div = GB; + }else if(sz >= big 1*MB){ + suf = "MB"; + div = MB; + }else if(sz >= big 1*KB){ + suf = "KB"; + div = KB; + } + + if(div == big 1) + sys->print("%c %-12s %*bd %-*bd (%bd sectors, %bd %s)\n", c, name, + edit.disk.width, a, edit.disk.width, b, b-a, sz, suf); + else + sys->print("%c %-12s %*bd %-*bd (%bd sectors, %bd.%.2d %s)\n", c, name, + edit.disk.width, a, edit.disk.width, b, b-a, + sz/div, int (((sz%div)*big 100)/div), suf); +} + +cmdadd(edit: ref Edit, name: string, start: big, end: big): string +{ + if(start < big 2 && name == "9fat") + return "overlaps with the pbs and/or the partition table"; + + return edit.addpart(mkpart(name, start, end, 1)); +} + +cmddel(edit: ref Edit, p: ref Part): string +{ + return edit.delpart(p); +} + +cmdwrite(edit: ref Edit): string +{ + wrpart(edit); + return nil; +} + +isfrog := array[256] of { + byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # NUL + byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # BKS + byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # DLE + byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # CAN + ' ' => byte 1, + '/' => byte 1, + 16r7f=> byte 1, + * => byte 0 +}; + +cmdokname(nil: ref Edit, elem: string): string +{ + for(i := 0; i < len elem; i++) + if(int isfrog[elem[i]]) + return "bad character in name"; + return nil; +} + +mkpart(name: string, start: big, end: big, changed: int): ref Part +{ + p := ref Part; + p.name = name; + p.ctlname = name; + p.start = start; + p.end = end; + p.changed = changed; + p.ctlstart = big 0; + p.ctlend = big 0; + return p; +} + +# plan9 partition table is first sector of the disk + +rdpart(edit: ref Edit) +{ + disk := edit.disk; + sys->seek(disk.fd, big disk.secsize, 0); + if(readn(disk.fd, osecbuf, disk.secsize) != disk.secsize) + return; + osecbuf[disk.secsize] = byte 0; + secbuf[0:] = osecbuf; + + for(i := 0; i < disk.secsize; i++) + if(secbuf[i] == byte 0) + break; + + tab := string secbuf[0:i]; + if(len tab < 4 || tab[0:4] != "part"){ + sys->fprint(stderr, "no plan9 partition table found\n"); + return; + } + + waserr := 0; + (nline, lines) := sys->tokenize(tab, "\n"); + for(i=0; i<nline; i++){ + line := hd lines; + lines = tl lines; + if(len line < 4 || line[0:4] != "part"){ + waserr = 1; + continue; + } + + (nf, f) := sys->tokenize(line, " \t\r"); + if(nf != 4 || hd f != "part"){ + waserr = 1; + continue; + } + + a := big hd tl tl f; + b := big hd tl tl tl f; + if(a >= b){ + waserr = 1; + continue; + } + + if((err := edit.addpart(mkpart(hd tl f, a, b, 0))) != nil) { + sys->fprint(stderr, "?%s: not continuing\n", err); + exits("partition"); + } + } + if(waserr) + sys->fprint(stderr, "syntax error reading partition\n"); +} + +min(a, b: big): big +{ + if(a < b) + return a; + return b; +} + +autopart(edit: ref Edit) +{ + if(len edit.part > 0) { + if(doauto) + sys->fprint(stderr, "partitions already exist; not repartitioning\n"); + return; + } + + secs := edit.disk.secs; + secsize := big edit.disk.secsize; + for(;;){ + # compute total weights + totw := 0; + for(i:=0; i<len auto; i++){ + if(auto[i].alloc==0 || auto[i].size != big 0) + continue; + totw += auto[i].weight; + } + if(totw == 0) + break; + + if(secs <= big 0){ + sys->fprint(stderr, "ran out of disk space during autopartition.\n"); + return; + } + + # assign any minimums for small disks + futz := 0; + for(i=0; i<len auto; i++){ + if(auto[i].alloc==0 || auto[i].size != big 0) + continue; + s := (secs*big auto[i].weight)/big totw; + if(s < big auto[i].min/secsize){ + auto[i].size = big auto[i].min/secsize; + secs -= auto[i].size; + futz = 1; + break; + } + } + if(futz) + continue; + + # assign any maximums for big disks + futz = 0; + for(i=0; i<len auto; i++){ + if(auto[i].alloc==0 || auto[i].size != big 0) + continue; + s := (secs*big auto[i].weight)/big totw; + if(auto[i].max != big 0 && s > auto[i].max/secsize){ + auto[i].size = auto[i].max/secsize; + secs -= auto[i].size; + futz = 1; + break; + } + } + if(futz) + continue; + + # finally, assign partition sizes according to weights + for(i=0; i<len auto; i++){ + if(auto[i].alloc==0 || auto[i].size != big 0) + continue; + s := (secs*big auto[i].weight)/big totw; + auto[i].size = s; + + # use entire disk even in face of rounding errors + secs -= auto[i].size; + totw -= auto[i].weight; + } + } + + for(i:=0; i<len auto; i++) + if(auto[i].alloc) + sys->print("%s %bud\n", auto[i].name, auto[i].size); + + s := big 0; + for(i=0; i<len auto; i++){ + if(auto[i].alloc == 0) + continue; + if((err := edit.addpart(mkpart(auto[i].name, s, s+auto[i].size, 1))) != nil) + sys->fprint(stderr, "addpart %s: %s\n", auto[i].name, err); + s += auto[i].size; + } +} + +restore(edit: ref Edit, ctlfd: ref Sys->FD) +{ + offset := edit.disk.offset; + sys->fprint(stderr, "attempting to restore partitions to previous state\n"); + if(sys->seek(edit.disk.wfd, big edit.disk.secsize, 0) != big 0){ + sys->fprint(stderr, "cannot restore: error seeking on disk: %r\n"); + exits("inconsistent"); + } + + if(sys->write(edit.disk.wfd, osecbuf, edit.disk.secsize) != edit.disk.secsize){ + sys->fprint(stderr, "cannot restore: couldn't write old partition table to disk: %r\n"); + exits("inconsistent"); + } + + if(ctlfd != nil){ + for(i:=0; i<len edit.part; i++) + sys->fprint(ctlfd, "delpart %s", edit.part[i].name); + for(i=0; i<len opart; i++){ + if(sys->fprint(ctlfd, "part %s %bd %bd", opart[i].name, opart[i].start+offset, opart[i].end+offset) < 0){ + sys->fprint(stderr, "restored disk partition table but not kernel table; reboot\n"); + exits("inconsistent"); + } + } + } + exits("restored"); +} + +wrpart(edit: ref Edit) +{ + disk := edit.disk; + + secbuf[0:] = zeroes; + n := 0; + for(i:=0; i<len edit.part; i++){ + a := sys->aprint("part %s %bd %bd\n", + edit.part[i].name, edit.part[i].start, edit.part[i].end); + if(n + len a > disk.secsize){ + sys->fprint(stderr, "partition table bigger than sector (%d bytes)\n", disk.secsize); + exits("overflow"); + } + secbuf[n:] = a; + n += len a; + } + + if(sys->seek(disk.wfd, big disk.secsize, 0) != big disk.secsize){ + sys->fprint(stderr, "error seeking to %d on disk: %r\n", disk.secsize); + exits("seek"); + } + + if(sys->write(disk.wfd, secbuf, disk.secsize) != disk.secsize){ + sys->fprint(stderr, "error writing partition table to disk: %r\n"); + restore(edit, nil); + } + + if(edit.ctldiff(disk.ctlfd) < 0) + sys->fprint(stderr, "?warning: partitions could not be updated in devsd\n"); +} + +# +# Look for a boot sector in sector 1, as would be +# the case if editing /dev/sdC0/data when that +# was really a bootable disk. +# +checkfat(disk: ref Disk) +{ + buf := array[32] of byte; + + if(sys->seek(disk.fd, big disk.secsize, 0) != big disk.secsize || + sys->read(disk.fd, buf, len buf) < len buf) + return; + + if(buf[0] != byte 16rEB || buf[1] != byte 16r3C || buf[2] != byte 16r90) + return; + + sys->fprint(stderr, + "there's a fat partition where the\n"+ + "plan9 partition table would go.\n"+ + "if you really want to overwrite it, zero\n"+ + "the second sector of the disk and try again\n"); + + exits("fat partition"); +} + +exits(s: string) +{ + if(s != nil) + raise "fail:"+s; + exit; +} |
