summaryrefslogtreecommitdiff
path: root/sys/src/cmd/disk
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/disk
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/disk')
-rwxr-xr-xsys/src/cmd/disk/9660/boot.c210
-rwxr-xr-xsys/src/cmd/disk/9660/cdrdwr.c636
-rwxr-xr-xsys/src/cmd/disk/9660/conform.c141
-rwxr-xr-xsys/src/cmd/disk/9660/direc.c222
-rwxr-xr-xsys/src/cmd/disk/9660/dump.c512
-rwxr-xr-xsys/src/cmd/disk/9660/dump9660.c417
-rwxr-xr-xsys/src/cmd/disk/9660/ichar.c206
-rwxr-xr-xsys/src/cmd/disk/9660/iso9660.h426
-rwxr-xr-xsys/src/cmd/disk/9660/jchar.c138
-rwxr-xr-xsys/src/cmd/disk/9660/mk9660.rc5
-rwxr-xr-xsys/src/cmd/disk/9660/mkfile43
-rwxr-xr-xsys/src/cmd/disk/9660/path.c159
-rwxr-xr-xsys/src/cmd/disk/9660/plan9.c27
-rwxr-xr-xsys/src/cmd/disk/9660/rune.c39
-rwxr-xr-xsys/src/cmd/disk/9660/sysuse.c615
-rwxr-xr-xsys/src/cmd/disk/9660/uid.c41
-rwxr-xr-xsys/src/cmd/disk/9660/unix.c83
-rwxr-xr-xsys/src/cmd/disk/9660/util.c98
-rwxr-xr-xsys/src/cmd/disk/9660/write.c411
-rwxr-xr-xsys/src/cmd/disk/exsort.c136
-rwxr-xr-xsys/src/cmd/disk/format.c799
-rwxr-xr-xsys/src/cmd/disk/kfs/9p1.c1444
-rwxr-xr-xsys/src/cmd/disk/kfs/9p1.h113
-rwxr-xr-xsys/src/cmd/disk/kfs/9p12.c114
-rwxr-xr-xsys/src/cmd/disk/kfs/9p1lib.c461
-rwxr-xr-xsys/src/cmd/disk/kfs/9p2.c1865
-rwxr-xr-xsys/src/cmd/disk/kfs/all.h8
-rwxr-xr-xsys/src/cmd/disk/kfs/auth.c117
-rwxr-xr-xsys/src/cmd/disk/kfs/chk.c654
-rwxr-xr-xsys/src/cmd/disk/kfs/con.c705
-rwxr-xr-xsys/src/cmd/disk/kfs/console.c371
-rwxr-xr-xsys/src/cmd/disk/kfs/dat.c87
-rwxr-xr-xsys/src/cmd/disk/kfs/dat.h170
-rwxr-xr-xsys/src/cmd/disk/kfs/dentry.c174
-rwxr-xr-xsys/src/cmd/disk/kfs/devmulti.c244
-rwxr-xr-xsys/src/cmd/disk/kfs/devwren.c174
-rwxr-xr-xsys/src/cmd/disk/kfs/errno.h35
-rwxr-xr-xsys/src/cmd/disk/kfs/fns.h42
-rwxr-xr-xsys/src/cmd/disk/kfs/ialloc.c9
-rwxr-xr-xsys/src/cmd/disk/kfs/iobuf.c235
-rwxr-xr-xsys/src/cmd/disk/kfs/main.c558
-rwxr-xr-xsys/src/cmd/disk/kfs/misc.c86
-rwxr-xr-xsys/src/cmd/disk/kfs/mkfile54
-rwxr-xr-xsys/src/cmd/disk/kfs/ofcallfmt.c181
-rwxr-xr-xsys/src/cmd/disk/kfs/portdat.h368
-rwxr-xr-xsys/src/cmd/disk/kfs/portfns.h104
-rwxr-xr-xsys/src/cmd/disk/kfs/porttime.c243
-rwxr-xr-xsys/src/cmd/disk/kfs/print.c288
-rwxr-xr-xsys/src/cmd/disk/kfs/sub.c690
-rwxr-xr-xsys/src/cmd/disk/kfs/uid.c427
-rwxr-xr-xsys/src/cmd/disk/kfscmd.c56
-rwxr-xr-xsys/src/cmd/disk/mbr.c197
-rwxr-xr-xsys/src/cmd/disk/mkext.c318
-rwxr-xr-xsys/src/cmd/disk/mkfile49
-rwxr-xr-xsys/src/cmd/disk/mkfs.c858
-rwxr-xr-xsys/src/cmd/disk/partfs.c562
-rwxr-xr-xsys/src/cmd/disk/prep/calc.y200
-rwxr-xr-xsys/src/cmd/disk/prep/edit.c551
-rwxr-xr-xsys/src/cmd/disk/prep/edit.h54
-rwxr-xr-xsys/src/cmd/disk/prep/fdisk.c1118
-rwxr-xr-xsys/src/cmd/disk/prep/mkfile22
-rwxr-xr-xsys/src/cmd/disk/prep/prep.c523
-rwxr-xr-xsys/src/cmd/disk/rd9660.c377
-rwxr-xr-xsys/src/cmd/disk/sacfs/mkfile20
-rwxr-xr-xsys/src/cmd/disk/sacfs/mksacfs.c295
-rwxr-xr-xsys/src/cmd/disk/sacfs/sac.c792
-rwxr-xr-xsys/src/cmd/disk/sacfs/sac.h2
-rwxr-xr-xsys/src/cmd/disk/sacfs/sacfs.c882
-rwxr-xr-xsys/src/cmd/disk/sacfs/sacfs.h29
-rwxr-xr-xsys/src/cmd/disk/sacfs/ssort.h2
-rwxr-xr-xsys/src/cmd/disk/sacfs/ssort6.c419
-rwxr-xr-xsys/src/cmd/disk/sacfs/unsac.c620
72 files changed, 23331 insertions, 0 deletions
diff --git a/sys/src/cmd/disk/9660/boot.c b/sys/src/cmd/disk/9660/boot.c
new file mode 100755
index 000000000..ef3f7c297
--- /dev/null
+++ b/sys/src/cmd/disk/9660/boot.c
@@ -0,0 +1,210 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+/* FreeBSD 4.5 installation CD for reference
+g% cdsector 17 | xd -b
+1+0 records in
+1+0 records out
+0000000 00 - volume descriptor type (0)
+ 43 44 30 30 31 - "CD001"
+ 01 - volume descriptor version (1)
+ 45 4c 20 54 4f 52 49 54 4f
+0000010 20 53 50 45 43 49 46 49 43 41 54 49 4f 4e 00 00
+0000020 00 00 00 00 00 00 00 - 7-39 boot system identifier
+"EL TORITO SPECIFICATION"
+
+ 00 00 00 00 00 00 00 00 00
+0000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000040 00 00 00 00 00 00 00 - 39-71 boot identifier
+
+boot system use:
+
+absolute pointer to the boot catalog??
+
+ 4d 0c 00 00 00 00 00 00 00
+0000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000580 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000590 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00005a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+g% cdsector 3149 | xd -b # 0x0c4d
+1+0 records in
+1+0 records out
+0000000 01 - header (1)
+ 00 - platform id (0 = 0x86)
+ 00 00 - reserved 0
+ 00 00 00 00 00 00 00 00 00 00 00 00
+0000010 00 00 00 00 00 00 00 00 00 00 00 00 - id string
+ aa 55 - checksum
+ 55 aa - magic
+
+0000020 88 - 88 = bootable
+ 03 - 3 = 2.88MB diskette
+ 00 00 - load segment 0 means default 0x7C0
+ 00 - system type (byte 5 of boot image)
+ 00 - unused (0)
+ 01 00 - 512-byte sector count for initial load
+ 4e 0c 00 00 - ptr to virtual disk
+ 00 00 00 00
+0000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+g% cdsector `{h2d 0c4e} | xd -b
+1+0 records in
+1+0 records out
+0000000 eb 3c 00 00 00 00 00 00 00 00 00 00 02 00 00 00
+0000010 00 00 00 00 00 00 00 00 12 00 02 00 00 00 00 00
+0000020 00 00 00 00 00 16 1f 66 6a 00 51 50 06 53
+ 31 c0
+
+FREEBSD
+0000000 eb 3c 00 00 00 00 00 00 00 00 00 00 02 00 00 00
+0000010 00 00 00 00 00 00 00 00 12 00 02 00 00 00 00 00
+0000020 00 00 00 00 00 16 1f 66 6a 00 51 50 06 53
+ 31 c0
+
+DOS 5
+0000000 eb 3c 90 4d 53 44 4f 53 35 2e 30 00 02 01 01 00
+0000010 02 e0 00 40 0b f0 09 00 12 00 02 00 00 00 00 00
+0000020 00 00 00 00 00 00 29 6a 2c e0 16 4e 4f 20 4e 41
+0000030 4d 45 20 20 20 20 46 41 54 31 32 20 20 20 fa 33
+0000040 c0 8e d0 bc 00 7c 16 07 bb 78 00 36 c5 37 1e 56
+0000050 16 53 bf 3e 7c b9 0b 00 fc f3 a4 06 1f c6 45 fe
+0000060 0f 8b 0e 18 7c 88 4d f9 89 47 02 c7 07 3e 7c fb
+0000070 cd 13 72 79 33 c0 39 06 13 7c 74 08 8b 0e 13 7c
+0000080 89 0e 20 7c a0 10 7c f7 26 16 7c 03 06 1c 7c 13
+0000090 16 1e 7c 03 06 0e 7c 83 d2 00 a3 50 7c 89 16 52
+00000a0 7c a3 49 7c 89 16 4b 7c b8 20 00 f7 26 11 7c 8b
+
+NDISK
+0000000 eb 3c 90 50 6c 61 6e 39 2e 30 30 00 02 01 01 00
+0000010 02 e0 00 40 0b f0 09 00 12 00 02 00 00 00 00 00
+0000020 40 0b 00 00 00 00 29 13 00 00 00 43 59 4c 49 4e
+0000030 44 52 49 43 41 4c 46 41 54 31 32 20 20 20 fa 31
+0000040 c0 8e d0 8e d8 8e c0 bc ec 7b 89 e5 88 56 12 fb
+0000050 be ea 7d bf 90 7d ff d7 bf 82 7d ff d7 8b 06 27
+0000060 7c 8b 16 29 7c bb 00 7e bf 2c 7d ff d7 bb 10 00
+
+PBSDISK
+0000000 eb 3c 90 50 6c 61 6e 39 2e 30 30 00 02 01 01 00
+0000010 02 e0 00 40 0b f0 09 00 12 00 02 00 00 00 00 00
+0000020 40 0b 00 00 00 00 29 13 00 00 00 43 59 4c 49 4e
+0000030 44 52 49 43 41 4c 46 41 54 31 32 20 20 20 fa 31
+0000040 c0 8e d0 8e d8 8e c0 bc f8 7b 89 e5 88 56 00 fb
+0000050 be f8 7d bf 00 7d ff d7 bf df 7c ff d7 8b 06 27
+0000060 7c 8b 16 29 7c bb 00 7e bf 89 7c ff d7 bf fb 7c
+*/
+
+void
+Cputbootvol(Cdimg *cd)
+{
+ Cputc(cd, 0x00);
+ Cputs(cd, "CD001", 5);
+ Cputc(cd, 0x01);
+ Cputs(cd, "EL TORITO SPECIFICATION", 2+1+6+1+13);
+ Crepeat(cd, 0, 2+16+16+7);
+ cd->bootcatptr = Cwoffset(cd);
+ Cpadblock(cd);
+}
+
+void
+Cupdatebootvol(Cdimg *cd)
+{
+ uvlong o;
+
+ o = Cwoffset(cd);
+ Cwseek(cd, cd->bootcatptr);
+ Cputnl(cd, cd->bootcatblock, 4);
+ Cwseek(cd, o);
+}
+
+void
+Cputbootcat(Cdimg *cd)
+{
+ cd->bootcatblock = Cwoffset(cd) / Blocksize;
+ Cputc(cd, 0x01);
+ Cputc(cd, 0x00);
+ Cputc(cd, 0x00);
+ Cputc(cd, 0x00);
+ Crepeat(cd, 0, 12+12);
+
+ /*
+ * either the checksum doesn't include the header word
+ * or it just doesn't matter.
+ */
+ Cputc(cd, 0xAA);
+ Cputc(cd, 0x55);
+ Cputc(cd, 0x55);
+ Cputc(cd, 0xAA);
+
+ cd->bootimageptr = Cwoffset(cd);
+ Cpadblock(cd);
+}
+
+enum {
+ Emusectsz = 512, /* bytes per emulated sector */
+};
+
+void
+Cupdatebootcat(Cdimg *cd)
+{
+ uvlong o;
+ int n;
+
+ if(cd->bootdirec == nil)
+ return;
+
+ o = Cwoffset(cd);
+ Cwseek(cd, cd->bootimageptr);
+ Cputc(cd, 0x88);
+
+ if(cd->flags & CDbootnoemu)
+ Cputc(cd, 0); /* no disk emulation */
+ else
+ switch(cd->bootdirec->length){
+ default:
+ fprint(2, "warning: boot image is not 1.44MB or 2.88MB; "
+ "pretending 1.44MB\n");
+ /* fall through */
+ case 1440*1024:
+ Cputc(cd, 0x02); /* 1.44MB disk */
+ break;
+ case 2880*1024:
+ Cputc(cd, 0x03); /* 2.88MB disk */
+ break;
+ }
+ Cputnl(cd, 0, 2); /* load segment */
+ Cputc(cd, 0); /* system type */
+ Cputc(cd, 0); /* unused */
+
+ n = 1;
+ if(cd->flags & CDbootnoemu){
+ n = (cd->bootdirec->length + Emusectsz - 1) / Emusectsz;
+ if(n > 4){
+ fprint(2, "warning: boot image too big; "
+ "will only load the first 2K\n");
+ n = 4;
+ }
+ }
+ Cputnl(cd, n, 2); /* Emusectsz-byte sector count for load */
+ Cputnl(cd, cd->bootdirec->block, 4); /* ptr to disk image */
+ Cwseek(cd, o);
+}
+
+void
+findbootimage(Cdimg *cd, Direc *root)
+{
+ Direc *d;
+
+ d = walkdirec(root, cd->bootimage);
+ if(d == nil){
+ fprint(2, "warning: did not encounter boot image\n");
+ return;
+ }
+
+ cd->bootdirec = d;
+}
diff --git a/sys/src/cmd/disk/9660/cdrdwr.c b/sys/src/cmd/disk/9660/cdrdwr.c
new file mode 100755
index 000000000..21c9c047a
--- /dev/null
+++ b/sys/src/cmd/disk/9660/cdrdwr.c
@@ -0,0 +1,636 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+static int readisodesc(Cdimg*, Voldesc*);
+static int readjolietdesc(Cdimg*, Voldesc*);
+
+/*
+ * It's not strictly conforming; instead it's enough to
+ * get us up and running; presumably the real CD writing
+ * will take care of being conforming.
+ *
+ * Things not conforming include:
+ * - no path table
+ * - root directories are of length zero
+ */
+Cdimg*
+createcd(char *file, Cdinfo info)
+{
+ int fd, xfd;
+ Cdimg *cd;
+
+ if(access(file, AEXIST) == 0){
+ werrstr("file already exists");
+ return nil;
+ }
+
+ if((fd = create(file, ORDWR, 0666)) < 0)
+ return nil;
+
+ cd = emalloc(sizeof *cd);
+ cd->file = atom(file);
+
+ Binit(&cd->brd, fd, OREAD);
+
+ if((xfd = open(file, ORDWR)) < 0)
+ sysfatal("can't open file again: %r");
+ Binit(&cd->bwr, xfd, OWRITE);
+
+ Crepeat(cd, 0, 16*Blocksize);
+ Cputisopvd(cd, info);
+ if(info.flags & CDbootable){
+ cd->bootimage = info.bootimage;
+ cd->flags |= info.flags & (CDbootable|CDbootnoemu);
+ Cputbootvol(cd);
+ }
+
+ if(readisodesc(cd, &cd->iso) < 0)
+ assert(0);
+ if(info.flags & CDplan9)
+ cd->flags |= CDplan9;
+ else if(info.flags & CDrockridge)
+ cd->flags |= CDrockridge;
+ if(info.flags & CDjoliet) {
+ Cputjolietsvd(cd, info);
+ if(readjolietdesc(cd, &cd->joliet) < 0)
+ assert(0);
+ cd->flags |= CDjoliet;
+ }
+ Cputendvd(cd);
+
+ if(info.flags & CDdump){
+ cd->nulldump = Cputdumpblock(cd);
+ cd->flags |= CDdump;
+ }
+ if(cd->flags & CDbootable){
+ Cputbootcat(cd);
+ Cupdatebootvol(cd);
+ }
+
+ if(info.flags & CDconform)
+ cd->flags |= CDconform;
+
+ cd->flags |= CDnew;
+ cd->nextblock = Cwoffset(cd) / Blocksize;
+ assert(cd->nextblock != 0);
+
+ return cd;
+}
+
+Cdimg*
+opencd(char *file, Cdinfo info)
+{
+ int fd, xfd;
+ Cdimg *cd;
+ Dir *d;
+
+ if((fd = open(file, ORDWR)) < 0) {
+ if(access(file, AEXIST) == 0)
+ return nil;
+ return createcd(file, info);
+ }
+
+ if((d = dirfstat(fd)) == nil) {
+ close(fd);
+ return nil;
+ }
+ if(d->length == 0 || d->length % Blocksize) {
+ werrstr("bad length %lld", d->length);
+ close(fd);
+ free(d);
+ return nil;
+ }
+
+ cd = emalloc(sizeof *cd);
+ cd->file = atom(file);
+ cd->nextblock = d->length / Blocksize;
+ assert(cd->nextblock != 0);
+ free(d);
+
+ Binit(&cd->brd, fd, OREAD);
+
+ if((xfd = open(file, ORDWR)) < 0)
+ sysfatal("can't open file again: %r");
+ Binit(&cd->bwr, xfd, OWRITE);
+
+ if(readisodesc(cd, &cd->iso) < 0) {
+ free(cd);
+ close(fd);
+ close(xfd);
+ return nil;
+ }
+
+ /* lowercase because of isostring */
+ if(strstr(cd->iso.systemid, "iso9660") == nil
+ && strstr(cd->iso.systemid, "utf8") == nil) {
+ werrstr("unknown systemid %s", cd->iso.systemid);
+ free(cd);
+ close(fd);
+ close(xfd);
+ return nil;
+ }
+
+ if(strstr(cd->iso.systemid, "plan 9"))
+ cd->flags |= CDplan9;
+ if(strstr(cd->iso.systemid, "iso9660"))
+ cd->flags |= CDconform;
+ if(strstr(cd->iso.systemid, "rrip"))
+ cd->flags |= CDrockridge;
+ if(strstr(cd->iso.systemid, "boot"))
+ cd->flags |= CDbootable;
+ if(readjolietdesc(cd, &cd->joliet) == 0)
+ cd->flags |= CDjoliet;
+ if(hasdump(cd))
+ cd->flags |= CDdump;
+
+ return cd;
+}
+
+ulong
+big(void *a, int n)
+{
+ uchar *p;
+ ulong v;
+ int i;
+
+ p = a;
+ v = 0;
+ for(i=0; i<n; i++)
+ v = (v<<8) | *p++;
+ return v;
+}
+
+ulong
+little(void *a, int n)
+{
+ uchar *p;
+ ulong v;
+ int i;
+
+ p = a;
+ v = 0;
+ for(i=0; i<n; i++)
+ v |= (*p++<<(i*8));
+ return v;
+}
+
+void
+Creadblock(Cdimg *cd, void *buf, ulong block, ulong len)
+{
+ assert(block != 0); /* nothing useful there */
+
+ Bflush(&cd->bwr);
+ if(Bseek(&cd->brd, (vlong)block * Blocksize, 0) !=
+ (vlong)block * Blocksize)
+ sysfatal("error seeking to block %lud", block);
+ if(Bread(&cd->brd, buf, len) != len)
+ sysfatal("error reading %lud bytes at block %lud: %r %lld",
+ len, block, Bseek(&cd->brd, 0, 2));
+}
+
+int
+parsedir(Cdimg *cd, Direc *d, uchar *buf, int len, char *(*cvtname)(uchar*, int))
+{
+ enum { NAMELEN = 28 };
+ char name[NAMELEN];
+ uchar *p;
+ Cdir *c;
+
+ memset(d, 0, sizeof *d);
+
+ c = (Cdir*)buf;
+
+ if(c->len > len) {
+ werrstr("buffer too small");
+ return -1;
+ }
+
+ if(c->namelen == 1 && c->name[0] == '\0')
+ d->name = atom(".");
+ else if(c->namelen == 1 && c->name[0] == '\001')
+ d->name = atom("..");
+ else if(cvtname)
+ d->name = cvtname(c->name, c->namelen);
+
+ d->block = little(c->dloc, 4);
+ d->length = little(c->dlen, 4);
+
+ if(c->flags & 2)
+ d->mode |= DMDIR;
+
+/*BUG: do we really need to parse the plan 9 fields? */
+ /* plan 9 use fields */
+ if((cd->flags & CDplan9) && cvtname == isostring
+ && (c->namelen != 1 || c->name[0] > 1)) {
+ p = buf+33+c->namelen;
+ if((p-buf)&1)
+ p++;
+ assert(p < buf+c->len);
+ assert(*p < NAMELEN);
+ if(*p != 0) {
+ memmove(name, p+1, *p);
+ name[*p] = '\0';
+ d->confname = d->name;
+ d->name = atom(name);
+ }
+ p += *p+1;
+ assert(*p < NAMELEN);
+ memmove(name, p+1, *p);
+ name[*p] = '\0';
+ d->uid = atom(name);
+ p += *p+1;
+ assert(*p < NAMELEN);
+ memmove(name, p+1, *p);
+ name[*p] = '\0';
+ d->gid = atom(name);
+ p += *p+1;
+ if((p-buf)&1)
+ p++;
+ d->mode = little(p, 4);
+ }
+
+ // BUG: rock ridge extensions
+ return 0;
+}
+
+void
+setroot(Cdimg *cd, ulong block, ulong dloc, ulong dlen)
+{
+ assert(block != 0);
+
+ Cwseek(cd, (vlong)block * Blocksize + offsetof(Cvoldesc, rootdir[0]) +
+ offsetof(Cdir, dloc[0]));
+ Cputn(cd, dloc, 4);
+ Cputn(cd, dlen, 4);
+}
+
+void
+setvolsize(Cdimg *cd, ulong block, ulong size)
+{
+ assert(block != 0);
+
+ Cwseek(cd, (vlong)block * Blocksize + offsetof(Cvoldesc, volsize[0]));
+ Cputn(cd, size, 4);
+}
+
+void
+setpathtable(Cdimg *cd, ulong block, ulong sz, ulong lloc, ulong bloc)
+{
+ assert(block != 0);
+
+ Cwseek(cd, (vlong)block * Blocksize + offsetof(Cvoldesc, pathsize[0]));
+ Cputn(cd, sz, 4);
+ Cputnl(cd, lloc, 4);
+ Cputnl(cd, 0, 4);
+ Cputnm(cd, bloc, 4);
+ Cputnm(cd, 0, 4);
+ assert(Cwoffset(cd) == (vlong)block * Blocksize +
+ offsetof(Cvoldesc, rootdir[0]));
+}
+
+
+static void
+parsedesc(Voldesc *v, Cvoldesc *cv, char *(*string)(uchar*, int))
+{
+ v->systemid = string(cv->systemid, sizeof cv->systemid);
+
+ v->pathsize = little(cv->pathsize, 4);
+ v->lpathloc = little(cv->lpathloc, 4);
+ v->mpathloc = little(cv->mpathloc, 4);
+
+ v->volumeset = string(cv->volumeset, sizeof cv->volumeset);
+ v->publisher = string(cv->publisher, sizeof cv->publisher);
+ v->preparer = string(cv->preparer, sizeof cv->preparer);
+ v->application = string(cv->application, sizeof cv->application);
+
+ v->abstract = string(cv->abstract, sizeof cv->abstract);
+ v->biblio = string(cv->biblio, sizeof cv->biblio);
+ v->notice = string(cv->notice, sizeof cv->notice);
+}
+
+static int
+readisodesc(Cdimg *cd, Voldesc *v)
+{
+ static uchar magic[] = { 0x01, 'C', 'D', '0', '0', '1', 0x01, 0x00 };
+ Cvoldesc cv;
+
+ memset(v, 0, sizeof *v);
+
+ Creadblock(cd, &cv, 16, sizeof cv);
+ if(memcmp(cv.magic, magic, sizeof magic) != 0) {
+ werrstr("bad pvd magic");
+ return -1;
+ }
+
+ if(little(cv.blocksize, 2) != Blocksize) {
+ werrstr("block size not %d", Blocksize);
+ return -1;
+ }
+
+ cd->iso9660pvd = 16;
+ parsedesc(v, &cv, isostring);
+
+ return parsedir(cd, &v->root, cv.rootdir, sizeof cv.rootdir, isostring);
+}
+
+static int
+readjolietdesc(Cdimg *cd, Voldesc *v)
+{
+ int i;
+ static uchar magic[] = { 0x02, 'C', 'D', '0', '0', '1', 0x01, 0x00 };
+ Cvoldesc cv;
+
+ memset(v, 0, sizeof *v);
+
+ for(i=16; i<24; i++) {
+ Creadblock(cd, &cv, i, sizeof cv);
+ if(memcmp(cv.magic, magic, sizeof magic) != 0)
+ continue;
+ if(cv.charset[0] != 0x25 || cv.charset[1] != 0x2F
+ || (cv.charset[2] != 0x40 && cv.charset[2] != 0x43 && cv.charset[2] != 0x45))
+ continue;
+ break;
+ }
+
+ if(i==24) {
+ werrstr("could not find Joliet SVD");
+ return -1;
+ }
+
+ if(little(cv.blocksize, 2) != Blocksize) {
+ werrstr("block size not %d", Blocksize);
+ return -1;
+ }
+
+ cd->jolietsvd = i;
+ parsedesc(v, &cv, jolietstring);
+
+ return parsedir(cd, &v->root, cv.rootdir, sizeof cv.rootdir, jolietstring);
+}
+
+/*
+ * CD image buffering routines.
+ */
+void
+Cputc(Cdimg *cd, int c)
+{
+ assert(Boffset(&cd->bwr) >= 16*Blocksize || c == 0);
+
+if(Boffset(&cd->bwr) == 0x9962)
+if(c >= 256) abort();
+ if(Bputc(&cd->bwr, c) < 0)
+ sysfatal("Bputc: %r");
+ Bflush(&cd->brd);
+}
+
+void
+Cputnl(Cdimg *cd, ulong val, int size)
+{
+ switch(size) {
+ default:
+ sysfatal("bad size %d in bputnl", size);
+ case 2:
+ Cputc(cd, val);
+ Cputc(cd, val>>8);
+ break;
+ case 4:
+ Cputc(cd, val);
+ Cputc(cd, val>>8);
+ Cputc(cd, val>>16);
+ Cputc(cd, val>>24);
+ break;
+ }
+}
+
+void
+Cputnm(Cdimg *cd, ulong val, int size)
+{
+ switch(size) {
+ default:
+ sysfatal("bad size %d in bputnl", size);
+ case 2:
+ Cputc(cd, val>>8);
+ Cputc(cd, val);
+ break;
+ case 4:
+ Cputc(cd, val>>24);
+ Cputc(cd, val>>16);
+ Cputc(cd, val>>8);
+ Cputc(cd, val);
+ break;
+ }
+}
+
+void
+Cputn(Cdimg *cd, long val, int size)
+{
+ Cputnl(cd, val, size);
+ Cputnm(cd, val, size);
+}
+
+/*
+ * ASCII/UTF string writing
+ */
+void
+Crepeat(Cdimg *cd, int c, int n)
+{
+ while(n-- > 0)
+ Cputc(cd, c);
+}
+
+void
+Cputs(Cdimg *cd, char *s, int size)
+{
+ int n;
+
+ if(s == nil) {
+ Crepeat(cd, ' ', size);
+ return;
+ }
+
+ for(n=0; n<size && *s; n++)
+ Cputc(cd, *s++);
+ if(n<size)
+ Crepeat(cd, ' ', size-n);
+}
+
+void
+Cwrite(Cdimg *cd, void *buf, int n)
+{
+ assert(Boffset(&cd->bwr) >= 16*Blocksize);
+
+ if(Bwrite(&cd->bwr, buf, n) != n)
+ sysfatal("Bwrite: %r");
+ Bflush(&cd->brd);
+}
+
+void
+Cputr(Cdimg *cd, Rune r)
+{
+ Cputc(cd, r>>8);
+ Cputc(cd, r);
+}
+
+void
+Crepeatr(Cdimg *cd, Rune r, int n)
+{
+ int i;
+
+ for(i=0; i<n; i++)
+ Cputr(cd, r);
+}
+
+void
+Cputrs(Cdimg *cd, Rune *s, int osize)
+{
+ int n, size;
+
+ size = osize/2;
+ if(s == nil)
+ Crepeatr(cd, (Rune)' ', size);
+ else {
+ for(n=0; *s && n<size; n++)
+ Cputr(cd, *s++);
+ if(n<size)
+ Crepeatr(cd, ' ', size-n);
+ }
+ if(osize&1)
+ Cputc(cd, 0); /* what else can we do? */
+}
+
+void
+Cputrscvt(Cdimg *cd, char *s, int size)
+{
+ Rune r[256];
+
+ strtorune(r, s);
+ Cputrs(cd, strtorune(r, s), size);
+}
+
+void
+Cpadblock(Cdimg *cd)
+{
+ int n;
+ ulong nb;
+
+ n = Blocksize - (Boffset(&cd->bwr) % Blocksize);
+ if(n != Blocksize)
+ Crepeat(cd, 0, n);
+
+ nb = Boffset(&cd->bwr)/Blocksize;
+ assert(nb != 0);
+ if(nb > cd->nextblock)
+ cd->nextblock = nb;
+}
+
+void
+Cputdate(Cdimg *cd, ulong ust)
+{
+ Tm *tm;
+
+ if(ust == 0) {
+ Crepeat(cd, 0, 7);
+ return;
+ }
+ tm = gmtime(ust);
+ Cputc(cd, tm->year);
+ Cputc(cd, tm->mon+1);
+ Cputc(cd, tm->mday);
+ Cputc(cd, tm->hour);
+ Cputc(cd, tm->min);
+ Cputc(cd, tm->sec);
+ Cputc(cd, 0);
+}
+
+void
+Cputdate1(Cdimg *cd, ulong ust)
+{
+ Tm *tm;
+ char str[20];
+
+ if(ust == 0) {
+ Crepeat(cd, '0', 16);
+ Cputc(cd, 0);
+ return;
+ }
+ tm = gmtime(ust);
+ sprint(str, "%.4d%.2d%.2d%.2d%.2d%.4d",
+ tm->year+1900,
+ tm->mon+1,
+ tm->mday,
+ tm->hour,
+ tm->min,
+ tm->sec*100);
+ Cputs(cd, str, 16);
+ Cputc(cd, 0);
+}
+
+void
+Cwseek(Cdimg *cd, vlong offset)
+{
+ Bseek(&cd->bwr, offset, 0);
+}
+
+uvlong
+Cwoffset(Cdimg *cd)
+{
+ return Boffset(&cd->bwr);
+}
+
+void
+Cwflush(Cdimg *cd)
+{
+ Bflush(&cd->bwr);
+}
+
+uvlong
+Croffset(Cdimg *cd)
+{
+ return Boffset(&cd->brd);
+}
+
+void
+Crseek(Cdimg *cd, vlong offset)
+{
+ Bseek(&cd->brd, offset, 0);
+}
+
+int
+Cgetc(Cdimg *cd)
+{
+ int c;
+
+ Cwflush(cd);
+ if((c = Bgetc(&cd->brd)) == Beof) {
+ fprint(2, "getc at %llud\n", Croffset(cd));
+ assert(0);
+ //sysfatal("Bgetc: %r");
+ }
+ return c;
+}
+
+void
+Cread(Cdimg *cd, void *buf, int n)
+{
+ Cwflush(cd);
+ if(Bread(&cd->brd, buf, n) != n)
+ sysfatal("Bread: %r");
+}
+
+char*
+Crdline(Cdimg *cd, int c)
+{
+ Cwflush(cd);
+ return Brdline(&cd->brd, c);
+}
+
+int
+Clinelen(Cdimg *cd)
+{
+ return Blinelen(&cd->brd);
+}
+
diff --git a/sys/src/cmd/disk/9660/conform.c b/sys/src/cmd/disk/9660/conform.c
new file mode 100755
index 000000000..df1986bfa
--- /dev/null
+++ b/sys/src/cmd/disk/9660/conform.c
@@ -0,0 +1,141 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <ctype.h>
+#include "iso9660.h"
+
+/*
+ * We keep an array sorted by bad atom pointer.
+ * The theory is that since we don't free memory very often,
+ * the array will be mostly sorted already and insertions will
+ * usually be near the end, so we won't spend much time
+ * keeping it sorted.
+ */
+
+/*
+ * Binary search a Tx list.
+ * If no entry is found, return a pointer to
+ * where a new such entry would go.
+ */
+static Tx*
+txsearch(char *atom, Tx *t, int n)
+{
+ while(n > 0) {
+ if(atom < t[n/2].bad)
+ n = n/2;
+ else if(atom > t[n/2].bad) {
+ t += n/2+1;
+ n -= (n/2+1);
+ } else
+ return &t[n/2];
+ }
+ return t;
+}
+
+void
+addtx(char *b, char *g)
+{
+ Tx *t;
+ Conform *c;
+
+ if(map == nil)
+ map = emalloc(sizeof(*map));
+ c = map;
+
+ if(c->nt%32 == 0)
+ c->t = erealloc(c->t, (c->nt+32)*sizeof(c->t[0]));
+ t = txsearch(b, c->t, c->nt);
+ if(t < c->t+c->nt && t->bad == b) {
+ fprint(2, "warning: duplicate entry for %s in _conform.map\n", b);
+ return;
+ }
+
+ if(t != c->t+c->nt)
+ memmove(t+1, t, (c->t+c->nt - t)*sizeof(Tx));
+ t->bad = b;
+ t->good = g;
+ c->nt++;
+}
+
+char*
+conform(char *s, int isdir)
+{
+ Tx *t;
+ char buf[10], *g;
+ Conform *c;
+
+ c = map;
+ s = atom(s);
+ if(c){
+ t = txsearch(s, c->t, c->nt);
+ if(t < c->t+c->nt && t->bad == s)
+ return t->good;
+ }
+
+ sprint(buf, "%c%.6d", isdir ? 'D' : 'F', c ? c->nt : 0);
+ g = atom(buf);
+ addtx(s, g);
+ return g;
+}
+
+#ifdef NOTUSED
+static int
+isalldigit(char *s)
+{
+ while(*s)
+ if(!isdigit(*s++))
+ return 0;
+ return 1;
+}
+#endif
+
+static int
+goodcmp(const void *va, const void *vb)
+{
+ Tx *a, *b;
+
+ a = (Tx*)va;
+ b = (Tx*)vb;
+ return strcmp(a->good, b->good);
+}
+
+static int
+badatomcmp(const void *va, const void *vb)
+{
+ Tx *a, *b;
+
+ a = (Tx*)va;
+ b = (Tx*)vb;
+ if(a->good < b->good)
+ return -1;
+ if(a->good > b->good)
+ return 1;
+ return 0;
+}
+
+void
+wrconform(Cdimg *cd, int n, ulong *pblock, uvlong *plength)
+{
+ char buf[1024];
+ int i;
+ Conform *c;
+
+ c = map;
+ *pblock = cd->nextblock;
+ if(c==nil || n==c->nt){
+ *plength = 0;
+ return;
+ }
+
+ Cwseek(cd, (vlong)cd->nextblock * Blocksize);
+ qsort(c->t, c->nt, sizeof(c->t[0]), goodcmp);
+ for(i=n; i<c->nt; i++) {
+ snprint(buf, sizeof buf, "%s %s\n", c->t[i].good, c->t[i].bad);
+ Cwrite(cd, buf, strlen(buf));
+ }
+ qsort(c->t, c->nt, sizeof(c->t[0]), badatomcmp);
+ *plength = Cwoffset(cd) - (vlong)*pblock * Blocksize;
+ chat("write _conform.map at %lud+%llud\n", *pblock, *plength);
+ Cpadblock(cd);
+}
diff --git a/sys/src/cmd/disk/9660/direc.c b/sys/src/cmd/disk/9660/direc.c
new file mode 100755
index 000000000..87b56034f
--- /dev/null
+++ b/sys/src/cmd/disk/9660/direc.c
@@ -0,0 +1,222 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+void
+mkdirec(Direc *direc, XDir *d)
+{
+ memset(direc, 0, sizeof(Direc));
+ direc->name = atom(d->name);
+ direc->uid = atom(d->uid);
+ direc->gid = atom(d->gid);
+ direc->uidno = d->uidno;
+ direc->gidno = d->gidno;
+ direc->mode = d->mode;
+ direc->length = d->length;
+ direc->mtime = d->mtime;
+ direc->atime = d->atime;
+ direc->ctime = d->ctime;
+ direc->symlink = d->symlink;
+}
+
+static int
+strecmp(char *a, char *ea, char *b)
+{
+ int r;
+
+ if((r = strncmp(a, b, ea-a)) != 0)
+ return r;
+
+ if(b[ea-a] == '\0')
+ return 0;
+ return 1;
+}
+
+/*
+ * Binary search a list of directories for the
+ * entry with name name.
+ * If no entry is found, return a pointer to
+ * where a new such entry would go.
+ */
+static Direc*
+dbsearch(char *name, int nname, Direc *d, int n)
+{
+ int i;
+
+ while(n > 0) {
+ i = strecmp(name, name+nname, d[n/2].name);
+ if(i < 0)
+ n = n/2;
+ else if(i > 0) {
+ d += n/2+1;
+ n -= (n/2+1);
+ } else
+ return &d[n/2];
+ }
+ return d;
+}
+
+/*
+ * Walk to name, starting at d.
+ */
+Direc*
+walkdirec(Direc *d, char *name)
+{
+ char *p, *nextp, *slashp;
+ Direc *nd;
+
+ for(p=name; p && *p; p=nextp) {
+ if((slashp = strchr(p, '/')) != nil)
+ nextp = slashp+1;
+ else
+ nextp = slashp = p+strlen(p);
+
+ nd = dbsearch(p, slashp-p, d->child, d->nchild);
+ if(nd >= d->child+d->nchild || strecmp(p, slashp, nd->name) != 0)
+ return nil;
+ d = nd;
+ }
+ return d;
+}
+
+/*
+ * Add the file ``name'' with attributes d to the
+ * directory ``root''. Name may contain multiple
+ * elements; all but the last must exist already.
+ *
+ * The child lists are kept sorted by utfname.
+ */
+Direc*
+adddirec(Direc *root, char *name, XDir *d)
+{
+ char *p;
+ Direc *nd;
+ int off;
+
+ if(name[0] == '/')
+ name++;
+ if((p = strrchr(name, '/')) != nil) {
+ *p = '\0';
+ root = walkdirec(root, name);
+ if(root == nil) {
+ sysfatal("error in proto file: no entry for /%s but /%s/%s", name, name, p+1);
+ return nil;
+ }
+ *p = '/';
+ p++;
+ } else
+ p = name;
+
+ nd = dbsearch(p, strlen(p), root->child, root->nchild);
+ off = nd - root->child;
+ if(off < root->nchild && strcmp(nd->name, p) == 0) {
+ if ((d->mode & DMDIR) == 0)
+ fprint(2, "warning: proto lists %s twice\n", name);
+ return nil;
+ }
+
+ if(root->nchild%Ndirblock == 0) {
+ root->child = erealloc(root->child, (root->nchild+Ndirblock)*sizeof(Direc));
+ nd = root->child + off;
+ }
+
+ memmove(nd+1, nd, (root->nchild - off)*sizeof(Direc));
+ mkdirec(nd, d);
+ nd->name = atom(p);
+ root->nchild++;
+ return nd;
+}
+
+/*
+ * Copy the tree src into dst.
+ */
+void
+copydirec(Direc *dst, Direc *src)
+{
+ int i, n;
+
+ *dst = *src;
+
+ if((src->mode & DMDIR) == 0)
+ return;
+
+ n = (src->nchild + Ndirblock - 1);
+ n -= n%Ndirblock;
+ dst->child = emalloc(n*sizeof(Direc));
+
+ n = dst->nchild;
+ for(i=0; i<n; i++)
+ copydirec(&dst->child[i], &src->child[i]);
+}
+
+/*
+ * Turn the Dbadname flag on for any entries
+ * that have non-conforming names.
+ */
+static void
+_checknames(Direc *d, int (*isbadname)(char*), int isroot)
+{
+ int i;
+
+ if(!isroot && isbadname(d->name))
+ d->flags |= Dbadname;
+
+ if(strcmp(d->name, "_conform.map") == 0)
+ d->flags |= Dbadname;
+
+ for(i=0; i<d->nchild; i++)
+ _checknames(&d->child[i], isbadname, 0);
+}
+
+void
+checknames(Direc *d, int (*isbadname)(char*))
+{
+ _checknames(d, isbadname, 1);
+}
+
+/*
+ * Set the names to conform to 8.3
+ * by changing them to numbers.
+ * Plan 9 gets the right names from its
+ * own directory entry.
+ *
+ * We used to write a _conform.map file to translate
+ * names. Joliet should take care of most of the
+ * interoperability with other systems now.
+ */
+void
+convertnames(Direc *d, char* (*cvt)(char*, char*))
+{
+ int i;
+ char new[1024];
+
+ if(d->flags & Dbadname)
+ cvt(new, conform(d->name, d->mode & DMDIR));
+ else
+ cvt(new, d->name);
+ d->confname = atom(new);
+
+ for(i=0; i<d->nchild; i++)
+ convertnames(&d->child[i], cvt);
+}
+
+/*
+ * Sort a directory with a given comparison function.
+ * After this is called on a tree, adddirec should not be,
+ * since the entries may no longer be sorted as adddirec expects.
+ */
+void
+dsort(Direc *d, int (*cmp)(const void*, const void*))
+{
+ int i, n;
+
+ n = d->nchild;
+ qsort(d->child, n, sizeof(d[0]), cmp);
+
+ for(i=0; i<n; i++)
+ dsort(&d->child[i], cmp);
+}
+
diff --git a/sys/src/cmd/disk/9660/dump.c b/sys/src/cmd/disk/9660/dump.c
new file mode 100755
index 000000000..02fd0b246
--- /dev/null
+++ b/sys/src/cmd/disk/9660/dump.c
@@ -0,0 +1,512 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <ctype.h>
+#include "iso9660.h"
+
+static void
+md5cd(Cdimg *cd, ulong block, ulong length, uchar *digest)
+{
+ int n;
+ uchar buf[Blocksize];
+ DigestState *s;
+
+ s = md5(nil, 0, nil, nil);
+ while(length > 0) {
+ n = length;
+ if(n > Blocksize)
+ n = Blocksize;
+
+ Creadblock(cd, buf, block, n);
+
+ md5(buf, n, nil, s);
+
+ block++;
+ length -= n;
+ }
+ md5(nil, 0, digest, s);
+}
+
+static Dumpdir*
+mkdumpdir(char *name, uchar *md5, ulong block, ulong length)
+{
+ Dumpdir *d;
+
+ assert(block != 0);
+
+ d = emalloc(sizeof *d);
+ d->name = name;
+ memmove(d->md5, md5, sizeof d->md5);
+ d->block = block;
+ d->length = length;
+
+ return d;
+}
+
+static Dumpdir**
+ltreewalkmd5(Dumpdir **l, uchar *md5)
+{
+ int i;
+
+ while(*l) {
+ i = memcmp(md5, (*l)->md5, MD5dlen);
+ if(i < 0)
+ l = &(*l)->md5left;
+ else if(i == 0)
+ return l;
+ else
+ l = &(*l)->md5right;
+ }
+ return l;
+}
+
+static Dumpdir**
+ltreewalkblock(Dumpdir **l, ulong block)
+{
+ while(*l) {
+ if(block < (*l)->block)
+ l = &(*l)->blockleft;
+ else if(block == (*l)->block)
+ return l;
+ else
+ l = &(*l)->blockright;
+ }
+ return l;
+}
+
+/*
+ * Add a particular file to our binary tree.
+ */
+static void
+addfile(Cdimg *cd, Dump *d, char *name, Direc *dir)
+{
+ uchar md5[MD5dlen];
+ Dumpdir **lblock;
+
+ assert((dir->mode & DMDIR) == 0);
+
+ if(dir->length == 0)
+ return;
+
+ lblock = ltreewalkblock(&d->blockroot, dir->block);
+ if(*lblock != nil) {
+ if((*lblock)->length == dir->length)
+ return;
+ fprint(2, "block %lud length %lud %s %lud %s\n", dir->block, (*lblock)->length, (*lblock)->name,
+ dir->length, dir->name);
+ assert(0);
+ }
+
+ md5cd(cd, dir->block, dir->length, md5);
+ if(chatty > 1)
+ fprint(2, "note file %.16H %lud (%s)\n", md5, dir->length, dir->name);
+ insertmd5(d, name, md5, dir->block, dir->length);
+}
+
+void
+insertmd5(Dump *d, char *name, uchar *md5, ulong block, ulong length)
+{
+ Dumpdir **lmd5;
+ Dumpdir **lblock;
+
+ lblock = ltreewalkblock(&d->blockroot, block);
+ if(*lblock != nil) {
+ if((*lblock)->length == length)
+ return;
+ fprint(2, "block %lud length %lud %lud\n", block, (*lblock)->length, length);
+ assert(0);
+ }
+
+ assert(length != 0);
+ *lblock = mkdumpdir(name, md5, block, length);
+
+ lmd5 = ltreewalkmd5(&d->md5root, md5);
+ if(*lmd5 != nil)
+ fprint(2, "warning: data duplicated on CD\n");
+ else
+ *lmd5 = *lblock;
+}
+
+/*
+ * Fill all the children entries for a particular directory;
+ * all we care about is block, length, and whether it is a directory.
+ */
+void
+readkids(Cdimg *cd, Direc *dir, char *(*cvt)(uchar*, int))
+{
+ char *dot, *dotdot;
+ int m, n;
+ uchar buf[Blocksize], *ebuf, *p;
+ ulong b, nb;
+ Cdir *c;
+ Direc dx;
+
+ assert(dir->mode & DMDIR);
+
+ dot = atom(".");
+ dotdot = atom("..");
+ ebuf = buf+Blocksize;
+ nb = (dir->length+Blocksize-1) / Blocksize;
+
+ n = 0;
+ for(b=0; b<nb; b++) {
+ Creadblock(cd, buf, dir->block + b, Blocksize);
+ p = buf;
+ while(p < ebuf) {
+ c = (Cdir*)p;
+ if(c->len == 0)
+ break;
+ if(p+c->len > ebuf)
+ break;
+ if(parsedir(cd, &dx, p, ebuf-p, cvt) == 0 && dx.name != dot && dx.name != dotdot)
+ n++;
+ p += c->len;
+ }
+ }
+
+ m = (n+Ndirblock-1)/Ndirblock * Ndirblock;
+ dir->child = emalloc(m*sizeof dir->child[0]);
+ dir->nchild = n;
+
+ n = 0;
+ for(b=0; b<nb; b++) {
+ assert(n <= dir->nchild);
+ Creadblock(cd, buf, dir->block + b, Blocksize);
+ p = buf;
+ while(p < ebuf) {
+ c = (Cdir*)p;
+ if(c->len == 0)
+ break;
+ if(p+c->len > ebuf)
+ break;
+ if(parsedir(cd, &dx, p, ebuf-p, cvt) == 0 && dx.name != dot && dx.name != dotdot) {
+ assert(n < dir->nchild);
+ dir->child[n++] = dx;
+ }
+ p += c->len;
+ }
+ }
+}
+
+/*
+ * Free the children. Make sure their children are free too.
+ */
+void
+freekids(Direc *dir)
+{
+ int i;
+
+ for(i=0; i<dir->nchild; i++)
+ assert(dir->child[i].nchild == 0);
+
+ free(dir->child);
+ dir->child = nil;
+ dir->nchild = 0;
+}
+
+/*
+ * Add a whole directory and all its children to our binary tree.
+ */
+static void
+adddir(Cdimg *cd, Dump *d, Direc *dir)
+{
+ int i;
+
+ readkids(cd, dir, isostring);
+ for(i=0; i<dir->nchild; i++) {
+ if(dir->child[i].mode & DMDIR)
+ adddir(cd, d, &dir->child[i]);
+ else
+ addfile(cd, d, atom(dir->name), &dir->child[i]);
+ }
+ freekids(dir);
+}
+
+Dumpdir*
+lookupmd5(Dump *d, uchar *md5)
+{
+ return *ltreewalkmd5(&d->md5root, md5);
+}
+
+void
+adddirx(Cdimg *cd, Dump *d, Direc *dir, int lev)
+{
+ int i;
+ Direc dd;
+
+ if(lev == 2){
+ dd = *dir;
+ adddir(cd, d, &dd);
+ return;
+ }
+ for(i=0; i<dir->nchild; i++)
+ adddirx(cd, d, &dir->child[i], lev+1);
+}
+
+Dump*
+dumpcd(Cdimg *cd, Direc *dir)
+{
+ Dump *d;
+
+ d = emalloc(sizeof *d);
+ d->cd = cd;
+ adddirx(cd, d, dir, 0);
+ return d;
+}
+
+/*
+static ulong
+minblock(Direc *root, int lev)
+{
+ int i;
+ ulong m, n;
+
+ m = root->block;
+ for(i=0; i<root->nchild; i++) {
+ n = minblock(&root->child[i], lev-1);
+ if(m > n)
+ m = n;
+ }
+ return m;
+}
+*/
+
+void
+copybutname(Direc *d, Direc *s)
+{
+ Direc x;
+
+ x = *d;
+ *d = *s;
+ d->name = x.name;
+ d->confname = x.confname;
+}
+
+Direc*
+createdumpdir(Direc *root, XDir *dir, char *utfname)
+{
+ char *p;
+ Direc *d;
+
+ if(utfname[0]=='/')
+ sysfatal("bad dump name '%s'", utfname);
+ p = strchr(utfname, '/');
+ if(p == nil || strchr(p+1, '/'))
+ sysfatal("bad dump name '%s'", utfname);
+ *p++ = '\0';
+ if((d = walkdirec(root, utfname)) == nil)
+ d = adddirec(root, utfname, dir);
+ if(walkdirec(d, p))
+ sysfatal("duplicate dump name '%s/%s'", utfname, p);
+ d = adddirec(d, p, dir);
+ return d;
+}
+
+static void
+rmdirec(Direc *d, Direc *kid)
+{
+ Direc *ekid;
+
+ ekid = d->child+d->nchild;
+ assert(d->child <= kid && kid < ekid);
+ if(ekid != kid+1)
+ memmove(kid, kid+1, (ekid-(kid+1))*sizeof(*kid));
+ d->nchild--;
+}
+
+void
+rmdumpdir(Direc *root, char *utfname)
+{
+ char *p;
+ Direc *d, *dd;
+
+ if(utfname[0]=='/')
+ sysfatal("bad dump name '%s'", utfname);
+ p = strchr(utfname, '/');
+ if(p == nil || strchr(p+1, '/'))
+ sysfatal("bad dump name '%s'", utfname);
+ *p++ = '\0';
+ if((d = walkdirec(root, utfname)) == nil)
+ sysfatal("cannot remove %s/%s: %s does not exist", utfname, p, utfname);
+ p[-1] = '/';
+
+ if((dd = walkdirec(d, p)) == nil)
+ sysfatal("cannot remove %s: does not exist", utfname);
+
+ rmdirec(d, dd);
+ if(d->nchild == 0)
+ rmdirec(root, d);
+}
+
+char*
+adddumpdir(Direc *root, ulong now, XDir *dir)
+{
+ char buf[40], *p;
+ int n;
+ Direc *dday, *dyear;
+ Tm tm;
+
+ tm = *localtime(now);
+
+ sprint(buf, "%d", tm.year+1900);
+ if((dyear = walkdirec(root, buf)) == nil) {
+ dyear = adddirec(root, buf, dir);
+ assert(dyear != nil);
+ }
+
+ n = 0;
+ sprint(buf, "%.2d%.2d", tm.mon+1, tm.mday);
+ p = buf+strlen(buf);
+ while(walkdirec(dyear, buf))
+ sprint(p, "%d", ++n);
+
+ dday = adddirec(dyear, buf, dir);
+ assert(dday != nil);
+
+ sprint(buf, "%s/%s", dyear->name, dday->name);
+assert(walkdirec(root, buf)==dday);
+ return atom(buf);
+}
+
+/*
+ * The dump directory tree is inferred from a linked list of special blocks.
+ * One block is written at the end of each dump.
+ * The blocks have the form
+ *
+ * plan 9 dump cd
+ * <dump-name> <dump-time> <next-block> <conform-block> <conform-length> \
+ * <iroot-block> <iroot-length> <jroot-block> <jroot-length>
+ *
+ * If only the first line is present, this is the end of the chain.
+ */
+static char magic[] = "plan 9 dump cd\n";
+ulong
+Cputdumpblock(Cdimg *cd)
+{
+ uvlong x;
+
+ Cwseek(cd, (vlong)cd->nextblock * Blocksize);
+ x = Cwoffset(cd);
+ Cwrite(cd, magic, sizeof(magic)-1);
+ Cpadblock(cd);
+ return x/Blocksize;
+}
+
+int
+hasdump(Cdimg *cd)
+{
+ int i;
+ char buf[128];
+
+ for(i=16; i<24; i++) {
+ Creadblock(cd, buf, i, sizeof buf);
+ if(memcmp(buf, magic, sizeof(magic)-1) == 0)
+ return i;
+ }
+ return 0;
+}
+
+Direc
+readdumpdirs(Cdimg *cd, XDir *dir, char *(*cvt)(uchar*, int))
+{
+ char buf[Blocksize];
+ char *p, *q, *f[16];
+ int i, nf;
+ ulong db, t;
+ Direc *nr, root;
+ XDir xd;
+
+ mkdirec(&root, dir);
+ db = hasdump(cd);
+ xd = *dir;
+ for(;;){
+ if(db == 0)
+ sysfatal("error in dump blocks");
+
+ Creadblock(cd, buf, db, sizeof buf);
+ if(memcmp(buf, magic, sizeof(magic)-1) != 0)
+ break;
+ p = buf+sizeof(magic)-1;
+ if(p[0] == '\0')
+ break;
+ if((q = strchr(p, '\n')) != nil)
+ *q = '\0';
+
+ nf = tokenize(p, f, nelem(f));
+ i = 5;
+ if(nf < i || (cvt==jolietstring && nf < i+2))
+ sysfatal("error in dump block %lud: nf=%d; p='%s'", db, nf, p);
+ nr = createdumpdir(&root, &xd, f[0]);
+ t = strtoul(f[1], 0, 0);
+ xd.mtime = xd.ctime = xd.atime = t;
+ db = strtoul(f[2], 0, 0);
+ if(cvt == jolietstring)
+ i += 2;
+ nr->block = strtoul(f[i], 0, 0);
+ nr->length = strtoul(f[i+1], 0, 0);
+ }
+ cd->nulldump = db;
+ return root;
+}
+
+extern void addtx(char*, char*);
+
+static int
+isalldigit(char *s)
+{
+ while(*s)
+ if(!isdigit(*s++))
+ return 0;
+ return 1;
+}
+
+void
+readdumpconform(Cdimg *cd)
+{
+ char buf[Blocksize];
+ char *p, *q, *f[10];
+ int nf;
+ ulong cb, nc, db;
+ uvlong m;
+
+ db = hasdump(cd);
+ assert(map==nil || map->nt == 0);
+
+ for(;;){
+ if(db == 0)
+ sysfatal("error0 in dump blocks");
+
+ Creadblock(cd, buf, db, sizeof buf);
+ if(memcmp(buf, magic, sizeof(magic)-1) != 0)
+ break;
+ p = buf+sizeof(magic)-1;
+ if(p[0] == '\0')
+ break;
+ if((q = strchr(p, '\n')) != nil)
+ *q = '\0';
+
+ nf = tokenize(p, f, nelem(f));
+ if(nf < 5)
+ sysfatal("error0 in dump block %lud", db);
+
+ db = strtoul(f[2], 0, 0);
+ cb = strtoul(f[3], 0, 0);
+ nc = strtoul(f[4], 0, 0);
+
+ Crseek(cd, (vlong)cb * Blocksize);
+ m = (vlong)cb * Blocksize + nc;
+ while(Croffset(cd) < m && (p = Crdline(cd, '\n')) != nil){
+ p[Clinelen(cd)-1] = '\0';
+ if(tokenize(p, f, 2) != 2 || (f[0][0] != 'D' && f[0][0] != 'F')
+ || strlen(f[0]) != 7 || !isalldigit(f[0]+1))
+ break;
+
+ addtx(atom(f[1]), atom(f[0]));
+ }
+ }
+ if(map)
+ cd->nconform = map->nt;
+ else
+ cd->nconform = 0;
+}
diff --git a/sys/src/cmd/disk/9660/dump9660.c b/sys/src/cmd/disk/9660/dump9660.c
new file mode 100755
index 000000000..83ae9807a
--- /dev/null
+++ b/sys/src/cmd/disk/9660/dump9660.c
@@ -0,0 +1,417 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <disk.h>
+#include <libsec.h>
+#include "iso9660.h"
+
+ulong now;
+int chatty;
+int doabort;
+int docolon;
+int mk9660;
+vlong dataoffset;
+int blocksize;
+Conform *map;
+
+static void addprotofile(char *new, char *old, Dir *d, void *a);
+void usage(void);
+
+char *argv0;
+
+void
+usage(void)
+{
+ if(mk9660)
+ fprint(2, "usage: disk/mk9660 [-D:] [-9cjr] [-b bootfile] [-o offset blocksize] [-p proto] [-s src] cdimage\n");
+ else
+ fprint(2, "usage: disk/dump9660 [-D:] [-9cjr] [-m maxsize] [-n now] [-p proto] [-s src] cdimage\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int fix;
+ ulong block, newnull, cblock;
+ vlong maxsize;
+ uvlong length, clength;
+ char buf[256], *dumpname, *proto, *s, *src, *status;
+ Cdimg *cd;
+ Cdinfo info;
+ XDir dir;
+ Direc *iconform, idumproot, iroot, *jconform, jdumproot, jroot, *r;
+ Dump *dump;
+
+ fix = 0;
+ status = nil;
+ memset(&info, 0, sizeof info);
+ proto = "/sys/lib/sysconfig/proto/allproto";
+ src = "./";
+
+ info.volumename = atom("9CD");
+ info.volumeset = atom("9VolumeSet");
+ info.publisher = atom("9Publisher");
+ info.preparer = atom("dump9660");
+ info.application = atom("dump9660");
+ info.flags = CDdump;
+ maxsize = 0;
+ mk9660 = 0;
+ fmtinstall('H', encodefmt);
+
+ ARGBEGIN{
+ case 'D':
+ chatty++;
+ break;
+ case 'M':
+ mk9660 = 1;
+ argv0 = "disk/mk9660";
+ info.flags &= ~CDdump;
+ break;
+ case '9':
+ info.flags |= CDplan9;
+ break;
+ case ':':
+ docolon = 1;
+ break;
+ case 'a':
+ doabort = 1;
+ break;
+ case 'B':
+ info.flags |= CDbootnoemu;
+ /* fall through */
+ case 'b':
+ if(!mk9660)
+ usage();
+ info.flags |= CDbootable;
+ info.bootimage = EARGF(usage());
+ break;
+ case 'c':
+ info.flags |= CDconform;
+ break;
+ case 'f':
+ fix = 1;
+ break;
+ case 'j':
+ info.flags |= CDjoliet;
+ break;
+ case 'n':
+ now = atoi(EARGF(usage()));
+ break;
+ case 'm':
+ maxsize = strtoull(EARGF(usage()), 0, 0);
+ break;
+ case 'o':
+ dataoffset = atoll(EARGF(usage()));
+ blocksize = atoi(EARGF(usage()));
+ if(blocksize%Blocksize)
+ sysfatal("bad block size %d -- must be multiple of 2048", blocksize);
+ blocksize /= Blocksize;
+ break;
+ case 'p':
+ proto = EARGF(usage());
+ break;
+ case 'r':
+ info.flags |= CDrockridge;
+ break;
+ case 's':
+ src = EARGF(usage());
+ break;
+ case 'v':
+ info.volumename = atom(EARGF(usage()));
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(mk9660 && (fix || now || maxsize))
+ usage();
+
+ if(argc != 1)
+ usage();
+
+ if(now == 0)
+ now = (ulong)time(0);
+ if(mk9660){
+ if((cd = createcd(argv[0], info)) == nil)
+ sysfatal("cannot create '%s': %r", argv[0]);
+ }else{
+ if((cd = opencd(argv[0], info)) == nil)
+ sysfatal("cannot open '%s': %r", argv[0]);
+ if(!(cd->flags & CDdump))
+ sysfatal("not a dump cd");
+ }
+
+ /* create ISO9660/Plan 9 tree in memory */
+ memset(&dir, 0, sizeof dir);
+ dir.name = atom("");
+ dir.uid = atom("sys");
+ dir.gid = atom("sys");
+ dir.uidno = 0;
+ dir.gidno = 0;
+ dir.mode = DMDIR | 0755;
+ dir.mtime = now;
+ dir.atime = now;
+ dir.ctime = now;
+
+ mkdirec(&iroot, &dir);
+ iroot.srcfile = src;
+
+ /*
+ * Read new files into memory
+ */
+ if(rdproto(proto, src, addprotofile, nil, &iroot) < 0)
+ sysfatal("rdproto: %r");
+
+ if(mk9660){
+ dump = emalloc(sizeof *dump);
+ dumpname = nil;
+ }else{
+ /*
+ * Read current dump tree and _conform.map.
+ */
+ idumproot = readdumpdirs(cd, &dir, isostring);
+ readdumpconform(cd);
+ if(cd->flags & CDjoliet)
+ jdumproot = readdumpdirs(cd, &dir, jolietstring);
+
+ if(fix){
+ dumpname = nil;
+ cd->nextblock = cd->nulldump+1;
+ cd->nulldump = 0;
+ Cwseek(cd, (vlong)cd->nextblock * Blocksize);
+ goto Dofix;
+ }
+
+ dumpname = adddumpdir(&idumproot, now, &dir);
+ /* note that we assume all names are conforming and thus sorted */
+ if(cd->flags & CDjoliet) {
+ s = adddumpdir(&jdumproot, now, &dir);
+ if(s != dumpname)
+ sysfatal("dumpnames don't match %s %s", dumpname, s);
+ }
+ dump = dumpcd(cd, &idumproot);
+ cd->nextblock = cd->nulldump+1;
+ }
+
+ /*
+ * Write new files, starting where the dump tree was.
+ * Must be done before creation of the Joliet tree so that
+ * blocks and lengths are correct.
+ */
+ if(dataoffset > (vlong)cd->nextblock * Blocksize)
+ cd->nextblock = (dataoffset+Blocksize-1)/Blocksize;
+ Cwseek(cd, (vlong)cd->nextblock * Blocksize);
+ writefiles(dump, cd, &iroot);
+
+ if(cd->bootimage){
+ findbootimage(cd, &iroot);
+ Cupdatebootcat(cd);
+ }
+
+ /* create Joliet tree */
+ if(cd->flags & CDjoliet)
+ copydirec(&jroot, &iroot);
+
+ if(info.flags & CDconform) {
+ checknames(&iroot, isbadiso9660);
+ convertnames(&iroot, struprcpy);
+ } else
+ convertnames(&iroot, (void *) strcpy);
+
+// isoabstract = findconform(&iroot, abstract);
+// isobiblio = findconform(&iroot, biblio);
+// isonotice = findconform(&iroot, notice);
+
+ dsort(&iroot, isocmp);
+
+ if(cd->flags & CDjoliet) {
+ // jabstract = findconform(&jroot, abstract);
+ // jbiblio = findconform(&jroot, biblio);
+ // jnotice = findconform(&jroot, notice);
+
+ checknames(&jroot, isbadjoliet);
+ convertnames(&jroot, (void *) strcpy);
+ dsort(&jroot, jolietcmp);
+ }
+
+ /*
+ * Write directories.
+ */
+ writedirs(cd, &iroot, Cputisodir);
+ if(cd->flags & CDjoliet)
+ writedirs(cd, &jroot, Cputjolietdir);
+
+ if(mk9660){
+ cblock = 0;
+ clength = 0;
+ newnull = 0;
+ }else{
+ /*
+ * Write incremental _conform.map block.
+ */
+ wrconform(cd, cd->nconform, &cblock, &clength);
+
+ /* jump here if we're just fixing up the cd */
+Dofix:
+ /*
+ * Write null dump header block; everything after this will be
+ * overwritten at the next dump. Because of this, it needs to be
+ * reconstructable. We reconstruct the _conform.map and dump trees
+ * from the header blocks in dump.c, and we reconstruct the path
+ * tables by walking the cd.
+ */
+ newnull = Cputdumpblock(cd);
+ }
+
+ /*
+ * Write _conform.map.
+ */
+ dir.mode = 0444;
+ if(cd->flags & (CDconform|CDjoliet)) {
+ if(!mk9660 && cd->nconform == 0){
+ block = cblock;
+ length = clength;
+ }else
+ wrconform(cd, 0, &block, &length);
+
+ if(mk9660)
+{
+ idumproot = iroot;
+ jdumproot = jroot;
+ }
+ if(length) {
+ /* The ISO9660 name will get turned into uppercase when written. */
+ if((iconform = walkdirec(&idumproot, "_conform.map")) == nil)
+ iconform = adddirec(&idumproot, "_conform.map", &dir);
+ jconform = nil;
+ if(cd->flags & CDjoliet) {
+ if((jconform = walkdirec(&jdumproot, "_conform.map")) == nil)
+ jconform = adddirec(&jdumproot, "_conform.map", &dir);
+ }
+ iconform->block = block;
+ iconform->length = length;
+ if(cd->flags & CDjoliet) {
+ jconform->block = block;
+ jconform->length = length;
+ }
+ }
+ if(mk9660) {
+ iroot = idumproot;
+ jroot = jdumproot;
+ }
+ }
+
+ if(mk9660){
+ /*
+ * Patch in root directories.
+ */
+ setroot(cd, cd->iso9660pvd, iroot.block, iroot.length);
+ setvolsize(cd, cd->iso9660pvd, (vlong)cd->nextblock * Blocksize);
+ if(cd->flags & CDjoliet){
+ setroot(cd, cd->jolietsvd, jroot.block, jroot.length);
+ setvolsize(cd, cd->jolietsvd,
+ (vlong)cd->nextblock * Blocksize);
+ }
+ }else{
+ /*
+ * Write dump tree at end. We assume the name characters
+ * are all conforming, so everything is already sorted properly.
+ */
+ convertnames(&idumproot, (info.flags & CDconform) ? (void *) struprcpy : (void *) strcpy);
+ if(cd->nulldump) {
+ r = walkdirec(&idumproot, dumpname);
+ assert(r != nil);
+ copybutname(r, &iroot);
+ }
+ if(cd->flags & CDjoliet) {
+ convertnames(&jdumproot, (void *) strcpy);
+ if(cd->nulldump) {
+ r = walkdirec(&jdumproot, dumpname);
+ assert(r != nil);
+ copybutname(r, &jroot);
+ }
+ }
+
+ writedumpdirs(cd, &idumproot, Cputisodir);
+ if(cd->flags & CDjoliet)
+ writedumpdirs(cd, &jdumproot, Cputjolietdir);
+
+ /*
+ * Patch in new root directory entry.
+ */
+ setroot(cd, cd->iso9660pvd, idumproot.block, idumproot.length);
+ setvolsize(cd, cd->iso9660pvd, (vlong)cd->nextblock * Blocksize);
+ if(cd->flags & CDjoliet){
+ setroot(cd, cd->jolietsvd, jdumproot.block, jdumproot.length);
+ setvolsize(cd, cd->jolietsvd,
+ (vlong)cd->nextblock * Blocksize);
+ }
+ }
+ writepathtables(cd);
+
+ if(!mk9660){
+ /*
+ * If we've gotten too big, truncate back to what we started with,
+ * fix up the cd, and exit with a non-zero status.
+ */
+ Cwflush(cd);
+ if(cd->nulldump && maxsize && Cwoffset(cd) > maxsize){
+ fprint(2, "too big; writing old tree back\n");
+ status = "cd too big; aborted";
+
+ rmdumpdir(&idumproot, dumpname);
+ rmdumpdir(&jdumproot, dumpname);
+
+ cd->nextblock = cd->nulldump+1;
+ cd->nulldump = 0;
+ Cwseek(cd, (vlong)cd->nextblock * Blocksize);
+ goto Dofix;
+ }
+
+ /*
+ * Write old null header block; this commits all our changes.
+ */
+ if(cd->nulldump){
+ Cwseek(cd, (vlong)cd->nulldump * Blocksize);
+ sprint(buf, "plan 9 dump cd\n");
+ sprint(buf+strlen(buf), "%s %lud %lud %lud %llud %lud %lud",
+ dumpname, now, newnull, cblock, clength,
+ iroot.block, iroot.length);
+ if(cd->flags & CDjoliet)
+ sprint(buf+strlen(buf), " %lud %lud",
+ jroot.block, jroot.length);
+ strcat(buf, "\n");
+ Cwrite(cd, buf, strlen(buf));
+ Cpadblock(cd);
+ Cwflush(cd);
+ }
+ }
+ fdtruncate(cd->fd, (vlong)cd->nextblock * Blocksize);
+ exits(status);
+}
+
+static void
+addprotofile(char *new, char *old, Dir *d, void *a)
+{
+ char *name, *p;
+ Direc *direc;
+ XDir xd;
+
+ dirtoxdir(&xd, d);
+ name = nil;
+ if(docolon && strchr(new, ':')) {
+ name = emalloc(strlen(new)+1);
+ strcpy(name, new);
+ while((p=strchr(name, ':')))
+ *p=' ';
+ new = name;
+ }
+ if((direc = adddirec((Direc*)a, new, &xd))) {
+ direc->srcfile = atom(old);
+
+ // BUG: abstract, biblio, notice
+ }
+ if(name)
+ free(name);
+}
diff --git a/sys/src/cmd/disk/9660/ichar.c b/sys/src/cmd/disk/9660/ichar.c
new file mode 100755
index 000000000..35b0c0564
--- /dev/null
+++ b/sys/src/cmd/disk/9660/ichar.c
@@ -0,0 +1,206 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <ctype.h>
+
+#include "iso9660.h"
+
+/*
+ * ISO 9660 file names must be uppercase, digits, or underscore.
+ * We use lowercase, digits, and underscore, translating lower to upper
+ * in mkisostring, and upper to lower in isostring.
+ * Files with uppercase letters in their names are thus nonconforming.
+ * Conforming files also must have a basename
+ * at most 8 letters and at most one suffix of at most 3 letters.
+ */
+char*
+isostring(uchar *buf, int len)
+{
+ char *p, *q;
+
+ p = emalloc(len+1);
+ memmove(p, buf, len);
+ p[len] = '\0';
+ while(len > 0 && p[len-1] == ' ')
+ p[--len] = '\0';
+ for(q=p; *q; q++)
+ *q = tolower(*q);
+
+ q = atom(p);
+ free(p);
+ return q;
+}
+
+int
+isisofrog(char c)
+{
+ if(c >= '0' && c <= '9')
+ return 0;
+ if(c >= 'a' && c <= 'z')
+ return 0;
+ if(c == '_')
+ return 0;
+
+ return 1;
+}
+
+int
+isbadiso9660(char *s)
+{
+ char *p, *q;
+ int i;
+
+ if((p = strchr(s, '.')) != nil) {
+ if(p-s > 8)
+ return 1;
+ for(q=s; q<p; q++)
+ if(isisofrog(*q))
+ return 1;
+ if(strlen(p+1) > 3)
+ return 1;
+ for(q=p+1; *q; q++)
+ if(isisofrog(*q))
+ return 1;
+ } else {
+ if(strlen(s) > 8)
+ return 1;
+ for(q=s; *q; q++)
+ if(isisofrog(*q))
+ return 1;
+
+ /*
+ * we rename files of the form [FD]dddddd
+ * so they don't interfere with us.
+ */
+ if(strlen(s) == 7 && (s[0] == 'D' || s[0] == 'F')) {
+ for(i=1; i<7; i++)
+ if(s[i] < '0' || s[i] > '9')
+ break;
+ if(i == 7)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * ISO9660 name comparison
+ *
+ * The standard algorithm is as follows:
+ * Take the filenames without extensions, pad the shorter with 0x20s (spaces),
+ * and do strcmp. If they are equal, go on.
+ * Take the extensions, pad the shorter with 0x20s (spaces),
+ * and do strcmp. If they are equal, go on.
+ * Compare the version numbers.
+ *
+ * Since Plan 9 names are not allowed to contain characters 0x00-0x1F,
+ * the padded comparisons are equivalent to using strcmp directly.
+ * We still need to handle the base and extension differently,
+ * so that .foo sorts before !foo.foo.
+ */
+int
+isocmp(const void *va, const void *vb)
+{
+ int i;
+ char s1[32], s2[32], *b1, *b2, *e1, *e2;
+ const Direc *a, *b;
+
+ a = va;
+ b = vb;
+
+ strecpy(s1, s1+sizeof s1, a->confname);
+ b1 = s1;
+ strecpy(s2, s2+sizeof s2, b->confname);
+ b2 = s2;
+ if((e1 = strchr(b1, '.')) != nil)
+ *e1++ = '\0';
+ else
+ e1 = "";
+ if((e2 = strchr(b2, '.')) != nil)
+ *e2++ = '\0';
+ else
+ e2 = "";
+
+ if((i = strcmp(b1, b2)) != 0)
+ return i;
+
+ return strcmp(e1, e2);
+}
+
+static char*
+mkisostring(char *isobuf, int n, char *s)
+{
+ char *p, *q, *eq;
+
+ eq = isobuf+n;
+ for(p=s, q=isobuf; *p && q < eq; p++)
+ if('a' <= *p && *p <= 'z')
+ *q++ = *p+'A'-'a';
+ else
+ *q++ = *p;
+
+ while(q < eq)
+ *q++ = ' ';
+
+ return isobuf;
+}
+
+void
+Cputisopvd(Cdimg *cd, Cdinfo info)
+{
+ char buf[130];
+
+ Cputc(cd, 1); /* primary volume descriptor */
+ Cputs(cd, "CD001", 5); /* standard identifier */
+ Cputc(cd, 1); /* volume descriptor version */
+ Cputc(cd, 0); /* unused */
+
+ assert(~info.flags & (CDplan9|CDrockridge));
+
+ /* system identifier */
+ strcpy(buf, "");
+ if(info.flags & CDplan9)
+ strcat(buf, "plan 9 ");
+ if(info.flags & CDrockridge)
+ strcat(buf, "rrip ");
+ if(info.flags & CDbootable)
+ strcat(buf, "boot ");
+ if(info.flags & CDconform)
+ strcat(buf, "iso9660");
+ else
+ strcat(buf, "utf8");
+
+ struprcpy(buf, buf);
+ Cputs(cd, buf, 32);
+
+ Cputs(cd, mkisostring(buf, 32, info.volumename), 32); /* volume identifier */
+
+ Crepeat(cd, 0, 8); /* unused */
+ Cputn(cd, 0, 4); /* volume space size */
+ Crepeat(cd, 0, 32); /* unused */
+ Cputn(cd, 1, 2); /* volume set size */
+ Cputn(cd, 1, 2); /* volume sequence number */
+ Cputn(cd, Blocksize, 2); /* logical block size */
+ Cputn(cd, 0, 4); /* path table size */
+ Cputnl(cd, 0, 4); /* location of Lpath */
+ Cputnl(cd, 0, 4); /* location of optional Lpath */
+ Cputnm(cd, 0, 4); /* location of Mpath */
+ Cputnm(cd, 0, 4); /* location of optional Mpath */
+ Cputisodir(cd, nil, DTroot, 1, Cwoffset(cd)); /* root directory */
+
+ Cputs(cd, mkisostring(buf, 128, info.volumeset), 128); /* volume set identifier */
+ Cputs(cd, mkisostring(buf, 128, info.publisher), 128); /* publisher identifier */
+ Cputs(cd, mkisostring(buf, 128, info.preparer), 128); /* data preparer identifier */
+ Cputs(cd, mkisostring(buf, 128, info.application), 128); /* application identifier */
+
+ Cputs(cd, "", 37); /* copyright notice */
+ Cputs(cd, "", 37); /* abstract */
+ Cputs(cd, "", 37); /* bibliographic file */
+ Cputdate1(cd, now); /* volume creation date */
+ Cputdate1(cd, now); /* volume modification date */
+ Cputdate1(cd, 0); /* volume expiration date */
+ Cputdate1(cd, 0); /* volume effective date */
+ Cputc(cd, 1); /* file structure version */
+ Cpadblock(cd);
+}
diff --git a/sys/src/cmd/disk/9660/iso9660.h b/sys/src/cmd/disk/9660/iso9660.h
new file mode 100755
index 000000000..edd0c7df2
--- /dev/null
+++ b/sys/src/cmd/disk/9660/iso9660.h
@@ -0,0 +1,426 @@
+/*
+ * iso9660.h
+ *
+ * Routines and data structures to support reading and writing ISO 9660 CD images.
+ * See the ISO 9660 or ECMA 119 standards.
+ *
+ * Also supports Rock Ridge extensions for long file names and Unix stuff.
+ * Also supports Microsoft's Joliet extensions for Unicode and long file names.
+ * Also supports El Torito bootable CD spec.
+ */
+
+typedef struct Cdimg Cdimg;
+typedef struct Cdinfo Cdinfo;
+typedef struct Conform Conform;
+typedef struct Direc Direc;
+typedef struct Dumproot Dumproot;
+typedef struct Voldesc Voldesc;
+typedef struct XDir XDir;
+
+#ifndef CHLINK
+#define CHLINK 0
+#endif
+
+struct XDir {
+ char *name;
+ char *uid;
+ char *gid;
+ char *symlink;
+ ulong uidno; /* Numeric uid */
+ ulong gidno; /* Numeric gid */
+
+ ulong mode;
+ ulong atime;
+ ulong mtime;
+ ulong ctime;
+
+ vlong length;
+};
+
+/*
+ * A directory entry in a ISO9660 tree.
+ * The extra data (uid, etc.) here is put into the system use areas.
+ */
+struct Direc {
+ char *name; /* real name */
+ char *confname; /* conformant name */
+ char *srcfile; /* file to copy onto the image */
+
+ ulong block;
+ ulong length;
+ int flags;
+
+ char *uid;
+ char *gid;
+ char *symlink;
+ ulong mode;
+ long atime;
+ long ctime;
+ long mtime;
+
+ ulong uidno;
+ ulong gidno;
+
+ Direc *child;
+ int nchild;
+};
+enum { /* Direc flags */
+ Dbadname = 1<<0, /* Non-conformant name */
+};
+
+/*
+ * Data found in a volume descriptor.
+ */
+struct Voldesc {
+ char *systemid;
+ char *volumeset;
+ char *publisher;
+ char *preparer;
+ char *application;
+
+ /* file names for various parameters */
+ char *abstract;
+ char *biblio;
+ char *notice;
+
+ /* path table */
+ ulong pathsize;
+ ulong lpathloc;
+ ulong mpathloc;
+
+ /* root of file tree */
+ Direc root;
+};
+
+/*
+ * An ISO9660 CD image. Various parameters are kept in memory but the
+ * real image file is opened for reading and writing on fd.
+ *
+ * The bio buffers brd and bwr moderate reading and writing to the image.
+ * The routines we use are careful to flush one before or after using the other,
+ * as necessary.
+ */
+struct Cdimg {
+ char *file;
+ int fd;
+ ulong dumpblock;
+ ulong nextblock;
+ ulong iso9660pvd;
+ ulong jolietsvd;
+ ulong pathblock;
+ uvlong rrcontin; /* rock ridge continuation offset */
+ ulong nulldump; /* next dump block */
+ ulong nconform; /* number of conform entries written already */
+ uvlong bootcatptr;
+ ulong bootcatblock;
+ uvlong bootimageptr;
+ Direc *bootdirec;
+ char *bootimage;
+
+ Biobuf brd;
+ Biobuf bwr;
+
+ int flags;
+
+ Voldesc iso;
+ Voldesc joliet;
+};
+enum { /* Cdimg->flags, Cdinfo->flags */
+ CDjoliet = 1<<0,
+ CDplan9 = 1<<1,
+ CDconform = 1<<2,
+ CDrockridge = 1<<3,
+ CDnew = 1<<4,
+ CDdump = 1<<5,
+ CDbootable = 1<<6,
+ CDbootnoemu = 1<<7,
+};
+
+typedef struct Tx Tx;
+struct Tx {
+ char *bad; /* atoms */
+ char *good;
+};
+
+struct Conform {
+ Tx *t;
+ int nt; /* delta = 32 */
+};
+
+struct Cdinfo {
+ int flags;
+
+ char *volumename;
+
+ char *volumeset;
+ char *publisher;
+ char *preparer;
+ char *application;
+ char *bootimage;
+};
+
+//enum {
+// Blocklen = 2048, /* unused */
+//};
+
+/*
+ * This is a doubly binary tree.
+ * We have a tree keyed on the MD5 values
+ * as well as a tree keyed on the block numbers.
+ */
+typedef struct Dump Dump;
+typedef struct Dumpdir Dumpdir;
+
+struct Dump {
+ Cdimg *cd;
+ Dumpdir *md5root;
+ Dumpdir *blockroot;
+};
+
+struct Dumpdir {
+ char *name;
+ uchar md5[MD5dlen];
+ ulong block;
+ ulong length;
+ Dumpdir *md5left;
+ Dumpdir *md5right;
+ Dumpdir *blockleft;
+ Dumpdir *blockright;
+};
+
+struct Dumproot {
+ char *name;
+ int nkid;
+ Dumproot *kid;
+ Direc root;
+ Direc jroot;
+};
+
+/*
+ * ISO9660 on-CD structures.
+ */
+typedef struct Cdir Cdir;
+typedef struct Cpath Cpath;
+typedef struct Cvoldesc Cvoldesc;
+
+/* a volume descriptor block */
+struct Cvoldesc {
+ uchar magic[8]; /* 0x01, "CD001", 0x01, 0x00 */
+ uchar systemid[32]; /* system identifier */
+ uchar volumeid[32]; /* volume identifier */
+ uchar unused[8]; /* character set in secondary desc */
+ uchar volsize[8]; /* volume size */
+ uchar charset[32];
+ uchar volsetsize[4]; /* volume set size = 1 */
+ uchar volseqnum[4]; /* volume sequence number = 1 */
+ uchar blocksize[4]; /* logical block size */
+ uchar pathsize[8]; /* path table size */
+ uchar lpathloc[4]; /* Lpath */
+ uchar olpathloc[4]; /* optional Lpath */
+ uchar mpathloc[4]; /* Mpath */
+ uchar ompathloc[4]; /* optional Mpath */
+ uchar rootdir[34]; /* directory entry for root */
+ uchar volumeset[128]; /* volume set identifier */
+ uchar publisher[128];
+ uchar preparer[128]; /* data preparer identifier */
+ uchar application[128]; /* application identifier */
+ uchar notice[37]; /* copyright notice file */
+ uchar abstract[37]; /* abstract file */
+ uchar biblio[37]; /* bibliographic file */
+ uchar cdate[17]; /* creation date */
+ uchar mdate[17]; /* modification date */
+ uchar xdate[17]; /* expiration date */
+ uchar edate[17]; /* effective date */
+ uchar fsvers; /* file system version = 1 */
+};
+
+/* a directory entry */
+struct Cdir {
+ uchar len;
+ uchar xlen;
+ uchar dloc[8];
+ uchar dlen[8];
+ uchar date[7];
+ uchar flags;
+ uchar unitsize;
+ uchar gapsize;
+ uchar volseqnum[4];
+ uchar namelen;
+ uchar name[1]; /* chumminess */
+};
+
+/* a path table entry */
+struct Cpath {
+ uchar namelen;
+ uchar xlen;
+ uchar dloc[4];
+ uchar parent[2];
+ uchar name[1]; /* chumminess */
+};
+
+enum { /* Rockridge flags */
+ RR_PX = 1<<0,
+ RR_PN = 1<<1,
+ RR_SL = 1<<2,
+ RR_NM = 1<<3,
+ RR_CL = 1<<4,
+ RR_PL = 1<<5,
+ RR_RE = 1<<6,
+ RR_TF = 1<<7,
+};
+
+enum { /* CputrripTF type argument */
+ TFcreation = 1<<0,
+ TFmodify = 1<<1,
+ TFaccess = 1<<2,
+ TFattributes = 1<<3,
+ TFbackup = 1<<4,
+ TFexpiration = 1<<5,
+ TFeffective = 1<<6,
+ TFlongform = 1<<7,
+};
+
+enum { /* CputrripNM flag types */
+ NMcontinue = 1<<0,
+ NMcurrent = 1<<1,
+ NMparent = 1<<2,
+ NMroot = 1<<3,
+ NMvolroot = 1<<4,
+ NMhost = 1<<5,
+};
+
+/* boot.c */
+void Cputbootvol(Cdimg*);
+void Cputbootcat(Cdimg*);
+void Cupdatebootvol(Cdimg*);
+void Cupdatebootcat(Cdimg*);
+void findbootimage(Cdimg*, Direc*);
+
+/* cdrdwr.c */
+Cdimg *createcd(char*, Cdinfo);
+Cdimg *opencd(char*, Cdinfo);
+void Creadblock(Cdimg*, void*, ulong, ulong);
+ulong big(void*, int);
+ulong little(void*, int);
+int parsedir(Cdimg*, Direc*, uchar*, int, char *(*)(uchar*, int));
+void setroot(Cdimg*, ulong, ulong, ulong);
+void setvolsize(Cdimg*, ulong, ulong);
+void setpathtable(Cdimg*, ulong, ulong, ulong, ulong);
+void Cputc(Cdimg*, int);
+void Cputnl(Cdimg*, ulong, int);
+void Cputnm(Cdimg*, ulong, int);
+void Cputn(Cdimg*, long, int);
+void Crepeat(Cdimg*, int, int);
+void Cputs(Cdimg*, char*, int);
+void Cwrite(Cdimg*, void*, int);
+void Cputr(Cdimg*, Rune);
+void Crepeatr(Cdimg*, Rune, int);
+void Cputrs(Cdimg*, Rune*, int);
+void Cputrscvt(Cdimg*, char*, int);
+void Cpadblock(Cdimg*);
+void Cputdate(Cdimg*, ulong);
+void Cputdate1(Cdimg*, ulong);
+void Cread(Cdimg*, void*, int);
+void Cwflush(Cdimg*);
+void Cwseek(Cdimg*, vlong);
+uvlong Cwoffset(Cdimg*);
+uvlong Croffset(Cdimg*);
+int Cgetc(Cdimg*);
+void Crseek(Cdimg*, vlong);
+char *Crdline(Cdimg*, int);
+int Clinelen(Cdimg*);
+
+/* conform.c */
+void rdconform(Cdimg*);
+char *conform(char*, int);
+void wrconform(Cdimg*, int, ulong*, uvlong*);
+
+/* direc.c */
+void mkdirec(Direc*, XDir*);
+Direc *walkdirec(Direc*, char*);
+Direc *adddirec(Direc*, char*, XDir*);
+void copydirec(Direc*, Direc*);
+void checknames(Direc*, int (*)(char*));
+void convertnames(Direc*, char* (*)(char*, char*));
+void dsort(Direc*, int (*)(const void*, const void*));
+void setparents(Direc*);
+
+/* dump.c */
+ulong Cputdumpblock(Cdimg*);
+int hasdump(Cdimg*);
+Dump *dumpcd(Cdimg*, Direc*);
+Dumpdir *lookupmd5(Dump*, uchar*);
+void insertmd5(Dump*, char*, uchar*, ulong, ulong);
+
+Direc readdumpdirs(Cdimg*, XDir*, char*(*)(uchar*,int));
+char *adddumpdir(Direc*, ulong, XDir*);
+void copybutname(Direc*, Direc*);
+
+void readkids(Cdimg*, Direc*, char*(*)(uchar*,int));
+void freekids(Direc*);
+void readdumpconform(Cdimg*);
+void rmdumpdir(Direc*, char*);
+
+/* ichar.c */
+char *isostring(uchar*, int);
+int isbadiso9660(char*);
+int isocmp(const void*, const void*);
+int isisofrog(char);
+void Cputisopvd(Cdimg*, Cdinfo);
+
+/* jchar.c */
+char *jolietstring(uchar*, int);
+int isbadjoliet(char*);
+int jolietcmp(const void*, const void*);
+int isjolietfrog(Rune);
+void Cputjolietsvd(Cdimg*, Cdinfo);
+
+/* path.c */
+void writepathtables(Cdimg*);
+
+/* util.c */
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+char *atom(char*);
+char *struprcpy(char*, char*);
+int chat(char*, ...);
+
+/* unix.c, plan9.c */
+void dirtoxdir(XDir*, Dir*);
+void fdtruncate(int, ulong);
+long uidno(char*);
+long gidno(char*);
+
+/* rune.c */
+Rune *strtorune(Rune*, char*);
+Rune *runechr(Rune*, Rune);
+int runecmp(Rune*, Rune*);
+
+/* sysuse.c */
+int Cputsysuse(Cdimg*, Direc*, int, int, int);
+
+/* write.c */
+void writefiles(Dump*, Cdimg*, Direc*);
+void writedirs(Cdimg*, Direc*, int(*)(Cdimg*, Direc*, int, int, int));
+void writedumpdirs(Cdimg*, Direc*, int(*)(Cdimg*, Direc*, int, int, int));
+int Cputisodir(Cdimg*, Direc*, int, int, int);
+int Cputjolietdir(Cdimg*, Direc*, int, int, int);
+void Cputendvd(Cdimg*);
+
+enum {
+ Blocksize = 2048,
+ Ndirblock = 16, /* directory blocks allocated at once */
+
+ DTdot = 0,
+ DTdotdot,
+ DTiden,
+ DTroot,
+ DTrootdot,
+};
+
+extern ulong now;
+extern Conform *map;
+extern int chatty;
+extern int docolon;
+extern int mk9660;
+extern int blocksize;
diff --git a/sys/src/cmd/disk/9660/jchar.c b/sys/src/cmd/disk/9660/jchar.c
new file mode 100755
index 000000000..c49da6351
--- /dev/null
+++ b/sys/src/cmd/disk/9660/jchar.c
@@ -0,0 +1,138 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+char*
+jolietstring(uchar *buf, int len)
+{
+ char *p, *q;
+ int i;
+ Rune *rp;
+
+ rp = emalloc(sizeof(Rune)*(len/2+1));
+ p = emalloc(UTFmax*(len/2+1));
+
+ for(i=0; i<len/2; i++)
+ rp[i] = (buf[2*i]<<8) | buf[2*i+1];
+ rp[i] = (Rune)'\0';
+
+ snprint(p, UTFmax*(len/2+1), "%S", rp);
+ q = atom(p);
+ free(p);
+ return q;
+}
+
+/*
+ * Joliet name validity check
+ *
+ * Joliet names have length at most 128 bytes (64 runes),
+ * and cannot contain '*', '/', ':', ';', '?', or '\'.
+ */
+int
+isjolietfrog(Rune r)
+{
+ return r==L'*' || r==L'/' || r==L':'
+ || r==';' || r=='?' || r=='\\';
+}
+
+int
+isbadjoliet(char *s)
+{
+ Rune r[256], *p;
+
+ if(utflen(s) > 64)
+ return 1;
+ strtorune(r, s);
+ for(p=r; *p; p++)
+ if(isjolietfrog(*p))
+ return 1;
+ return 0;
+}
+
+/*
+ * Joliet name comparison
+ *
+ * The standard algorithm is the ISO9660 algorithm but
+ * on the encoded Runes. Runes are encoded in big endian
+ * format, so we can just use runecmp.
+ *
+ * Padding is with zeros, but that still doesn't affect us.
+ */
+
+static Rune emptystring[] = { (Rune)0 };
+int
+jolietcmp(const void *va, const void *vb)
+{
+ int i;
+ Rune s1[256], s2[256], *b1, *b2, *e1, *e2; /*BUG*/
+ const Direc *a, *b;
+
+ a = va;
+ b = vb;
+
+ b1 = strtorune(s1, a->confname);
+ b2 = strtorune(s2, b->confname);
+ if((e1 = runechr(b1, (Rune)'.')) != nil)
+ *e1++ = '\0';
+ else
+ e1 = emptystring;
+
+ if((e2 = runechr(b2, (Rune)'.')) != nil)
+ *e2++ = '\0';
+ else
+ e2 = emptystring;
+
+ if((i = runecmp(b1, b2)) != 0)
+ return i;
+
+ return runecmp(e1, e2);
+}
+
+/*
+ * Write a Joliet secondary volume descriptor.
+ */
+void
+Cputjolietsvd(Cdimg *cd, Cdinfo info)
+{
+ Cputc(cd, 2); /* secondary volume descriptor */
+ Cputs(cd, "CD001", 5); /* standard identifier */
+ Cputc(cd, 1); /* volume descriptor version */
+ Cputc(cd, 0); /* unused */
+
+ Cputrscvt(cd, "Joliet Plan 9", 32); /* system identifier */
+ Cputrscvt(cd, info.volumename, 32); /* volume identifier */
+
+ Crepeat(cd, 0, 8); /* unused */
+ Cputn(cd, 0, 4); /* volume space size */
+ Cputc(cd, 0x25); /* escape sequences: UCS-2 Level 2 */
+ Cputc(cd, 0x2F);
+ Cputc(cd, 0x43);
+
+ Crepeat(cd, 0, 29);
+ Cputn(cd, 1, 2); /* volume set size */
+ Cputn(cd, 1, 2); /* volume sequence number */
+ Cputn(cd, Blocksize, 2); /* logical block size */
+ Cputn(cd, 0, 4); /* path table size */
+ Cputnl(cd, 0, 4); /* location of Lpath */
+ Cputnl(cd, 0, 4); /* location of optional Lpath */
+ Cputnm(cd, 0, 4); /* location of Mpath */
+ Cputnm(cd, 0, 4); /* location of optional Mpath */
+ Cputjolietdir(cd, nil, DTroot, 1, Cwoffset(cd)); /* root directory */
+ Cputrscvt(cd, info.volumeset, 128); /* volume set identifier */
+ Cputrscvt(cd, info.publisher, 128); /* publisher identifier */
+ Cputrscvt(cd, info.preparer, 128); /* data preparer identifier */
+ Cputrscvt(cd, info.application, 128); /* application identifier */
+ Cputrscvt(cd, "", 37); /* copyright notice */
+ Cputrscvt(cd, "", 37); /* abstract */
+ Cputrscvt(cd, "", 37); /* bibliographic file */
+ Cputdate1(cd, now); /* volume creation date */
+ Cputdate1(cd, now); /* volume modification date */
+ Cputdate1(cd, 0); /* volume expiration date */
+ Cputdate1(cd, 0); /* volume effective date */
+ Cputc(cd, 1); /* file structure version */
+ Cpadblock(cd);
+}
+
diff --git a/sys/src/cmd/disk/9660/mk9660.rc b/sys/src/cmd/disk/9660/mk9660.rc
new file mode 100755
index 000000000..1824f8e74
--- /dev/null
+++ b/sys/src/cmd/disk/9660/mk9660.rc
@@ -0,0 +1,5 @@
+#!/bin/rc
+
+# the master copy of this file is /sys/src/cmd/disk/9660/mk9660.rc
+# do NOT edit the copies in /*/bin/disk.
+exec disk/dump9660 -M $*
diff --git a/sys/src/cmd/disk/9660/mkfile b/sys/src/cmd/disk/9660/mkfile
new file mode 100755
index 000000000..e7c11fecd
--- /dev/null
+++ b/sys/src/cmd/disk/9660/mkfile
@@ -0,0 +1,43 @@
+</$objtype/mkfile
+
+TARG=dump9660 mk9660
+
+OFILES=
+
+DFILES=\
+ boot.$O\
+ cdrdwr.$O\
+ conform.$O\
+ direc.$O\
+ dump.$O\
+ dump9660.$O\
+ ichar.$O\
+ jchar.$O\
+ path.$O\
+ plan9.$O\
+ rune.$O\
+ sysuse.$O\
+ util.$O\
+ write.$O\
+
+HFILES=iso9660.h
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${DFILES:%.$O=%.c}\
+ dump9660.c\
+ mk9660.rc\
+ ${TARG:%=/386/bin/disk/%}\
+
+BIN=/$objtype/bin/disk
+</sys/src/cmd/mkmany
+
+$O.dump9660: $DFILES
+
+mk9660.$O:V:
+ # nothing
+
+$O.mk9660: mk9660.rc
+ cp mk9660.rc $target
diff --git a/sys/src/cmd/disk/9660/path.c b/sys/src/cmd/disk/9660/path.c
new file mode 100755
index 000000000..5523afd7a
--- /dev/null
+++ b/sys/src/cmd/disk/9660/path.c
@@ -0,0 +1,159 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+/*
+ * Add the requisite path tables to the CD image.
+ * They get put on the end once everything else is done.
+ * We use the path table itself as a queue in the breadth-first
+ * traversal of the tree.
+ *
+ * The only problem with this is that the path table does not
+ * store the lengths of the directories. So we keep an explicit
+ * map in an array in memory.
+ */
+
+enum {
+ Big,
+ Little
+};
+
+static void
+Crdpath(Cdimg *cd, Cpath *p)
+{
+ p->namelen = Cgetc(cd);
+ if(p->namelen == 0) {
+ Crseek(cd, (Croffset(cd)+Blocksize-1)/Blocksize * Blocksize);
+ p->namelen = Cgetc(cd);
+ assert(p->namelen != 0);
+ }
+
+ p->xlen = Cgetc(cd);
+ assert(p->xlen == 0); /* sanity, might not be true if we start using the extended fields */
+
+ Cread(cd, p->dloc, 4);
+ Cread(cd, p->parent, 2);
+ p->name[0] = '\0';
+ Crseek(cd, Croffset(cd)+p->namelen+p->xlen+(p->namelen&1)); /* skip name, ext data */
+}
+
+static void
+writepath(Cdimg *cd, Cdir *c, int parent, int size)
+{
+/*
+ DO NOT UNCOMMENT THIS CODE.
+ This commented-out code is here only so that no one comes
+ along and adds it later.
+
+ The ISO 9660 spec is silent about whether path table entries
+ need to be padded so that they never cross block boundaries.
+ It would be reasonable to assume that they are like every other
+ data structure in the bloody spec; this code pads them out.
+
+ Empirically, though, they're NOT padded. Windows NT and
+ derivatives are the only known current operating systems
+ that actually read these things.
+
+ int l;
+
+ l = 1+1+4+2+c->namelen;
+ if(Cwoffset(cd)/Blocksize != (Cwoffset(cd)+l)/Blocksize)
+ Cpadblock(cd);
+*/
+ Cputc(cd, c->namelen);
+ Cputc(cd, 0);
+ Cwrite(cd, c->dloc + (size==Little ? 0 : 4), 4);
+ (size==Little ? Cputnl : Cputnm)(cd, parent, 2);
+ Cwrite(cd, c->name, c->namelen);
+ if(c->namelen & 1)
+ Cputc(cd, 0);
+}
+
+static ulong*
+addlength(ulong *a, ulong x, int n)
+{
+ if(n%128==0)
+ a = erealloc(a, (n+128)*sizeof a[0]);
+ a[n] = x;
+ return a;
+}
+
+static ulong
+writepathtable(Cdimg *cd, ulong vdblock, int size)
+{
+ int rp, wp;
+ uchar buf[Blocksize];
+ ulong bk, i, *len, n;
+ uvlong start, end, rdoff;
+ Cdir *c;
+ Cpath p;
+
+ Creadblock(cd, buf, vdblock, Blocksize);
+ c = (Cdir*)(buf + offsetof(Cvoldesc, rootdir[0]));
+
+ rp = 0;
+ wp = 0;
+ len = nil;
+ start = (vlong)cd->nextblock * Blocksize;
+ Cwseek(cd, start);
+ Crseek(cd, start);
+ writepath(cd, c, 1, size);
+ len = addlength(len, little(c->dlen, 4), wp);
+ wp++;
+
+ while(rp < wp) {
+ Crdpath(cd, &p);
+ n = (len[rp]+Blocksize-1)/Blocksize;
+ rp++;
+ bk = (size==Big ? big : little)(p.dloc, 4);
+ rdoff = Croffset(cd);
+ for(i=0; i<n; i++) {
+ Creadblock(cd, buf, bk+i, Blocksize);
+ c = (Cdir*)buf;
+ if(i != 0 && c->namelen == 1 && c->name[0] == '\0')
+ break; /* hit another directory; stop */
+ while(c->len && c->namelen &&
+ (uchar*)c + c->len < buf + Blocksize) {
+ if(c->flags & 0x02 &&
+ (c->namelen > 1 || c->name[0] > '\001')) {
+ /* directory */
+ writepath(cd, c, rp, size);
+ len = addlength(len, little(c->dlen, 4), wp);
+ wp++;
+ }
+ c = (Cdir*)((uchar*)c+c->len);
+ }
+ }
+ Crseek(cd, rdoff);
+ }
+ end = Cwoffset(cd);
+ Cpadblock(cd);
+ return end-start;
+}
+
+
+static void
+writepathtablepair(Cdimg *cd, ulong vdblock)
+{
+ ulong bloc, lloc, sz, sz2;
+
+ lloc = cd->nextblock;
+ sz = writepathtable(cd, vdblock, Little);
+ bloc = cd->nextblock;
+ sz2 = writepathtable(cd, vdblock, Big);
+ assert(sz == sz2);
+ setpathtable(cd, vdblock, sz, lloc, bloc);
+}
+
+void
+writepathtables(Cdimg *cd)
+{
+ cd->pathblock = cd->nextblock;
+
+ writepathtablepair(cd, cd->iso9660pvd);
+ if(cd->flags & CDjoliet)
+ writepathtablepair(cd, cd->jolietsvd);
+}
diff --git a/sys/src/cmd/disk/9660/plan9.c b/sys/src/cmd/disk/9660/plan9.c
new file mode 100755
index 000000000..25f04e921
--- /dev/null
+++ b/sys/src/cmd/disk/9660/plan9.c
@@ -0,0 +1,27 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <disk.h>
+#include "iso9660.h"
+
+void
+dirtoxdir(XDir *xd, Dir *d)
+{
+ xd->name = atom(d->name);
+ xd->uid = atom(d->uid);
+ xd->gid = atom(d->gid);
+ xd->uidno = 0;
+ xd->gidno = 0;
+ xd->mode = d->mode;
+ xd->atime = d->atime;
+ xd->mtime = d->mtime;
+ xd->ctime = 0;
+ xd->length = d->length;
+};
+
+void
+fdtruncate(int fd, ulong size)
+{
+ USED(fd, size);
+}
diff --git a/sys/src/cmd/disk/9660/rune.c b/sys/src/cmd/disk/9660/rune.c
new file mode 100755
index 000000000..3a436f4a3
--- /dev/null
+++ b/sys/src/cmd/disk/9660/rune.c
@@ -0,0 +1,39 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+Rune*
+strtorune(Rune *r, char *s)
+{
+ Rune *or;
+
+ if(s == nil)
+ return nil;
+
+ or = r;
+ while(*s)
+ s += chartorune(r++, s);
+ *r = L'\0';
+ return or;
+}
+
+Rune*
+runechr(Rune *s, Rune c)
+{
+ for(; *s; s++)
+ if(*s == c)
+ return s;
+ return nil;
+}
+
+int
+runecmp(Rune *s, Rune *t)
+{
+ while(*s && *t && *s == *t)
+ s++, t++;
+ return *s - *t;
+}
+
diff --git a/sys/src/cmd/disk/9660/sysuse.c b/sys/src/cmd/disk/9660/sysuse.c
new file mode 100755
index 000000000..b7732b3a6
--- /dev/null
+++ b/sys/src/cmd/disk/9660/sysuse.c
@@ -0,0 +1,615 @@
+/*
+ * To understand this code, see Rock Ridge Interchange Protocol
+ * standard 1.12 and System Use Sharing Protocol version 1.12
+ * (search for rrip112.ps and susp112.ps on the web).
+ *
+ * Even better, go read something else.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include "iso9660.h"
+
+static long mode(Direc*, int);
+static long nlink(Direc*);
+static ulong suspdirflags(Direc*, int);
+static ulong CputsuspCE(Cdimg *cd, vlong offset);
+static int CputsuspER(Cdimg*, int);
+static int CputsuspRR(Cdimg*, int, int);
+static int CputsuspSP(Cdimg*, int);
+//static int CputsuspST(Cdimg*, int);
+static int Cputrripname(Cdimg*, char*, int, char*, int);
+static int CputrripSL(Cdimg*, int, int, char*, int);
+static int CputrripPX(Cdimg*, Direc*, int, int);
+static int CputrripTF(Cdimg*, Direc*, int, int);
+
+/*
+ * Patch the length field in a CE record.
+ */
+static void
+setcelen(Cdimg *cd, vlong woffset, ulong len)
+{
+ vlong o;
+
+ o = Cwoffset(cd);
+ Cwseek(cd, woffset);
+ Cputn(cd, len, 4);
+ Cwseek(cd, o);
+}
+
+/*
+ * Rock Ridge data is put into little blockettes, which can be
+ * at most 256 bytes including a one-byte length. Some number
+ * of blockettes get packed together into a normal 2048-byte block.
+ * Blockettes cannot cross block boundaries.
+ *
+ * A Cbuf is a blockette buffer. Len contains
+ * the length of the buffer written so far, and we can
+ * write up to 254-28.
+ *
+ * We only have one active Cbuf at a time; cdimg.rrcontin is the byte
+ * offset of the beginning of that Cbuf.
+ *
+ * The blockette can be at most 255 bytes. The last 28
+ * will be (in the worst case) a CE record pointing at
+ * a new blockette. If we do write 255 bytes though,
+ * we'll try to pad it out to be even, and overflow.
+ * So the maximum is 254-28.
+ *
+ * Ceoffset contains the offset to be used with setcelen
+ * to patch the CE pointing at the Cbuf once we know how
+ * long the Cbuf is.
+ */
+typedef struct Cbuf Cbuf;
+struct Cbuf {
+ int len; /* written so far, of 254-28 */
+ uvlong ceoffset;
+};
+
+static int
+freespace(Cbuf *cp)
+{
+ return (254-28) - cp->len;
+}
+
+static Cbuf*
+ensurespace(Cdimg *cd, int n, Cbuf *co, Cbuf *cn, int dowrite)
+{
+ uvlong end;
+
+ if(co->len+n <= 254-28) {
+ co->len += n;
+ return co;
+ }
+
+ co->len += 28;
+ assert(co->len <= 254);
+
+ if(dowrite == 0) {
+ cn->len = n;
+ return cn;
+ }
+
+ /*
+ * the current blockette is full; update cd->rrcontin and then
+ * write a CE record to finish it. Unfortunately we need to
+ * figure out which block will be next before we write the CE.
+ */
+ end = Cwoffset(cd)+28;
+
+ /*
+ * if we're in a continuation blockette, update rrcontin.
+ * also, write our length into the field of the CE record
+ * that points at us.
+ */
+ if(cd->rrcontin+co->len == end) {
+ assert(cd->rrcontin != 0);
+ assert(co == cn);
+ cd->rrcontin += co->len;
+ setcelen(cd, co->ceoffset, co->len);
+ } else
+ assert(co != cn);
+
+ /*
+ * if the current continuation block can't fit another
+ * blockette, then start a new continuation block.
+ * rrcontin = 0 (mod Blocksize) means we just finished
+ * one, not that we've just started one.
+ */
+ if(cd->rrcontin%Blocksize == 0
+ || cd->rrcontin/Blocksize != (cd->rrcontin+256)/Blocksize) {
+ cd->rrcontin = (vlong)cd->nextblock * Blocksize;
+ cd->nextblock++;
+ }
+
+ cn->ceoffset = CputsuspCE(cd, cd->rrcontin);
+
+ assert(Cwoffset(cd) == end);
+
+ cn->len = n;
+ Cwseek(cd, cd->rrcontin);
+ assert(cd->rrcontin != 0);
+
+ return cn;
+}
+
+/*
+ * Put down the name, but we might need to break it
+ * into chunks so that each chunk fits in 254-28-5 bytes.
+ * What a crock.
+ *
+ * The new Plan 9 format uses strings of this form too,
+ * since they're already there.
+ */
+Cbuf*
+Cputstring(Cdimg *cd, Cbuf *cp, Cbuf *cn, char *nm, char *p, int flags, int dowrite)
+{
+ char buf[256], *q;
+ int free;
+
+ for(; p[0] != '\0'; p = q) {
+ cp = ensurespace(cd, 5+1, cp, cn, dowrite);
+ cp->len -= 5+1;
+ free = freespace(cp);
+ assert(5+1 <= free && free < 256);
+
+ strncpy(buf, p, free-5);
+ buf[free-5] = '\0';
+ q = p+strlen(buf);
+ p = buf;
+
+ ensurespace(cd, 5+strlen(p), cp, nil, dowrite); /* nil: better not use this. */
+ Cputrripname(cd, nm, flags | (q[0] ? NMcontinue : 0), p, dowrite);
+ }
+ return cp;
+}
+
+/*
+ * Write a Rock Ridge SUSP set of records for a directory entry.
+ */
+int
+Cputsysuse(Cdimg *cd, Direc *d, int dot, int dowrite, int initlen)
+{
+ char buf[256], buf0[256], *nextpath, *p, *path, *q;
+ int flags, free, m, what;
+ uvlong o;
+ Cbuf cn, co, *cp;
+
+ assert(cd != nil);
+ assert((initlen&1) == 0);
+
+ if(dot == DTroot)
+ return 0;
+
+ co.len = initlen;
+
+ o = Cwoffset(cd);
+
+ assert(dowrite==0 || Cwoffset(cd) == o+co.len-initlen);
+ cp = &co;
+
+ if (dot == DTrootdot) {
+ m = CputsuspSP(cd, 0);
+ cp = ensurespace(cd, m, cp, &cn, dowrite);
+ CputsuspSP(cd, dowrite);
+
+ m = CputsuspER(cd, 0);
+ cp = ensurespace(cd, m, cp, &cn, dowrite);
+ CputsuspER(cd, dowrite);
+ }
+
+ /*
+ * In a perfect world, we'd be able to omit the NM
+ * entries when our name was all lowercase and conformant,
+ * but OpenBSD insists on uppercasing (really, not lowercasing)
+ * the ISO9660 names.
+ */
+ what = RR_PX | RR_TF | RR_NM;
+ if(d != nil && (d->mode & CHLINK))
+ what |= RR_SL;
+
+ m = CputsuspRR(cd, what, 0);
+ cp = ensurespace(cd, m, cp, &cn, dowrite);
+ CputsuspRR(cd, what, dowrite);
+
+ if(what & RR_PX) {
+ m = CputrripPX(cd, d, dot, 0);
+ cp = ensurespace(cd, m, cp, &cn, dowrite);
+ CputrripPX(cd, d, dot, dowrite);
+ }
+
+ if(what & RR_NM) {
+ if(dot == DTiden)
+ p = d->name;
+ else if(dot == DTdotdot)
+ p = "..";
+ else
+ p = ".";
+
+ flags = suspdirflags(d, dot);
+ assert(dowrite==0 || cp != &co || Cwoffset(cd) == o+co.len-initlen);
+ cp = Cputstring(cd, cp, &cn, "NM", p, flags, dowrite);
+ }
+
+ /*
+ * Put down the symbolic link. This is even more of a crock.
+ * Not only are the individual elements potentially split,
+ * but the whole path itself can be split across SL blocks.
+ * To keep the code simple as possible (really), we write
+ * only one element per SL block, wasting 6 bytes per element.
+ */
+ if(what & RR_SL) {
+ for(path=d->symlink; path[0] != '\0'; path=nextpath) {
+ /* break off one component */
+ if((nextpath = strchr(path, '/')) == nil)
+ nextpath = path+strlen(path);
+ strncpy(buf0, path, nextpath-path);
+ buf0[nextpath-path] = '\0';
+ if(nextpath[0] == '/')
+ nextpath++;
+ p = buf0;
+
+ /* write the name, perhaps broken into pieces */
+ if(strcmp(p, "") == 0)
+ flags = NMroot;
+ else if(strcmp(p, ".") == 0)
+ flags = NMcurrent;
+ else if(strcmp(p, "..") == 0)
+ flags = NMparent;
+ else
+ flags = 0;
+
+ /* the do-while handles the empty string properly */
+ do {
+ /* must have room for at least 1 byte of name */
+ cp = ensurespace(cd, 7+1, cp, &cn, dowrite);
+ cp->len -= 7+1;
+ free = freespace(cp);
+ assert(7+1 <= free && free < 256);
+
+ strncpy(buf, p, free-7);
+ buf[free-7] = '\0';
+ q = p+strlen(buf);
+ p = buf;
+
+ /* nil: better not need to expand */
+ assert(7+strlen(p) <= free);
+ ensurespace(cd, 7+strlen(p), cp, nil, dowrite);
+ CputrripSL(cd, nextpath[0], flags | (q[0] ? NMcontinue : 0), p, dowrite);
+ p = q;
+ } while(p[0] != '\0');
+ }
+ }
+
+ assert(dowrite==0 || cp != &co || Cwoffset(cd) == o+co.len-initlen);
+
+ if(what & RR_TF) {
+ m = CputrripTF(cd, d, TFcreation|TFmodify|TFaccess|TFattributes, 0);
+ cp = ensurespace(cd, m, cp, &cn, dowrite);
+ CputrripTF(cd, d, TFcreation|TFmodify|TFaccess|TFattributes, dowrite);
+ }
+ assert(dowrite==0 || cp != &co || Cwoffset(cd) == o+co.len-initlen);
+
+ if(cp == &cn && dowrite) {
+ /* seek out of continuation, but mark our place */
+ cd->rrcontin = Cwoffset(cd);
+ setcelen(cd, cn.ceoffset, cn.len);
+ Cwseek(cd, o+co.len-initlen);
+ }
+
+ if(co.len & 1) {
+ co.len++;
+ if(dowrite)
+ Cputc(cd, 0);
+ }
+
+ if(dowrite) {
+ if(Cwoffset(cd) != o+co.len-initlen)
+ fprint(2, "offset %llud o+co.len-initlen %llud\n",
+ Cwoffset(cd), o+co.len-initlen);
+ assert(Cwoffset(cd) == o+co.len-initlen);
+ } else
+ assert(Cwoffset(cd) == o);
+
+ assert(co.len <= 255);
+ return co.len - initlen;
+}
+
+static char SUSPrrip[10] = "RRIP_1991A";
+static char SUSPdesc[84] = "RRIP <more garbage here>";
+static char SUSPsrc[135] = "RRIP <more garbage here>";
+
+static ulong
+CputsuspCE(Cdimg *cd, vlong offset)
+{
+ vlong o, x;
+
+ chat("writing SUSP CE record pointing to %ld, %ld\n",
+ offset/Blocksize, offset%Blocksize);
+ o = Cwoffset(cd);
+ Cputc(cd, 'C');
+ Cputc(cd, 'E');
+ Cputc(cd, 28);
+ Cputc(cd, 1);
+ Cputn(cd, offset/Blocksize, 4);
+ Cputn(cd, offset%Blocksize, 4);
+ x = Cwoffset(cd);
+ Cputn(cd, 0, 4);
+ assert(Cwoffset(cd) == o+28);
+
+ return x;
+}
+
+static int
+CputsuspER(Cdimg *cd, int dowrite)
+{
+ assert(cd != nil);
+
+ if(dowrite) {
+ chat("writing SUSP ER record\n");
+ Cputc(cd, 'E'); /* ER field marker */
+ Cputc(cd, 'R');
+ Cputc(cd, 26); /* Length */
+ Cputc(cd, 1); /* Version */
+ Cputc(cd, 10); /* LEN_ID */
+ Cputc(cd, 4); /* LEN_DESC */
+ Cputc(cd, 4); /* LEN_SRC */
+ Cputc(cd, 1); /* EXT_VER */
+ Cputs(cd, SUSPrrip, 10); /* EXT_ID */
+ Cputs(cd, SUSPdesc, 4); /* EXT_DESC */
+ Cputs(cd, SUSPsrc, 4); /* EXT_SRC */
+ }
+ return 8+10+4+4;
+}
+
+static int
+CputsuspRR(Cdimg *cd, int what, int dowrite)
+{
+ assert(cd != nil);
+
+ if(dowrite) {
+ Cputc(cd, 'R'); /* RR field marker */
+ Cputc(cd, 'R');
+ Cputc(cd, 5); /* Length */
+ Cputc(cd, 1); /* Version number */
+ Cputc(cd, what); /* Flags */
+ }
+ return 5;
+}
+
+static int
+CputsuspSP(Cdimg *cd, int dowrite)
+{
+ assert(cd!=0);
+
+ if(dowrite) {
+chat("writing SUSP SP record\n");
+ Cputc(cd, 'S'); /* SP field marker */
+ Cputc(cd, 'P');
+ Cputc(cd, 7); /* Length */
+ Cputc(cd, 1); /* Version */
+ Cputc(cd, 0xBE); /* Magic */
+ Cputc(cd, 0xEF);
+ Cputc(cd, 0);
+ }
+
+ return 7;
+}
+
+#ifdef NOTUSED
+static int
+CputsuspST(Cdimg *cd, int dowrite)
+{
+ assert(cd!=0);
+
+ if(dowrite) {
+ Cputc(cd, 'S'); /* ST field marker */
+ Cputc(cd, 'T');
+ Cputc(cd, 4); /* Length */
+ Cputc(cd, 1); /* Version */
+ }
+ return 4;
+}
+#endif
+
+static ulong
+suspdirflags(Direc *d, int dot)
+{
+ uchar flags;
+
+ USED(d);
+ flags = 0;
+ switch(dot) {
+ default:
+ assert(0);
+ case DTdot:
+ case DTrootdot:
+ flags |= NMcurrent;
+ break;
+ case DTdotdot:
+ flags |= NMparent;
+ break;
+ case DTroot:
+ flags |= NMvolroot;
+ break;
+ case DTiden:
+ break;
+ }
+ return flags;
+}
+
+static int
+Cputrripname(Cdimg *cd, char *nm, int flags, char *name, int dowrite)
+{
+ int l;
+
+ l = strlen(name);
+ if(dowrite) {
+ Cputc(cd, nm[0]); /* NM field marker */
+ Cputc(cd, nm[1]);
+ Cputc(cd, l+5); /* Length */
+ Cputc(cd, 1); /* Version */
+ Cputc(cd, flags); /* Flags */
+ Cputs(cd, name, l); /* Alternate name */
+ }
+ return 5+l;
+}
+
+static int
+CputrripSL(Cdimg *cd, int contin, int flags, char *name, int dowrite)
+{
+ int l;
+
+ l = strlen(name);
+ if(dowrite) {
+ Cputc(cd, 'S');
+ Cputc(cd, 'L');
+ Cputc(cd, l+7);
+ Cputc(cd, 1);
+ Cputc(cd, contin ? 1 : 0);
+ Cputc(cd, flags);
+ Cputc(cd, l);
+ Cputs(cd, name, l);
+ }
+ return 7+l;
+}
+
+static int
+CputrripPX(Cdimg *cd, Direc *d, int dot, int dowrite)
+{
+ assert(cd!=0);
+
+ if(dowrite) {
+ Cputc(cd, 'P'); /* PX field marker */
+ Cputc(cd, 'X');
+ Cputc(cd, 36); /* Length */
+ Cputc(cd, 1); /* Version */
+
+ Cputn(cd, mode(d, dot), 4); /* POSIX File mode */
+ Cputn(cd, nlink(d), 4); /* POSIX st_nlink */
+ Cputn(cd, d?d->uidno:0, 4); /* POSIX st_uid */
+ Cputn(cd, d?d->gidno:0, 4); /* POSIX st_gid */
+ }
+
+ return 36;
+}
+
+static int
+CputrripTF(Cdimg *cd, Direc *d, int type, int dowrite)
+{
+ int i, length;
+
+ assert(cd!=0);
+ assert(!(type & TFlongform));
+
+ length = 0;
+ for(i=0; i<7; i++)
+ if (type & (1<<i))
+ length++;
+ assert(length == 4);
+
+ if(dowrite) {
+ Cputc(cd, 'T'); /* TF field marker */
+ Cputc(cd, 'F');
+ Cputc(cd, 5+7*length); /* Length */
+ Cputc(cd, 1); /* Version */
+ Cputc(cd, type); /* Flags (types) */
+
+ if (type & TFcreation)
+ Cputdate(cd, d?d->ctime:0);
+ if (type & TFmodify)
+ Cputdate(cd, d?d->mtime:0);
+ if (type & TFaccess)
+ Cputdate(cd, d?d->atime:0);
+ if (type & TFattributes)
+ Cputdate(cd, d?d->ctime:0);
+
+ // if (type & TFbackup)
+ // Cputdate(cd, 0);
+ // if (type & TFexpiration)
+ // Cputdate(cd, 0);
+ // if (type & TFeffective)
+ // Cputdate(cd, 0);
+ }
+ return 5+7*length;
+}
+
+
+#define NONPXMODES (DMDIR | DMAPPEND | DMEXCL | DMMOUNT)
+#define POSIXMODEMASK (0177777)
+#ifndef S_IFMT
+#define S_IFMT (0170000)
+#endif
+#ifndef S_IFDIR
+#define S_IFDIR (0040000)
+#endif
+#ifndef S_IFREG
+#define S_IFREG (0100000)
+#endif
+#ifndef S_IFLNK
+#define S_IFLNK (0120000)
+#endif
+#undef ISTYPE
+#define ISTYPE(mode, mask) (((mode) & S_IFMT) == (mask))
+#ifndef S_ISDIR
+#define S_ISDIR(mode) ISTYPE(mode, S_IFDIR)
+#endif
+#ifndef S_ISREG
+#define S_ISREG(mode) ISTYPE(mode, S_IREG)
+#endif
+#ifndef S_ISLNK
+#define S_ISLNK(mode) ISTYPE(mode, S_ILNK)
+#endif
+
+
+static long
+mode(Direc *d, int dot)
+{
+ long mode;
+
+ if (!d)
+ return 0;
+
+ if ((dot != DTroot) && (dot != DTrootdot)) {
+ mode = (d->mode & ~(NONPXMODES));
+ if (d->mode & DMDIR)
+ mode |= S_IFDIR;
+ else if (d->mode & CHLINK)
+ mode |= S_IFLNK;
+ else
+ mode |= S_IFREG;
+ } else
+ mode = S_IFDIR | (0755);
+
+ mode &= POSIXMODEMASK;
+
+ /* Botch: not all POSIX types supported yet */
+ assert(mode & (S_IFDIR|S_IFREG));
+
+chat("writing PX record mode field %ulo with dot %d and name \"%s\"\n", mode, dot, d->name);
+
+ return mode;
+}
+
+static long
+nlink(Direc *d) /* Trump up the nlink field for POSIX compliance */
+{
+ int i;
+ long n;
+
+ if (!d)
+ return 0;
+
+ n = 1;
+ if (d->mode & DMDIR) /* One for "." and one more for ".." */
+ n++;
+
+ for(i=0; i<d->nchild; i++)
+ if (d->child[i].mode & DMDIR)
+ n++;
+
+ return n;
+}
+
diff --git a/sys/src/cmd/disk/9660/uid.c b/sys/src/cmd/disk/9660/uid.c
new file mode 100755
index 000000000..a63484249
--- /dev/null
+++ b/sys/src/cmd/disk/9660/uid.c
@@ -0,0 +1,41 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ * /adm/users is
+ * id:user/group:head member:other members
+ *
+ * /etc/{passwd,group}
+ * name:x:nn:other stuff
+ */
+
+static int isnumber(char *s);
+
+sniff(Biobuf *b)
+{
+ read first line of file into p;
+
+ nf = getfields(p, f, nelem(f), ":");
+ if(nf < 4)
+ return nil;
+
+ if(isnumber(f[0]) && !isnumber(f[2]))
+ return _plan9;
+
+ if(!isnumber(f[0]) && isnumber(f[2]))
+ return _unix;
+
+ return nil;
+}
+
+
+int
+isnumber(char *s)
+{
+ char *q;
+
+ strtol(s, &q, 10);
+ return *q == '\0';
+}
+
+/* EOF */
diff --git a/sys/src/cmd/disk/9660/unix.c b/sys/src/cmd/disk/9660/unix.c
new file mode 100755
index 000000000..942dac85d
--- /dev/null
+++ b/sys/src/cmd/disk/9660/unix.c
@@ -0,0 +1,83 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <libproto.h>
+#include <ctype.h>
+
+#include "iso9660.h"
+
+#include <grp.h>
+#include <pwd.h>
+
+typedef struct Xarg Xarg;
+struct Xarg {
+ void (*enm)(char*,char*,XDir*,void*);
+ void (*warn)(char*,void*);
+ void *arg;
+};
+
+static long numericuid(char *user);
+static long numericgid(char *gp);
+
+void
+dirtoxdir(XDir *xd, Dir *d)
+{
+ // char buf[NAMELEN+1];
+ memset(xd, 0, sizeof *xd);
+
+ xd->name = atom(d->name);
+ xd->uid = atom(d->uid);
+ xd->gid = atom(d->gid);
+ xd->uidno = numericuid(d->uid);
+ xd->gidno = numericgid(d->gid);
+ xd->mode = d->mode;
+ xd->atime = d->atime;
+ xd->mtime = d->mtime;
+ xd->ctime = 0;
+ xd->length = d->length;
+ if(xd->mode & CHLINK) {
+ xd->mode |= 0777;
+ xd->symlink = atom(d->symlink);
+ }
+};
+
+void
+fdtruncate(int fd, ulong size)
+{
+ ftruncate(fd, size);
+
+ return;
+}
+
+static long
+numericuid(char *user)
+{
+ struct passwd *pass;
+ static int warned = 0;
+
+ if (! (pass = getpwnam(user))) {
+ if (!warned)
+ fprint(2, "Warning: getpwnam(3) failed for \"%s\"\n", user);
+ warned = 1;
+ return 0;
+ }
+
+ return pass->pw_uid;
+}
+
+static long
+numericgid(char *gp)
+{
+ struct group *gr;
+ static int warned = 0;
+
+ if (! (gr = getgrnam(gp))) {
+ if (!warned)
+ fprint(2, "Warning: getgrnam(3) failed for \"%s\"\n", gp);
+ warned = 1;
+ return 0;
+ }
+
+ return gr->gr_gid;
+}
diff --git a/sys/src/cmd/disk/9660/util.c b/sys/src/cmd/disk/9660/util.c
new file mode 100755
index 000000000..16fb21f3d
--- /dev/null
+++ b/sys/src/cmd/disk/9660/util.c
@@ -0,0 +1,98 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <ctype.h>
+
+#include "iso9660.h"
+
+typedef struct Stringtab Stringtab;
+struct Stringtab {
+ Stringtab *link;
+ char *str;
+};
+
+static Stringtab *stab[1024];
+
+static uint
+hash(char *s)
+{
+ uint h;
+ uchar *p;
+
+ h = 0;
+ for(p=(uchar*)s; *p; p++)
+ h = h*37 + *p;
+ return h;
+}
+
+static char*
+estrdup(char *s)
+{
+ if((s = strdup(s)) == nil)
+ sysfatal("strdup(%.10s): out of memory", s);
+ return s;
+}
+
+char*
+atom(char *str)
+{
+ uint h;
+ Stringtab *tab;
+
+ h = hash(str) % nelem(stab);
+ for(tab=stab[h]; tab; tab=tab->link)
+ if(strcmp(str, tab->str) == 0)
+ return tab->str;
+
+ tab = emalloc(sizeof *tab);
+ tab->str = estrdup(str);
+ tab->link = stab[h];
+ stab[h] = tab;
+ return tab->str;
+}
+
+void*
+emalloc(ulong n)
+{
+ void *p;
+
+ if((p = malloc(n)) == nil)
+ sysfatal("malloc(%lud): out of memory", n);
+ memset(p, 0, n);
+ return p;
+}
+
+void*
+erealloc(void *v, ulong n)
+{
+ if((v = realloc(v, n)) == nil)
+ sysfatal("realloc(%p, %lud): out of memory", v, n);
+ return v;
+}
+
+char*
+struprcpy(char *p, char *s)
+{
+ char *op;
+
+ op = p;
+ for(; *s; s++)
+ *p++ = toupper(*s);
+ *p = '\0';
+
+ return op;
+}
+
+int
+chat(char *fmt, ...)
+{
+ va_list arg;
+
+ if(!chatty)
+ return 0;
+ va_start(arg, fmt);
+ vfprint(2, fmt, arg);
+ va_end(arg);
+ return 1;
+}
diff --git a/sys/src/cmd/disk/9660/write.c b/sys/src/cmd/disk/9660/write.c
new file mode 100755
index 000000000..e45f21c19
--- /dev/null
+++ b/sys/src/cmd/disk/9660/write.c
@@ -0,0 +1,411 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+static void
+writelittlebig4(uchar *buf, ulong x)
+{
+ buf[0] = buf[7] = x;
+ buf[1] = buf[6] = x>>8;
+ buf[2] = buf[5] = x>>16;
+ buf[3] = buf[4] = x>>24;
+}
+
+void
+rewritedot(Cdimg *cd, Direc *d)
+{
+ uchar buf[Blocksize];
+ Cdir *c;
+
+ Creadblock(cd, buf, d->block, Blocksize);
+ c = (Cdir*)buf;
+ assert(c->len != 0);
+ assert(c->namelen == 1 && c->name[0] == '\0'); /* dot */
+ writelittlebig4(c->dloc, d->block);
+ writelittlebig4(c->dlen, d->length);
+
+ Cwseek(cd, (vlong)d->block * Blocksize);
+ Cwrite(cd, buf, Blocksize);
+}
+
+void
+rewritedotdot(Cdimg *cd, Direc *d, Direc *dparent)
+{
+ uchar buf[Blocksize];
+ Cdir *c;
+
+ Creadblock(cd, buf, d->block, Blocksize);
+ c = (Cdir*)buf;
+ assert(c->len != 0);
+ assert(c->namelen == 1 && c->name[0] == '\0'); /* dot */
+
+ c = (Cdir*)(buf+c->len);
+ assert(c->len != 0);
+ assert(c->namelen == 1 && c->name[0] == '\001'); /* dotdot*/
+
+ writelittlebig4(c->dloc, dparent->block);
+ writelittlebig4(c->dlen, dparent->length);
+
+ Cwseek(cd, (vlong)d->block * Blocksize);
+ Cwrite(cd, buf, Blocksize);
+}
+
+/*
+ * Write each non-directory file. We copy the file to
+ * the cd image, and then if it turns out that we've
+ * seen this stream of bits before, we push the next block
+ * pointer back. This ensures consistency between the MD5s
+ * and the data on the CD image. MD5 summing on one pass
+ * and copying on another would not ensure this.
+ */
+void
+writefiles(Dump *d, Cdimg *cd, Direc *direc)
+{
+ int i;
+ uchar buf[8192], digest[MD5dlen];
+ ulong length, n, start;
+ Biobuf *b;
+ DigestState *s;
+ Dumpdir *dd;
+
+ if(direc->mode & DMDIR) {
+ for(i=0; i<direc->nchild; i++)
+ writefiles(d, cd, &direc->child[i]);
+ return;
+ }
+
+ assert(direc->block == 0);
+
+ if((b = Bopen(direc->srcfile, OREAD)) == nil){
+ fprint(2, "warning: cannot open '%s': %r\n", direc->srcfile);
+ direc->block = 0;
+ direc->length = 0;
+ return;
+ }
+
+ start = cd->nextblock;
+ assert(start != 0);
+ if(blocksize && start%blocksize)
+ start += blocksize-start%blocksize;
+
+ Cwseek(cd, (vlong)start * Blocksize);
+
+ s = md5(nil, 0, nil, nil);
+ length = 0;
+ while((n = Bread(b, buf, sizeof buf)) > 0) {
+ md5(buf, n, nil, s);
+ Cwrite(cd, buf, n);
+ length += n;
+ }
+ md5(nil, 0, digest, s);
+ Bterm(b);
+ Cpadblock(cd);
+
+ if(length != direc->length) {
+ fprint(2, "warning: %s changed size underfoot\n", direc->srcfile);
+ direc->length = length;
+ }
+
+ if(length == 0)
+ direc->block = 0;
+ else if((dd = lookupmd5(d, digest))) {
+ assert(dd->length == length);
+ assert(dd->block != 0);
+ direc->block = dd->block;
+ cd->nextblock = start;
+ } else {
+ direc->block = start;
+ if(chatty > 1)
+ fprint(2, "lookup %.16H %lud (%s) failed\n", digest, length, direc->name);
+ insertmd5(d, atom(direc->name), digest, start, length);
+ }
+}
+
+/*
+ * Write a directory tree. We work from the leaves,
+ * and patch the dotdot pointers afterward.
+ */
+static void
+_writedirs(Cdimg *cd, Direc *d, int (*put)(Cdimg*, Direc*, int, int, int), int level)
+{
+ int i, l, ll;
+ ulong start, next;
+
+ if((d->mode & DMDIR) == 0)
+ return;
+
+ if(chatty)
+ fprint(2, "%*s%s\n", 4*level, "", d->name);
+
+ for(i=0; i<d->nchild; i++)
+ _writedirs(cd, &d->child[i], put, level+1);
+
+ l = 0;
+ l += put(cd, d, (level == 0) ? DTrootdot : DTdot, 0, l);
+ l += put(cd, nil, DTdotdot, 0, l);
+ for(i=0; i<d->nchild; i++)
+ l += put(cd, &d->child[i], DTiden, 0, l);
+
+ start = cd->nextblock;
+ cd->nextblock += (l+Blocksize-1)/Blocksize;
+ next = cd->nextblock;
+
+ Cwseek(cd, (vlong)start * Blocksize);
+ ll = 0;
+ ll += put(cd, d, (level == 0) ? DTrootdot : DTdot, 1, ll);
+ ll += put(cd, nil, DTdotdot, 1, ll);
+ for(i=0; i<d->nchild; i++)
+ ll += put(cd, &d->child[i], DTiden, 1, ll);
+ assert(ll == l);
+ Cpadblock(cd);
+ assert(Cwoffset(cd) == (vlong)next * Blocksize);
+
+ d->block = start;
+ d->length = (vlong)(next - start) * Blocksize;
+ rewritedot(cd, d);
+ rewritedotdot(cd, d, d);
+
+ for(i=0; i<d->nchild; i++)
+ if(d->child[i].mode & DMDIR)
+ rewritedotdot(cd, &d->child[i], d);
+}
+
+void
+writedirs(Cdimg *cd, Direc *d, int (*put)(Cdimg*, Direc*, int, int, int))
+{
+ /*
+ * If we're writing a mk9660 image, then the root really
+ * is the root, so start at level 0. If we're writing a dump image,
+ * then the "root" is really going to be two levels down once
+ * we patch in the dump hierarchy above it, so start at level non-zero.
+ */
+ if(chatty)
+ fprint(2, ">>> writedirs\n");
+ _writedirs(cd, d, put, mk9660 ? 0 : 1);
+}
+
+
+/*
+ * Write the dump tree. This is like writedirs but once we get to
+ * the roots of the individual days we just patch the parent dotdot blocks.
+ */
+static void
+_writedumpdirs(Cdimg *cd, Direc *d, int (*put)(Cdimg*, Direc*, int, int, int), int level)
+{
+ int i;
+ ulong start;
+
+ switch(level) {
+ case 0:
+ /* write root, list of years, also conform.map */
+ for(i=0; i<d->nchild; i++)
+ if(d->child[i].mode & DMDIR)
+ _writedumpdirs(cd, &d->child[i], put, level+1);
+ chat("write dump root dir at %lud\n", cd->nextblock);
+ goto Writedir;
+
+ case 1: /* write year, list of days */
+ for(i=0; i<d->nchild; i++)
+ _writedumpdirs(cd, &d->child[i], put, level+1);
+ chat("write dump %s dir at %lud\n", d->name, cd->nextblock);
+ goto Writedir;
+
+ Writedir:
+ start = cd->nextblock;
+ Cwseek(cd, (vlong)start * Blocksize);
+
+ put(cd, d, (level == 0) ? DTrootdot : DTdot, 1, Cwoffset(cd));
+ put(cd, nil, DTdotdot, 1, Cwoffset(cd));
+ for(i=0; i<d->nchild; i++)
+ put(cd, &d->child[i], DTiden, 1, Cwoffset(cd));
+ Cpadblock(cd);
+
+ d->block = start;
+ d->length = (vlong)(cd->nextblock - start) * Blocksize;
+
+ rewritedot(cd, d);
+ rewritedotdot(cd, d, d);
+
+ for(i=0; i<d->nchild; i++)
+ if(d->child[i].mode & DMDIR)
+ rewritedotdot(cd, &d->child[i], d);
+ break;
+
+ case 2: /* write day: already written, do nothing */
+ break;
+
+ default:
+ assert(0);
+ }
+}
+
+void
+writedumpdirs(Cdimg *cd, Direc *d, int (*put)(Cdimg*, Direc*, int, int, int))
+{
+ _writedumpdirs(cd, d, put, 0);
+}
+
+static int
+Cputplan9(Cdimg *cd, Direc *d, int dot, int dowrite)
+{
+ int l, n;
+
+ if(dot != DTiden)
+ return 0;
+
+ l = 0;
+ if(d->flags & Dbadname) {
+ n = strlen(d->name);
+ l += 1+n;
+ if(dowrite) {
+ Cputc(cd, n);
+ Cputs(cd, d->name, n);
+ }
+ } else {
+ l++;
+ if(dowrite)
+ Cputc(cd, 0);
+ }
+
+ n = strlen(d->uid);
+ l += 1+n;
+ if(dowrite) {
+ Cputc(cd, n);
+ Cputs(cd, d->uid, n);
+ }
+
+ n = strlen(d->gid);
+ l += 1+n;
+ if(dowrite) {
+ Cputc(cd, n);
+ Cputs(cd, d->gid, n);
+ }
+
+ if(l & 1) {
+ l++;
+ if(dowrite)
+ Cputc(cd, 0);
+ }
+ l += 8;
+ if(dowrite)
+ Cputn(cd, d->mode, 4);
+
+ return l;
+}
+
+/*
+ * Write a directory entry.
+ */
+static int
+genputdir(Cdimg *cd, Direc *d, int dot, int joliet, int dowrite, int offset)
+{
+ int f, n, l, lp;
+ vlong o;
+
+ f = 0;
+ if(dot != DTiden || (d->mode & DMDIR))
+ f |= 2;
+
+ n = 1;
+ if(dot == DTiden) {
+ if(joliet)
+ n = 2*utflen(d->confname);
+ else
+ n = strlen(d->confname);
+ }
+
+ l = 33+n;
+ if(l & 1)
+ l++;
+ assert(l <= 255);
+
+ if(joliet == 0) {
+ if(cd->flags & CDplan9)
+ l += Cputplan9(cd, d, dot, 0);
+ else if(cd->flags & CDrockridge)
+ l += Cputsysuse(cd, d, dot, 0, l);
+ assert(l <= 255);
+ }
+
+ if(dowrite == 0) {
+ if(Blocksize - offset%Blocksize < l)
+ l += Blocksize - offset%Blocksize;
+ return l;
+ }
+
+ assert(offset%Blocksize == Cwoffset(cd)%Blocksize);
+
+ o = Cwoffset(cd);
+ lp = 0;
+ if(Blocksize - Cwoffset(cd)%Blocksize < l) {
+ lp = Blocksize - Cwoffset(cd)%Blocksize;
+ Cpadblock(cd);
+ }
+
+ Cputc(cd, l); /* length of directory record */
+ Cputc(cd, 0); /* extended attribute record length */
+ if(d) {
+ if((d->mode & DMDIR) == 0)
+ assert(d->length == 0 || d->block >= 18);
+
+ Cputn(cd, d->block, 4); /* location of extent */
+ Cputn(cd, d->length, 4); /* data length */
+ } else {
+ Cputn(cd, 0, 4);
+ Cputn(cd, 0, 4);
+ }
+ Cputdate(cd, d ? d->mtime : now); /* recorded date */
+ Cputc(cd, f); /* file flags */
+ Cputc(cd, 0); /* file unit size */
+ Cputc(cd, 0); /* interleave gap size */
+ Cputn(cd, 1, 2); /* volume sequence number */
+ Cputc(cd, n); /* length of file identifier */
+
+ if(dot == DTiden) { /* identifier */
+ if(joliet)
+ Cputrscvt(cd, d->confname, n);
+ else
+ Cputs(cd, d->confname, n);
+ }else
+ if(dot == DTdotdot)
+ Cputc(cd, 1);
+ else
+ Cputc(cd, 0);
+
+ if(Cwoffset(cd) & 1) /* pad */
+ Cputc(cd, 0);
+
+ if(joliet == 0) {
+ if(cd->flags & CDplan9)
+ Cputplan9(cd, d, dot, 1);
+ else if(cd->flags & CDrockridge)
+ Cputsysuse(cd, d, dot, 1, Cwoffset(cd)-(o+lp));
+ }
+
+ assert(o+lp+l == Cwoffset(cd));
+ return lp+l;
+}
+
+int
+Cputisodir(Cdimg *cd, Direc *d, int dot, int dowrite, int offset)
+{
+ return genputdir(cd, d, dot, 0, dowrite, offset);
+}
+
+int
+Cputjolietdir(Cdimg *cd, Direc *d, int dot, int dowrite, int offset)
+{
+ return genputdir(cd, d, dot, 1, dowrite, offset);
+}
+
+void
+Cputendvd(Cdimg *cd)
+{
+ Cputc(cd, 255); /* volume descriptor set terminator */
+ Cputs(cd, "CD001", 5); /* standard identifier */
+ Cputc(cd, 1); /* volume descriptor version */
+ Cpadblock(cd);
+}
diff --git a/sys/src/cmd/disk/exsort.c b/sys/src/cmd/disk/exsort.c
new file mode 100755
index 000000000..4a04c845a
--- /dev/null
+++ b/sys/src/cmd/disk/exsort.c
@@ -0,0 +1,136 @@
+#include <u.h>
+#include <libc.h>
+
+int ulcmp(void*, void*);
+void swapem(ulong*, long);
+
+enum
+{
+ Wormsize = 157933,
+};
+int wflag;
+
+void
+main(int argc, char *argv[])
+{
+ long i, l, x, lobits, hibits, tot;
+ int f, j;
+ char *file;
+ ulong *b, a, lo, hi;
+
+ ARGBEGIN {
+ default:
+ print("usage: disk/exsort [-w] [file]\n");
+ exits("usage");
+ case 'w':
+ wflag++;
+ break;
+ } ARGEND;
+
+ file = "/adm/cache";
+ if(argc > 0)
+ file = argv[0];
+
+ if(wflag)
+ f = open(file, ORDWR);
+ else
+ f = open(file, OREAD);
+ if(f < 0) {
+ print("cant open %s: %r\n", file);
+ exits("open");
+ }
+ l = seek(f, 0, 2) / sizeof(long);
+
+ b = malloc(l*sizeof(long));
+ if(b == 0) {
+ print("cant malloc %s: %r\n", file);
+ exits("malloc");
+ }
+ seek(f, 0, 0);
+ if(read(f, b, l*sizeof(long)) != l*sizeof(long)) {
+ print("short read %s: %r\n", file);
+ exits("read");
+ }
+
+ lobits = 0;
+ hibits = 0;
+ for(i=0; i<l; i++) {
+ a = b[i];
+ if(a & (1L<<7))
+ lobits++;
+ if(a & (1L<<31))
+ hibits++;
+ }
+
+ print("lobits = %6ld\n", lobits);
+ print("hibits = %6ld\n", hibits);
+
+ if(hibits > lobits) {
+ print("swapping\n");
+ swapem(b, l);
+ }
+
+ qsort(b, l, sizeof(ulong), ulcmp);
+
+ tot = 0;
+ for(j=0; j<100; j++) {
+ lo = j*Wormsize;
+ hi = lo + Wormsize;
+
+ x = 0;
+ for(i=0; i<l; i++) {
+ a = b[i];
+ if(a >= lo && a < hi)
+ x++;
+ }
+ if(x) {
+ print("disk %2d %6ld blocks\n", j, x);
+ tot += x;
+ }
+ }
+ print("total %6ld blocks\n", tot);
+
+
+ if(wflag) {
+ if(hibits > lobits)
+ swapem(b, l);
+ seek(f, 0, 0);
+ if(write(f, b, l*sizeof(long)) != l*sizeof(long)) {
+ print("short write %s\n", file);
+ exits("write");
+ }
+ }
+
+ exits(0);
+}
+
+int
+ulcmp(void *va, void *vb)
+{
+ ulong *a, *b;
+
+ a = va;
+ b = vb;
+
+ if(*a > *b)
+ return 1;
+ if(*a < *b)
+ return -1;
+ return 0;
+}
+
+void
+swapem(ulong *b, long l)
+{
+ long i;
+ ulong x, a;
+
+ for(i=0; i<l; i++, b++) {
+ a = *b;
+ x = (((a>>0) & 0xff) << 24) |
+ (((a>>8) & 0xff) << 16) |
+ (((a>>16) & 0xff) << 8) |
+ (((a>>24) & 0xff) << 0);
+ *b = x;
+ }
+}
diff --git a/sys/src/cmd/disk/format.c b/sys/src/cmd/disk/format.c
new file mode 100755
index 000000000..2ada3abfa
--- /dev/null
+++ b/sys/src/cmd/disk/format.c
@@ -0,0 +1,799 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <disk.h>
+
+/*
+ * disk types (all MFM encoding)
+ */
+typedef struct Type Type;
+struct Type
+{
+ char *name;
+ int bytes; /* bytes/sector */
+ int sectors; /* sectors/track */
+ int heads; /* number of heads */
+ int tracks; /* tracks/disk */
+ int media; /* media descriptor byte */
+ int cluster; /* default cluster size */
+};
+Type floppytype[] =
+{
+ { "3½HD", 512, 18, 2, 80, 0xf0, 1, },
+ { "3½DD", 512, 9, 2, 80, 0xf9, 2, },
+ { "3½QD", 512, 36, 2, 80, 0xf9, 2, }, /* invented */
+ { "5¼HD", 512, 15, 2, 80, 0xf9, 1, },
+ { "5¼DD", 512, 9, 2, 40, 0xfd, 2, },
+ { "hard", 512, 0, 0, 0, 0xf8, 4, },
+};
+
+#define NTYPES (sizeof(floppytype)/sizeof(Type))
+
+typedef struct Dosboot Dosboot;
+struct Dosboot{
+ uchar magic[3]; /* really an x86 JMP instruction */
+ uchar version[8];
+ uchar sectsize[2];
+ uchar clustsize;
+ uchar nresrv[2];
+ uchar nfats;
+ uchar rootsize[2];
+ uchar volsize[2];
+ uchar mediadesc;
+ uchar fatsize[2];
+ uchar trksize[2];
+ uchar nheads[2];
+ uchar nhidden[4];
+ uchar bigvolsize[4];
+ uchar driveno;
+ uchar reserved0;
+ uchar bootsig;
+ uchar volid[4];
+ uchar label[11];
+ uchar type[8];
+};
+#define PUTSHORT(p, v) { (p)[1] = (v)>>8; (p)[0] = (v); }
+#define PUTLONG(p, v) { PUTSHORT((p), (v)); PUTSHORT((p)+2, (v)>>16); }
+#define GETSHORT(p) (((p)[1]<<8)|(p)[0])
+#define GETLONG(p) (((ulong)GETSHORT(p+2)<<16)|(ulong)GETSHORT(p))
+
+typedef struct Dosdir Dosdir;
+struct Dosdir
+{
+ uchar name[8];
+ uchar ext[3];
+ uchar attr;
+ uchar reserved[10];
+ uchar time[2];
+ uchar date[2];
+ uchar start[2];
+ uchar length[4];
+};
+
+#define DRONLY 0x01
+#define DHIDDEN 0x02
+#define DSYSTEM 0x04
+#define DVLABEL 0x08
+#define DDIR 0x10
+#define DARCH 0x20
+
+/*
+ * the boot program for the boot sector.
+ */
+int nbootprog = 188; /* no. of bytes of boot program, including the first 0x3E */
+uchar bootprog[512] =
+{
+[0x000] 0xEB, 0x3C, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+[0x03E] 0xFA, 0xFC, 0x8C, 0xC8, 0x8E, 0xD8, 0x8E, 0xD0,
+ 0xBC, 0x00, 0x7C, 0xBE, 0x77, 0x7C, 0xE8, 0x19,
+ 0x00, 0x33, 0xC0, 0xCD, 0x16, 0xBB, 0x40, 0x00,
+ 0x8E, 0xC3, 0xBB, 0x72, 0x00, 0xB8, 0x34, 0x12,
+ 0x26, 0x89, 0x07, 0xEA, 0x00, 0x00, 0xFF, 0xFF,
+ 0xEB, 0xD6, 0xAC, 0x0A, 0xC0, 0x74, 0x09, 0xB4,
+ 0x0E, 0xBB, 0x07, 0x00, 0xCD, 0x10, 0xEB, 0xF2,
+ 0xC3, 'N', 'o', 't', ' ', 'a', ' ', 'b',
+ 'o', 'o', 't', 'a', 'b', 'l', 'e', ' ',
+ 'd', 'i', 's', 'c', ' ', 'o', 'r', ' ',
+ 'd', 'i', 's', 'c', ' ', 'e', 'r', 'r',
+ 'o', 'r', '\r', '\n', 'P', 'r', 'e', 's',
+ 's', ' ', 'a', 'l', 'm', 'o', 's', 't',
+ ' ', 'a', 'n', 'y', ' ', 'k', 'e', 'y',
+ ' ', 't', 'o', ' ', 'r', 'e', 'b', 'o',
+ 'o', 't', '.', '.', '.', 0x00, 0x00, 0x00,
+[0x1F0] 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xAA,
+};
+
+char *dev;
+int clustersize;
+uchar *fat; /* the fat */
+int fatbits;
+int fatsecs;
+int fatlast; /* last cluster allocated */
+int clusters;
+int fatsecs;
+vlong volsecs;
+uchar *root; /* first block of root */
+int rootsecs;
+int rootfiles;
+int rootnext;
+int nresrv = 1;
+int chatty;
+vlong length;
+Type *t;
+int fflag;
+int hflag;
+int xflag;
+char *file;
+char *pbs;
+char *type;
+char *bootfile;
+int dos;
+
+enum
+{
+ Sof = 1, /* start of file */
+ Eof = 2, /* end of file */
+};
+
+void dosfs(int, int, Disk*, char*, int, char*[], int);
+ulong clustalloc(int);
+void addrname(uchar*, Dir*, char*, ulong);
+void sanitycheck(Disk*);
+
+void
+usage(void)
+{
+ fprint(2, "usage: disk/format [-df] [-b bootblock] [-c csize] "
+ "[-l label] [-r nresrv] [-t type] disk [files ...]\n");
+ exits("usage");
+}
+
+void
+fatal(char *fmt, ...)
+{
+ char err[128];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vsnprint(err, sizeof(err), fmt, arg);
+ va_end(arg);
+ fprint(2, "format: %s\n", err);
+ if(fflag && file)
+ remove(file);
+ exits(err);
+}
+
+void
+main(int argc, char **argv)
+{
+ int fd, n, writepbs;
+ char buf[512], label[11];
+ char *a;
+ Disk *disk;
+
+ dos = 0;
+ type = nil;
+ clustersize = 0;
+ writepbs = 0;
+ memmove(label, "CYLINDRICAL", sizeof(label));
+ ARGBEGIN {
+ case 'b':
+ pbs = EARGF(usage());
+ writepbs = 1;
+ break;
+ case 'c':
+ clustersize = atoi(EARGF(usage()));
+ break;
+ case 'd':
+ dos = 1;
+ writepbs = 1;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'l':
+ a = EARGF(usage());
+ n = strlen(a);
+ if(n > sizeof(label))
+ n = sizeof(label);
+ memmove(label, a, n);
+ while(n < sizeof(label))
+ label[n++] = ' ';
+ break;
+ case 'r':
+ nresrv = atoi(EARGF(usage()));
+ break;
+ case 't':
+ type = EARGF(usage());
+ break;
+ case 'v':
+ chatty++;
+ break;
+ case 'x':
+ xflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND
+
+ if(argc < 1)
+ usage();
+
+ disk = opendisk(argv[0], 0, 0);
+ if(disk == nil) {
+ if(fflag) {
+ if((fd = create(argv[0], ORDWR, 0666)) >= 0) {
+ file = argv[0];
+ close(fd);
+ disk = opendisk(argv[0], 0, 0);
+ }
+ }
+ }
+ if(disk == nil)
+ fatal("opendisk: %r");
+
+ if(disk->type == Tfile)
+ fflag = 1;
+
+ if(type == nil) {
+ switch(disk->type){
+ case Tfile:
+ type = "3½HD";
+ break;
+ case Tfloppy:
+ seek(disk->ctlfd, 0, 0);
+ n = read(disk->ctlfd, buf, 10);
+ if(n <= 0 || n >= 10)
+ fatal("reading floppy type");
+ buf[n] = 0;
+ type = strdup(buf);
+ if(type == nil)
+ fatal("out of memory");
+ break;
+ case Tsd:
+ type = "hard";
+ break;
+ default:
+ type = "unknown";
+ break;
+ }
+ }
+
+ if(!fflag && disk->type == Tfloppy)
+ if(fprint(disk->ctlfd, "format %s", type) < 0)
+ fatal("formatting floppy as %s: %r", type);
+
+ if(disk->type != Tfloppy)
+ sanitycheck(disk);
+
+ /* check that everything will succeed */
+ dosfs(dos, writepbs, disk, label, argc-1, argv+1, 0);
+
+ /* commit */
+ dosfs(dos, writepbs, disk, label, argc-1, argv+1, 1);
+
+ print("used %lld bytes\n", fatlast*clustersize*disk->secsize);
+ exits(0);
+}
+
+/*
+ * 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.
+ */
+void
+sanitycheck(Disk *disk)
+{
+ char buf[512];
+ int bad;
+
+ if(xflag)
+ return;
+
+ bad = 0;
+ if(dos && nresrv < 2 && seek(disk->fd, disk->secsize, 0) == disk->secsize
+ && read(disk->fd, buf, sizeof(buf)) >= 5 && strncmp(buf, "part ", 5) == 0) {
+ fprint(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->type == Tsd && disk->offset == 0LL) {
+ fprint(2,
+ "you're attempting to format your disk (/dev/sdXX/data)\n"
+ "rather than a partition like /dev/sdXX/9fat;\n"
+ "this is likely a mistake. specify -x to disable this check.\n");
+ bad = 1;
+ }
+
+ if(bad)
+ exits("failed disk sanity check");
+}
+
+/*
+ * Return the BIOS drive number for the disk.
+ * 0x80 is the first fixed disk, 0x81 the next, etc.
+ * We map sdC0=0x80, sdC1=0x81, sdD0=0x82, sdD1=0x83
+ */
+int
+getdriveno(Disk *disk)
+{
+ char buf[64], *p;
+
+ if(disk->type != Tsd)
+ return 0x80; /* first hard disk */
+
+ if(fd2path(disk->fd, buf, sizeof(buf)) < 0)
+ return 0x80;
+
+ /*
+ * 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(buf[0] == '#' && buf[1] == 'S')
+ buf[1] = '/';
+
+ for(p=buf; *p; p++)
+ if(p[0] == 's' && p[1] == 'd' && (p[2]=='C' || p[2]=='D') &&
+ (p[3]=='0' || p[3]=='1'))
+ return 0x80 + (p[2]-'C')*2 + (p[3]-'0');
+
+ return 0x80;
+}
+
+long
+writen(int fd, void *buf, long n)
+{
+ long m, tot;
+
+ /* write 8k at a time, to be nice to the disk subsystem */
+ for(tot=0; tot<n; tot+=m){
+ m = n - tot;
+ if(m > 8192)
+ m = 8192;
+ if(write(fd, (uchar*)buf+tot, m) != m)
+ break;
+ }
+ return tot;
+}
+
+void
+dosfs(int dofat, int dopbs, Disk *disk, char *label, int argc, char *argv[], int commit)
+{
+ char r[16];
+ Dosboot *b;
+ uchar *buf, *pbsbuf, *p;
+ Dir *d;
+ int i, data, newclusters, npbs, n, sysfd;
+ ulong x;
+ vlong length, secsize;
+
+ if(dofat == 0 && dopbs == 0)
+ return;
+
+ for(t = floppytype; t < &floppytype[NTYPES]; t++)
+ if(strcmp(type, t->name) == 0)
+ break;
+ if(t == &floppytype[NTYPES])
+ fatal("unknown floppy type %s", type);
+
+ if(t->sectors == 0 && strcmp(type, "hard") == 0) {
+ t->sectors = disk->s;
+ t->heads = disk->h;
+ t->tracks = disk->c;
+ }
+
+ if(t->sectors == 0 && dofat)
+ fatal("cannot format fat with type %s: geometry unknown\n", type);
+
+ if(fflag){
+ disk->size = t->bytes*t->sectors*t->heads*t->tracks;
+ disk->secsize = t->bytes;
+ disk->secs = disk->size / disk->secsize;
+ }
+
+ secsize = disk->secsize;
+ length = disk->size;
+
+ buf = malloc(secsize);
+ if(buf == 0)
+ fatal("out of memory");
+
+ /*
+ * Make disk full size if a file.
+ */
+ if(fflag && disk->type == Tfile){
+ if((d = dirfstat(disk->wfd)) == nil)
+ fatal("fstat disk: %r");
+ if(commit && d->length < disk->size) {
+ if(seek(disk->wfd, disk->size-1, 0) < 0)
+ fatal("seek to 9: %r");
+ if(write(disk->wfd, "9", 1) < 0)
+ fatal("writing 9: @%lld %r", seek(disk->wfd, 0LL, 1));
+ }
+ free(d);
+ }
+
+ /*
+ * Start with initial sector from disk
+ */
+ if(seek(disk->fd, 0, 0) < 0)
+ fatal("seek to boot sector: %r\n");
+ if(commit && read(disk->fd, buf, secsize) != secsize)
+ fatal("reading boot sector: %r");
+
+ if(dofat)
+ memset(buf, 0, sizeof(Dosboot));
+
+ /*
+ * Jump instruction and OEM name.
+ */
+ b = (Dosboot*)buf;
+ b->magic[0] = 0xEB;
+ b->magic[1] = 0x3C;
+ b->magic[2] = 0x90;
+ memmove(b->version, "Plan9.00", sizeof(b->version));
+
+ /*
+ * Add bootstrapping code; assume it starts
+ * at 0x3E (the destination of the jump we just
+ * wrote to b->magic).
+ */
+ if(dopbs) {
+ pbsbuf = malloc(secsize);
+ if(pbsbuf == 0)
+ fatal("out of memory");
+
+ if(pbs){
+ if((sysfd = open(pbs, OREAD)) < 0)
+ fatal("open %s: %r", pbs);
+ if((npbs = read(sysfd, pbsbuf, secsize)) < 0)
+ fatal("read %s: %r", pbs);
+
+ if(npbs > secsize-2)
+ fatal("boot block too large");
+
+ close(sysfd);
+ }
+ else {
+ memmove(pbsbuf, bootprog, sizeof(bootprog));
+ npbs = nbootprog;
+ }
+ if(npbs <= 0x3E)
+ fprint(2, "warning: pbs too small\n");
+ else
+ memmove(buf+0x3E, pbsbuf+0x3E, npbs-0x3E);
+
+ free(pbsbuf);
+ }
+
+ /*
+ * Add FAT BIOS parameter block.
+ */
+ if(dofat) {
+ if(commit) {
+ print("Initializing FAT file system\n");
+ print("type %s, %d tracks, %d heads, %d sectors/track, %lld 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.
+ */
+ fatbits = 12;
+Tryagain:
+ volsecs = length/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/sizeof(Dosdir));
+ if(rootfiles > 512){
+ rootfiles = 512;
+ rootsecs = rootfiles/(secsize/sizeof(Dosdir));
+ }
+ data = nresrv + 2*fatsecs + (rootfiles*sizeof(Dosdir) + secsize-1)/secsize;
+ newclusters = 2 + (volsecs - data)/clustersize;
+ if(newclusters == clusters)
+ break;
+ clusters = newclusters;
+ if(i > 10)
+ fatal("can't decide how many clusters to use (%d? %d?)", clusters, newclusters);
+if(chatty) print("clusters %d\n", clusters);
+ }
+
+if(chatty) print("try %d fatbits => %d clusters of %d\n", fatbits, clusters, clustersize);
+ switch(fatbits){
+ case 12:
+ if(clusters >= 4087){
+ fatbits = 16;
+ goto Tryagain;
+ }
+ break;
+ case 16:
+ if(clusters >= 65527)
+ fatal("disk too big; implement fat32");
+ break;
+ }
+ PUTSHORT(b->sectsize, secsize);
+ b->clustsize = clustersize;
+ PUTSHORT(b->nresrv, nresrv);
+ b->nfats = 2;
+ PUTSHORT(b->rootsize, rootfiles);
+ if(volsecs < (1<<16))
+ PUTSHORT(b->volsize, volsecs);
+ b->mediadesc = t->media;
+ PUTSHORT(b->fatsize, fatsecs);
+ PUTSHORT(b->trksize, t->sectors);
+ PUTSHORT(b->nheads, t->heads);
+ PUTLONG(b->nhidden, disk->offset);
+ PUTLONG(b->bigvolsize, volsecs);
+
+ /*
+ * Extended BIOS Parameter Block.
+ */
+ if(t->media == 0xF8)
+ b->driveno = getdriveno(disk);
+ else
+ b->driveno = 0;
+if(chatty) print("driveno = %ux\n", b->driveno);
+
+ b->bootsig = 0x29;
+ x = disk->offset + b->nfats*fatsecs + nresrv;
+ PUTLONG(b->volid, x);
+if(chatty) print("volid = %lux %lux\n", x, GETLONG(b->volid));
+ memmove(b->label, label, sizeof(b->label));
+ sprint(r, "FAT%d ", fatbits);
+ memmove(b->type, r, sizeof(b->type));
+ }
+
+ buf[secsize-2] = 0x55;
+ buf[secsize-1] = 0xAA;
+
+ if(commit) {
+ if(seek(disk->wfd, 0, 0) < 0)
+ fatal("seek to boot sector: %r\n");
+ if(write(disk->wfd, buf, secsize) != secsize)
+ fatal("writing boot sector: %r");
+ }
+
+ free(buf);
+
+ /*
+ * If we were only called to write the PBS, leave now.
+ */
+ if(dofat == 0)
+ return;
+
+ /*
+ * allocate an in memory fat
+ */
+ if(seek(disk->wfd, nresrv*secsize, 0) < 0)
+ fatal("seek to fat: %r\n");
+if(chatty) print("fat @%lluX\n", seek(disk->wfd, 0, 1));
+ fat = malloc(fatsecs*secsize);
+ if(fat == 0)
+ fatal("out of memory");
+ memset(fat, 0, fatsecs*secsize);
+ fat[0] = t->media;
+ fat[1] = 0xff;
+ fat[2] = 0xff;
+ if(fatbits == 16)
+ fat[3] = 0xff;
+ fatlast = 1;
+ if(seek(disk->wfd, 2*fatsecs*secsize, 1) < 0) /* 2 fats */
+ fatal("seek to root: %r");
+if(chatty) print("root @%lluX\n", seek(disk->wfd, 0LL, 1));
+
+ /*
+ * allocate an in memory root
+ */
+ root = malloc(rootsecs*secsize);
+ if(root == 0)
+ fatal("out of memory");
+ memset(root, 0, rootsecs*secsize);
+ if(seek(disk->wfd, rootsecs*secsize, 1) < 0) /* rootsecs */
+ fatal("seek to files: %r");
+if(chatty) print("files @%lluX\n", seek(disk->wfd, 0LL, 1));
+
+ /*
+ * Now positioned at the Files Area.
+ * If we have any arguments, process
+ * them and write out.
+ */
+ for(p = root; argc > 0; argc--, argv++, p += sizeof(Dosdir)){
+ if(p >= (root+(rootsecs*secsize)))
+ fatal("too many files in root");
+ /*
+ * Open the file and get its length.
+ */
+ if((sysfd = open(*argv, OREAD)) < 0)
+ fatal("open %s: %r", *argv);
+ if((d = dirfstat(sysfd)) == nil)
+ fatal("stat %s: %r", *argv);
+ if(d->length > 0xFFFFFFFFU)
+ fatal("file %s too big\n", *argv, d->length);
+ if(commit)
+ print("Adding file %s, length %lld\n", *argv, d->length);
+
+ length = d->length;
+ if(length){
+ /*
+ * 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 += secsize*clustersize - 1;
+ length /= secsize*clustersize;
+ length *= secsize*clustersize;
+ if((buf = malloc(length)) == 0)
+ fatal("out of memory");
+
+ if(readn(sysfd, buf, d->length) != d->length)
+ fatal("read %s: %r", *argv);
+ memset(buf+d->length, 0, length-d->length);
+if(chatty) print("%s @%lluX\n", d->name, seek(disk->wfd, 0LL, 1));
+ if(commit && writen(disk->wfd, buf, length) != length)
+ fatal("write %s: %r", *argv);
+ free(buf);
+
+ close(sysfd);
+
+ /*
+ * 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 /= secsize*clustersize;
+ x = clustalloc(Sof);
+ for(n = 0; n < length-1; n++)
+ clustalloc(0);
+ clustalloc(Eof);
+ }
+ else
+ x = 0;
+
+ /*
+ * Add the filename to the root.
+ */
+fprint(2, "add %s at clust %lux\n", d->name, x);
+ addrname(p, d, *argv, x);
+ free(d);
+ }
+
+ /*
+ * write the fats and root
+ */
+ if(commit) {
+ if(seek(disk->wfd, nresrv*secsize, 0) < 0)
+ fatal("seek to fat #1: %r");
+ if(write(disk->wfd, fat, fatsecs*secsize) < 0)
+ fatal("writing fat #1: %r");
+ if(write(disk->wfd, fat, fatsecs*secsize) < 0)
+ fatal("writing fat #2: %r");
+ if(write(disk->wfd, root, rootsecs*secsize) < 0)
+ fatal("writing root: %r");
+ }
+
+ free(fat);
+ free(root);
+}
+
+/*
+ * allocate a cluster
+ */
+ulong
+clustalloc(int flag)
+{
+ ulong o, x;
+
+ if(flag != Sof){
+ x = (flag == Eof) ? 0xffff : (fatlast+1);
+ if(fatbits == 12){
+ x &= 0xfff;
+ o = (3*fatlast)/2;
+ if(fatlast & 1){
+ fat[o] = (fat[o]&0x0f) | (x<<4);
+ fat[o+1] = (x>>4);
+ } else {
+ fat[o] = x;
+ fat[o+1] = (fat[o+1]&0xf0) | ((x>>8) & 0x0F);
+ }
+ } else {
+ o = 2*fatlast;
+ fat[o] = x;
+ fat[o+1] = x>>8;
+ }
+ }
+
+ if(flag == Eof)
+ return 0;
+ else{
+ ++fatlast;
+ if(fatlast >= clusters)
+ sysfatal("data does not fit on disk (%d %d)", fatlast, clusters);
+ return fatlast;
+ }
+}
+
+void
+putname(char *p, Dosdir *d)
+{
+ int i;
+
+ memset(d->name, ' ', sizeof d->name+sizeof d->ext);
+ for(i = 0; i< sizeof(d->name); i++){
+ if(*p == 0 || *p == '.')
+ break;
+ d->name[i] = toupper(*p++);
+ }
+ p = strrchr(p, '.');
+ if(p){
+ for(i = 0; i < sizeof d->ext; i++){
+ if(*++p == 0)
+ break;
+ d->ext[i] = toupper(*p);
+ }
+ }
+}
+
+void
+puttime(Dosdir *d)
+{
+ Tm *t = localtime(time(0));
+ ushort x;
+
+ x = (t->hour<<11) | (t->min<<5) | (t->sec>>1);
+ d->time[0] = x;
+ d->time[1] = x>>8;
+ x = ((t->year-80)<<9) | ((t->mon+1)<<5) | t->mday;
+ d->date[0] = x;
+ d->date[1] = x>>8;
+}
+
+void
+addrname(uchar *entry, Dir *dir, char *name, ulong start)
+{
+ char *s;
+ Dosdir *d;
+
+ s = strrchr(name, '/');
+ if(s)
+ s++;
+ else
+ s = name;
+
+ d = (Dosdir*)entry;
+ putname(s, d);
+ if(strcmp(s, "9load") == 0)
+ d->attr = DSYSTEM;
+ else
+ d->attr = 0;
+ puttime(d);
+ d->start[0] = start;
+ d->start[1] = start>>8;
+ d->length[0] = dir->length;
+ d->length[1] = dir->length>>8;
+ d->length[2] = dir->length>>16;
+ d->length[3] = dir->length>>24;
+}
diff --git a/sys/src/cmd/disk/kfs/9p1.c b/sys/src/cmd/disk/kfs/9p1.c
new file mode 100755
index 000000000..2a3b5e9c8
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/9p1.c
@@ -0,0 +1,1444 @@
+#include "all.h"
+#include "9p1.h"
+
+/*
+ * buggery to give false qid for
+ * the top 2 levels of the dump fs
+ */
+void
+mkqid(Qid* qid, Dentry *d, int buggery)
+{
+ int c;
+
+ if(buggery && d->qid.path == QPROOT && (d->qid.path & QPDIR)){
+ c = d->name[0];
+ if(c >= '0' && c <= '9'){
+ qid->path = 3;
+ qid->vers = d->qid.version;
+ qid->type = QTDIR;
+
+ c = (c-'0')*10 + (d->name[1]-'0');
+ if(c >= 1 && c <= 12)
+ qid->path = 4;
+ return;
+ }
+ }
+
+ mkqid9p2(qid, &d->qid, d->mode);
+}
+
+int
+mkqidcmp(Qid* qid, Dentry *d)
+{
+ Qid tmp;
+
+ mkqid(&tmp, d, 1);
+ if(qid->path==tmp.path && (qid->type&QTDIR)==(tmp.type&QTDIR))
+ return 0;
+ return Eqid;
+}
+
+void
+f_nop(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+
+ USED(in);
+ USED(ou);
+ if(CHAT(cp))
+ print("c_nop %d\n", cp->chan);
+}
+
+void
+f_flush(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+
+ USED(in);
+ USED(ou);
+ if(CHAT(cp))
+ print("c_flush %d\n", cp->chan);
+ runlock(&cp->reflock);
+ wlock(&cp->reflock);
+ wunlock(&cp->reflock);
+ rlock(&cp->reflock);
+}
+
+void
+f_session(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ if(CHAT(cp))
+ print("c_session %d\n", cp->chan);
+
+ memmove(cp->rchal, in->chal, sizeof(cp->rchal));
+ if(wstatallow || cp == cons.srvchan){
+ memset(ou->chal, 0, sizeof(ou->chal));
+ memset(ou->authid, 0, sizeof(ou->authid));
+ }else{
+ mkchallenge(cp);
+ memmove(ou->chal, cp->chal, sizeof(ou->chal));
+ memmove(ou->authid, nvr.authid, sizeof(ou->authid));
+ }
+ sprint(ou->authdom, "%s.%s", service, nvr.authdom);
+ fileinit(cp);
+}
+
+void
+f_attach(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ Iobuf *p;
+ Dentry *d;
+ File *f;
+ int u;
+ Filsys *fs;
+ long raddr;
+
+ if(CHAT(cp)) {
+ print("c_attach %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ print(" uid = %s\n", in->uname);
+ print(" arg = %s\n", in->aname);
+ }
+
+ ou->qid = QID9P1(0,0);
+ ou->fid = in->fid;
+ if(!in->aname[0]) /* default */
+ strncpy(in->aname, filesys[0].name, sizeof(in->aname));
+ p = 0;
+ f = filep(cp, in->fid, 1);
+ if(!f) {
+ ou->err = Efid;
+ goto out;
+ }
+ u = -1;
+ if(cp != cons.chan){
+ if(authorize(cp, in, ou) == 0 || strcmp(in->uname, "adm") == 0){
+ ou->err = Eauth;
+ goto out;
+ }
+ u = strtouid(in->uname);
+ if(u < 0){
+ ou->err = Ebadu;
+ goto out;
+ }
+ }
+
+ fs = fsstr(in->aname);
+ if(fs == 0) {
+ ou->err = Ebadspc;
+ goto out;
+ }
+ raddr = getraddr(fs->dev);
+ p = getbuf(fs->dev, raddr, Bread);
+ d = getdir(p, 0);
+ if(!d || checktag(p, Tdir, QPROOT) || !(d->mode & DALLOC)) {
+ ou->err = Ealloc;
+ goto out;
+ }
+ f->uid = u;
+ if(iaccess(f, d, DREAD)) {
+ ou->err = Eaccess;
+ goto out;
+ }
+ accessdir(p, d, FREAD);
+ mkqid(&f->qid, d, 1);
+ f->fs = fs;
+ f->addr = raddr;
+ f->slot = 0;
+ f->open = 0;
+ freewp(f->wpath);
+ f->wpath = 0;
+
+ mkqid9p1(&ou->qid, &f->qid);
+
+out:
+ if(p)
+ putbuf(p);
+ if(f) {
+ qunlock(f);
+ if(ou->err)
+ freefp(f);
+ }
+}
+
+void
+f_clone(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ File *f1, *f2;
+ int fid, fid1;
+
+ if(CHAT(cp)) {
+ print("c_clone %d\n", cp->chan);
+ print(" old fid = %d\n", in->fid);
+ print(" new fid = %d\n", in->newfid);
+ }
+
+ fid = in->fid;
+ fid1 = in->newfid;
+
+ f1 = 0;
+ f2 = 0;
+ if(fid < fid1) {
+ f1 = filep(cp, fid, 0);
+ f2 = filep(cp, fid1, 1);
+ } else
+ if(fid1 < fid) {
+ f2 = filep(cp, fid1, 1);
+ f1 = filep(cp, fid, 0);
+ }
+ if(!f1 || !f2) {
+ ou->err = Efid;
+ goto out;
+ }
+
+
+ f2->fs = f1->fs;
+ f2->addr = f1->addr;
+ f2->open = f1->open & ~FREMOV;
+ f2->uid = f1->uid;
+ f2->slot = f1->slot;
+ f2->qid = f1->qid;
+
+ freewp(f2->wpath);
+ f2->wpath = getwp(f1->wpath);
+
+out:
+ ou->fid = fid;
+ if(f1)
+ qunlock(f1);
+ if(f2)
+ qunlock(f2);
+}
+
+void
+f_walk(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ Iobuf *p, *p1;
+ Dentry *d, *d1;
+ File *f;
+ Wpath *w, *ow;
+ int slot;
+ long addr;
+
+ if(CHAT(cp)) {
+ print("c_walk %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ print(" name = %s\n", in->name);
+ }
+
+ ou->fid = in->fid;
+ ou->qid = QID9P1(0,0);
+ p = 0;
+ f = filep(cp, in->fid, 0);
+ if(!f) {
+ ou->err = Efid;
+ goto out;
+ }
+ p = getbuf(f->fs->dev, f->addr, Bread);
+ d = getdir(p, f->slot);
+ if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+ ou->err = Ealloc;
+ goto out;
+ }
+ if(!(d->mode & DDIR)) {
+ ou->err = Edir1;
+ goto out;
+ }
+ if(ou->err = mkqidcmp(&f->qid, d))
+ goto out;
+ if(cp != cons.chan && iaccess(f, d, DEXEC)) {
+ ou->err = Eaccess;
+ goto out;
+ }
+ accessdir(p, d, FREAD);
+ if(strcmp(in->name, ".") == 0)
+ goto setdot;
+ if(strcmp(in->name, "..") == 0) {
+ if(f->wpath == 0)
+ goto setdot;
+ putbuf(p);
+ p = 0;
+ addr = f->wpath->addr;
+ slot = f->wpath->slot;
+ p1 = getbuf(f->fs->dev, addr, Bread);
+ d1 = getdir(p1, slot);
+ if(!d1 || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)) {
+ if(p1)
+ putbuf(p1);
+ ou->err = Ephase;
+ goto out;
+ }
+ ow = f->wpath;
+ f->wpath = ow->up;
+ putwp(ow);
+ goto found;
+ }
+ for(addr=0;; addr++) {
+ p1 = dnodebuf(p, d, addr, 0);
+ if(!p1 || checktag(p1, Tdir, d->qid.path) ) {
+ if(p1)
+ putbuf(p1);
+ ou->err = Eentry;
+ goto out;
+ }
+ for(slot=0; slot<DIRPERBUF; slot++) {
+ d1 = getdir(p1, slot);
+ if(!(d1->mode & DALLOC))
+ continue;
+ if(strncmp(in->name, d1->name, sizeof(in->name)))
+ continue;
+ /*
+ * update walk path
+ */
+ w = newwp();
+ if(!w) {
+ ou->err = Ewalk;
+ putbuf(p1);
+ goto out;
+ }
+ w->addr = f->addr;
+ w->slot = f->slot;
+ w->up = f->wpath;
+ f->wpath = w;
+ slot += DIRPERBUF*addr;
+ goto found;
+ }
+ putbuf(p1);
+ }
+
+found:
+ f->addr = p1->addr;
+ mkqid(&f->qid, d1, 1);
+ putbuf(p1);
+ f->slot = slot;
+
+setdot:
+ mkqid9p1(&ou->qid, &f->qid);
+ f->open = 0;
+
+out:
+ if(p)
+ putbuf(p);
+ if(f)
+ qunlock(f);
+}
+
+void
+f_clunk(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ File *f;
+ Tlock *t;
+ long tim;
+
+ if(CHAT(cp)) {
+ print("c_clunk %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ }
+
+ f = filep(cp, in->fid, 0);
+ if(!f) {
+ print("%p\n", f);
+ ou->err = Efid;
+ goto out;
+ }
+ if(t = f->tlock) {
+ tim = time(0);
+ if(t->time < tim || t->file != f)
+ ou->err = Ebroken;
+ t->time = 0; /* free the lock */
+ f->tlock = 0;
+ }
+ if(f->open & FREMOV)
+ ou->err = doremove(f, 0);
+ f->open = 0;
+ freewp(f->wpath);
+ freefp(f);
+
+out:
+ if(f)
+ qunlock(f);
+ ou->fid = in->fid;
+}
+
+void
+f_clwalk(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ int er, fid;
+
+ if(CHAT(cp))
+ print("c_clwalk macro\n");
+
+ f_clone(cp, in, ou); /* sets tag, fid */
+ if(ou->err)
+ return;
+ fid = in->fid;
+ in->fid = in->newfid;
+ f_walk(cp, in, ou); /* sets tag, fid, qid */
+ er = ou->err;
+ if(er == Eentry) {
+ /*
+ * if error is "no entry"
+ * return non error and fid
+ */
+ ou->err = 0;
+ f_clunk(cp, in, ou); /* sets tag, fid */
+ ou->err = 0;
+ ou->fid = fid;
+ if(CHAT(cp))
+ print(" error: %s\n", errstring[er]);
+ return;
+ }
+ if(er) {
+ /*
+ * if any other error
+ * return an error
+ */
+ ou->err = 0;
+ f_clunk(cp, in, ou); /* sets tag, fid */
+ ou->err = er;
+ return;
+ }
+ /*
+ * non error
+ * return newfid
+ */
+}
+
+void
+f_open(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ Iobuf *p;
+ Dentry *d;
+ File *f;
+ Tlock *t;
+ Qid qid;
+ int ro, fmod;
+
+ if(CHAT(cp)) {
+ print("c_open %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ print(" mode = %o\n", in->mode);
+ }
+
+ p = 0;
+ f = filep(cp, in->fid, 0);
+ if(!f) {
+ ou->err = Efid;
+ goto out;
+ }
+
+ /*
+ * if remove on close, check access here
+ */
+ ro = isro(f->fs->dev) || (cp != cons.chan && writegroup && !ingroup(f->uid, writegroup));
+ if(in->mode & MRCLOSE) {
+ if(ro) {
+ ou->err = Eronly;
+ goto out;
+ }
+ /*
+ * check on parent directory of file to be deleted
+ */
+ if(f->wpath == 0 || f->wpath->addr == f->addr) {
+ ou->err = Ephase;
+ goto out;
+ }
+ p = getbuf(f->fs->dev, f->wpath->addr, Bread);
+ d = getdir(p, f->wpath->slot);
+ if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+ ou->err = Ephase;
+ goto out;
+ }
+ if(iaccess(f, d, DWRITE)) {
+ ou->err = Eaccess;
+ goto out;
+ }
+ putbuf(p);
+ }
+ p = getbuf(f->fs->dev, f->addr, Bread);
+ d = getdir(p, f->slot);
+ if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+ ou->err = Ealloc;
+ goto out;
+ }
+ if(ou->err = mkqidcmp(&f->qid, d))
+ goto out;
+ mkqid(&qid, d, 1);
+ switch(in->mode & 7) {
+
+ case MREAD:
+ if(iaccess(f, d, DREAD) && !writeallow)
+ goto badaccess;
+ fmod = FREAD;
+ break;
+
+ case MWRITE:
+ if((d->mode & DDIR) ||
+ (iaccess(f, d, DWRITE) && !writeallow))
+ goto badaccess;
+ if(ro) {
+ ou->err = Eronly;
+ goto out;
+ }
+ fmod = FWRITE;
+ break;
+
+ case MBOTH:
+ if((d->mode & DDIR) ||
+ (iaccess(f, d, DREAD) && !writeallow) ||
+ (iaccess(f, d, DWRITE) && !writeallow))
+ goto badaccess;
+ if(ro) {
+ ou->err = Eronly;
+ goto out;
+ }
+ fmod = FREAD+FWRITE;
+ break;
+
+ case MEXEC:
+ if((d->mode & DDIR) ||
+ iaccess(f, d, DEXEC))
+ goto badaccess;
+ fmod = FREAD;
+ break;
+
+ default:
+ ou->err = Emode;
+ goto out;
+ }
+ if(in->mode & MTRUNC) {
+ if((d->mode & DDIR) ||
+ (iaccess(f, d, DWRITE) && !writeallow))
+ goto badaccess;
+ if(ro) {
+ ou->err = Eronly;
+ goto out;
+ }
+ }
+ t = 0;
+ if(d->mode & DLOCK) {
+ t = tlocked(p, d);
+ if(t == 0) {
+ ou->err = Elocked;
+ goto out;
+ }
+ t->file = f;
+ }
+ if(in->mode & MRCLOSE)
+ fmod |= FREMOV;
+ f->open = fmod;
+ if(in->mode & MTRUNC)
+ if(!(d->mode & DAPND))
+ dtrunc(p, d);
+ f->tlock = t;
+ f->lastra = 0;
+ mkqid9p1(&ou->qid, &qid);
+ goto out;
+
+badaccess:
+ ou->err = Eaccess;
+ f->open = 0;
+
+out:
+ if(p)
+ putbuf(p);
+ if(f)
+ qunlock(f);
+ ou->fid = in->fid;
+}
+
+void
+f_create(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ Iobuf *p, *p1;
+ Dentry *d, *d1;
+ File *f;
+ int slot, slot1, fmod;
+ long addr, addr1, path;
+ Qid qid;
+ Tlock *t;
+ Wpath *w;
+
+ if(CHAT(cp)) {
+ print("c_create %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ print(" name = %s\n", in->name);
+ print(" perm = %lx+%lo\n", (in->perm>>28)&0xf,
+ in->perm&0777);
+ print(" mode = %d\n", in->mode);
+ }
+
+ p = 0;
+ f = filep(cp, in->fid, 0);
+ if(!f) {
+ ou->err = Efid;
+ goto out;
+ }
+ if(isro(f->fs->dev) || (cp != cons.chan && writegroup && !ingroup(f->uid, writegroup))) {
+ ou->err = Eronly;
+ goto out;
+ }
+
+ p = getbuf(f->fs->dev, f->addr, Bread);
+ d = getdir(p, f->slot);
+ if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+ ou->err = Ealloc;
+ goto out;
+ }
+ if(ou->err = mkqidcmp(&f->qid, d))
+ goto out;
+ if(!(d->mode & DDIR)) {
+ ou->err = Edir2;
+ goto out;
+ }
+ if(cp != cons.chan && iaccess(f, d, DWRITE) && !writeallow) {
+ ou->err = Eaccess;
+ goto out;
+ }
+ accessdir(p, d, FREAD);
+ if(!strncmp(in->name, ".", sizeof(in->name)) ||
+ !strncmp(in->name, "..", sizeof(in->name))) {
+ ou->err = Edot;
+ goto out;
+ }
+ if(checkname(in->name)) {
+ ou->err = Ename;
+ goto out;
+ }
+ addr1 = 0;
+ slot1 = 0; /* set */
+ for(addr=0;; addr++) {
+ p1 = dnodebuf(p, d, addr, 0);
+ if(!p1) {
+ if(addr1)
+ break;
+ p1 = dnodebuf(p, d, addr, Tdir);
+ }
+ if(p1 == 0) {
+ ou->err = Efull;
+ goto out;
+ }
+ if(checktag(p1, Tdir, d->qid.path)) {
+ putbuf(p1);
+ goto phase;
+ }
+ for(slot=0; slot<DIRPERBUF; slot++) {
+ d1 = getdir(p1, slot);
+ if(!(d1->mode & DALLOC)) {
+ if(!addr1) {
+ addr1 = p1->addr;
+ slot1 = slot + addr*DIRPERBUF;
+ }
+ continue;
+ }
+ if(!strncmp(in->name, d1->name, sizeof(in->name))) {
+ putbuf(p1);
+ ou->err = Eexist;
+ goto out;
+ }
+ }
+ putbuf(p1);
+ }
+ switch(in->mode & 7) {
+ case MEXEC:
+ case MREAD: /* seems only useful to make directories */
+ fmod = FREAD;
+ break;
+
+ case MWRITE:
+ fmod = FWRITE;
+ break;
+
+ case MBOTH:
+ fmod = FREAD+FWRITE;
+ break;
+
+ default:
+ ou->err = Emode;
+ goto out;
+ }
+ if(in->perm & PDIR)
+ if((in->mode & MTRUNC) || (in->perm & PAPND) || (fmod & FWRITE))
+ goto badaccess;
+ /*
+ * do it
+ */
+ path = qidpathgen(&f->fs->dev);
+ p1 = getbuf(f->fs->dev, addr1, Bread|Bimm|Bmod);
+ d1 = getdir(p1, slot1);
+ if(!d1 || checktag(p1, Tdir, d->qid.path)) {
+ if(p1)
+ putbuf(p1);
+ goto phase;
+ }
+ if(d1->mode & DALLOC) {
+ putbuf(p1);
+ goto phase;
+ }
+
+ strncpy(d1->name, in->name, sizeof(in->name));
+ /*
+ * bogus argument passing -- see console.c
+ */
+ if(cp == cons.chan) {
+ d1->uid = cons.uid;
+ d1->gid = cons.gid;
+ } else {
+ d1->uid = f->uid;
+ d1->gid = d->gid;
+ in->perm &= d->mode | ~0666;
+ if(in->perm & PDIR)
+ in->perm &= d->mode | ~0777;
+ }
+ d1->qid.path = path;
+ d1->qid.version = 0;
+ d1->mode = DALLOC | (in->perm & 0777);
+ if(in->perm & PDIR) {
+ d1->mode |= DDIR;
+ d1->qid.path |= QPDIR;
+ }
+ if(in->perm & PAPND)
+ d1->mode |= DAPND;
+ t = 0;
+ if(in->perm & PLOCK) {
+ d1->mode |= DLOCK;
+ t = tlocked(p1, d1);
+ }
+ accessdir(p1, d1, FWRITE);
+ mkqid(&qid, d1, 0);
+ putbuf(p1);
+ accessdir(p, d, FWRITE);
+
+ /*
+ * do a walk to new directory entry
+ */
+ w = newwp();
+ if(!w) {
+ ou->err = Ewalk;
+ goto out;
+ }
+ w->addr = f->addr;
+ w->slot = f->slot;
+ w->up = f->wpath;
+ f->wpath = w;
+ f->qid = qid;
+ f->tlock = t;
+ f->lastra = 0;
+ if(in->mode & MRCLOSE)
+ fmod |= FREMOV;
+ f->open = fmod;
+ f->addr = addr1;
+ f->slot = slot1;
+ if(t)
+ t->file = f;
+ mkqid9p1(&ou->qid, &qid);
+ goto out;
+
+badaccess:
+ ou->err = Eaccess;
+ goto out;
+
+phase:
+ ou->err = Ephase;
+
+out:
+ if(p)
+ putbuf(p);
+ if(f)
+ qunlock(f);
+ ou->fid = in->fid;
+}
+
+void
+f_read(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ Iobuf *p, *p1;
+ File *f;
+ Dentry *d, *d1;
+ Tlock *t;
+ long addr, offset, tim;
+ int nread, count, n, o, slot;
+
+ if(CHAT(cp)) {
+ print("c_read %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ print(" offset = %ld\n", in->offset);
+ print(" count = %ld\n", in->count);
+ }
+
+ p = 0;
+ count = in->count;
+ offset = in->offset;
+ nread = 0;
+ f = filep(cp, in->fid, 0);
+ if(!f) {
+ ou->err = Efid;
+ goto out;
+ }
+ if(!(f->open & FREAD)) {
+ ou->err = Eopen;
+ goto out;
+ }
+ if(count < 0 || count > MAXDAT) {
+ ou->err = Ecount;
+ goto out;
+ }
+ if(offset < 0) {
+ ou->err = Eoffset;
+ goto out;
+ }
+ p = getbuf(f->fs->dev, f->addr, Bread);
+ d = getdir(p, f->slot);
+ if(!d || !(d->mode & DALLOC)) {
+ ou->err = Ealloc;
+ goto out;
+ }
+ if(ou->err = mkqidcmp(&f->qid, d))
+ goto out;
+ if(t = f->tlock) {
+ tim = time(0);
+ if(t->time < tim || t->file != f) {
+ ou->err = Ebroken;
+ goto out;
+ }
+ /* renew the lock */
+ t->time = tim + TLOCK;
+ }
+ accessdir(p, d, FREAD);
+ if(d->mode & DDIR) {
+ addr = 0;
+ goto dread;
+ }
+ if(offset+count > d->size)
+ count = d->size - offset;
+ while(count > 0) {
+ addr = offset / BUFSIZE;
+ if(addr == f->lastra+1)
+ dbufread(p, d, addr+1);
+ f->lastra = addr;
+ o = offset % BUFSIZE;
+ n = BUFSIZE - o;
+ if(n > count)
+ n = count;
+ p1 = dnodebuf(p, d, addr, 0);
+ if(p1) {
+ if(checktag(p1, Tfile, QPNONE)) {
+ ou->err = Ephase;
+ putbuf(p1);
+ goto out;
+ }
+ memmove(ou->data+nread, p1->iobuf+o, n);
+ putbuf(p1);
+ } else
+ memset(ou->data+nread, 0, n);
+ count -= n;
+ nread += n;
+ offset += n;
+ }
+ goto out;
+
+dread:
+ p1 = dnodebuf(p, d, addr, 0);
+ if(!p1)
+ goto out;
+ if(checktag(p1, Tdir, QPNONE)) {
+ ou->err = Ephase;
+ putbuf(p1);
+ goto out;
+ }
+ n = DIRREC;
+ for(slot=0; slot<DIRPERBUF; slot++) {
+ d1 = getdir(p1, slot);
+ if(!(d1->mode & DALLOC))
+ continue;
+ if(offset >= n) {
+ offset -= n;
+ continue;
+ }
+ if(count < n) {
+ putbuf(p1);
+ goto out;
+ }
+ if(convD2M9p1(d1, ou->data+nread) != n)
+ print("dirread convD2M\n");
+ nread += n;
+ count -= n;
+ }
+ putbuf(p1);
+ addr++;
+ goto dread;
+
+out:
+ count = in->count - nread;
+ if(count > 0)
+ memset(ou->data+nread, 0, count);
+ if(p)
+ putbuf(p);
+ if(f)
+ qunlock(f);
+ ou->fid = in->fid;
+ ou->count = nread;
+ if(CHAT(cp))
+ print(" nread = %d\n", nread);
+}
+
+void
+f_write(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ Iobuf *p, *p1;
+ Dentry *d;
+ File *f;
+ Tlock *t;
+ long offset, addr, tim;
+ int count, nwrite, o, n;
+
+ if(CHAT(cp)) {
+ print("c_write %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ print(" offset = %ld\n", in->offset);
+ print(" count = %ld\n", in->count);
+ }
+
+ offset = in->offset;
+ count = in->count;
+ nwrite = 0;
+ p = 0;
+ f = filep(cp, in->fid, 0);
+ if(!f) {
+ ou->err = Efid;
+ goto out;
+ }
+ if(!(f->open & FWRITE)) {
+ ou->err = Eopen;
+ goto out;
+ }
+ if(isro(f->fs->dev) || (cp != cons.chan && writegroup && !ingroup(f->uid, writegroup))) {
+ ou->err = Eronly;
+ goto out;
+ }
+ if(count < 0 || count > MAXDAT) {
+ ou->err = Ecount;
+ goto out;
+ }
+ if(offset < 0) {
+ ou->err = Eoffset;
+ goto out;
+ }
+ p = getbuf(f->fs->dev, f->addr, Bread|Bmod);
+ d = getdir(p, f->slot);
+ if(!d || !(d->mode & DALLOC)) {
+ ou->err = Ealloc;
+ goto out;
+ }
+ if(ou->err = mkqidcmp(&f->qid, d))
+ goto out;
+ if(t = f->tlock) {
+ tim = time(0);
+ if(t->time < tim || t->file != f) {
+ ou->err = Ebroken;
+ goto out;
+ }
+ /* renew the lock */
+ t->time = tim + TLOCK;
+ }
+ accessdir(p, d, FWRITE);
+ if(d->mode & DAPND)
+ offset = d->size;
+ if(offset+count > d->size)
+ d->size = offset+count;
+ while(count > 0) {
+ addr = offset / BUFSIZE;
+ o = offset % BUFSIZE;
+ n = BUFSIZE - o;
+ if(n > count)
+ n = count;
+ p1 = dnodebuf(p, d, addr, Tfile);
+ if(p1 == 0) {
+ ou->err = Efull;
+ goto out;
+ }
+ if(checktag(p1, Tfile, d->qid.path)) {
+ putbuf(p1);
+ ou->err = Ephase;
+ goto out;
+ }
+ memmove(p1->iobuf+o, in->data+nwrite, n);
+ p1->flags |= Bmod;
+ putbuf(p1);
+ count -= n;
+ nwrite += n;
+ offset += n;
+ }
+ if(CHAT(cp))
+ print(" nwrite = %d\n", nwrite);
+
+out:
+ if(p)
+ putbuf(p);
+ if(f)
+ qunlock(f);
+ ou->fid = in->fid;
+ ou->count = nwrite;
+}
+
+int
+doremove(File *f, int iscon)
+{
+ Iobuf *p, *p1;
+ Dentry *d, *d1;
+ long addr;
+ int slot, err;
+
+ p = 0;
+ p1 = 0;
+ if(isro(f->fs->dev) || (f->cp != cons.chan && writegroup && !ingroup(f->uid, writegroup))) {
+ err = Eronly;
+ goto out;
+ }
+ /*
+ * check on parent directory of file to be deleted
+ */
+ if(f->wpath == 0 || f->wpath->addr == f->addr) {
+ err = Ephase;
+ goto out;
+ }
+ p1 = getbuf(f->fs->dev, f->wpath->addr, Bread);
+ d1 = getdir(p1, f->wpath->slot);
+ if(!d1 || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)) {
+ err = Ephase;
+ goto out;
+ }
+ if(!iscon && iaccess(f, d1, DWRITE)) {
+ err = Eaccess;
+ goto out;
+ }
+ accessdir(p1, d1, FWRITE);
+ putbuf(p1);
+ p1 = 0;
+
+ /*
+ * check on file to be deleted
+ */
+ p = getbuf(f->fs->dev, f->addr, Bread);
+ d = getdir(p, f->slot);
+ if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+ err = Ealloc;
+ goto out;
+ }
+ if(err = mkqidcmp(&f->qid, d))
+ goto out;
+
+ /*
+ * if deleting a directory, make sure it is empty
+ */
+ if((d->mode & DDIR))
+ for(addr=0;; addr++) {
+ p1 = dnodebuf(p, d, addr, 0);
+ if(!p1)
+ break;
+ if(checktag(p1, Tdir, d->qid.path)) {
+ err = Ephase;
+ goto out;
+ }
+ for(slot=0; slot<DIRPERBUF; slot++) {
+ d1 = getdir(p1, slot);
+ if(!(d1->mode & DALLOC))
+ continue;
+ err = Eempty;
+ goto out;
+ }
+ putbuf(p1);
+ }
+
+ /*
+ * do it
+ */
+ dtrunc(p, d);
+ memset(d, 0, sizeof(Dentry));
+ settag(p, Tdir, QPNONE);
+
+out:
+ if(p1)
+ putbuf(p1);
+ if(p)
+ putbuf(p);
+ return err;
+}
+
+void
+f_remove(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ File *f;
+
+ if(CHAT(cp)) {
+ print("c_remove %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ }
+
+ f = filep(cp, in->fid, 0);
+ if(!f) {
+ ou->err = Efid;
+ goto out;
+ }
+ ou->err = doremove(f, cp==cons.chan);
+
+out:
+ ou->fid = in->fid;
+ if(f)
+ qunlock(f);
+}
+
+void
+f_stat(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ Iobuf *p;
+ Dentry *d;
+ File *f;
+
+ if(CHAT(cp)) {
+ print("c_stat %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ }
+
+ p = 0;
+ memset(ou->stat, 0, sizeof(ou->stat));
+ f = filep(cp, in->fid, 0);
+ if(!f) {
+ ou->err = Efid;
+ goto out;
+ }
+ p = getbuf(f->fs->dev, f->addr, Bread);
+ d = getdir(p, f->slot);
+ if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+ ou->err = Ealloc;
+ goto out;
+ }
+ if(ou->err = mkqidcmp(&f->qid, d))
+ goto out;
+ if(d->qid.path == QPROOT) /* stat of root gives time */
+ d->atime = time(0);
+ if(convD2M9p1(d, ou->stat) != DIRREC)
+ print("stat convD2M\n");
+
+out:
+ if(p)
+ putbuf(p);
+ if(f)
+ qunlock(f);
+ ou->fid = in->fid;
+}
+
+void
+f_wstat(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ Iobuf *p, *p1;
+ Dentry *d, *d1, xd;
+ File *f;
+ int slot;
+ long addr;
+
+ if(CHAT(cp)) {
+ print("c_wstat %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ }
+
+ p = 0;
+ p1 = 0;
+ d1 = 0;
+ f = filep(cp, in->fid, 0);
+ if(!f) {
+ ou->err = Efid;
+ goto out;
+ }
+ if(isro(f->fs->dev) || (cp != cons.chan && writegroup && !ingroup(f->uid, writegroup))) {
+ ou->err = Eronly;
+ goto out;
+ }
+
+ /*
+ * first get parent
+ */
+ if(f->wpath) {
+ p1 = getbuf(f->fs->dev, f->wpath->addr, Bread);
+ d1 = getdir(p1, f->wpath->slot);
+ if(!d1 || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)) {
+ ou->err = Ephase;
+ goto out;
+ }
+ }
+
+ p = getbuf(f->fs->dev, f->addr, Bread);
+ d = getdir(p, f->slot);
+ if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+ ou->err = Ealloc;
+ goto out;
+ }
+ if(ou->err = mkqidcmp(&f->qid, d))
+ goto out;
+
+ convM2D9p1(in->stat, &xd);
+ if(CHAT(cp)) {
+ print(" d.name = %s\n", xd.name);
+ print(" d.uid = %d\n", xd.uid);
+ print(" d.gid = %d\n", xd.gid);
+ print(" d.mode = %.4x\n", xd.mode);
+ }
+
+ /*
+ * if chown,
+ * must be god
+ */
+ while(xd.uid != d->uid) {
+ if(wstatallow) /* set to allow chown during boot */
+ break;
+ ou->err = Enotu;
+ goto out;
+ }
+
+ /*
+ * if chgroup,
+ * must be either
+ * a) owner and in new group
+ * b) leader of both groups
+ */
+ while(xd.gid != d->gid) {
+ if(wstatallow || writeallow) /* set to allow chgrp during boot */
+ break;
+ if(d->uid == f->uid && ingroup(f->uid, xd.gid))
+ break;
+ if(leadgroup(f->uid, xd.gid))
+ if(leadgroup(f->uid, d->gid))
+ break;
+ ou->err = Enotg;
+ goto out;
+ }
+
+ /*
+ * if rename,
+ * must have write permission in parent
+ */
+ if(xd.name[0] == 0)
+ strncpy(xd.name, d->name, sizeof(xd.name));
+ while(strncmp(d->name, xd.name, sizeof(d->name)) != 0) {
+ if(checkname(xd.name)) {
+ ou->err = Ename;
+ goto out;
+ }
+
+ if(strcmp(xd.name, ".") == 0 || strcmp(xd.name, "..") == 0) {
+ ou->err = Ename;
+ goto out;
+ }
+
+ /*
+ * drop entry to prevent lock, then
+ * check that destination name is unique,
+ */
+ putbuf(p);
+ for(addr=0;; addr++) {
+ p = dnodebuf(p1, d1, addr, 0);
+ if(!p)
+ break;
+ if(checktag(p, Tdir, d1->qid.path)) {
+ putbuf(p);
+ continue;
+ }
+ for(slot=0; slot<DIRPERBUF; slot++) {
+ d = getdir(p, slot);
+ if(!(d->mode & DALLOC))
+ continue;
+ if(!strncmp(xd.name, d->name, sizeof(xd.name))) {
+ ou->err = Eexist;
+ goto out;
+ }
+ }
+ putbuf(p);
+ }
+
+ /*
+ * reacquire entry
+ */
+ p = getbuf(f->fs->dev, f->addr, Bread);
+ d = getdir(p, f->slot);
+ if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+ ou->err = Ephase;
+ goto out;
+ }
+
+ if(wstatallow || writeallow) /* set to allow rename during boot */
+ break;
+ if(!d1 || iaccess(f, d1, DWRITE)) {
+ ou->err = Eaccess;
+ goto out;
+ }
+ break;
+ }
+
+ /*
+ * if mode/time, either
+ * a) owner
+ * b) leader of either group
+ */
+ while(d->mtime != xd.mtime ||
+ ((d->mode^xd.mode) & (DAPND|DLOCK|0777))) {
+ if(wstatallow) /* set to allow chmod during boot */
+ break;
+ if(d->uid == f->uid)
+ break;
+ if(leadgroup(f->uid, xd.gid))
+ break;
+ if(leadgroup(f->uid, d->gid))
+ break;
+ ou->err = Enotu;
+ goto out;
+ }
+ d->mtime = xd.mtime;
+ d->uid = xd.uid;
+ d->gid = xd.gid;
+ d->mode = (xd.mode & (DAPND|DLOCK|0777)) | (d->mode & (DALLOC|DDIR));
+
+ strncpy(d->name, xd.name, sizeof(d->name));
+ if(wstatallow) {
+ p->flags |= Bmod;
+ if(xd.atime)
+ d->atime = xd.atime;
+ if(xd.mtime)
+ d->mtime = xd.mtime;
+ } else
+ accessdir(p, d, FWSTAT);
+
+out:
+ if(p)
+ putbuf(p);
+ if(p1)
+ putbuf(p1);
+ if(f)
+ qunlock(f);
+ ou->fid = in->fid;
+}
+
+void
+(*call9p1[MAXSYSCALL])(Chan*, Oldfcall*, Oldfcall*) =
+{
+ [Tnop9p1] f_nop,
+ [Tosession9p1] f_session,
+ [Tsession9p1] f_session,
+ [Tflush9p1] f_flush,
+ [Toattach9p1] f_attach,
+ [Tattach9p1] f_attach,
+ [Tclone9p1] f_clone,
+ [Twalk9p1] f_walk,
+ [Topen9p1] f_open,
+ [Tcreate9p1] f_create,
+ [Tread9p1] f_read,
+ [Twrite9p1] f_write,
+ [Tclunk9p1] f_clunk,
+ [Tremove9p1] f_remove,
+ [Tstat9p1] f_stat,
+ [Twstat9p1] f_wstat,
+ [Tclwalk9p1] f_clwalk,
+};
+
+static void
+send(Chan *c, uchar *buf, int n)
+{
+ int fd, m;
+
+ fd = c->chan;
+ m = write(fd, buf, n);
+ if(m == n)
+ return;
+ panic("write failed");
+}
+
+void
+error9p1(Chan *c, uchar *buf)
+{
+ buf[0] = Rnop9p1;
+ buf[1] = ~0;
+ buf[2] = ~0;
+
+ send(c, buf, 3);
+}
+
+void
+serve9p1(Chan *chan, uchar *ib, int nib)
+{
+ int n, t;
+ uchar inbuf[MAXMSG+MAXDAT], outbuf[MAXMSG+MAXDAT];
+ Oldfcall fi, fo;
+
+ for(;;){
+ if(nib){
+ memmove(inbuf, ib, nib);
+ n = nib;
+ nib = 0;
+ }else
+ n = read(chan->chan, inbuf, sizeof inbuf);
+ if(chat)
+ print("read msg %d\n", n);
+ if(n == 0 && (chan == cons.srvchan || chan == cons.chan))
+ continue;
+ if(n <= 0)
+ return;
+ if(convM2S9p1(inbuf, &fi, n) != n){
+ error9p1(chan, outbuf);
+ continue;
+ }
+
+ t = fi.type;
+ if(t < 0 || t >= MAXSYSCALL || (t&1) || !call9p1[t]) {
+ print("9p1: bad message type\n");
+ error9p1(chan, outbuf);
+ continue;
+ }
+
+ if(CHAT(chan))
+ print("9p1: fi %O\n", &fi);
+
+ /*
+ * set up reply message
+ */
+ fo.err = 0;
+ if(t == Tread9p1)
+ fo.data = (char*)outbuf + 8;
+
+ /*
+ * call the file system
+ */
+ cons.work.count++;
+ cons.rate.count += n;
+
+ /*
+ * call the file system
+ */
+ rlock(&mainlock);
+ rlock(&chan->reflock);
+
+ (*call9p1[t])(chan, &fi, &fo);
+
+ runlock(&chan->reflock);
+ runlock(&mainlock);
+
+ fo.type = t+1;
+ fo.tag = fi.tag;
+
+ if(chat)
+ print("9p1: fo %O\n", &fo);
+
+ if(fo.err) {
+ strcpy(fo.ename, errstring[fo.err]);
+ if(CHAT(cp))
+ print(" error: %s\n", fo.ename);
+ fo.type = Terror9p1+1;
+ }
+
+ n = convS2M9p1(&fo, outbuf);
+ if(n == 0) {
+ print("9p1: bad S2M conversion\n");
+ error9p1(chan, outbuf);
+ continue;
+ }
+
+ cons.rate.count += n;
+ send(chan, outbuf, n);
+ }
+}
diff --git a/sys/src/cmd/disk/kfs/9p1.h b/sys/src/cmd/disk/kfs/9p1.h
new file mode 100755
index 000000000..7949ae0e0
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/9p1.h
@@ -0,0 +1,113 @@
+#define DIRREC 116 /* size of a directory ascii record */
+#define ERRREC 64 /* size of a error record */
+#define MAXMSG 160 /* max header sans data */
+
+typedef struct Oldfcall Oldfcall;
+
+struct Oldfcall
+{
+ char type;
+ ushort fid;
+ short err;
+ short tag;
+ union
+ {
+ struct
+ {
+ short uid; /* T-Userstr */
+ short oldtag; /* T-nFlush */
+ Qid9p1 qid; /* R-Attach, R-Clwalk, R-Walk,
+ * R-Open, R-Create */
+ char rauth[AUTHENTLEN]; /* R-attach */
+ };
+ struct
+ {
+ char uname[NAMELEN]; /* T-nAttach */
+ char aname[NAMELEN]; /* T-nAttach */
+ char ticket[TICKETLEN]; /* T-attach */
+ char auth[AUTHENTLEN]; /* T-attach */
+ };
+ struct
+ {
+ char ename[ERRREC]; /* R-nError */
+ char chal[CHALLEN]; /* T-session, R-session */
+ char authid[NAMELEN]; /* R-session */
+ char authdom[DOMLEN]; /* R-session */
+ };
+ struct
+ {
+ char name[NAMELEN]; /* T-Walk, T-Clwalk, T-Create, T-Remove */
+ long perm; /* T-Create */
+ ushort newfid; /* T-Clone, T-Clwalk */
+ char mode; /* T-Create, T-Open */
+ };
+ struct
+ {
+ long offset; /* T-Read, T-Write */
+ long count; /* T-Read, T-Write, R-Read */
+ char* data; /* T-Write, R-Read */
+ };
+ struct
+ {
+ char stat[DIRREC]; /* T-Wstat, R-Stat */
+ };
+ };
+};
+
+/*
+ * P9 protocol message types
+ */
+enum
+{
+ Tnop9p1 = 50,
+ Rnop9p1,
+ Tosession9p1 = 52,
+ Rosession9p1,
+ Terror9p1 = 54, /* illegal */
+ Rerror9p1,
+ Tflush9p1 = 56,
+ Rflush9p1,
+ Toattach9p1 = 58,
+ Roattach9p1,
+ Tclone9p1 = 60,
+ Rclone9p1,
+ Twalk9p1 = 62,
+ Rwalk9p1,
+ Topen9p1 = 64,
+ Ropen9p1,
+ Tcreate9p1 = 66,
+ Rcreate9p1,
+ Tread9p1 = 68,
+ Rread9p1,
+ Twrite9p1 = 70,
+ Rwrite9p1,
+ Tclunk9p1 = 72,
+ Rclunk9p1,
+ Tremove9p1 = 74,
+ Rremove9p1,
+ Tstat9p1 = 76,
+ Rstat9p1,
+ Twstat9p1 = 78,
+ Rwstat9p1,
+ Tclwalk9p1 = 80,
+ Rclwalk9p1,
+ Tauth9p1 = 82, /* illegal */
+ Rauth9p1, /* illegal */
+ Tsession9p1 = 84,
+ Rsession9p1,
+ Tattach9p1 = 86,
+ Rattach9p1,
+
+ MAXSYSCALL
+};
+
+int convD2M9p1(Dentry*, char*);
+int convM2D9p1(char*, Dentry*);
+int convM2S9p1(uchar*, Oldfcall*, int);
+int convS2M9p1(Oldfcall*, uchar*);
+void fcall9p1(Chan*, Oldfcall*, Oldfcall*);
+int authorize(Chan*, Oldfcall*, Oldfcall*);
+
+void (*call9p1[MAXSYSCALL])(Chan*, Oldfcall*, Oldfcall*);
+
+extern Nvrsafe nvr;
diff --git a/sys/src/cmd/disk/kfs/9p12.c b/sys/src/cmd/disk/kfs/9p12.c
new file mode 100755
index 000000000..a1865b5e7
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/9p12.c
@@ -0,0 +1,114 @@
+#include "all.h"
+
+static int
+readmsg(Chan *c, void *abuf, int n, int *ninep)
+{
+ int fd, len;
+ uchar *buf;
+
+ buf = abuf;
+ fd = c->chan;
+ qlock(&c->rlock);
+ if(readn(fd, buf, 3) != 3){
+ qunlock(&c->rlock);
+ print("readn(3) fails: %r\n");
+ return -1;
+ }
+ if((50 <= buf[0] && buf[0] <= 87 && (buf[0]&1)==0 && GBIT16(buf+1) == 0xFFFF)
+ || buf[0] == 86 /* Tattach */){
+ *ninep = 1;
+ /* assume message boundaries */
+ n = read(fd, buf+3, n-3);
+ if(n < 0){
+ qunlock(&c->rlock);
+ return -1;
+ }
+ return n+3;
+ }
+
+ *ninep = 2;
+ if(read(fd, buf+3, 1) != 1){
+ qunlock(&c->rlock);
+ print("read(1) fails: %r\n");
+ return -1;
+ }
+ len = GBIT32(buf);
+ if(len > n){
+ print("msg too large\n");
+ qunlock(&c->rlock);
+ return -1;
+ }
+ if(readn(fd, buf+4, len-4) != len-4){
+ print("readn(%d) fails: %r\n", len-4);
+ qunlock(&c->rlock);
+ return -1;
+ }
+ qunlock(&c->rlock);
+ return len;
+}
+
+int
+startserveproc(void (*f)(Chan*, uchar*, int), char *name, Chan *c, uchar *b, int nb)
+{
+ int pid;
+
+ switch(pid = rfork(RFMEM|RFPROC)){
+ case -1:
+ panic("can't fork");
+ case 0:
+ break;
+ default:
+ return pid;
+ }
+ procname = name;
+ f(c, b, nb);
+ _exits(nil);
+ return -1; /* can't happen */
+}
+
+void
+serve(Chan *chan)
+{
+ int i, nin, p9, npid;
+ uchar inbuf[1024];
+ void (*s)(Chan*, uchar*, int);
+ int *pid;
+ Waitmsg *w;
+
+ p9 = 0;
+ if((nin = readmsg(chan, inbuf, sizeof inbuf, &p9)) < 0)
+ return;
+
+ switch(p9){
+ default:
+ print("unknown 9P type\n");
+ return;
+ case 1:
+ s = serve9p1;
+ break;
+ case 2:
+ s = serve9p2;
+ break;
+ }
+
+ pid = malloc(sizeof(pid)*(conf.nserve-1));
+ if(pid == nil)
+ return;
+ for(i=1; i<conf.nserve; i++)
+ pid[i-1] = startserveproc(s, "srv", chan, nil, 0);
+
+ (*s)(chan, inbuf, nin);
+
+ /* wait till all other servers for this chan are done */
+ for(npid = conf.nserve-1; npid > 0;){
+ w = wait();
+ if(w == 0)
+ break;
+ for(i = 0; i < conf.nserve-1; i++)
+ if(pid[i] == w->pid)
+ npid--;
+ free(w);
+ }
+ free(pid);
+}
+
diff --git a/sys/src/cmd/disk/kfs/9p1lib.c b/sys/src/cmd/disk/kfs/9p1lib.c
new file mode 100755
index 000000000..d61d76220
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/9p1lib.c
@@ -0,0 +1,461 @@
+#include "all.h"
+#include "9p1.h"
+
+#define CHAR(x) *p++ = f->x
+#define SHORT(x) { ulong vvv = f->x; p[0] = vvv; p[1] = vvv>>8; p += 2; }
+#define VLONG(q) p[0] = (q); p[1] = (q)>>8; p[2] = (q)>>16; p[3] = (q)>>24; p += 4
+#define LONG(x) { ulong vvv = f->x; VLONG(vvv); }
+#define BYTES(x,n) memmove(p, f->x, n); p += n
+#define STRING(x,n) strncpy((char*)p, f->x, n); p += n
+
+int
+convS2M9p1(Oldfcall *f, uchar *ap)
+{
+ uchar *p;
+ int t;
+
+ p = ap;
+ CHAR(type);
+ t = f->type;
+ SHORT(tag);
+ switch(t)
+ {
+ default:
+ print("convS2M9p1: bad type: %d\n", t);
+ return 0;
+
+ case Tnop9p1:
+ case Tosession9p1:
+ break;
+
+ case Tsession9p1:
+ BYTES(chal, sizeof(f->chal));
+ break;
+
+ case Tflush9p1:
+ SHORT(oldtag);
+ break;
+
+ case Tattach9p1:
+ SHORT(fid);
+ STRING(uname, sizeof(f->uname));
+ STRING(aname, sizeof(f->aname));
+ BYTES(ticket, sizeof(f->ticket));
+ BYTES(auth, sizeof(f->auth));
+ break;
+
+ case Toattach9p1:
+ SHORT(fid);
+ STRING(uname, sizeof(f->uname));
+ STRING(aname, sizeof(f->aname));
+ BYTES(ticket, NAMELEN);
+ break;
+
+ case Tclone9p1:
+ SHORT(fid);
+ SHORT(newfid);
+ break;
+
+ case Twalk9p1:
+ SHORT(fid);
+ STRING(name, sizeof(f->name));
+ break;
+
+ case Tclwalk9p1:
+ SHORT(fid);
+ SHORT(newfid);
+ STRING(name, sizeof(f->name));
+ break;
+
+ case Topen9p1:
+ SHORT(fid);
+ CHAR(mode);
+ break;
+
+ case Tcreate9p1:
+ SHORT(fid);
+ STRING(name, sizeof(f->name));
+ LONG(perm);
+ CHAR(mode);
+ break;
+
+ case Tread9p1:
+ SHORT(fid);
+ LONG(offset); VLONG(0);
+ SHORT(count);
+ break;
+
+ case Twrite9p1:
+ SHORT(fid);
+ LONG(offset); VLONG(0);
+ SHORT(count);
+ p++;
+ if((uchar*)p == (uchar*)f->data) {
+ p += f->count;
+ break;
+ }
+ BYTES(data, f->count);
+ break;
+
+ case Tclunk9p1:
+ case Tremove9p1:
+ case Tstat9p1:
+ SHORT(fid);
+ break;
+
+ case Twstat9p1:
+ SHORT(fid);
+ BYTES(stat, sizeof(f->stat));
+ break;
+/*
+ */
+ case Rnop9p1:
+ case Rosession9p1:
+ case Rflush9p1:
+ break;
+
+ case Rsession9p1:
+ BYTES(chal, sizeof(f->chal));
+ BYTES(authid, sizeof(f->authid));
+ BYTES(authdom, sizeof(f->authdom));
+ break;
+
+ case Rerror9p1:
+ STRING(ename, sizeof(f->ename));
+ break;
+
+ case Rclone9p1:
+ case Rclunk9p1:
+ case Rremove9p1:
+ case Rwstat9p1:
+ SHORT(fid);
+ break;
+
+ case Rwalk9p1:
+ case Ropen9p1:
+ case Rcreate9p1:
+ case Rclwalk9p1:
+ SHORT(fid);
+ LONG(qid.path);
+ LONG(qid.version);
+ break;
+
+ case Rattach9p1:
+ SHORT(fid);
+ LONG(qid.path);
+ LONG(qid.version);
+ BYTES(rauth, sizeof(f->rauth));
+ break;
+
+ case Roattach9p1:
+ SHORT(fid);
+ LONG(qid.path);
+ LONG(qid.version);
+ break;
+
+ case Rread9p1:
+ SHORT(fid);
+ SHORT(count);
+ p++;
+ if((uchar*)p == (uchar*)f->data) {
+ p += f->count;
+ break;
+ }
+ BYTES(data, f->count);
+ break;
+
+ case Rwrite9p1:
+ SHORT(fid);
+ SHORT(count);
+ break;
+
+ case Rstat9p1:
+ SHORT(fid);
+ BYTES(stat, sizeof(f->stat));
+ break;
+ }
+ return p - (uchar*)ap;
+}
+
+/*
+ * buggery to give false qid for
+ * the top 2 levels of the dump fs
+ */
+static ulong
+fakeqid9p1(Dentry *f)
+{
+ ulong q;
+ int c;
+
+ q = f->qid.path;
+ if(q == (QPROOT|QPDIR)) {
+ c = f->name[0];
+ if(c >= '0' && c <= '9') {
+ q = 3|QPDIR;
+ c = (c-'0')*10 + (f->name[1]-'0');
+ if(c >= 1 && c <= 12)
+ q = 4|QPDIR;
+ }
+ }
+ return q;
+}
+
+int
+convD2M9p1(Dentry *f, char *ap)
+{
+ uchar *p;
+ ulong q;
+
+ p = (uchar*)ap;
+ STRING(name, sizeof(f->name));
+
+ memset(p, 0, 2*NAMELEN);
+ uidtostr((char*)p, f->uid);
+ p += NAMELEN;
+
+ uidtostr((char*)p, f->gid);
+ p += NAMELEN;
+
+ q = fakeqid9p1(f);
+ VLONG(q);
+ LONG(qid.version);
+ {
+ q = f->mode & 0x0fff;
+ if(f->mode & DDIR)
+ q |= PDIR;
+ if(f->mode & DAPND)
+ q |= PAPND;
+ if(f->mode & DLOCK)
+ q |= PLOCK;
+ VLONG(q);
+ }
+ LONG(atime);
+ LONG(mtime);
+ LONG(size); VLONG(0);
+ VLONG(0);
+ return p - (uchar*)ap;
+}
+
+#undef CHAR
+#undef SHORT
+#undef LONG
+#undef VLONG
+#undef BYTES
+#undef STRING
+
+#define CHAR(x) f->x = *p++
+#define SHORT(x) f->x = (p[0] | (p[1]<<8)); p += 2
+#define VLONG(q) q = (p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24)); p += 4
+#define LONG(x) VLONG(f->x)
+#define BYTES(x,n) memmove(f->x, p, n); p += n
+#define STRING(x,n) memmove(f->x, p, n); p += n
+
+int
+convM2S9p1(uchar *ap, Oldfcall *f, int n)
+{
+ uchar *p;
+ int t;
+
+ p = ap;
+ CHAR(type);
+ t = f->type;
+ SHORT(tag);
+ switch(t)
+ {
+ default:
+ /*
+ * only whine if it couldn't be a 9P2000 Tversion9p1.
+ */
+ if(t != 19 || ap[4] != 100)
+ print("convM2S9p1: bad type: %d\n", f->type);
+ return 0;
+
+ case Tnop9p1:
+ case Tosession9p1:
+ break;
+
+ case Tsession9p1:
+ BYTES(chal, sizeof(f->chal));
+ break;
+
+ case Tflush9p1:
+ SHORT(oldtag);
+ break;
+
+ case Tattach9p1:
+ SHORT(fid);
+ BYTES(uname, sizeof(f->uname));
+ BYTES(aname, sizeof(f->aname));
+ BYTES(ticket, sizeof(f->ticket));
+ BYTES(auth, sizeof(f->auth));
+ break;
+
+ case Toattach9p1:
+ SHORT(fid);
+ BYTES(uname, sizeof(f->uname));
+ BYTES(aname, sizeof(f->aname));
+ BYTES(ticket, NAMELEN);
+ break;
+
+ case Tclone9p1:
+ SHORT(fid);
+ SHORT(newfid);
+ break;
+
+ case Twalk9p1:
+ SHORT(fid);
+ BYTES(name, sizeof(f->name));
+ break;
+
+ case Tclwalk9p1:
+ SHORT(fid);
+ SHORT(newfid);
+ BYTES(name, sizeof(f->name));
+ break;
+
+ case Tremove9p1:
+ SHORT(fid);
+ break;
+
+ case Topen9p1:
+ SHORT(fid);
+ CHAR(mode);
+ break;
+
+ case Tcreate9p1:
+ SHORT(fid);
+ BYTES(name, sizeof(f->name));
+ LONG(perm);
+ CHAR(mode);
+ break;
+
+ case Tread9p1:
+ SHORT(fid);
+ LONG(offset); p += 4;
+ SHORT(count);
+ break;
+
+ case Twrite9p1:
+ SHORT(fid);
+ LONG(offset); p += 4;
+ SHORT(count);
+ p++;
+ f->data = (char*)p; p += f->count;
+ break;
+
+ case Tclunk9p1:
+ case Tstat9p1:
+ SHORT(fid);
+ break;
+
+ case Twstat9p1:
+ SHORT(fid);
+ BYTES(stat, sizeof(f->stat));
+ break;
+
+/*
+ */
+ case Rnop9p1:
+ case Rosession9p1:
+ break;
+
+ case Rsession9p1:
+ BYTES(chal, sizeof(f->chal));
+ BYTES(authid, sizeof(f->authid));
+ BYTES(authdom, sizeof(f->authdom));
+ break;
+
+ case Rerror9p1:
+ BYTES(ename, sizeof(f->ename));
+ break;
+
+ case Rflush9p1:
+ break;
+
+ case Rclone9p1:
+ case Rclunk9p1:
+ case Rremove9p1:
+ case Rwstat9p1:
+ SHORT(fid);
+ break;
+
+ case Rwalk9p1:
+ case Rclwalk9p1:
+ case Ropen9p1:
+ case Rcreate9p1:
+ SHORT(fid);
+ LONG(qid.path);
+ LONG(qid.version);
+ break;
+
+ case Rattach9p1:
+ SHORT(fid);
+ LONG(qid.path);
+ LONG(qid.version);
+ BYTES(rauth, sizeof(f->rauth));
+ break;
+
+ case Roattach9p1:
+ SHORT(fid);
+ LONG(qid.path);
+ LONG(qid.version);
+ break;
+
+ case Rread9p1:
+ SHORT(fid);
+ SHORT(count);
+ p++;
+ f->data = (char*)p; p += f->count;
+ break;
+
+ case Rwrite9p1:
+ SHORT(fid);
+ SHORT(count);
+ break;
+
+ case Rstat9p1:
+ SHORT(fid);
+ BYTES(stat, sizeof(f->stat));
+ break;
+ }
+ if((uchar*)ap+n == p)
+ return n;
+ return 0;
+}
+
+int
+convM2D9p1(char *ap, Dentry *f)
+{
+ uchar *p;
+ char str[28];
+
+ p = (uchar*)ap;
+ BYTES(name, sizeof(f->name));
+
+ memmove(str, p, NAMELEN);
+ p += NAMELEN;
+ f->uid = strtouid(str);
+
+ memmove(str, p, NAMELEN);
+ p += NAMELEN;
+ f->gid = strtouid(str);
+
+ LONG(qid.path);
+ LONG(qid.version);
+ {
+ LONG(atime);
+ f->mode = (f->atime & 0x0fff) | DALLOC;
+ if(f->atime & PDIR)
+ f->mode |= DDIR;
+ if(f->atime & PAPND)
+ f->mode |= DAPND;
+ if(f->atime & PLOCK)
+ f->mode |= DLOCK;
+ }
+ LONG(atime);
+ LONG(mtime);
+ LONG(size); p += 4;
+ p += 4;
+ return p - (uchar*)ap;
+}
+
diff --git a/sys/src/cmd/disk/kfs/9p2.c b/sys/src/cmd/disk/kfs/9p2.c
new file mode 100755
index 000000000..0559d85c4
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/9p2.c
@@ -0,0 +1,1865 @@
+#include "all.h"
+
+#define MSIZE (MAXDAT+128)
+
+static void
+seterror(Fcall *ou, int err)
+{
+
+ if(0 <= err && err < MAXERR)
+ ou->ename = errstring[err];
+ else
+ ou->ename = "unknown error";
+}
+
+static int
+fsversion(Chan* chan, Fcall* f, Fcall* r)
+{
+ if(f->msize < MSIZE)
+ r->msize = f->msize;
+ else
+ r->msize = MSIZE;
+ /*
+ * Should check the '.' stuff here.
+ * What happens if Tversion has already been seen?
+ */
+ if(strcmp(f->version, VERSION9P) == 0){
+ r->version = VERSION9P;
+ chan->msize = r->msize;
+ }else
+ r->version = "unknown";
+
+ fileinit(chan);
+ return 0;
+}
+
+char *keyspec = "proto=p9any role=server";
+
+static int
+fsauth(Chan *chan, Fcall *f, Fcall *r)
+{
+ int err, fd;
+ char *aname;
+ File *file;
+ int afd;
+ AuthRpc *rpc;
+
+ err = 0;
+ if(chan == cons.srvchan)
+ return Eauthmsg;
+ file = filep(chan, f->afid, 1);
+ if(file == nil)
+ return Efidinuse;
+
+ /* forget any previous authentication */
+ file->cuid = 0;
+
+ if(access("/mnt/factotum", 0) < 0)
+ if((fd = open("/srv/factotum", ORDWR)) >= 0)
+ mount(fd, -1, "/mnt", MBEFORE, "");
+
+ afd = open("/mnt/factotum/rpc", ORDWR);
+ if(afd < 0){
+ err = Esystem;
+ goto out;
+ }
+ rpc = auth_allocrpc(afd);
+ if(rpc == nil){
+ close(afd);
+ err = Esystem;
+ goto out;
+ }
+ file->rpc = rpc;
+ if(auth_rpc(rpc, "start", keyspec, strlen(keyspec)) != ARok){
+ err = Esystem;
+ goto out;
+ }
+
+ aname = f->aname;
+ if(!aname[0])
+ aname = "main";
+ file->fs = fsstr(aname);
+ if(file->fs == nil){
+ err = Ebadspc;
+ goto out;
+ }
+ file->uid = strtouid(f->uname);
+ if(file->uid < 0){
+ err = Ebadu;
+ goto out;
+ }
+ file->qid.path = 0;
+ file->qid.vers = 0;
+ file->qid.type = QTAUTH;
+ r->qid = file->qid;
+
+out:
+ if(file != nil){
+ qunlock(file);
+ if(err != 0)
+ freefp(file);
+ }
+ return err;
+}
+
+int
+authread(File *file, uchar *data, int count)
+{
+ AuthInfo *ai;
+ AuthRpc *rpc;
+ int rv;
+
+ rpc = file->rpc;
+ if(rpc == nil)
+ return -1;
+
+ rv = auth_rpc(rpc, "read", nil, 0);
+ switch(rv){
+ case ARdone:
+ ai = auth_getinfo(rpc);
+ if(ai == nil)
+ return -1;
+ if(chat)
+ print("authread identifies user as %s\n", ai->cuid);
+ file->cuid = strtouid(ai->cuid);
+ auth_freeAI(ai);
+ if(file->cuid == 0)
+ return -1;
+ if(chat)
+ print("%s is a known user\n", ai->cuid);
+ return 0;
+ case ARok:
+ if(count < rpc->narg)
+ return -1;
+ memmove(data, rpc->arg, rpc->narg);
+ return rpc->narg;
+ case ARphase:
+ return -1;
+ default:
+ return -1;
+ }
+}
+
+int
+authwrite(File *file, uchar *data, int count)
+{
+ int ret;
+
+ ret = auth_rpc(file->rpc, "write", data, count);
+ if(ret != ARok)
+ return -1;
+ return count;
+}
+
+void
+mkqid9p1(Qid9p1* qid9p1, Qid* qid)
+{
+ if(qid->path & 0xFFFFFFFF00000000LL)
+ panic("mkqid9p1: path %lluX\n", qid->path);
+ qid9p1->path = qid->path & 0xFFFFFFFF;
+ if(qid->type & QTDIR)
+ qid9p1->path |= QPDIR;
+ qid9p1->version = qid->vers;
+}
+
+void
+authfree(File *fp)
+{
+ if(fp->rpc != nil){
+ close(fp->rpc->afd);
+ free(fp->rpc);
+ fp->rpc = nil;
+ }
+}
+
+void
+mkqid9p2(Qid* qid, Qid9p1* qid9p1, int mode)
+{
+ qid->path = (ulong)(qid9p1->path & ~QPDIR);
+ qid->vers = qid9p1->version;
+ qid->type = 0;
+ if(mode & DDIR)
+ qid->type |= QTDIR;
+ if(mode & DAPND)
+ qid->type |= QTAPPEND;
+ if(mode & DLOCK)
+ qid->type |= QTEXCL;
+}
+
+static int
+checkattach(Chan *chan, File *afile, File *file, Filsys *fs)
+{
+ uchar buf[1];
+
+ if(chan == cons.srvchan || chan == cons.chan)
+ return 0;
+
+ /* if no afile, this had better be none */
+ if(afile == nil){
+ if(file->uid == 0){
+ if(!allownone && !chan->authed)
+ return Eauth;
+ return 0;
+ }
+ return Eauth;
+ }
+
+ /* otherwise, we'ld better have a usable cuid */
+ if(!(afile->qid.type&QTAUTH))
+ return Eauth;
+ if(afile->uid != file->uid || afile->fs != fs)
+ return Eauth;
+ if(afile->cuid <= 0){
+ if(authread(afile, buf, 0) != 0)
+ return Eauth;
+ if(afile->cuid <= 0)
+ return Eauth;
+ }
+ file->uid = afile->cuid;
+
+ /* once someone has authenticated on the channel, others can become none */
+ chan->authed = 1;
+
+ return 0;
+}
+
+static int
+fsattach(Chan* chan, Fcall* f, Fcall* r)
+{
+ char *aname;
+ Iobuf *p;
+ Dentry *d;
+ File *file;
+ File *afile;
+ Filsys *fs;
+ long raddr;
+ int error, u;
+
+ aname = f->aname;
+ if(!aname[0]) /* default */
+ aname = "main";
+ p = nil;
+ afile = filep(chan, f->afid, 0);
+ file = filep(chan, f->fid, 1);
+ if(file == nil){
+ error = Efidinuse;
+ goto out;
+ }
+
+ u = -1;
+ if(chan != cons.chan){
+ if(strcmp(f->uname, "adm") == 0){
+ error = Eauth;
+ goto out;
+ }
+ u = strtouid(f->uname);
+ if(u < 0){
+ error = Ebadu;
+ goto out;
+ }
+ }
+ file->uid = u;
+
+ fs = fsstr(aname);
+ if(fs == nil){
+ error = Ebadspc;
+ goto out;
+ }
+
+ if(error = checkattach(chan, afile, file, fs))
+ goto out;
+
+ raddr = getraddr(fs->dev);
+ p = getbuf(fs->dev, raddr, Bread);
+ d = getdir(p, 0);
+ if(d == nil || checktag(p, Tdir, QPROOT) || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(iaccess(file, d, DEXEC)){
+ error = Eaccess;
+ goto out;
+ }
+ if(file->uid == 0 && isro(fs->dev)) {
+ /*
+ * 'none' not allowed on dump
+ */
+ error = Eaccess;
+ goto out;
+ }
+ accessdir(p, d, FREAD);
+ mkqid(&file->qid, d, 1);
+ file->fs = fs;
+ file->addr = raddr;
+ file->slot = 0;
+ file->open = 0;
+ freewp(file->wpath);
+ file->wpath = 0;
+
+ r->qid = file->qid;
+
+// if(cons.flags & attachflag)
+// print("9p2: attach %s %T to \"%s\" C%d\n",
+// chan->whoname, chan->whotime, fs->name, chan->chan);
+
+out:
+// if((cons.flags & attachflag) && error)
+// print("9p2: attach %s %T SUCK EGGS --- %s\n",
+// f->uname, time(), errstr[error]);
+ if(p != nil)
+ putbuf(p);
+ if(afile != nil)
+ qunlock(afile);
+ if(file != nil){
+ qunlock(file);
+ if(error)
+ freefp(file);
+ }
+
+ return error;
+}
+
+static int
+fsflush(Chan* chan, Fcall*, Fcall*)
+{
+ runlock(&chan->reflock);
+ wlock(&chan->reflock);
+ wunlock(&chan->reflock);
+ rlock(&chan->reflock);
+
+ return 0;
+}
+
+static void
+clone(File* nfile, File* file)
+{
+ Wpath *wpath;
+
+ nfile->qid = file->qid;
+
+ lock(&wpathlock);
+ nfile->wpath = file->wpath;
+ for(wpath = nfile->wpath; wpath != nil; wpath = wpath->up)
+ wpath->refs++;
+ unlock(&wpathlock);
+
+ nfile->fs = file->fs;
+ nfile->addr = file->addr;
+ nfile->slot = file->slot;
+ nfile->uid = file->uid;
+ nfile->cuid = 0;
+ nfile->open = file->open & ~FREMOV;
+}
+
+static int
+walkname(File* file, char* wname, Qid* wqid)
+{
+ Wpath *w;
+ Iobuf *p, *p1;
+ Dentry *d, *d1;
+ int error, slot;
+ long addr, qpath;
+
+ p = p1 = nil;
+
+ /*
+ * File must not have been opened for I/O by an open
+ * or create message and must represent a directory.
+ */
+ if(file->open != 0){
+ error = Emode;
+ goto out;
+ }
+
+ p = getbuf(file->fs->dev, file->addr, Bread);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Edir1;
+ goto out;
+ }
+ if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(!(d->mode & DDIR)){
+ error = Edir1;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+
+ /*
+ * For walked elements the implied user must
+ * have permission to search the directory.
+ */
+ if(file->cp != cons.chan && iaccess(file, d, DEXEC)){
+ error = Eaccess;
+ goto out;
+ }
+ accessdir(p, d, FREAD);
+
+ if(strcmp(wname, ".") == 0){
+setdot:
+ if(wqid != nil)
+ *wqid = file->qid;
+ goto out;
+ }
+ if(strcmp(wname, "..") == 0){
+ if(file->wpath == 0)
+ goto setdot;
+ putbuf(p);
+ p = nil;
+ addr = file->wpath->addr;
+ slot = file->wpath->slot;
+ p1 = getbuf(file->fs->dev, addr, Bread);
+ if(p1 == nil || checktag(p1, Tdir, QPNONE)){
+ error = Edir1;
+ goto out;
+ }
+ if((d1 = getdir(p1, slot)) == nil || !(d1->mode & DALLOC)){
+ error = Ephase;
+ goto out;
+ }
+ lock(&wpathlock);
+ file->wpath->refs--;
+ file->wpath = file->wpath->up;
+ unlock(&wpathlock);
+ goto found;
+ }
+
+ for(addr = 0; ; addr++){
+ if(p == nil){
+ p = getbuf(file->fs->dev, file->addr, Bread);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ }
+ qpath = d->qid.path;
+ p1 = dnodebuf1(p, d, addr, 0);
+ p = nil;
+ if(p1 == nil || checktag(p1, Tdir, qpath)){
+ error = Eentry;
+ goto out;
+ }
+ for(slot = 0; slot < DIRPERBUF; slot++){
+ d1 = getdir(p1, slot);
+ if(!(d1->mode & DALLOC))
+ continue;
+ if(strncmp(wname, d1->name, NAMELEN) != 0)
+ continue;
+ /*
+ * update walk path
+ */
+ if((w = newwp()) == nil){
+ error = Ewalk;
+ goto out;
+ }
+ w->addr = file->addr;
+ w->slot = file->slot;
+ w->up = file->wpath;
+ file->wpath = w;
+ slot += DIRPERBUF*addr;
+ goto found;
+ }
+ putbuf(p1);
+ p1 = nil;
+ }
+
+found:
+ file->addr = p1->addr;
+ mkqid(&file->qid, d1, 1);
+ putbuf(p1);
+ p1 = nil;
+ file->slot = slot;
+ if(wqid != nil)
+ *wqid = file->qid;
+
+out:
+ if(p1 != nil)
+ putbuf(p1);
+ if(p != nil)
+ putbuf(p);
+
+ return error;
+}
+
+static int
+fswalk(Chan* chan, Fcall* f, Fcall* r)
+{
+ int error, nwname;
+ File *file, *nfile, tfile;
+
+ /*
+ * The file identified by f->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 = filep(chan, f->fid, 0)) == nil)
+ return Efid;
+ if(file->open != 0){
+ qunlock(file);
+ return Emode;
+ }
+
+ /*
+ * 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 f->nwname
+ * is 0.
+ */
+ r->nwqid = 0;
+ if(f->newfid != f->fid){
+ if((nfile = filep(chan, f->newfid, 1)) == nil){
+ qunlock(file);
+ return Efidinuse;
+ }
+ }
+ else if(f->nwname != 0){
+ nfile = &tfile;
+ memset(nfile, 0, sizeof(File));
+ nfile->cp = chan;
+ nfile->fid = ~0;
+ }
+ else{
+ qunlock(file);
+ return 0;
+ }
+ clone(nfile, file);
+
+ /*
+ * Should check name is not too long.
+ */
+ error = 0;
+ for(nwname = 0; nwname < f->nwname; nwname++){
+ error = walkname(nfile, f->wname[nwname], &r->wqid[r->nwqid]);
+ if(error != 0 || ++r->nwqid >= MAXDAT/sizeof(Qid))
+ break;
+ }
+
+ if(f->nwname == 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.
+ */
+ if(error){
+ freewp(nfile->wpath);
+ qunlock(nfile);
+ freefp(nfile);
+ }
+ else
+ qunlock(nfile);
+ }
+ else if(r->nwqid < f->nwname){
+ /*
+ * Didn't walk all elements, 'clunk' nfile
+ * and leave 'file' alone.
+ * Clear error if some of the elements were
+ * walked OK.
+ */
+ freewp(nfile->wpath);
+ if(nfile != &tfile){
+ qunlock(nfile);
+ freefp(nfile);
+ }
+ if(r->nwqid != 0)
+ error = 0;
+ }
+ 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;
+ freewp(file->wpath);
+ file->wpath = nfile->wpath;
+ file->addr = nfile->addr;
+ file->slot = nfile->slot;
+ }
+ else
+ qunlock(nfile);
+ }
+ qunlock(file);
+
+ return error;
+}
+
+static int
+fsopen(Chan* chan, Fcall* f, Fcall* r)
+{
+ Iobuf *p;
+ Dentry *d;
+ File *file;
+ Tlock *t;
+ Qid qid;
+ int error, ro, fmod, wok;
+
+ wok = 0;
+ p = nil;
+
+ if(chan == cons.chan || writeallow)
+ wok = 1;
+
+ if((file = filep(chan, f->fid, 0)) == nil){
+ error = Efid;
+ goto out;
+ }
+
+ /*
+ * if remove on close, check access here
+ */
+ ro = isro(file->fs->dev) || (writegroup && !ingroup(file->uid, writegroup));
+ if(f->mode & ORCLOSE){
+ if(ro){
+ error = Eronly;
+ goto out;
+ }
+ /*
+ * check on parent directory of file to be deleted
+ */
+ if(file->wpath == 0 || file->wpath->addr == file->addr){
+ error = Ephase;
+ goto out;
+ }
+ p = getbuf(file->fs->dev, file->wpath->addr, Bread);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ephase;
+ goto out;
+ }
+ if((d = getdir(p, file->wpath->slot)) == nil || !(d->mode & DALLOC)){
+ error = Ephase;
+ goto out;
+ }
+ if(iaccess(file, d, DWRITE)){
+ error = Eaccess;
+ goto out;
+ }
+ putbuf(p);
+ }
+ p = getbuf(file->fs->dev, file->addr, Bread);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+ mkqid(&qid, d, 1);
+ switch(f->mode & 7){
+
+ case OREAD:
+ if(iaccess(file, d, DREAD) && !wok)
+ goto badaccess;
+ fmod = FREAD;
+ break;
+
+ case OWRITE:
+ if((d->mode & DDIR) || (iaccess(file, d, DWRITE) && !wok))
+ goto badaccess;
+ if(ro){
+ error = Eronly;
+ goto out;
+ }
+ fmod = FWRITE;
+ break;
+
+ case ORDWR:
+ if((d->mode & DDIR)
+ || (iaccess(file, d, DREAD) && !wok)
+ || (iaccess(file, d, DWRITE) && !wok))
+ goto badaccess;
+ if(ro){
+ error = Eronly;
+ goto out;
+ }
+ fmod = FREAD+FWRITE;
+ break;
+
+ case OEXEC:
+ if((d->mode & DDIR) || (iaccess(file, d, DEXEC) && !wok))
+ goto badaccess;
+ fmod = FREAD;
+ break;
+
+ default:
+ error = Emode;
+ goto out;
+ }
+ if(f->mode & OTRUNC){
+ if((d->mode & DDIR) || (iaccess(file, d, DWRITE) && !wok))
+ goto badaccess;
+ if(ro){
+ error = Eronly;
+ goto out;
+ }
+ }
+ t = 0;
+ if(d->mode & DLOCK){
+ if((t = tlocked(p, d)) == nil){
+ error = Elocked;
+ goto out;
+ }
+ }
+ if(f->mode & ORCLOSE)
+ fmod |= FREMOV;
+ file->open = fmod;
+ if((f->mode & OTRUNC) && !(d->mode & DAPND)){
+ dtrunc(p, d);
+ qid.vers = d->qid.version;
+ }
+ r->qid = qid;
+ file->tlock = t;
+ if(t != nil)
+ t->file = file;
+ file->lastra = 1;
+ goto out;
+
+badaccess:
+ error = Eaccess;
+ file->open = 0;
+
+out:
+ if(p != nil)
+ putbuf(p);
+ if(file != nil)
+ qunlock(file);
+
+ r->iounit = chan->msize-IOHDRSZ;
+
+ return error;
+}
+
+static int
+dir9p2(Dir* dir, Dentry* dentry, void* strs)
+{
+ char *op, *p;
+
+ memset(dir, 0, sizeof(Dir));
+ mkqid(&dir->qid, dentry, 1);
+ dir->mode = (dir->qid.type<<24)|(dentry->mode & 0777);
+ dir->atime = dentry->atime;
+ dir->mtime = dentry->mtime;
+ dir->length = dentry->size;
+
+ op = p = strs;
+ dir->name = p;
+ p += sprint(p, "%s", dentry->name)+1;
+
+ dir->uid = p;
+ uidtostr(p, dentry->uid);
+ p += strlen(p)+1;
+
+ dir->gid = p;
+ uidtostr(p, dentry->gid);
+ p += strlen(p)+1;
+
+ dir->muid = p;
+ strcpy(p, "");
+ p += strlen(p)+1;
+
+ return p-op;
+}
+
+static int
+checkname9p2(char* name)
+{
+ char *p;
+
+ /*
+ * Return length of string if valid, 0 if not.
+ */
+ if(name == nil)
+ return 0;
+
+ for(p = name; *p != 0; p++){
+ if((*p & 0xFF) <= 040)
+ return 0;
+ }
+
+ return p-name;
+}
+
+static int
+fscreate(Chan* chan, Fcall* f, Fcall* r)
+{
+ Iobuf *p, *p1;
+ Dentry *d, *d1;
+ File *file;
+ int error, slot, slot1, fmod, wok, l;
+ long addr, addr1, path;
+ Tlock *t;
+ Wpath *w;
+
+ wok = 0;
+ p = nil;
+
+ if(chan == cons.chan || writeallow)
+ wok = 1;
+
+ if((file = filep(chan, f->fid, 0)) == nil){
+ error = Efid;
+ goto out;
+ }
+ if(isro(file->fs->dev) || (writegroup && !ingroup(file->uid, writegroup))){
+ error = Eronly;
+ goto out;
+ }
+
+ p = getbuf(file->fs->dev, file->addr, Bread);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+ if(!(d->mode & DDIR)){
+ error = Edir2;
+ goto out;
+ }
+ if(iaccess(file, d, DWRITE) && !wok) {
+ error = Eaccess;
+ goto out;
+ }
+ accessdir(p, d, FREAD);
+
+ /*
+ * Check the name is valid and will fit in an old
+ * directory entry.
+ */
+ if((l = checkname9p2(f->name)) == 0){
+ error = Ename;
+ goto out;
+ }
+ if(l+1 > NAMELEN){
+ error = Etoolong;
+ goto out;
+ }
+ if(strcmp(f->name, ".") == 0 || strcmp(f->name, "..") == 0){
+ error = Edot;
+ goto out;
+ }
+
+ addr1 = 0;
+ slot1 = 0; /* set */
+ for(addr = 0; ; addr++){
+ if((p1 = dnodebuf(p, d, addr, 0)) == nil){
+ if(addr1 != 0)
+ break;
+ p1 = dnodebuf(p, d, addr, Tdir);
+ }
+ if(p1 == nil){
+ error = Efull;
+ goto out;
+ }
+ if(checktag(p1, Tdir, d->qid.path)){
+ putbuf(p1);
+ goto phase;
+ }
+ for(slot = 0; slot < DIRPERBUF; slot++){
+ d1 = getdir(p1, slot);
+ if(!(d1->mode & DALLOC)){
+ if(addr1 == 0){
+ addr1 = p1->addr;
+ slot1 = slot + addr*DIRPERBUF;
+ }
+ continue;
+ }
+ if(strncmp(f->name, d1->name, sizeof(d1->name)) == 0){
+ putbuf(p1);
+ error = Eexist;
+ goto out;
+ }
+ }
+ putbuf(p1);
+ }
+
+ switch(f->mode & 7){
+ case OEXEC:
+ case OREAD: /* seems only useful to make directories */
+ fmod = FREAD;
+ break;
+
+ case OWRITE:
+ fmod = FWRITE;
+ break;
+
+ case ORDWR:
+ fmod = FREAD+FWRITE;
+ break;
+
+ default:
+ error = Emode;
+ goto out;
+ }
+ if(f->perm & PDIR)
+ if((f->mode & OTRUNC) || (f->perm & PAPND) || (fmod & FWRITE))
+ goto badaccess;
+ /*
+ * do it
+ */
+ path = qidpathgen(&file->fs->dev);
+ if((p1 = getbuf(file->fs->dev, addr1, Bread|Bimm|Bmod)) == nil)
+ goto phase;
+ d1 = getdir(p1, slot1);
+ if(d1 == nil || checktag(p1, Tdir, d->qid.path)) {
+ putbuf(p1);
+ goto phase;
+ }
+ if(d1->mode & DALLOC){
+ putbuf(p1);
+ goto phase;
+ }
+
+ strncpy(d1->name, f->name, sizeof(d1->name));
+ if(chan == cons.chan){
+ d1->uid = cons.uid;
+ d1->gid = cons.gid;
+ }
+ else{
+ d1->uid = file->uid;
+ d1->gid = d->gid;
+ f->perm &= d->mode | ~0666;
+ if(f->perm & PDIR)
+ f->perm &= d->mode | ~0777;
+ }
+ d1->qid.path = path;
+ d1->qid.version = 0;
+ d1->mode = DALLOC | (f->perm & 0777);
+ if(f->perm & PDIR) {
+ d1->mode |= DDIR;
+ d1->qid.path |= QPDIR;
+ }
+ if(f->perm & PAPND)
+ d1->mode |= DAPND;
+ t = nil;
+ if(f->perm & PLOCK){
+ d1->mode |= DLOCK;
+ t = tlocked(p1, d1);
+ /* if nil, out of tlock structures */
+ }
+ accessdir(p1, d1, FWRITE);
+ mkqid(&r->qid, d1, 0);
+ putbuf(p1);
+ accessdir(p, d, FWRITE);
+
+ /*
+ * do a walk to new directory entry
+ */
+ if((w = newwp()) == nil){
+ error = Ewalk;
+ goto out;
+ }
+ w->addr = file->addr;
+ w->slot = file->slot;
+ w->up = file->wpath;
+ file->wpath = w;
+ file->qid = r->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;
+ goto out;
+
+badaccess:
+ error = Eaccess;
+ goto out;
+
+phase:
+ error = Ephase;
+
+out:
+ if(p != nil)
+ putbuf(p);
+ if(file != nil)
+ qunlock(file);
+
+ r->iounit = chan->msize-IOHDRSZ;
+
+ return error;
+}
+
+static int
+fsread(Chan* chan, Fcall* f, Fcall* r)
+{
+ uchar *data;
+ Iobuf *p, *p1;
+ File *file;
+ Dentry *d, *d1;
+ Tlock *t;
+ long addr, offset, start, tim;
+ int error, iounit, nread, count, n, o, slot;
+ Dir dir;
+ char strdata[28*10];
+
+ p = nil;
+ data = (uchar*)r->data;
+ count = f->count;
+ offset = f->offset;
+ nread = 0;
+ if((file = filep(chan, f->fid, 0)) == nil){
+ error = Efid;
+ goto out;
+ }
+ if(file->qid.type & QTAUTH){
+ nread = authread(file, data, count);
+ if(nread < 0)
+ error = Esystem;
+ else
+ error = 0;
+ goto out;
+ }
+ if(!(file->open & FREAD)){
+ error = Eopen;
+ goto out;
+ }
+ iounit = chan->msize-IOHDRSZ;
+ if(count < 0 || count > iounit){
+ error = Ecount;
+ goto out;
+ }
+ if(offset < 0){
+ error = Eoffset;
+ goto out;
+ }
+ p = getbuf(file->fs->dev, file->addr, Bread);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+ if(t = file->tlock){
+ tim = time(0);
+ if(t->time < tim || t->file != file){
+ error = Ebroken;
+ goto out;
+ }
+ /* renew the lock */
+ t->time = tim + TLOCK;
+ }
+ accessdir(p, d, FREAD);
+ if(d->mode & DDIR)
+ goto dread;
+ if(offset+count > d->size)
+ count = d->size - offset;
+ while(count > 0){
+ if(p == nil){
+ p = getbuf(file->fs->dev, file->addr, Bread);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ }
+ addr = offset / BUFSIZE;
+ o = offset % BUFSIZE;
+ n = BUFSIZE - o;
+ if(n > count)
+ n = count;
+ p1 = dnodebuf1(p, d, addr, 0);
+ p = nil;
+ if(p1 != nil){
+ if(checktag(p1, Tfile, QPNONE)){
+ error = Ephase;
+ putbuf(p1);
+ goto out;
+ }
+ memmove(data+nread, p1->iobuf+o, n);
+ putbuf(p1);
+ }
+ else
+ memset(data+nread, 0, n);
+ count -= n;
+ nread += n;
+ offset += n;
+ }
+ goto out;
+
+dread:
+ /*
+ * Pick up where we left off last time if nothing has changed,
+ * otherwise must scan from the beginning.
+ */
+ 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 = 0;
+ }
+
+dread1:
+ if(p == nil){
+ /*
+ * This is just a check to ensure the entry hasn't
+ * gone away during the read of each directory block.
+ */
+ p = getbuf(file->fs->dev, file->addr, Bread);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out1;
+ }
+ if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out1;
+ }
+ }
+ p1 = dnodebuf1(p, d, addr, 0);
+ p = nil;
+ if(p1 == nil)
+ goto out1;
+ if(checktag(p1, Tdir, QPNONE)){
+ error = Ephase;
+ putbuf(p1);
+ goto out1;
+ }
+
+ for(; slot < DIRPERBUF; slot++){
+ d1 = getdir(p1, slot);
+ if(!(d1->mode & DALLOC))
+ continue;
+ dir9p2(&dir, d1, strdata);
+ if((n = convD2M(&dir, data+nread, iounit - nread)) <= BIT16SZ){
+ putbuf(p1);
+ goto out1;
+ }
+ start += n;
+ if(start < offset)
+ continue;
+ if(count < n){
+ putbuf(p1);
+ goto out1;
+ }
+ count -= n;
+ nread += n;
+ offset += n;
+ }
+ putbuf(p1);
+ slot = 0;
+ addr++;
+ goto dread1;
+
+out1:
+ if(error == 0){
+ file->doffset = offset;
+ file->dvers = file->qid.vers;
+ file->dslot = slot+DIRPERBUF*addr;
+ }
+
+out:
+ /*
+ * Do we need this any more?
+ count = f->count - nread;
+ if(count > 0)
+ memset(data+nread, 0, count);
+ */
+ if(p != nil)
+ putbuf(p);
+ if(file != nil)
+ qunlock(file);
+ r->count = nread;
+ r->data = (char*)data;
+
+ return error;
+}
+
+static int
+fswrite(Chan* chan, Fcall* f, Fcall* r)
+{
+ Iobuf *p, *p1;
+ Dentry *d;
+ File *file;
+ Tlock *t;
+ long offset, addr, tim, qpath;
+ int count, error, nwrite, o, n;
+
+ offset = f->offset;
+ count = f->count;
+
+ nwrite = 0;
+ p = nil;
+
+ if((file = filep(chan, f->fid, 0)) == nil){
+ error = Efid;
+ goto out;
+ }
+ if(file->qid.type & QTAUTH){
+ nwrite = authwrite(file, (uchar*)f->data, count);
+ if(nwrite < 0)
+ error = Esystem;
+ else
+ error = 0;
+ goto out;
+ }
+ if(!(file->open & FWRITE)){
+ error = Eopen;
+ goto out;
+ }
+ if(isro(file->fs->dev) || (writegroup && !ingroup(file->uid, writegroup))){
+ error = Eronly;
+ goto out;
+ }
+ if(count < 0 || count > chan->msize-IOHDRSZ){
+ error = Ecount;
+ goto out;
+ }
+ if(offset < 0) {
+ error = Eoffset;
+ goto out;
+ }
+ if((p = getbuf(file->fs->dev, file->addr, Bread|Bmod)) == nil){
+ error = Ealloc;
+ goto out;
+ }
+ if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+ if(t = file->tlock) {
+ tim = time(0);
+ if(t->time < tim || t->file != file){
+ error = Ebroken;
+ goto out;
+ }
+ /* renew the lock */
+ t->time = tim + TLOCK;
+ }
+ accessdir(p, d, FWRITE);
+ if(d->mode & DAPND)
+ offset = d->size;
+ if(offset+count > d->size)
+ d->size = offset+count;
+ while(count > 0){
+ if(p == nil){
+ p = getbuf(file->fs->dev, file->addr, Bread|Bmod);
+ if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ }
+ addr = offset / BUFSIZE;
+ o = offset % BUFSIZE;
+ n = BUFSIZE - o;
+ if(n > count)
+ n = count;
+ qpath = d->qid.path;
+ p1 = dnodebuf1(p, d, addr, Tfile);
+ p = nil;
+ if(p1 == nil) {
+ error = Efull;
+ goto out;
+ }
+ if(checktag(p1, Tfile, qpath)){
+ putbuf(p1);
+ error = Ephase;
+ goto out;
+ }
+ memmove(p1->iobuf+o, f->data+nwrite, n);
+ p1->flags |= Bmod;
+ putbuf(p1);
+ count -= n;
+ nwrite += n;
+ offset += n;
+ }
+
+out:
+ if(p != nil)
+ putbuf(p);
+ if(file != nil)
+ qunlock(file);
+ r->count = nwrite;
+
+ return error;
+}
+
+static int
+_clunk(File* file, int remove, int wok)
+{
+ Tlock *t;
+ int error;
+
+ error = 0;
+ if(t = file->tlock){
+ if(t->file == file)
+ t->time = 0; /* free the lock */
+ file->tlock = 0;
+ }
+ if(remove)
+ error = doremove(file, wok);
+ file->open = 0;
+ freewp(file->wpath);
+ freefp(file);
+ qunlock(file);
+
+ return error;
+}
+
+static int
+fsclunk(Chan* chan, Fcall* f, Fcall*)
+{
+ File *file;
+
+ if((file = filep(chan, f->fid, 0)) == nil)
+ return Efid;
+
+ _clunk(file, file->open & FREMOV, 0);
+ return 0;
+}
+
+static int
+fsremove(Chan* chan, Fcall* f, Fcall*)
+{
+ File *file;
+
+ if((file = filep(chan, f->fid, 0)) == nil)
+ return Efid;
+
+ return _clunk(file, 1, chan == cons.chan);
+}
+
+static int
+fsstat(Chan* chan, Fcall* f, Fcall* r, uchar* data)
+{
+ Dir dir;
+ Iobuf *p;
+ Dentry *d;
+ File *file;
+ int error, len;
+
+ if((file = filep(chan, f->fid, 0)) == nil)
+ return Efid;
+
+ p = getbuf(file->fs->dev, file->addr, Bread);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Edir1;
+ goto out;
+ }
+ if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+
+ if(d->qid.path == QPROOT) /* stat of root gives time */
+ d->atime = time(0);
+
+ len = dir9p2(&dir, d, data);
+ data += len;
+ if((r->nstat = convD2M(&dir, data, chan->msize - len)) == 0)
+ error = Ersc;
+ else
+ r->stat = data;
+
+out:
+ if(p != nil)
+ putbuf(p);
+ if(file != nil)
+ qunlock(file);
+
+ return error;
+}
+
+static int
+fswstat(Chan* chan, Fcall* f, Fcall*, char *strs)
+{
+ Iobuf *p, *p1;
+ Dentry *d, *d1, xd;
+ File *file;
+ int error, slot, uid, gid, l;
+ long addr;
+ Dir dir;
+ ulong mode;
+
+ p = p1 = nil;
+ d1 = nil;
+
+ if((file = filep(chan, f->fid, 0)) == nil){
+ error = Efid;
+ goto out;
+ }
+
+ /*
+ * if user none,
+ * can't do anything
+ * unless allow.
+ */
+ if(file->uid == 0 && !wstatallow){
+ error = Eaccess;
+ goto out;
+ }
+
+ if(isro(file->fs->dev) || (writegroup && !ingroup(file->uid, writegroup))){
+ error = Eronly;
+ goto out;
+ }
+
+ /*
+ * first get parent
+ */
+ if(file->wpath){
+ p1 = getbuf(file->fs->dev, file->wpath->addr, Bread);
+ if(p1 == nil){
+ error = Ephase;
+ goto out;
+ }
+ d1 = getdir(p1, file->wpath->slot);
+ if(d1 == nil || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)){
+ error = Ephase;
+ goto out;
+ }
+ }
+
+ if((p = getbuf(file->fs->dev, file->addr, Bread)) == nil){
+ error = Ealloc;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+
+ /*
+ * Convert the message and fix up
+ * fields not to be changed.
+ */
+ if(convM2D(f->stat, f->nstat, &dir, strs) == 0){
+ print("9p2: convM2D returns 0\n");
+ error = Econvert;
+ goto out;
+ }
+ if(dir.uid == nil || strlen(dir.uid) == 0)
+ uid = d->uid;
+ else
+ uid = strtouid(dir.uid);
+ if(dir.gid == nil || strlen(dir.gid) == 0)
+ gid = d->gid;
+ else
+ gid = strtouid(dir.gid);
+ if(dir.name == nil || strlen(dir.name) == 0)
+ dir.name = d->name;
+ else{
+ if((l = checkname9p2(dir.name)) == 0){
+ error = Ename;
+ goto out;
+ }
+ if(l > NAMELEN){
+ error = Etoolong;
+ goto out;
+ }
+ }
+
+ /*
+ * 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.type == 0xFF && dir.mode == ~0){
+ dir.mode = d->mode & 0777;
+ 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.type == 0xFF){
+ /* nothing to do */
+ }
+ else if(dir.mode == ~0)
+ dir.mode = (dir.qid.type<<24)|(d->mode & 0777);
+ else if(dir.qid.type != ((dir.mode>>24) & 0xFF)){
+ error = Eqidmode;
+ goto out;
+ }
+
+ /*
+ * Check for unknown type/mode bits
+ * and an attempt to change the directory bit.
+ */
+ if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|0777)){
+ error = Enotm;
+ goto out;
+ }
+ if(d->mode & DDIR)
+ mode = DMDIR;
+ else
+ mode = 0;
+ if((dir.mode^mode) & DMDIR){
+ error = Enotd;
+ goto out;
+ }
+
+ if(dir.mtime == ~0)
+ dir.mtime = d->mtime;
+ if(dir.length == ~0)
+ dir.length = d->size;
+
+ /*
+ * Currently, can't change length.
+ */
+ if(dir.length != d->size){
+ error = Enotl;
+ goto out;
+ }
+
+ /*
+ * if chown,
+ * must be god
+ * wstatallow set to allow chown during boot
+ */
+ if(uid != d->uid && !wstatallow) {
+ error = Enotu;
+ goto out;
+ }
+
+ /*
+ * 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;
+ error = Enotg;
+ goto out;
+ }
+
+ /*
+ * if rename,
+ * must have write permission in parent
+ */
+ while(strncmp(d->name, dir.name, sizeof(d->name)) != 0) {
+ if(checkname(dir.name) || d1 == nil) {
+ error = Ename;
+ goto out;
+ }
+ if(strcmp(dir.name, ".") == 0 || strcmp(xd.name, "..") == 0) {
+ error = Ename;
+ goto out;
+ }
+
+ /*
+ * drop entry to prevent lock, then
+ * check that destination name is unique,
+ */
+ putbuf(p);
+ for(addr = 0; ; addr++) {
+ if((p = dnodebuf(p1, d1, addr, 0)) == nil)
+ break;
+ if(checktag(p, Tdir, d1->qid.path)) {
+ putbuf(p);
+ continue;
+ }
+ for(slot = 0; slot < DIRPERBUF; slot++) {
+ d = getdir(p, slot);
+ if(!(d->mode & DALLOC))
+ continue;
+ if(strncmp(dir.name, d->name, sizeof(d->name)) == 0) {
+ error = Eexist;
+ goto out;
+ }
+ }
+ putbuf(p);
+ }
+
+ /*
+ * reacquire entry
+ */
+ if((p = getbuf(file->fs->dev, file->addr, Bread)) == nil){
+ error = Ephase;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+ error = Ephase;
+ goto out;
+ }
+
+ if(wstatallow || writeallow) /* set to allow rename during boot */
+ break;
+ if(d1 == nil || iaccess(file, d1, DWRITE)) {
+ error = Eaccess;
+ goto out;
+ }
+ break;
+ }
+
+ /*
+ * if mode/time, either
+ * a) owner
+ * b) leader of either group
+ */
+ mode = dir.mode & 0777;
+ if(dir.mode & DMAPPEND)
+ mode |= DAPND;
+ if(dir.mode & DMEXCL)
+ mode |= DLOCK;
+ while(d->mtime != dir.mtime || ((d->mode^mode) & (DAPND|DLOCK|0777))) {
+ 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;
+ error = Enotu;
+ goto out;
+ }
+ d->mtime = dir.mtime;
+ d->uid = uid;
+ d->gid = gid;
+ d->mode = (mode & (DAPND|DLOCK|0777)) | (d->mode & (DALLOC|DDIR));
+
+ strncpy(d->name, dir.name, sizeof(d->name));
+ accessdir(p, d, FWSTAT);
+
+out:
+ if(p != nil)
+ putbuf(p);
+ if(p1 != nil)
+ putbuf(p1);
+ if(file != nil)
+ qunlock(file);
+
+ return error;
+}
+
+static int
+recv(Chan *c, uchar *buf, int n)
+{
+ int fd, m, len;
+
+ fd = c->chan;
+ /* read count */
+ qlock(&c->rlock);
+ m = readn(fd, buf, BIT32SZ);
+ if(m != BIT32SZ){
+ qunlock(&c->rlock);
+ if(m < 0){
+ print("readn(BIT32SZ) fails: %r\n");
+ return -1;
+ }
+ print("readn(BIT32SZ) returns %d: %r\n", m);
+ return 0;
+ }
+
+ len = GBIT32(buf);
+ if(len <= BIT32SZ || len > n){
+ print("recv bad length %d\n", len);
+ werrstr("bad length in 9P2000 message header");
+ qunlock(&c->rlock);
+ return -1;
+ }
+ len -= BIT32SZ;
+ m = readn(fd, buf+BIT32SZ, len);
+ qunlock(&c->rlock);
+ if(m < len){
+ print("recv wanted %d got %d\n", len, m);
+ return 0;
+ }
+ return BIT32SZ+m;
+}
+
+static void
+send(Chan *c, uchar *buf, int n)
+{
+ int fd, m;
+
+ fd = c->chan;
+ qlock(&c->wlock);
+ m = write(fd, buf, n);
+ qunlock(&c->wlock);
+ if(m == n)
+ return;
+ panic("write failed");
+}
+
+void
+serve9p2(Chan *chan, uchar *ib, int nib)
+{
+ uchar inbuf[MSIZE+IOHDRSZ], outbuf[MSIZE+IOHDRSZ];
+ Fcall f, r;
+ char ename[64];
+ int error, n, type;
+
+ chan->msize = MSIZE;
+ fmtinstall('F', fcallfmt);
+
+ for(;;){
+ if(nib){
+ memmove(inbuf, ib, nib);
+ n = nib;
+ nib = 0;
+ }else
+ n = recv(chan, inbuf, sizeof inbuf);
+ if(chat){
+ print("read msg %d (fd %d)\n", n, chan->chan);
+ if(n <= 0)
+ print("\terr: %r\n");
+ }
+ if(n == 0 && (chan == cons.srvchan || chan == cons.chan))
+ continue;
+ if(n <= 0)
+ break;
+ if(convM2S(inbuf, n, &f) != n){
+ print("9p2: cannot decode\n");
+ continue;
+ }
+
+ type = f.type;
+ if(type < Tversion || type >= Tmax || (type&1) || type == Terror){
+ print("9p2: bad message type %d\n", type);
+ continue;
+ }
+
+ if(CHAT(chan))
+ print("9p2: f %F\n", &f);
+
+ r.type = type+1;
+ r.tag = f.tag;
+ error = 0;
+
+ rlock(&mainlock);
+ rlock(&chan->reflock);
+ switch(type){
+ default:
+ r.type = Rerror;
+ snprint(ename, sizeof ename, "unknown message: %F", &f);
+ r.ename = ename;
+ break;
+ case Tversion:
+ error = fsversion(chan, &f, &r);
+ break;
+ case Tauth:
+ error = fsauth(chan, &f, &r);
+ break;
+ case Tattach:
+ error = fsattach(chan, &f, &r);
+ break;
+ case Tflush:
+ error = fsflush(chan, &f, &r);
+ break;
+ case Twalk:
+ error = fswalk(chan, &f, &r);
+ break;
+ case Topen:
+ error = fsopen(chan, &f, &r);
+ break;
+ case Tcreate:
+ error = fscreate(chan, &f, &r);
+ break;
+ case Tread:
+ r.data = (char*)inbuf;
+ error = fsread(chan, &f, &r);
+ break;
+ case Twrite:
+ error = fswrite(chan, &f, &r);
+ break;
+ case Tclunk:
+ error = fsclunk(chan, &f, &r);
+ break;
+ case Tremove:
+ error = fsremove(chan, &f, &r);
+ break;
+ case Tstat:
+ error = fsstat(chan, &f, &r, inbuf);
+ break;
+ case Twstat:
+ error = fswstat(chan, &f, &r, (char*)outbuf);
+ break;
+ }
+ runlock(&chan->reflock);
+ runlock(&mainlock);
+
+ if(error != 0){
+ r.type = Rerror;
+ if(error >= MAXERR){
+ snprint(ename, sizeof(ename), "error %d", error);
+ r.ename = ename;
+ }
+ else
+ r.ename = errstring[error];
+ }
+ if(CHAT(chan))
+ print("9p2: r %F\n", &r);
+
+ n = convS2M(&r, outbuf, sizeof outbuf);
+ if(n == 0){
+ type = r.type;
+ r.type = Rerror;
+ snprint(ename, sizeof(ename), "9p2: convS2M: type %d", type);
+ r.ename = ename;
+ print(ename);
+ n = convS2M(&r, outbuf, sizeof outbuf);
+ if(n == 0){
+ /*
+ * What to do here, the failure notification failed?
+ */
+ panic("can't write anything at all");
+ }
+ }
+ send(chan, outbuf, n);
+ }
+ fileinit(chan);
+ close(chan->chan);
+ if(chan == cons.srvchan || chan == cons.chan)
+ print("console chan read error");
+}
+
diff --git a/sys/src/cmd/disk/kfs/all.h b/sys/src/cmd/disk/kfs/all.h
new file mode 100755
index 000000000..3f1e165c5
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/all.h
@@ -0,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+#include "dat.h"
+#include "fns.h"
+
+#include <fcall.h>
+#include <auth.h>
+#include <authsrv.h>
diff --git a/sys/src/cmd/disk/kfs/auth.c b/sys/src/cmd/disk/kfs/auth.c
new file mode 100755
index 000000000..4d02c035f
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/auth.c
@@ -0,0 +1,117 @@
+/*
+ * Network listening authentication.
+ * This and all the other network-related
+ * code is due to Richard Miller.
+ */
+#include "all.h"
+#include "9p1.h"
+
+int allownone;
+Nvrsafe nvr;
+int didread;
+
+/*
+ * create a challenge for a fid space
+ */
+void
+mkchallenge(Chan *cp)
+{
+ int i;
+
+ if(!didread && readnvram(&nvr, 0) >= 0)
+ didread = 1;
+
+ srand(truerand());
+ for(i = 0; i < CHALLEN; i++)
+ cp->chal[i] = nrand(256);
+
+ cp->idoffset = 0;
+ cp->idvec = 0;
+}
+
+/*
+ * match a challenge from an attach
+ */
+Nvrsafe nvr;
+
+int
+authorize(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ Ticket t;
+ Authenticator a;
+ int x;
+ ulong bit;
+
+ if (cp == cons.srvchan) /* local channel already safe */
+ return 1;
+
+ if(wstatallow) /* set to allow entry during boot */
+ return 1;
+
+ if(strcmp(in->uname, "none") == 0)
+ return allownone || cp->authed;
+
+ if(in->type == Toattach9p1)
+ return 0;
+
+ if(!didread)
+ return 0;
+
+ /* decrypt and unpack ticket */
+ convM2T(in->ticket, &t, nvr.machkey);
+ if(t.num != AuthTs){
+print("bad AuthTs num\n");
+ return 0;
+ }
+
+ /* decrypt and unpack authenticator */
+ convM2A(in->auth, &a, t.key);
+ if(a.num != AuthAc){
+print("bad AuthAc num\n");
+ return 0;
+ }
+
+ /* challenges must match */
+ if(memcmp(a.chal, cp->chal, sizeof(a.chal)) != 0){
+print("bad challenge\n");
+ return 0;
+ }
+
+ /*
+ * the id must be in a valid range. the range is specified by a
+ * lower bount (idoffset) and a bit vector (idvec) where a
+ * bit set to 1 means unusable
+ */
+ lock(&cp->idlock);
+ x = a.id - cp->idoffset;
+ bit = 1<<x;
+ if(x < 0 || x > 31 || (bit&cp->idvec)){
+ unlock(&cp->idlock);
+ return 0;
+ }
+ cp->idvec |= bit;
+
+ /* normalize the vector */
+ while(cp->idvec&0xffff0001){
+ cp->idvec >>= 1;
+ cp->idoffset++;
+ }
+ unlock(&cp->idlock);
+
+ /* ticket name and attach name must match */
+ if(memcmp(in->uname, t.cuid, sizeof(in->uname)) != 0){
+print("names don't match\n");
+ return 0;
+ }
+
+ /* copy translated name into input record */
+ memmove(in->uname, t.suid, sizeof(in->uname));
+
+ /* craft a reply */
+ a.num = AuthAs;
+ memmove(a.chal, cp->rchal, CHALLEN);
+ convA2M(&a, ou->rauth, t.key);
+
+ cp->authed = 1;
+ return 1;
+}
diff --git a/sys/src/cmd/disk/kfs/chk.c b/sys/src/cmd/disk/kfs/chk.c
new file mode 100755
index 000000000..bea1cbee1
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/chk.c
@@ -0,0 +1,654 @@
+#include "all.h"
+
+#define DSIZE 546000
+#define MAXDEPTH 100
+
+static char* abits;
+static long sizabits;
+static char* qbits;
+static long sizqbits;
+static char* name;
+static long sizname;
+static long fstart;
+static long fsize;
+static long nfiles;
+static long maxq;
+static char* fence;
+static char* fencebase;
+static Device dev;
+static long ndup;
+static long nused;
+static long nfdup;
+static long nqbad;
+static long nfree;
+static long nbad;
+static int mod;
+static int flags;
+static int ronly;
+static int cwflag;
+static long sbaddr;
+static long oldblock;
+static int depth;
+static int maxdepth;
+
+/* local prototypes */
+static int fsck(Dentry*);
+static void ckfreelist(Superb*);
+static void mkfreelist(Superb*);
+static Dentry* maked(long, int, long);
+static void modd(long, int, Dentry*);
+static void xread(long, long);
+static int amark(long);
+static int fmark(long);
+static void missing(void);
+static void qmark(long);
+static void* zalloc(ulong);
+static void* dalloc(ulong);
+static Iobuf* xtag(long, int, long);
+
+static
+void*
+zalloc(ulong n)
+{
+ char *p;
+
+ p = malloc(n);
+ if(p == 0)
+ panic("zalloc: out of memory\n");
+ memset(p, '\0', n);
+ return p;
+}
+
+static
+void*
+dalloc(ulong n)
+{
+ char *p;
+
+ if(fencebase == 0)
+ fence = fencebase = zalloc(MAXDEPTH*sizeof(Dentry));
+ p = fence;
+ fence += n;
+ if(fence > fencebase+MAXDEPTH*sizeof(Dentry))
+ panic("dalloc too much memory\n");
+ return p;
+}
+
+void
+check(Filsys *fs, long flag)
+{
+ Iobuf *p;
+ Superb *sb;
+ Dentry *d;
+ long raddr;
+ long nqid;
+
+ wlock(&mainlock);
+ dev = fs->dev;
+ flags = flag;
+ fence = fencebase;
+
+ sizname = 4000;
+ name = zalloc(sizname);
+ sizname -= NAMELEN+10; /* for safety */
+
+ sbaddr = superaddr(dev);
+ raddr = getraddr(dev);
+ p = xtag(sbaddr, Tsuper, QPSUPER);
+ if(!p){
+ cprint("bad superblock\n");
+ goto out;
+ }
+ sb = (Superb*)p->iobuf;
+ fstart = 1;
+
+ fsize = sb->fsize;
+ sizabits = (fsize-fstart + 7)/8;
+ abits = zalloc(sizabits);
+
+ 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;
+
+ sizqbits = (nqid+7)/8;
+ qbits = zalloc(sizqbits);
+
+ mod = 0;
+ nfree = 0;
+ nfdup = 0;
+ nused = 0;
+ nbad = 0;
+ ndup = 0;
+ nqbad = 0;
+ depth = 0;
+ maxdepth = 0;
+
+ if(flags & Ctouch) {
+ oldblock = fsize/DSIZE;
+ oldblock *= DSIZE;
+ if(oldblock < 0)
+ oldblock = 0;
+ cprint("oldblock = %ld\n", oldblock);
+ }
+ if(amark(sbaddr))
+ {}
+ if(cwflag) {
+ if(amark(sb->roraddr))
+ {}
+ if(amark(sb->next))
+ {}
+ }
+
+ if(!(flags & Cquiet))
+ cprint("checking file system: %s\n", fs->name);
+ nfiles = 0;
+ maxq = 0;
+
+ d = maked(raddr, 0, QPROOT);
+ if(d) {
+ if(amark(raddr))
+ {}
+ if(fsck(d))
+ modd(raddr, 0, d);
+ depth--;
+ fence -= sizeof(Dentry);
+ if(depth)
+ cprint("depth not zero on return\n");
+ }
+
+ if(flags & Cfree) {
+ mkfreelist(sb);
+ sb->qidgen = maxq;
+ settag(p, Tsuper, QPNONE);
+ }
+
+ if(sb->qidgen < maxq)
+ cprint("qid generator low path=%ld maxq=%ld\n",
+ sb->qidgen, maxq);
+ if(!(flags & Cfree))
+ ckfreelist(sb);
+ if(mod) {
+ cprint("file system was modified\n");
+ settag(p, Tsuper, QPNONE);
+ }
+
+ if(!(flags & Cquiet)){
+ cprint("%8ld files\n", nfiles);
+ cprint("%8ld blocks in the file system\n", fsize-fstart);
+ cprint("%8ld used blocks\n", nused);
+ cprint("%8ld free blocks\n", sb->tfree);
+ }
+ if(!(flags & Cfree)){
+ if(nfree != sb->tfree)
+ cprint("%8ld free blocks found\n", nfree);
+ if(nfdup)
+ cprint("%8ld blocks duplicated in the free list\n", nfdup);
+ if(fsize-fstart-nused-nfree)
+ cprint("%8ld missing blocks\n", fsize-fstart-nused-nfree);
+ }
+ if(ndup)
+ cprint("%8ld address duplications\n", ndup);
+ if(nbad)
+ cprint("%8ld bad block addresses\n", nbad);
+ if(nqbad)
+ cprint("%8ld bad qids\n", nqbad);
+ if(!(flags & Cquiet))
+ cprint("%8ld maximum qid path\n", maxq);
+ missing();
+
+out:
+ if(p)
+ putbuf(p);
+ free(abits);
+ free(name);
+ free(qbits);
+ wunlock(&mainlock);
+}
+
+static
+int
+touch(long a)
+{
+ Iobuf *p;
+
+ if((flags&Ctouch) && a && a < oldblock){
+ p = getbuf(dev, a, Bread|Bmod);
+ if(p)
+ putbuf(p);
+ return 1;
+ }
+ return 0;
+}
+
+static
+int
+checkdir(long a, long qpath)
+{
+ Dentry *nd;
+ int i, ns, dmod;
+
+ ns = strlen(name);
+ dmod = touch(a);
+ for(i=0; i<DIRPERBUF; i++) {
+ nd = maked(a, i, qpath);
+ if(!nd)
+ break;
+ if(fsck(nd)) {
+ modd(a, i, nd);
+ dmod++;
+ }
+ depth--;
+ fence -= sizeof(Dentry);
+ name[ns] = 0;
+ }
+ name[ns] = 0;
+ return dmod;
+}
+
+static
+int
+checkindir(long a, Dentry *d, long qpath)
+{
+ Iobuf *p;
+ int i, dmod;
+
+ dmod = touch(a);
+ p = xtag(a, Tind1, qpath);
+ if(!p)
+ return dmod;
+ for(i=0; i<INDPERBUF; i++) {
+ a = ((long*)p->iobuf)[i];
+ if(!a)
+ continue;
+ if(amark(a)) {
+ if(flags & Cbad) {
+ ((long*)p->iobuf)[i] = 0;
+ p->flags |= Bmod;
+ }
+ continue;
+ }
+ if(d->mode & DDIR)
+ dmod += checkdir(a, qpath);
+ else if(flags & Crdall)
+ xread(a, qpath);
+ }
+ putbuf(p);
+ return dmod;
+}
+
+static
+int
+fsck(Dentry *d)
+{
+ char *s;
+ Rune r;
+ Iobuf *p;
+ int l, i, ns, dmod;
+ long a, qpath;
+
+ depth++;
+ if(depth >= maxdepth){
+ maxdepth = depth;
+ if(maxdepth >= MAXDEPTH){
+ cprint("max depth exceeded: %s\n", name);
+ return 0;
+ }
+ }
+ dmod = 0;
+ if(!(d->mode & DALLOC))
+ return 0;
+ nfiles++;
+
+ ns = strlen(name);
+ i = strlen(d->name);
+ if(i >= NAMELEN){
+ d->name[NAMELEN-1] = 0;
+ cprint("%s->name (%s) not terminated\n", name, d->name);
+ return 0;
+ }
+ ns += i;
+ if(ns >= sizname){
+ cprint("%s->name (%s) name too large\n", name, d->name);
+ return 0;
+ }
+ for (s = d->name; *s; s += l){
+ l = chartorune(&r, s);
+ if (r == Runeerror)
+ for (i = 0; i < l; i++){
+ s[i] = '_';
+ cprint("%s->name (%s) bad UTF\n", name, d->name);
+ dmod++;
+ }
+ }
+ strcat(name, d->name);
+
+ if(d->mode & DDIR){
+ if(ns > 1)
+ strcat(name, "/");
+ if(flags & Cpdir)
+ cprint("%s\n", name);
+ } else
+ if(flags & Cpfile)
+ cprint("%s\n", name);
+
+ qpath = d->qid.path & ~QPDIR;
+ qmark(qpath);
+ if(qpath > maxq)
+ maxq = qpath;
+ for(i=0; i<NDBLOCK; i++) {
+ a = d->dblock[i];
+ if(!a)
+ continue;
+ if(amark(a)) {
+ d->dblock[i] = 0;
+ dmod++;
+ continue;
+ }
+ if(d->mode & DDIR)
+ dmod += checkdir(a, qpath);
+ else if(flags & Crdall)
+ xread(a, qpath);
+ }
+ a = d->iblock;
+ if(a && amark(a)) {
+ d->iblock = 0;
+ dmod++;
+ }
+ else if(a)
+ dmod += checkindir(a, d, qpath);
+
+ a = d->diblock;
+ if(a && amark(a)) {
+ d->diblock = 0;
+ return dmod + 1;
+ }
+ dmod += touch(a);
+ if(p = xtag(a, Tind2, qpath)){
+ for(i=0; i<INDPERBUF; i++){
+ a = ((long*)p->iobuf)[i];
+ if(!a)
+ continue;
+ if(amark(a)) {
+ if(flags & Cbad) {
+ ((long*)p->iobuf)[i] = 0;
+ p->flags |= Bmod;
+ }
+ continue;
+ }
+ dmod += checkindir(a, d, qpath);
+ }
+ putbuf(p);
+ }
+ return dmod;
+}
+
+static
+void
+ckfreelist(Superb *sb)
+{
+ long a, lo, hi;
+ int n, i;
+ Iobuf *p;
+ Fbuf *fb;
+
+
+ strcpy(name, "free list");
+ cprint("check %s\n", name);
+ fb = &sb->fbuf;
+ a = sbaddr;
+ p = 0;
+ lo = 0;
+ hi = 0;
+ for(;;) {
+ n = fb->nfree;
+ if(n < 0 || n > FEPERBUF) {
+ cprint("check: nfree bad %ld\n", a);
+ break;
+ }
+ for(i=1; i<n; i++) {
+ a = fb->free[i];
+ if(a && !fmark(a)) {
+ if(!lo || lo > a)
+ lo = a;
+ if(!hi || hi < a)
+ hi = a;
+ }
+ }
+ a = fb->free[0];
+ if(!a)
+ break;
+ if(fmark(a))
+ break;
+ if(!lo || lo > a)
+ lo = a;
+ if(!hi || hi < a)
+ hi = a;
+ if(p)
+ putbuf(p);
+ p = xtag(a, Tfree, QPNONE);
+ if(!p)
+ break;
+ fb = (Fbuf*)p->iobuf;
+ }
+ if(p)
+ putbuf(p);
+ cprint("lo = %ld; hi = %ld\n", lo, hi);
+}
+
+/*
+ * make freelist from scratch
+ */
+static
+void
+mkfreelist(Superb *sb)
+{
+ long a;
+ int i, b;
+
+ strcpy(name, "free list");
+ memset(&sb->fbuf, 0, sizeof(sb->fbuf));
+ sb->fbuf.nfree = 1;
+ sb->tfree = 0;
+ for(a=fsize-fstart-1; a >= 0; a--) {
+ i = a/8;
+ if(i < 0 || i >= sizabits)
+ continue;
+ b = 1 << (a&7);
+ if(abits[i] & b)
+ continue;
+ addfree(dev, fstart+a, sb);
+ abits[i] |= b;
+ }
+}
+
+static
+Dentry*
+maked(long a, int s, long qpath)
+{
+ Iobuf *p;
+ Dentry *d, *d1;
+
+ p = xtag(a, Tdir, qpath);
+ if(!p)
+ return 0;
+ d = getdir(p, s);
+ d1 = dalloc(sizeof(Dentry));
+ memmove(d1, d, sizeof(Dentry));
+ putbuf(p);
+ return d1;
+}
+
+static
+void
+modd(long a, int s, Dentry *d1)
+{
+ Iobuf *p;
+ Dentry *d;
+
+ if(!(flags & Cbad))
+ return;
+ p = getbuf(dev, a, Bread);
+ d = getdir(p, s);
+ if(!d) {
+ if(p)
+ putbuf(p);
+ return;
+ }
+ memmove(d, d1, sizeof(Dentry));
+ p->flags |= Bmod;
+ putbuf(p);
+}
+
+static
+void
+xread(long a, long qpath)
+{
+ Iobuf *p;
+
+ p = xtag(a, Tfile, qpath);
+ if(p)
+ putbuf(p);
+}
+
+static
+Iobuf*
+xtag(long a, int tag, long qpath)
+{
+ Iobuf *p;
+
+ if(a == 0)
+ return 0;
+ p = getbuf(dev, a, Bread);
+ if(!p) {
+ cprint("check: \"%s\": xtag: p null\n", name);
+ if(flags & (Cream|Ctag)) {
+ p = getbuf(dev, a, Bmod);
+ if(p) {
+ memset(p->iobuf, 0, RBUFSIZE);
+ settag(p, tag, qpath);
+ mod++;
+ return p;
+ }
+ }
+ return 0;
+ }
+ if(checktag(p, tag, qpath)) {
+ cprint("check: \"%s\": xtag: checktag\n", name);
+ if(flags & Cream)
+ memset(p->iobuf, 0, RBUFSIZE);
+ if(flags & (Cream|Ctag)) {
+ settag(p, tag, qpath);
+ mod++;
+ }
+ return p;
+ }
+ return p;
+}
+
+static
+int
+amark(long a)
+{
+ long i;
+ int b;
+
+ if(a < fstart || a >= fsize) {
+ cprint("check: \"%s\": range %ld\n",
+ name, a);
+ nbad++;
+ return 1;
+ }
+ a -= fstart;
+ i = a/8;
+ b = 1 << (a&7);
+ if(abits[i] & b) {
+ if(!ronly) {
+ if(ndup < 10)
+ cprint("check: \"%s\": address dup %ld\n",
+ name, fstart+a);
+ else
+ if(ndup == 10)
+ cprint("...");
+ }
+ ndup++;
+ return 0; /* really?? */
+ }
+ abits[i] |= b;
+ nused++;
+ return 0;
+}
+
+static
+int
+fmark(long a)
+{
+ long i;
+ int b;
+
+ if(a < fstart || a >= fsize) {
+ cprint("check: \"%s\": range %ld\n",
+ name, a);
+ nbad++;
+ return 1;
+ }
+ a -= fstart;
+ i = a/8;
+ b = 1 << (a&7);
+ if(abits[i] & b) {
+ cprint("check: \"%s\": address dup %ld\n",
+ name, fstart+a);
+ nfdup++;
+ return 1;
+ }
+ abits[i] |= b;
+ nfree++;
+ return 0;
+}
+
+static
+void
+missing(void)
+{
+ long a, i;
+ int b, n;
+
+ n = 0;
+ for(a=fsize-fstart-1; a>=0; a--) {
+ i = a/8;
+ b = 1 << (a&7);
+ if(!(abits[i] & b)) {
+ cprint("missing: %ld\n", fstart+a);
+ n++;
+ }
+ if(n > 10) {
+ cprint(" ...\n");
+ break;
+ }
+ }
+}
+
+static
+void
+qmark(long qpath)
+{
+ int i, b;
+
+ i = qpath/8;
+ b = 1 << (qpath&7);
+ if(i < 0 || i >= sizqbits) {
+ nqbad++;
+ if(nqbad < 20)
+ cprint("check: \"%s\": qid out of range %lux\n",
+ name, qpath);
+ return;
+ }
+ if((qbits[i] & b) && !ronly) {
+ nqbad++;
+ if(nqbad < 20)
+ cprint("check: \"%s\": qid dup %lux\n",
+ name, qpath);
+ }
+ qbits[i] |= b;
+}
diff --git a/sys/src/cmd/disk/kfs/con.c b/sys/src/cmd/disk/kfs/con.c
new file mode 100755
index 000000000..7ed350a81
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/con.c
@@ -0,0 +1,705 @@
+#include "all.h"
+#include "9p1.h"
+
+static char elem[NAMELEN];
+static Filsys* cur_fs;
+static char conline[100];
+
+void
+consserve(void)
+{
+ con_session();
+ cmd_exec("cfs");
+ cmd_exec("user");
+}
+
+int
+cmd_exec(char *arg)
+{
+ char *s, *c;
+ int i;
+
+ for(i=0; s = command[i].string; i++) {
+ for(c=arg; *s; c++)
+ if(*c != *s++)
+ goto brk;
+ if(*c == '\0' || *c == ' ' || *c == '\t'){
+ cons.arg = c;
+ (*command[i].func)();
+ return 1;
+ }
+ brk:;
+ }
+ return 0;
+}
+
+void
+cmd_check(void)
+{
+ char *s;
+ int flags;
+
+ flags = 0;
+ for(s = cons.arg; *s; s++){
+ while(*s == ' ' || *s == '\t')
+ s++;
+ if(*s == '\0')
+ break;
+ switch(*s){
+ /* rebuild the free list */
+ case 'f': flags |= Cfree; break;
+ /* fix bad tags */
+ case 't': flags |= Ctag; break;
+ /* fix bad tags and clear the contents of the block */
+ case 'c': flags |= Cream; break;
+ /* delete all redundant references to a block */
+ case 'd': flags |= Cbad; break;
+ /* read and check tags on all blocks */
+ case 'r': flags |= Crdall; break;
+ /* write all of the blocks you touch */
+ case 'w': flags |= Ctouch; break;
+ /* print all directories as they are read */
+ case 'p': flags |= Cpdir; break;
+ /* print all files as they are read */
+ case 'P': flags |= Cpfile; break;
+ /* quiet, just report really nasty stuff */
+ case 'q': flags |= Cquiet; break;
+ }
+ }
+ check(cur_fs, flags);
+}
+
+enum
+{
+ Sset = (1<<0),
+ Setc = (1<<1),
+};
+void
+cmd_stats(void)
+{
+ cprint("work stats\n");
+ cprint(" work = %A rps\n", (Filta){&cons.work, 1});
+ cprint(" rate = %A tBps\n", (Filta){&cons.rate, 1000});
+ cprint(" hits = %A iops\n", (Filta){&cons.bhit, 1});
+ cprint(" read = %A iops\n", (Filta){&cons.bread, 1});
+ cprint(" init = %A iops\n", (Filta){&cons.binit, 1});
+/* for(i = 0; i < MAXTAG; i++)
+ cprint(" tag %G = %A iops\n", i, (Filta){&cons.tags[i], 1});
+*/
+}
+
+void
+cmd_sync(void)
+{
+ rlock(&mainlock);
+ syncall();
+ runlock(&mainlock);
+}
+
+void
+cmd_halt(void)
+{
+ wlock(&mainlock);
+ syncall();
+ superok(cur_fs->dev, superaddr(cur_fs->dev), 1);
+ print("kfs: file system halted\n");
+}
+
+void
+cmd_start(void)
+{
+ superok(cur_fs->dev, superaddr(cur_fs->dev), 0);
+ wunlock(&mainlock);
+ print("kfs: file system started\n");
+}
+
+void
+cmd_help(void)
+{
+ int i;
+
+ for(i=0; command[i].string; i++)
+ cprint(" %s %s\n", command[i].string, command[i].args);
+ cprint("check options:\n"
+ " r read all blocks\n"
+ " f rebuild the free list\n"
+ " t fix all bad tags\n"
+ " c fix bad tags and zero the blocks\n"
+ " d delete all redundant references to blocks\n"
+ " p print directories as they are checked\n"
+ " P print all files as they are checked\n"
+ " w write all blocks that are read\n");
+}
+
+void
+cmd_create(void)
+{
+ int uid, gid, err;
+ long perm;
+ char oelem[NAMELEN];
+ char name[NAMELEN];
+
+ if(err = con_clone(FID1, FID2)){
+ cprint("clone failed: %s\n", errstring[err]);
+ return;
+ }
+ if(skipbl(1)){
+ cprint("skipbl\n");
+ return;
+ }
+ oelem[0] = 0;
+ while(nextelem()) {
+ if(oelem[0])
+ if(err = con_walk(FID2, oelem)){
+ cprint("walk failed: %s\n", errstring[err]);
+ return;
+ }
+ memmove(oelem, elem, NAMELEN);
+ }
+ if(skipbl(1))
+ return;
+ uid = strtouid(cname(name));
+ if(uid == 0){
+ cprint("unknown user %s\n", name);
+ return;
+ }
+ gid = strtouid(cname(name));
+ if(gid == 0){
+ cprint("unknown group %s\n", name);
+ return;
+ }
+ perm = number(0777, 8);
+ skipbl(0);
+ for(; *cons.arg; cons.arg++){
+ if(*cons.arg == 'l')
+ perm |= PLOCK;
+ else
+ if(*cons.arg == 'a')
+ perm |= PAPND;
+ else
+ if(*cons.arg == 'd')
+ perm |= PDIR;
+ else
+ break;
+ }
+ err = con_create(FID2, elem, uid, gid, perm, 0);
+ if(err)
+ cprint("can't create %s: %s\n", elem, errstring[err]);
+}
+
+void
+cmd_clri(void)
+{
+ if(con_clone(FID1, FID2))
+ return;
+ if(skipbl(1))
+ return;
+ while(nextelem())
+ if(con_walk(FID2, elem)){
+ cprint("can't walk %s\n", elem);
+ return;
+ }
+ con_clri(FID2);
+}
+
+void
+cmd_rename(void)
+{
+ ulong perm;
+ Dentry d;
+ char stat[DIRREC];
+ char oelem[NAMELEN], noelem[NAMELEN], nxelem[NAMELEN];
+ int err;
+
+ if(con_clone(FID1, FID2))
+ return;
+ if(skipbl(1))
+ return;
+ oelem[0] = 0;
+ while(nextelem()) {
+ if(oelem[0])
+ if(con_walk(FID2, oelem)){
+ cprint("file does not exits");
+ return;
+ }
+ memmove(oelem, elem, NAMELEN);
+ }
+ if(skipbl(1))
+ return;
+ if(cons.arg[0]=='/'){
+ if(con_clone(FID1, FID3))
+ return;
+ noelem[0] = 0;
+ while(nextelem()){
+ if(noelem[0])
+ if(con_walk(FID3, noelem)){
+ cprint("target path %s does not exist", noelem);
+ return;
+ }
+ memmove(noelem, elem, NAMELEN);
+ }
+ if(!con_walk(FID3, elem)){
+ cprint("target %s already exists\n", elem);
+ return;
+ }
+ if(con_walk(FID2, oelem)){
+ cprint("src %s does not exist\n", oelem);
+ return;
+ }
+ /*
+ * we know the target does not exist,
+ * the source does exist.
+ * to do the rename, create the target and then
+ * copy the directory entry directly. then remove the source.
+ */
+ if(err = con_stat(FID2, stat)){
+ cprint("can't stat file: %s\n", errstring[err]);
+ return;
+ }
+ convM2D9p1(stat, &d);
+ perm = (d.mode&0777)|((d.mode&0x7000)<<17);
+ if(err = con_create(FID3, elem, d.uid, d.gid, perm, (d.mode&DDIR)?OREAD:ORDWR)){
+ cprint("can't create %s: %s\n", elem, errstring[err]);
+ return;
+ }
+ if(err = con_swap(FID2, FID3)){
+ cprint("can't swap data: %s\n", errstring[err]);
+ return;
+ }
+ if(err = con_remove(FID2)){
+ cprint("can't remove file: %s\n", errstring[err]);
+ return;
+ }
+ }else{
+ cname(nxelem);
+ if(strchr(nxelem, '/')){
+ cprint("bad rename target: not full path, but contains slashes\n");
+ return;
+ }
+ if(!con_walk(FID2, nxelem))
+ cprint("file %s already exists\n", nxelem);
+ else if(con_walk(FID2, oelem))
+ cprint("file does not already exist\n");
+ else if(err = con_stat(FID2, stat))
+ cprint("can't stat file: %s\n", errstring[err]);
+ else{
+ convM2D9p1(stat, &d);
+ strncpy(d.name, nxelem, NAMELEN);
+ convD2M9p1(&d, stat);
+ if(err = con_wstat(FID2, stat))
+ cprint("can't move file: %s\n", errstring[err]);
+ }
+ }
+}
+
+void
+cmd_remove(void)
+{
+ if(con_clone(FID1, FID2))
+ return;
+ if(skipbl(1))
+ return;
+ while(nextelem())
+ if(con_walk(FID2, elem)){
+ cprint("can't walk %s\n", elem);
+ return;
+ }
+ con_remove(FID2);
+}
+
+void
+cmd_cfs(void)
+{
+ Filsys *fs;
+
+ if(*cons.arg != ' ') {
+ fs = &filesys[0]; /* default */
+ } else {
+ if(skipbl(1)){
+ cprint("skipbl\n");
+ return;
+ }
+ if(!nextelem())
+ fs = &filesys[0]; /* default */
+ else
+ fs = fsstr(elem);
+ }
+ if(fs == 0) {
+ cprint("unknown file system %s\n", elem);
+ return;
+ }
+ if(con_attach(FID1, "adm", fs->name))
+ panic("FID1 attach to root");
+ cur_fs = fs;
+}
+
+/*
+ * find out the length of a file
+ * given the mesg version of a stat buffer
+ * we call this because convM2D is different
+ * for the file system than in the os
+ */
+static uvlong
+statlen(char *ap)
+{
+ uchar *p;
+ ulong ll, hl;
+
+ p = (uchar*)ap;
+ p += 3*28+5*4;
+ ll = p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
+ hl = p[4] | (p[5]<<8) | (p[6]<<16) | (p[7]<<24);
+ return ll | ((uvlong) hl << 32);
+}
+
+int
+adduser(char *user, int isgroup)
+{
+ char stat[DIRREC];
+ char msg[100];
+ Uid *u;
+ int i, c, nu;
+
+ /*
+ * check uniq of name
+ * and get next uid
+ */
+ cmd_exec("cfs");
+ cmd_exec("user");
+ if(isgroup)
+ nu = 9000;
+ else
+ nu = 0;
+ for(i=0, u=uid; i<conf.nuid; i++,u++) {
+ c = u->uid;
+ if(c == 0)
+ break;
+ if(strcmp(uidspace+u->offset, user) == 0)
+ return 1;
+ if(c >= 9000 && !isgroup)
+ continue;
+ if(c > nu)
+ nu = c;
+ }
+ nu++;
+ if(isgroup){
+ if(nu >= 0x10000) {
+ cprint("out of group ids\n");
+ return 0;
+ }
+ } else {
+ if(nu >= 9000) {
+ cprint("out of user ids\n");
+ return 0;
+ }
+ }
+
+ /*
+ * write onto adm/users
+ */
+ if(con_clone(FID1, FID2)
+ || con_path(FID2, "/adm/users")
+ || con_open(FID2, 1)) {
+ cprint("can't open /adm/users\n");
+ return 0;
+ }
+
+ sprint(msg, "%d:%s:%s:\n", nu, user, user);
+ cprint("add user %s", msg);
+ c = strlen(msg);
+ i = con_stat(FID2, stat);
+ if(i){
+ cprint("can't stat /adm/users: %s\n", errstring[i]);
+ return 0;
+ }
+ i = con_write(FID2, msg, statlen(stat), c);
+ if(i != c){
+ cprint("short write on /adm/users: %d %d\n", c, i);
+ return 0;
+ }
+ return 1;
+}
+
+void
+cmd_newuser(void)
+{
+ char user[NAMELEN], param[NAMELEN], msg[100];
+ int i, c;
+
+ /*
+ * get uid
+ */
+ cname(user);
+ cname(param);
+ for(i=0; i<NAMELEN; i++) {
+ c = user[i];
+ if(c == 0)
+ break;
+ if(c >= '0' && c <= '9'
+ || c >= 'a' && c <= 'z'
+ || c >= 'A' && c <= 'Z')
+ continue;
+ cprint("bad character in name: 0x%x\n", c);
+ return;
+ }
+ if(i < 2) {
+ cprint("name too short: %s\n", user);
+ return;
+ }
+ if(i >= NAMELEN) {
+ cprint("name too long: %s\n", user);
+ return;
+ }
+
+ switch(param[0]){
+ case 0:
+ if(!adduser(user, 0))
+ return;
+ cmd_exec("user");
+ break;
+ case ':':
+ adduser(user, 1);
+ cmd_exec("user");
+ return;
+ case '#':
+ adduser(user, 0);
+ cmd_exec("user");
+ return;
+ }
+
+ /*
+ * create directories
+ */
+ cmd_exec("user");
+ sprint(msg, "create /usr/%s %s %s 775 d", user, user, user);
+ cmd_exec(msg);
+ sprint(msg, "create /usr/%s/tmp %s %s 775 d", user, user, user);
+ cmd_exec(msg);
+ sprint(msg, "create /usr/%s/lib %s %s 775 d", user, user, user);
+ cmd_exec(msg);
+ sprint(msg, "create /usr/%s/bin %s %s 775 d", user, user, user);
+ cmd_exec(msg);
+ sprint(msg, "create /usr/%s/bin/rc %s %s 775 d", user, user, user);
+ cmd_exec(msg);
+ sprint(msg, "create /usr/%s/bin/mips %s %s 775 d", user, user, user);
+ cmd_exec(msg);
+ sprint(msg, "create /usr/%s/bin/386 %s %s 775 d", user, user, user);
+ cmd_exec(msg);
+ sprint(msg, "create /usr/%s/bin/power %s %s 775 d", user, user, user);
+ cmd_exec(msg);
+ sprint(msg, "create /usr/%s/bin/alpha %s %s 775 d", user, user, user);
+ cmd_exec(msg);
+}
+
+void
+cmd_checkuser(void)
+{
+ uchar buf[DIRREC], *p;
+ static char utime[4];
+
+ if(con_clone(FID1, FID2)
+ || con_path(FID2, "/adm/users")
+ || con_open(FID2, 0)
+ || con_stat(FID2, (char*)buf))
+ return;
+ p = buf + 3*NAMELEN + 4*4;
+ if(memcmp(utime, p, 4) == 0)
+ return;
+ memmove(utime, p, 4);
+ cmd_user();
+}
+
+void
+cmd_allow(void)
+{
+ wstatallow = 1;
+}
+
+void
+cmd_disallow(void)
+{
+ wstatallow = 0;
+}
+
+void
+cmd_chat(void)
+{
+ chat = 1 - chat;
+}
+
+void
+cmd_atime(void)
+{
+ noatime = !noatime;
+ if(noatime)
+ cprint("atimes will not be updated\n");
+ else
+ cprint("atimes will be updated\n");
+}
+
+void
+cmd_noneattach(void)
+{
+ allownone = !allownone;
+ if(allownone)
+ cprint("none can attach to new connections\n");
+ else
+ cprint("none can only attach on authenticated connections\n");
+}
+
+void
+cmd_listen(void)
+{
+ char addr[NAMELEN];
+
+ if(skipbl(0))
+ strcpy(addr, "tcp!*!564"); /* 9fs */
+ else
+ cname(addr);
+
+ if(netserve(addr))
+ cprint("announce %s failed\n", addr);
+ else
+ cprint("announce %s\n", addr);
+}
+
+void
+cmd_nowritegroup(void)
+{
+ writegroup = 0;
+}
+
+Command command[] =
+{
+ "allow", cmd_allow, "",
+ "allowoff", cmd_disallow, "",
+ "atime", cmd_atime, "",
+ "cfs", cmd_cfs, "[filesys]",
+ "chat", cmd_chat, "",
+ "check", cmd_check, "[cdfpPqrtw]",
+ "clri", cmd_clri, "filename",
+ "create", cmd_create, "filename user group perm [ald]",
+ "disallow", cmd_disallow, "",
+ "halt", cmd_halt, "",
+ "help", cmd_help, "",
+ "listen", cmd_listen, "[address]",
+ "newuser", cmd_newuser, "username",
+ "noneattach", cmd_noneattach, "",
+ "nowritegroup", cmd_nowritegroup, "",
+ "remove", cmd_remove, "filename",
+ "rename", cmd_rename, "file newname",
+ "start", cmd_start, "",
+ "stats", cmd_stats, "[fw]",
+ "sync", cmd_sync, "",
+ "user", cmd_user, "",
+ 0
+};
+
+int
+skipbl(int err)
+{
+ if(*cons.arg != ' ') {
+ if(err)
+ cprint("syntax error\n");
+ return 1;
+ }
+ while(*cons.arg == ' ')
+ cons.arg++;
+ return 0;
+}
+
+char*
+_cname(char *name)
+{
+ int i, c;
+
+ memset(name, 0, NAMELEN);
+ for(i=0;; i++) {
+ c = *cons.arg;
+ switch(c) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\0':
+ return name;
+ }
+ if(i < NAMELEN-1)
+ name[i] = c;
+ cons.arg++;
+ }
+}
+
+char*
+cname(char *name)
+{
+ skipbl(0);
+ return _cname(name);
+}
+
+int
+nextelem(void)
+{
+ char *e;
+ int i, c;
+
+ e = elem;
+ while(*cons.arg == '/')
+ cons.arg++;
+ c = *cons.arg;
+ if(c == 0 || c == ' ')
+ return 0;
+ for(i = 0; c = *cons.arg; i++) {
+ if(c == ' ' || c == '/')
+ break;
+ if(i == NAMELEN) {
+ cprint("path name component too long\n");
+ return 0;
+ }
+ *e++ = c;
+ cons.arg++;
+ }
+ *e = 0;
+ return 1;
+}
+
+long
+number(int d, int base)
+{
+ int c, sign, any;
+ long n;
+
+ sign = 0;
+ any = 0;
+ n = 0;
+
+ c = *cons.arg;
+ while(c == ' ') {
+ cons.arg++;
+ c = *cons.arg;
+ }
+ if(c == '-') {
+ sign = 1;
+ cons.arg++;
+ c = *cons.arg;
+ }
+ while((c >= '0' && c <= '9') ||
+ (base == 16 && c >= 'a' && c <= 'f') ||
+ (base == 16 && c >= 'A' && c <= 'F')) {
+ n *= base;
+ if(c >= 'a' && c <= 'f')
+ n += c - 'a' + 10;
+ else
+ if(c >= 'A' && c <= 'F')
+ n += c - 'A' + 10;
+ else
+ n += c - '0';
+ cons.arg++;
+ c = *cons.arg;
+ any = 1;
+ }
+ if(!any)
+ return d;
+ if(sign)
+ n = -n;
+ return n;
+}
diff --git a/sys/src/cmd/disk/kfs/console.c b/sys/src/cmd/disk/kfs/console.c
new file mode 100755
index 000000000..ec7385d42
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/console.c
@@ -0,0 +1,371 @@
+#include "all.h"
+#include "9p1.h"
+
+void
+fcall9p1(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ int t;
+
+ rlock(&mainlock);
+ t = in->type;
+ if(t < 0 || t >= MAXSYSCALL || (t&1) || !call9p1[t]) {
+ print("bad message type %d\n", t);
+ panic("");
+ }
+ ou->type = t+1;
+ ou->err = 0;
+
+ rlock(&cp->reflock);
+ (*call9p1[t])(cp, in, ou);
+ runlock(&cp->reflock);
+
+ if(ou->err)
+ if(CHAT(cp))
+ print(" error: %s\n", errstring[ou->err]);
+ cons.work.count++;
+ runlock(&mainlock);
+}
+
+int
+con_session(void)
+{
+ Oldfcall in, ou;
+
+ in.type = Tsession9p1;
+ fcall9p1(cons.chan, &in, &ou);
+ return ou.err;
+}
+
+int
+con_attach(int fid, char *uid, char *arg)
+{
+ Oldfcall in, ou;
+
+ in.type = Tattach9p1;
+ in.fid = fid;
+ strncpy(in.uname, uid, NAMELEN);
+ strncpy(in.aname, arg, NAMELEN);
+ fcall9p1(cons.chan, &in, &ou);
+ return ou.err;
+}
+
+int
+con_clone(int fid1, int fid2)
+{
+ Oldfcall in, ou;
+
+ in.type = Tclone9p1;
+ in.fid = fid1;
+ in.newfid = fid2;
+ fcall9p1(cons.chan, &in, &ou);
+ return ou.err;
+}
+
+int
+con_path(int fid, char *path)
+{
+ Oldfcall in, ou;
+ char *p;
+
+ in.type = Twalk9p1;
+ in.fid = fid;
+
+loop:
+ if(*path == 0)
+ return 0;
+ strncpy(in.name, path, NAMELEN);
+ if(p = strchr(path, '/')) {
+ path = p+1;
+ if(p = strchr(in.name, '/'))
+ *p = 0;
+ } else
+ path = strchr(path, 0);
+ if(in.name[0]) {
+ fcall9p1(cons.chan, &in, &ou);
+ if(ou.err)
+ return ou.err;
+ }
+ goto loop;
+}
+
+int
+con_walk(int fid, char *name)
+{
+ Oldfcall in, ou;
+
+ in.type = Twalk9p1;
+ in.fid = fid;
+ strncpy(in.name, name, NAMELEN);
+ fcall9p1(cons.chan, &in, &ou);
+ return ou.err;
+}
+
+int
+con_stat(int fid, char *data)
+{
+ Oldfcall in, ou;
+
+ in.type = Tstat9p1;
+ in.fid = fid;
+ fcall9p1(cons.chan, &in, &ou);
+ if(ou.err == 0)
+ memmove(data, ou.stat, sizeof ou.stat);
+ return ou.err;
+}
+
+int
+con_wstat(int fid, char *data)
+{
+ Oldfcall in, ou;
+
+ in.type = Twstat9p1;
+ in.fid = fid;
+ memmove(in.stat, data, sizeof in.stat);
+ fcall9p1(cons.chan, &in, &ou);
+ return ou.err;
+}
+
+int
+con_open(int fid, int mode)
+{
+ Oldfcall in, ou;
+
+ in.type = Topen9p1;
+ in.fid = fid;
+ in.mode = mode;
+ fcall9p1(cons.chan, &in, &ou);
+ return ou.err;
+}
+
+int
+con_read(int fid, char *data, long offset, int count)
+{
+ Oldfcall in, ou;
+
+ in.type = Tread9p1;
+ in.fid = fid;
+ in.offset = offset;
+ in.count = count;
+ ou.data = data;
+ fcall9p1(cons.chan, &in, &ou);
+ if(ou.err)
+ return 0;
+ return ou.count;
+}
+
+int
+con_write(int fid, char *data, long offset, int count)
+{
+ Oldfcall in, ou;
+
+ in.type = Twrite9p1;
+ in.fid = fid;
+ in.data = data;
+ in.offset = offset;
+ in.count = count;
+ fcall9p1(cons.chan, &in, &ou);
+ if(ou.err)
+ return 0;
+ return ou.count;
+}
+
+int
+con_remove(int fid)
+{
+ Oldfcall in, ou;
+
+ in.type = Tremove9p1;
+ in.fid = fid;
+ fcall9p1(cons.chan, &in, &ou);
+ return ou.err;
+}
+
+int
+con_create(int fid, char *name, int uid, int gid, long perm, int mode)
+{
+ Oldfcall in, ou;
+
+ in.type = Tcreate9p1;
+ in.fid = fid;
+ strncpy(in.name, name, NAMELEN);
+ in.perm = perm;
+ in.mode = mode;
+ cons.uid = uid; /* beyond ugly */
+ cons.gid = gid;
+ fcall9p1(cons.chan, &in, &ou);
+ return ou.err;
+}
+
+int
+doclri(File *f)
+{
+ Iobuf *p, *p1;
+ Dentry *d, *d1;
+ int err;
+
+ err = 0;
+ p = 0;
+ p1 = 0;
+ if(isro(f->fs->dev)) {
+ err = Eronly;
+ goto out;
+ }
+ /*
+ * check on parent directory of file to be deleted
+ */
+ if(f->wpath == 0 || f->wpath->addr == f->addr) {
+ err = Ephase;
+ goto out;
+ }
+ p1 = getbuf(f->fs->dev, f->wpath->addr, Bread);
+ d1 = getdir(p1, f->wpath->slot);
+ if(!d1 || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)) {
+ err = Ephase;
+ goto out;
+ }
+
+ accessdir(p1, d1, FWRITE);
+ putbuf(p1);
+ p1 = 0;
+
+ /*
+ * check on file to be deleted
+ */
+ p = getbuf(f->fs->dev, f->addr, Bread);
+ d = getdir(p, f->slot);
+
+
+ /*
+ * do it
+ */
+ memset(d, 0, sizeof(Dentry));
+ settag(p, Tdir, QPNONE);
+ freewp(f->wpath);
+ freefp(f);
+
+out:
+ if(p1)
+ putbuf(p1);
+ if(p)
+ putbuf(p);
+ return err;
+}
+
+void
+f_clri(Chan *cp, Oldfcall *in, Oldfcall *ou)
+{
+ File *f;
+
+ if(CHAT(cp)) {
+ print("c_clri %d\n", cp->chan);
+ print(" fid = %d\n", in->fid);
+ }
+
+ f = filep(cp, in->fid, 0);
+ if(!f) {
+ ou->err = Efid;
+ goto out;
+ }
+ ou->err = doclri(f);
+
+out:
+ ou->fid = in->fid;
+ if(f)
+ qunlock(f);
+}
+
+int
+con_clri(int fid)
+{
+ Oldfcall in, ou;
+ Chan *cp;
+
+ in.type = Tremove9p1;
+ in.fid = fid;
+ cp = cons.chan;
+
+ rlock(&mainlock);
+ ou.type = Tremove9p1+1;
+ ou.err = 0;
+
+ rlock(&cp->reflock);
+
+ f_clri(cp, &in, &ou);
+
+ runlock(&cp->reflock);
+
+ cons.work.count++;
+ runlock(&mainlock);
+ return ou.err;
+}
+
+int
+con_swap(int fid1, int fid2)
+{
+ int err;
+ Iobuf *p1, *p2;
+ File *f1, *f2;
+ Dentry *d1, *d2;
+ Dentry dt1, dt2;
+ Chan *cp;
+
+ cp = cons.chan;
+ err = 0;
+ rlock(&mainlock);
+ rlock(&cp->reflock);
+
+ f2 = nil;
+ p1 = p2 = nil;
+ f1 = filep(cp, fid1, 0);
+ if(!f1){
+ err = Efid;
+ goto out;
+ }
+ p1 = getbuf(f1->fs->dev, f1->addr, Bread|Bmod);
+ d1 = getdir(p1, f1->slot);
+ if(!d1 || !(d1->mode&DALLOC)){
+ err = Ealloc;
+ goto out;
+ }
+
+ f2 = filep(cp, fid2, 0);
+ if(!f2){
+ err = Efid;
+ goto out;
+ }
+ if(memcmp(&f1->fs->dev, &f2->fs->dev, 4)==0
+ && f2->addr == f1->addr)
+ p2 = p1;
+ else
+ p2 = getbuf(f2->fs->dev, f2->addr, Bread|Bmod);
+ d2 = getdir(p2, f2->slot);
+ if(!d2 || !(d2->mode&DALLOC)){
+ err = Ealloc;
+ goto out;
+ }
+
+ dt1 = *d1;
+ dt2 = *d2;
+ *d1 = dt2;
+ *d2 = dt1;
+ memmove(d1->name, dt1.name, NAMELEN);
+ memmove(d2->name, dt2.name, NAMELEN);
+
+ mkqid(&f1->qid, d1, 1);
+ mkqid(&f2->qid, d2, 1);
+out:
+ if(f1)
+ qunlock(f1);
+ if(f2)
+ qunlock(f2);
+ if(p1)
+ putbuf(p1);
+ if(p2 && p2!=p1)
+ putbuf(p2);
+
+ runlock(&cp->reflock);
+ cons.work.count++;
+ runlock(&mainlock);
+
+ return err;
+}
diff --git a/sys/src/cmd/disk/kfs/dat.c b/sys/src/cmd/disk/kfs/dat.c
new file mode 100755
index 000000000..5ab3c0eb8
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/dat.c
@@ -0,0 +1,87 @@
+#include "all.h"
+
+Uid* uid;
+char* uidspace;
+short* gidspace;
+RWLock mainlock;
+long boottime;
+Tlock *tlocks;
+Conf conf;
+Cons cons;
+Chan *chan;
+char service[2*NAMELEN];
+char *progname;
+char *procname;
+int RBUFSIZE;
+int BUFSIZE;
+int DIRPERBUF;
+int INDPERBUF;
+int INDPERBUF2;
+int FEPERBUF;
+
+Filsys filesys[MAXFILSYS] =
+{
+ {"main", {Devwren, 0, 0, 0}, 0},
+};
+
+Device devnone = {Devnone, 0, 0, 0};
+
+Devcall devcall[MAXDEV] = {
+ [Devnone] {0},
+ [Devwren] {wreninit, wrenream, wrencheck, wrensuper, wrenroot, wrensize, wrenread, wrenwrite},
+};
+
+char* tagnames[] =
+{
+ [Tbuck] "Tbuck",
+ [Tdir] "Tdir",
+ [Tfile] "Tfile",
+ [Tfree] "Tfree",
+ [Tind1] "Tind1",
+ [Tind2] "Tind2",
+ [Tnone] "Tnone",
+ [Tsuper] "Tsuper",
+ [Tvirgo] "Tvirgo",
+ [Tcache] "Tcache",
+};
+
+char *errstring[MAXERR] =
+{
+ [Ebadspc] "attach -- bad specifier",
+ [Efid] "unknown fid",
+ [Echar] "bad character in directory name",
+ [Eopen] "read/write -- on non open fid",
+ [Ecount] "read/write -- count too big",
+ [Ealloc] "phase error -- directory entry not allocated",
+ [Eqid] "phase error -- qid does not match",
+ [Eauth] "authentication failed",
+ [Eauthmsg] "kfs: authentication not required",
+ [Eaccess] "access permission denied",
+ [Eentry] "directory entry not found",
+ [Emode] "open/create -- unknown mode",
+ [Edir1] "walk -- in a non-directory",
+ [Edir2] "create -- in a non-directory",
+ [Ephase] "phase error -- cannot happen",
+ [Eexist] "create -- file exists",
+ [Edot] "create -- . and .. illegal names",
+ [Eempty] "remove -- directory not empty",
+ [Ebadu] "attach -- privileged user",
+ [Enotu] "wstat -- not owner",
+ [Enotg] "wstat -- not in group",
+ [Enotl] "wstat -- attempt to change length",
+ [Enotd] "wstat -- attempt to change directory",
+ [Enotm] "wstat -- unknown type/mode",
+ [Ename] "create/wstat -- bad character in file name",
+ [Ewalk] "walk -- too many (system wide)",
+ [Eronly] "file system read only",
+ [Efull] "file system full",
+ [Eoffset] "read/write -- offset negative",
+ [Elocked] "open/create -- file is locked",
+ [Ebroken] "close/read/write -- lock is broken",
+ [Efidinuse] "fid already in use",
+ [Etoolong] "name too long",
+ [Ersc] "it's russ's fault. bug him.",
+ [Econvert] "protocol botch",
+ [Eqidmode] "wstat -- qid.type/dir.mode mismatch",
+ [Esystem] "kfs system error",
+};
diff --git a/sys/src/cmd/disk/kfs/dat.h b/sys/src/cmd/disk/kfs/dat.h
new file mode 100755
index 000000000..127b1b825
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/dat.h
@@ -0,0 +1,170 @@
+typedef struct Chan Chan;
+typedef struct Command Command;
+typedef struct Conf Conf;
+typedef struct Cons Cons;
+typedef struct Devcall Devcall;
+
+#define MAXBUFSIZE (16*1024) /* max. buffer size */
+
+#include "portdat.h"
+
+struct Chan
+{
+ int chan; /* fd request came in on */
+ QLock rlock, wlock; /* lock for reading/writing messages on chan */
+ int type;
+ int flags;
+ long whotime;
+ File* flist; /* base of file structures */
+ Lock flock; /* manipulate flist */
+ RWLock reflock; /* lock for Tflush */
+ int msize; /* version */
+ int authed; /* someone other than ``none'' has authed */
+
+/* 9p1 auth */
+ uchar chal[8];
+ uchar rchal[8];
+ int idoffset;
+ int idvec;
+ Lock idlock;
+};
+
+/*
+ * console cons.flag flags
+ */
+enum
+{
+ Fchat = (1<<0), /* print out filesys rpc traffic */
+ Fuid = (1<<2), /* print out uids */
+ /* debugging flags for drivers */
+};
+
+struct Cons
+{
+ int flags; /* overall flags for all channels */
+ int uid; /* botch -- used to get uid on cons_create */
+ int gid; /* botch -- used to get gid on cons_create */
+ int allow; /* no-protection flag */
+ long offset; /* used to read files, c.f. fchar */
+ char* arg; /* pointer to remaining line */
+
+ Chan *chan; /* console channel */
+ Chan *srvchan; /* local server channel */
+
+ Filter work; /* thruput in messages */
+ Filter rate; /* thruput in bytes */
+ Filter bhit; /* getbufs that hit */
+ Filter bread; /* getbufs that miss and read */
+ Filter binit; /* getbufs that miss and dont read */
+ Filter tags[MAXTAG]; /* reads of each type of block */
+};
+
+struct Conf
+{
+ ulong niobuf; /* number of iobufs to allocate */
+ ulong nuid; /* distinct uids */
+ ulong uidspace; /* space for uid names -- derrived from nuid */
+ ulong gidspace; /* space for gid names -- derrived from nuid */
+ ulong nserve; /* server processes */
+ ulong nfile; /* number of fid -- system wide */
+ ulong nwpath; /* number of active paths, derrived from nfile */
+ ulong bootsize; /* number of bytes reserved for booting */
+};
+
+struct Command
+{
+ char *string;
+ void (*func)(void);
+ char *args;
+};
+
+struct Devcall
+{
+ void (*init)(Device);
+ void (*ream)(Device);
+ int (*check)(Device);
+ long (*super)(Device);
+ long (*root)(Device);
+ long (*size)(Device);
+ int (*read)(Device, long, void*);
+ int (*write)(Device, long, void*);
+};
+
+/*
+ * device types
+ */
+enum
+{
+ Devnone = 0,
+ Devwren,
+ MAXDEV
+};
+
+/*
+ * file systems
+ */
+enum
+{
+ MAXFILSYS = 4
+};
+
+/*
+ * should be in portdat.h
+ */
+#define QPDIR 0x80000000L
+#define QPNONE 0
+#define QPROOT 1
+#define QPSUPER 2
+
+/*
+ * perm argument in p9 create
+ */
+#define PDIR (1L<<31) /* is a directory */
+#define PAPND (1L<<30) /* is append only */
+#define PLOCK (1L<<29) /* is locked on open */
+
+#define NOF (-1)
+
+#define FID1 1
+#define FID2 2
+#define FID3 3
+
+#define SECOND(n) (n)
+#define MINUTE(n) (n*SECOND(60))
+#define HOUR(n) (n*MINUTE(60))
+#define DAY(n) (n*HOUR(24))
+#define TLOCK MINUTE(5)
+
+#define CHAT(cp) (chat)
+#define QID9P1(a,b) (Qid9p1){a,b}
+
+extern Uid* uid;
+extern char* uidspace;
+extern short* gidspace;
+extern char* errstring[MAXERR];
+extern Chan* chans;
+extern RWLock mainlock;
+extern long boottime;
+extern Tlock *tlocks;
+extern Device devnone;
+extern Filsys filesys[];
+extern char service[];
+extern char* tagnames[];
+extern Conf conf;
+extern Cons cons;
+extern Command command[];
+extern Chan *chan;
+extern Devcall devcall[];
+extern char *progname;
+extern char *procname;
+extern long niob;
+extern long nhiob;
+extern Hiob *hiob;
+extern int chat;
+extern int writeallow;
+extern int wstatallow;
+extern int allownone;
+extern int noatime;
+extern int writegroup;
+
+extern Lock wpathlock;
diff --git a/sys/src/cmd/disk/kfs/dentry.c b/sys/src/cmd/disk/kfs/dentry.c
new file mode 100755
index 000000000..c9f8c486b
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/dentry.c
@@ -0,0 +1,174 @@
+#include "all.h"
+
+Dentry*
+getdir(Iobuf *p, int slot)
+{
+ Dentry *d;
+
+ if(!p)
+ return 0;
+ d = (Dentry*)p->iobuf + slot%DIRPERBUF;
+ return d;
+}
+
+void
+accessdir(Iobuf *p, Dentry *d, int f)
+{
+ long t;
+
+ if(p && !isro(p->dev)) {
+ if(!(f & (FWRITE|FWSTAT)) && noatime)
+ return;
+ t = time(nil);
+ if(f & (FREAD|FWRITE|FWSTAT)){
+ d->atime = t;
+ p->flags |= Bmod;
+ }
+ if(f & FWRITE) {
+ d->mtime = t;
+ d->qid.version++;
+ p->flags |= Bmod;
+ }
+ }
+}
+
+void
+dbufread(Iobuf *p, Dentry *d, long a)
+{
+ USED(p, d, a);
+}
+
+long
+rel2abs(Iobuf *p, Dentry *d, long a, int tag, int putb)
+{
+ long addr, qpath;
+ Device dev;
+
+ if(a < 0) {
+ print("dnodebuf: neg\n");
+ return 0;
+ }
+ qpath = d->qid.path;
+ dev = p->dev;
+ if(a < NDBLOCK) {
+ addr = d->dblock[a];
+ if(!addr && tag) {
+ addr = balloc(dev, tag, qpath);
+ d->dblock[a] = addr;
+ p->flags |= Bmod|Bimm;
+ }
+ if(putb)
+ putbuf(p);
+ return addr;
+ }
+ a -= NDBLOCK;
+ if(a < INDPERBUF) {
+ addr = d->iblock;
+ if(!addr && tag) {
+ addr = balloc(dev, Tind1, qpath);
+ d->iblock = addr;
+ p->flags |= Bmod|Bimm;
+ }
+ if(putb)
+ putbuf(p);
+ addr = indfetch(p, d, addr, a, Tind1, tag);
+ return addr;
+ }
+ a -= INDPERBUF;
+ if(a < INDPERBUF2) {
+ addr = d->diblock;
+ if(!addr && tag) {
+ addr = balloc(dev, Tind2, qpath);
+ d->diblock = addr;
+ p->flags |= Bmod|Bimm;
+ }
+ if(putb)
+ putbuf(p);
+ addr = indfetch(p, d, addr, a/INDPERBUF, Tind2, Tind1);
+ addr = indfetch(p, d, addr, a%INDPERBUF, Tind1, tag);
+ return addr;
+ }
+ if(putb)
+ putbuf(p);
+ print("dnodebuf: trip indirect\n");
+ return 0;
+}
+
+Iobuf*
+dnodebuf(Iobuf *p, Dentry *d, long a, int tag)
+{
+ long addr;
+
+ addr = rel2abs(p, d, a, tag, 0);
+ if(addr)
+ return getbuf(p->dev, addr, Bread);
+ return 0;
+}
+
+/*
+ * same as dnodebuf but it calls putpuf(p)
+ * to reduce interference.
+ */
+Iobuf*
+dnodebuf1(Iobuf *p, Dentry *d, long a, int tag)
+{
+ long addr;
+ Device dev;
+
+ dev = p->dev;
+ addr = rel2abs(p, d, a, tag, 1);
+ if(addr)
+ return getbuf(dev, addr, Bread);
+ return 0;
+
+}
+
+long
+indfetch(Iobuf *p, Dentry *d, long addr, long a, int itag, int tag)
+{
+ Iobuf *bp;
+
+ if(!addr)
+ return 0;
+ bp = getbuf(p->dev, addr, Bread);
+ if(!bp || checktag(bp, itag, d->qid.path)) {
+ if(!bp) {
+ print("ind fetch bp = 0\n");
+ return 0;
+ }
+ print("ind fetch tag\n");
+ putbuf(bp);
+ return 0;
+ }
+ addr = ((long*)bp->iobuf)[a];
+ if(!addr && tag) {
+ addr = balloc(p->dev, tag, d->qid.path);
+ if(addr) {
+ ((long*)bp->iobuf)[a] = addr;
+ bp->flags |= Bmod;
+ if(localfs || tag == Tdir)
+ bp->flags |= Bimm;
+ settag(bp, itag, d->qid.path);
+ }
+ }
+ putbuf(bp);
+ return addr;
+}
+
+void
+dtrunc(Iobuf *p, Dentry *d)
+{
+ int i;
+
+ bfree(p->dev, d->diblock, 2);
+ d->diblock = 0;
+ bfree(p->dev, d->iblock, 1);
+ d->iblock = 0;
+ for(i=NDBLOCK-1; i>=0; i--) {
+ bfree(p->dev, d->dblock[i], 0);
+ d->dblock[i] = 0;
+ }
+ d->size = 0;
+ p->flags |= Bmod|Bimm;
+ accessdir(p, d, FWRITE);
+}
diff --git a/sys/src/cmd/disk/kfs/devmulti.c b/sys/src/cmd/disk/kfs/devmulti.c
new file mode 100755
index 000000000..b6548f997
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/devmulti.c
@@ -0,0 +1,244 @@
+#include "all.h"
+
+enum{
+ MAXWREN = 7,
+};
+
+static char WMAGIC[] = "kfs wren device\n";
+static char MMAGIC[] = "kfs multi-wren device %4d/%4d\n";
+
+typedef struct Wren Wren;
+
+struct Wren{
+ QLock;
+ Device dev;
+ ulong nblocks;
+ int fd;
+};
+
+static char *wmagic = WMAGIC;
+static Wren *wrens;
+static int maxwren;
+char *wrenfile;
+int nwren;
+int badmagic;
+
+static Wren *
+wren(Device dev)
+{
+ int i;
+
+ for(i = 0; i < maxwren; i++)
+ if(devcmp(dev, wrens[i].dev) == 0)
+ return &wrens[i];
+ panic("can't find wren for %D", dev);
+ return 0;
+}
+
+/*
+ * find out the length of a file
+ * given the mesg version of a stat buffer
+ * we call this because convM2D is different
+ * for the file system than in the os
+ */
+uvlong
+statlen(char *ap)
+{
+ uchar *p;
+ ulong ll, hl;
+
+ p = (uchar*)ap;
+ p += 3*NAMELEN+5*4;
+ ll = p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
+ hl = p[4] | (p[5]<<8) | (p[6]<<16) | (p[7]<<24);
+ return ll | ((uvlong) hl << 32);
+}
+
+static void
+wrenpartinit(Device dev, int k)
+{
+ char buf[MAXBUFSIZE], d[DIRREC];
+ char file[128], magic[64];
+ Wren *w;
+ int fd, i, nmagic;
+
+ if(wrens == 0)
+ wrens = ialloc(MAXWREN * sizeof *wrens);
+ w = &wrens[maxwren];
+ if(nwren > 0)
+ sprint(file, "%s%d", wrenfile, k);
+ else
+ strcpy(file, wrenfile);
+ fd = open(file, ORDWR);
+ if(fd < 0)
+ panic("can't open %s", file);
+ if(fstat(fd, d) < 0)
+ panic("can't stat %s\n", file);
+ seek(fd, 0, 0);
+ i = read(fd, buf, sizeof buf);
+ if(i < sizeof buf)
+ panic("can't read %s", file);
+ badmagic = 0;
+ RBUFSIZE = 1024;
+ sprint(magic, wmagic, k, nwren);
+ nmagic = strlen(magic);
+ if(strncmp(buf+256, magic, nmagic) == 0){
+ RBUFSIZE = atol(buf+256+nmagic);
+ if(RBUFSIZE % 512){
+ fprint(2, "kfs: bad buffersize(%d): assuming 1k blocks\n", RBUFSIZE);
+ RBUFSIZE = 1024;
+ }
+ }else
+ badmagic = 1;
+ w->dev = dev;
+ w->nblocks = statlen(d)/RBUFSIZE;
+ if(k > 0)
+ w->nblocks -= 1; /* don't count magic */
+ w->fd = fd;
+ maxwren++;
+}
+
+void
+wreninit(Device dev)
+{
+ int i;
+
+ if(nwren > 0)
+ wmagic = MMAGIC;
+ i = 0;
+ do{
+ wrenpartinit(dev, i);
+ }while(++i < nwren);
+}
+
+static void
+wrenpartream(Device dev, int k)
+{
+ Wren *w;
+ char buf[MAXBUFSIZE], magic[64];
+ int fd, i;
+
+ if(RBUFSIZE % 512)
+ panic("kfs: bad buffersize(%d): restart a multiple of 512\n", RBUFSIZE);
+ print("kfs: reaming the file system using %d byte blocks\n", RBUFSIZE);
+ w = wren(dev)+k;
+ fd = w->fd;
+ memset(buf, 0, sizeof buf);
+ sprint(magic, wmagic, k, nwren);
+ sprint(buf+256, "%s%d\n", magic, RBUFSIZE);
+ qlock(w);
+ i = seek(fd, 0, 0) < 0 || write(fd, buf, RBUFSIZE) != RBUFSIZE;
+ qunlock(w);
+ if(i < 0)
+ panic("can't ream disk");
+}
+
+void
+wrenream(Device dev)
+{
+ int i;
+
+ i = 0;
+ do{
+ wrenpartream(dev, i);
+ }while(++i < nwren);
+}
+
+static int
+wrentag(char *p, int tag, long qpath)
+{
+ Tag *t;
+
+ t = (Tag*)(p+BUFSIZE);
+ return t->tag != tag || (qpath&~QPDIR) != t->path;
+}
+
+int
+wrencheck(Device dev)
+{
+ char buf[MAXBUFSIZE];
+
+ if(badmagic)
+ return 1;
+ if(RBUFSIZE > sizeof buf)
+ panic("bufsize too big");
+ if(wrenread(dev, wrensuper(dev), buf) || wrentag(buf, Tsuper, QPSUPER)
+ || wrenread(dev, wrenroot(dev), buf) || wrentag(buf, Tdir, QPROOT))
+ return 1;
+ if(((Dentry *)buf)[0].mode & DALLOC)
+ return 0;
+ return 1;
+}
+
+long
+wrensize(Device dev)
+{
+ Wren *w;
+ int i, nb;
+
+ w = wren(dev);
+ nb = 0;
+ i = 0;
+ do{
+ nb += w[i].nblocks;
+ }while(++i < nwren);
+ return nb;
+}
+
+long
+wrensuper(Device dev)
+{
+ USED(dev);
+ return 1;
+}
+
+long
+wrenroot(Device dev)
+{
+ USED(dev);
+ return 2;
+}
+
+int
+wrenread(Device dev, long addr, void *b)
+{
+ Wren *w;
+ int fd, i;
+
+ w = wren(dev);
+ for(i=0; i<nwren; i++){
+ if(addr < w->nblocks)
+ break;
+ addr -= w->nblocks;
+ ++w;
+ }
+ if(i > 0)
+ addr++;
+ fd = w->fd;
+ qlock(w);
+ i = seek(fd, (vlong)addr*RBUFSIZE, 0) == -1 || read(fd, b, RBUFSIZE) != RBUFSIZE;
+ qunlock(w);
+ return i;
+}
+
+int
+wrenwrite(Device dev, long addr, void *b)
+{
+ Wren *w;
+ int fd, i;
+
+ w = wren(dev);
+ for(i=0; i<nwren; i++){
+ if(addr < w->nblocks)
+ break;
+ addr -= w->nblocks;
+ ++w;
+ }
+ if(i > 0)
+ addr++;
+ fd = w->fd;
+ qlock(w);
+ i = seek(fd, (vlong)addr*RBUFSIZE, 0) == -1 || write(fd, b, RBUFSIZE) != RBUFSIZE;
+ qunlock(w);
+ return i;
+}
diff --git a/sys/src/cmd/disk/kfs/devwren.c b/sys/src/cmd/disk/kfs/devwren.c
new file mode 100755
index 000000000..3cb53be99
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/devwren.c
@@ -0,0 +1,174 @@
+#include "all.h"
+
+enum{
+ MAXWREN = 7,
+};
+
+#define WMAGIC "kfs wren device\n"
+
+typedef struct Wren Wren;
+
+struct Wren{
+ QLock;
+ Device dev;
+ uvlong size;
+ int fd;
+};
+
+static Wren *wrens;
+static int maxwren;
+char *wrenfile;
+int nwren;
+int badmagic;
+
+static Wren *
+wren(Device dev)
+{
+ int i;
+
+ for(i = 0; i < maxwren; i++)
+ if(devcmp(dev, wrens[i].dev) == 0)
+ return &wrens[i];
+ panic("can't find wren for %D", dev);
+ return 0;
+}
+
+void
+wreninit(Device dev)
+{
+ char buf[MAXBUFSIZE];
+ Wren *w;
+ Dir *d;
+ int fd, i;
+
+ if(wrens == 0)
+ wrens = ialloc(MAXWREN * sizeof *wrens);
+ w = &wrens[maxwren];
+ fd = open(wrenfile, ORDWR);
+ if(fd < 0)
+ panic("can't open %s", wrenfile);
+ if((d = dirfstat(fd)) == nil)
+ panic("can't stat %s\n", wrenfile);
+ seek(fd, 0, 0);
+ i = read(fd, buf, sizeof buf);
+ if(i < sizeof buf)
+ panic("can't read %s", wrenfile);
+ badmagic = 0;
+ RBUFSIZE = 1024;
+ if(strncmp(buf+256, WMAGIC, strlen(WMAGIC)) == 0){
+ RBUFSIZE = atol(buf+256+strlen(WMAGIC));
+ if(RBUFSIZE % 512){
+ fprint(2, "kfs: bad buffersize(%d): assuming 1k blocks\n", RBUFSIZE);
+ RBUFSIZE = 1024;
+ }
+ }else
+ badmagic = 1;
+ w->dev = dev;
+ w->size = d->length;
+ free(d);
+ w->fd = fd;
+ maxwren++;
+}
+
+void
+wrenream(Device dev)
+{
+ Wren *w;
+ char buf[MAXBUFSIZE];
+ int fd, i;
+
+ if(RBUFSIZE % 512)
+ panic("kfs: bad buffersize(%d): restart a multiple of 512\n", RBUFSIZE);
+ if(RBUFSIZE > sizeof(buf))
+ panic("kfs: bad buffersize(%d): must be at most %d\n", RBUFSIZE, sizeof(buf));
+
+ print("kfs: reaming the file system using %d byte blocks\n", RBUFSIZE);
+ w = wren(dev);
+ fd = w->fd;
+ memset(buf, 0, sizeof buf);
+ sprint(buf+256, "%s%d\n", WMAGIC, RBUFSIZE);
+ qlock(w);
+ i = seek(fd, 0, 0) < 0 || write(fd, buf, RBUFSIZE) != RBUFSIZE;
+ qunlock(w);
+ if(i < 0)
+ panic("can't ream disk");
+}
+
+int
+wrentag(char *p, int tag, long qpath)
+{
+ Tag *t;
+
+ t = (Tag*)(p+BUFSIZE);
+ return t->tag != tag || (qpath&~QPDIR) != t->path;
+}
+
+int
+wrencheck(Device dev)
+{
+ char buf[MAXBUFSIZE];
+
+ if(badmagic)
+ return 1;
+ if(RBUFSIZE > sizeof(buf))
+ panic("kfs: bad buffersize(%d): must be at most %d\n", RBUFSIZE, sizeof(buf));
+
+ if(wrenread(dev, wrensuper(dev), buf) || wrentag(buf, Tsuper, QPSUPER)
+ || wrenread(dev, wrenroot(dev), buf) || wrentag(buf, Tdir, QPROOT))
+ return 1;
+ if(((Dentry *)buf)[0].mode & DALLOC)
+ return 0;
+ return 1;
+}
+
+long
+wrensize(Device dev)
+{
+ return wren(dev)->size / RBUFSIZE;
+}
+
+long
+wrensuper(Device dev)
+{
+ USED(dev);
+ return 1;
+}
+
+long
+wrenroot(Device dev)
+{
+ USED(dev);
+ return 2;
+}
+
+int
+wrenread(Device dev, long addr, void *b)
+{
+ Wren *w;
+ int fd, i;
+
+ w = wren(dev);
+ fd = w->fd;
+ qlock(w);
+ i = seek(fd, (vlong)addr*RBUFSIZE, 0) == -1 || read(fd, b, RBUFSIZE) != RBUFSIZE;
+ qunlock(w);
+ if(i)
+ print("wrenread failed: %r\n");
+ return i;
+}
+
+int
+wrenwrite(Device dev, long addr, void *b)
+{
+ Wren *w;
+ int fd, i;
+
+ w = wren(dev);
+ fd = w->fd;
+ qlock(w);
+ i = seek(fd, (vlong)addr*RBUFSIZE, 0) == -1 || write(fd, b, RBUFSIZE) != RBUFSIZE;
+ qunlock(w);
+ if(i)
+ print("wrenwrite failed: %r\n");
+ return i;
+}
diff --git a/sys/src/cmd/disk/kfs/errno.h b/sys/src/cmd/disk/kfs/errno.h
new file mode 100755
index 000000000..3e32b1dd3
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/errno.h
@@ -0,0 +1,35 @@
+enum{
+ Enevermind, /* never mind */
+ Enofd, /* no free file descriptors */
+ Efidinuse, /* fid already in use */
+ Ebadfd, /* fid out of range or not open */
+ Ebadusefd, /* inappropriate use of fid */
+ Ebadarg, /* bad arg in system call */
+ Enonexist, /* file does not exist */
+ Efilename, /* file name syntax */
+ Ebadchar, /* bad character in file name */
+ Ebadsharp, /* unknown device in # filename */
+ Ebadexec, /* a.out header invalid */
+ Eioload, /* i/o error in demand load */
+ Eperm, /* permission denied */
+ Enotdir, /* not a directory */
+ Enochild, /* no living children */
+ Enoseg, /* no free segments */
+ Ebuf, /* buffer wrong size */
+ Ebadmount, /* inconsistent mount */
+ Enomount, /* mount table full */
+ Enomntdev, /* no free mount devices */
+ Eshutdown, /* mounted device shut down */
+ Einuse, /* device or object already in use */
+ Eio, /* i/o error */
+ Eisdir, /* file is a directory */
+ Ebaddirread, /* directory read not quantized */
+ Esegaddr, /* illegal segment addresses or size */
+ Enoenv, /* no free environment resources */
+ Eprocdied, /* process exited */
+ Enocreate, /* mounted directory forbids creation */
+ Enotunion, /* attempt to union with non-mounted directory */
+ Emount, /* inconsistent mount */
+ Enosrv, /* no free server slots */
+ Egreg, /* it's all greg's fault */
+};
diff --git a/sys/src/cmd/disk/kfs/fns.h b/sys/src/cmd/disk/kfs/fns.h
new file mode 100755
index 000000000..aeba9e80e
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/fns.h
@@ -0,0 +1,42 @@
+#include "portfns.h"
+
+long belong(char *);
+Chan* chaninit(char*);
+void check(Filsys *, long);
+int cmd_exec(char*);
+void consserve(void);
+void confinit(void);
+int fsinit(int, int);
+void *ialloc(ulong);
+int nextelem(void);
+long number(int, int);
+Device scsidev(char*);
+int skipbl(int);
+void startproc(void (*)(void), char *);
+void syncproc(void);
+void syncall(void);
+
+int fprint(int, char*, ...);
+void wreninit(Device);
+int wrencheck(Device);
+void wrenream(Device);
+long wrensize(Device);
+long wrensuper(Device);
+long wrenroot(Device);
+int wrenread(Device, long, void *);
+int wrenwrite(Device, long, void *);
+
+/*
+ * macros for compat with bootes
+ */
+#define localfs 1
+
+#define devgrow(d, s) 0
+#define nofree(d, a) 0
+#define isro(d) 0
+
+#define superaddr(d) ((*devcall[d.type].super)(d))
+#define getraddr(d) ((*devcall[d.type].root)(d))
+#define devsize(d) ((*devcall[d.type].size)(d))
+#define devwrite(d, a, v) ((*devcall[d.type].write)(d, a, v))
+#define devread(d, a, v) ((*devcall[d.type].read)(d, a, v))
diff --git a/sys/src/cmd/disk/kfs/ialloc.c b/sys/src/cmd/disk/kfs/ialloc.c
new file mode 100755
index 000000000..6ab880ae3
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/ialloc.c
@@ -0,0 +1,9 @@
+#include "all.h"
+
+void *ialloc(ulong n){
+ void *p;
+
+ if(p = malloc(n))
+ memset(p, 0, n);
+ return p;
+}
diff --git a/sys/src/cmd/disk/kfs/iobuf.c b/sys/src/cmd/disk/kfs/iobuf.c
new file mode 100755
index 000000000..c78eebc1e
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/iobuf.c
@@ -0,0 +1,235 @@
+#include "all.h"
+
+#define DEBUG 0
+
+long niob;
+long nhiob;
+Hiob *hiob;
+
+Iobuf*
+getbuf(Device dev, long addr, int flag)
+{
+ Iobuf *p, *s;
+ Hiob *hp;
+ long h;
+
+ if(DEBUG)
+ print("getbuf %D(%ld) f=%x\n", dev, addr, flag);
+ h = addr +
+ dev.type*1009L +
+ dev.ctrl*10007L +
+ dev.unit*100003L +
+ dev.part*1000003L;
+ if(h < 0)
+ h = ~h;
+ h %= nhiob;
+ hp = &hiob[h];
+
+loop:
+ lock(hp);
+
+/*
+ * look for it in the active list
+ */
+ s = hp->link;
+ for(p=s;;) {
+ if(p->addr == addr && !devcmp(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;
+ hp->link = p;
+ }
+ unlock(hp);
+ qlock(p);
+ if(p->addr != addr || devcmp(p->dev, dev)) {
+ qunlock(p);
+ goto loop;
+ }
+ p->flags |= flag;
+ cons.bhit.count++;
+ p->iobuf = p->xiobuf;
+ return p;
+ }
+ p = p->fore;
+ if(p == s)
+ break;
+ }
+ if(flag & Bprobe) {
+ unlock(hp);
+ return 0;
+ }
+
+/*
+ * not found
+ * take oldest unlocked entry in this queue
+ */
+xloop:
+ p = s->back;
+ if(!canqlock(p)) {
+ if(p == hp->link) {
+ unlock(hp);
+ print("iobuf all locked\n");
+ goto loop;
+ }
+ s = p;
+ goto xloop;
+ }
+ /*
+ * its dangerous to flush the pseudo
+ * devices since they recursively call
+ * getbuf/putbuf. deadlock!
+ */
+ if(p->flags & Bres) {
+ qunlock(p);
+ if(p == hp->link) {
+ unlock(hp);
+ print("iobuf all resed\n");
+ goto loop;
+ }
+ s = p;
+ goto xloop;
+ }
+ if(p->flags & Bmod) {
+ unlock(hp);
+ if(!devwrite(p->dev, p->addr, p->xiobuf))
+ p->flags &= ~(Bimm|Bmod);
+ qunlock(p);
+ goto loop;
+ }
+ hp->link = p;
+ p->addr = addr;
+ p->dev = dev;
+ p->flags = flag;
+ unlock(hp);
+ p->iobuf = p->xiobuf;
+ if(flag & Bread) {
+ if(devread(p->dev, p->addr, p->iobuf)) {
+ p->flags = 0;
+ p->dev = devnone;
+ p->addr = -1;
+ p->iobuf = (char*)-1;
+ qunlock(p);
+ return 0;
+ }
+ cons.bread.count++;
+ return p;
+ }
+ cons.binit.count++;
+ return p;
+}
+
+/*
+ * syncblock tries to put out a block per hashline
+ * returns 0 all done,
+ * returns 1 if it missed something
+ */
+int
+syncblock(void)
+{
+ Iobuf *p, *s, *q;
+ Hiob *hp;
+ long h;
+ int flag;
+
+ flag = 0;
+ for(h=0; h<nhiob; h++) {
+ q = 0;
+ hp = &hiob[h];
+ lock(hp);
+ s = hp->link;
+ for(p=s;;) {
+ if(p->flags & Bmod) {
+ if(q)
+ flag = 1; /* more than 1 mod/line */
+ q = p;
+ }
+ p = p->fore;
+ if(p == s)
+ break;
+ }
+ unlock(hp);
+ if(q) {
+ if(!canqlock(q)) {
+ flag = 1; /* missed -- was locked */
+ continue;
+ }
+ if(!(q->flags & Bmod)) {
+ qunlock(q);
+ continue;
+ }
+ if(!devwrite(q->dev, q->addr, q->xiobuf))
+ q->flags &= ~(Bmod|Bimm);
+ qunlock(q);
+ }
+ }
+ return flag;
+}
+
+void
+sync(char *reason)
+{
+ long i;
+
+ print("sync: %s\n", reason);
+ for(i=10*nhiob; i>0; i--)
+ if(!syncblock())
+ return;
+ print("sync shorted\n");
+}
+
+void
+putbuf(Iobuf *p)
+{
+ if(canqlock(p))
+ print("buffer not locked %D(%ld)\n", p->dev, p->addr);
+ if(p->flags & Bimm) {
+ if(!(p->flags & Bmod))
+ print("imm and no mod %D(%ld)\n", p->dev, p->addr);
+ if(!devwrite(p->dev, p->addr, p->iobuf))
+ p->flags &= ~(Bmod|Bimm);
+ }
+ p->iobuf = (char*)-1;
+ qunlock(p);
+}
+
+int
+checktag(Iobuf *p, int tag, long qpath)
+{
+ Tag *t;
+
+ t = (Tag*)(p->iobuf+BUFSIZE);
+ if(t->tag != tag) {
+ if(1 || CHAT(0))
+ print(" tag = %G; expected %G; addr = %lud\n",
+ t->tag, 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 || CHAT(0))
+ print(" tag/path = %lux; expected %G/%lux\n",
+ t->path, tag, qpath);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void
+settag(Iobuf *p, int tag, long qpath)
+{
+ Tag *t;
+
+ t = (Tag*)(p->iobuf+BUFSIZE);
+ t->tag = tag;
+ if(qpath != QPNONE)
+ t->path = qpath & ~QPDIR;
+ p->flags |= Bmod;
+}
diff --git a/sys/src/cmd/disk/kfs/main.c b/sys/src/cmd/disk/kfs/main.c
new file mode 100755
index 000000000..3ca31615b
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/main.c
@@ -0,0 +1,558 @@
+#include "all.h"
+
+int sfd;
+int cmdmode = 0660;
+int rfd;
+int chat;
+extern char *wrenfile;
+extern int nwren;
+char *myname;
+int cmdfd;
+int writeallow; /* never on; for compatibility with fs */
+int wstatallow;
+int writegroup;
+int allownone;
+int noatime;
+int srvfd(char*, int, int);
+void usage(void);
+void confinit(void);
+Chan *chaninit(char*);
+void consinit(void);
+void forkserve(void);
+
+void
+main(int argc, char *argv[])
+{
+ Filsys *fs;
+ int ream, fsok;
+ int newbufsize, nocheck;
+ char buf[NAMELEN];
+ int pid, ctl;
+
+ progname = "kfs";
+ procname = "init";
+
+ /*
+ * insulate from invoker's environment and keep it from swapping
+ */
+ rfork(RFNAMEG|RFNOTEG|RFREND);
+
+ confinit();
+ sfd = -1;
+ ream = 0;
+ newbufsize = 0;
+ nocheck = 0;
+ wrenfile = "/dev/sdC0/fs";
+
+ pid = getpid();
+ snprint(buf, sizeof buf, "/proc/%d/ctl", pid);
+ ctl = open(buf, OWRITE);
+ fprint(ctl, "noswap\n");
+ close(ctl);
+
+ buf[0] = '\0';
+
+ ARGBEGIN{
+ case 'b':
+ newbufsize = atol(ARGF());
+ break;
+ case 'c':
+ nocheck = 1;
+ break;
+ case 'f':
+ wrenfile = ARGF();
+ break;
+ case 'm':
+ nwren = atol(ARGF());
+ break;
+ case 'n':
+ strncpy(buf, ARGF(), NAMELEN-1);
+ buf[NAMELEN-1] = '\0';
+ break;
+ case 'p':
+ cmdmode = atol(ARGF());
+ break;
+ case 'r':
+ ream = 1;
+ break;
+ case 's':
+ sfd = 0;
+ rfd = dup(1, -1);
+ close(1);
+ if(open("/dev/cons", OWRITE) < 0)
+ open("#c/cons", OWRITE);
+ break;
+ case 'B':
+ conf.niobuf = strtoul(ARGF(), 0, 0);
+ break;
+ case 'C':
+ chat = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 0)
+ usage();
+
+ cmdfd = 2;
+
+ if (access(wrenfile, AREAD|AWRITE) == -1)
+ sysfatal("%s cannot access device", wrenfile);
+
+ formatinit();
+ sublockinit();
+
+ if(buf[0])
+ sprint(service, "kfs.%s", buf);
+ else
+ strcpy(service, "kfs");
+ chan = chaninit(service);
+ consinit();
+ tlocks = ialloc(NTLOCK * sizeof *tlocks);
+ uid = ialloc(conf.nuid * sizeof(*uid));
+ uidspace = ialloc(conf.uidspace * sizeof(*uidspace));
+ gidspace = ialloc(conf.gidspace * sizeof(*gidspace));
+
+ /*
+ * init global locks
+ */
+ wlock(&mainlock); wunlock(&mainlock);
+
+ /*
+ * init the file system, ream it if needed, and get the block sizes
+ */
+ ream = fsinit(ream, newbufsize);
+ iobufinit();
+ for(fs=filesys; fs->name; fs++)
+ if(fs->flags & FREAM){ /* set by fsinit if reamed */
+ ream++;
+ rootream(fs->dev, getraddr(fs->dev));
+ superream(fs->dev, superaddr(fs->dev));
+ }
+
+ boottime = time(nil);
+
+ consserve();
+ fsok = superok(filesys[0].dev, superaddr(filesys[0].dev), 0);
+ if(!nocheck && !ream && !fsok)
+ cmd_exec("check fq");
+
+ startproc(forkserve, "srv");
+ startproc(syncproc, "sync");
+
+ exits(0);
+}
+
+void
+forkserve(void)
+{
+ serve(chan);
+}
+
+static
+struct
+{
+ int nfilter;
+ Filter* filters[100];
+}f;
+
+int alarmed;
+
+void
+catchalarm(void *regs, char *msg)
+{
+ USED(regs, msg);
+ if(strcmp(msg, "alarm") == 0){
+ alarmed = 1;
+ noted(NCONT);
+ } else
+ noted(NDFLT);
+}
+
+/*
+ * process to synch blocks
+ * it puts out a block/line every second
+ * it waits 10 seconds if catches up.
+ * in both cases, it takes about 10 seconds
+ * to get up-to-date.
+ *
+ * it also updates the filter stats
+ * and executes commands
+ */
+void
+syncproc(void)
+{
+ char buf[4*1024];
+ Filter *ft;
+ ulong c0, c1;
+ long t, n, d;
+ int i, p[2];
+
+ /*
+ * make a pipe for commands
+ */
+ if(pipe(p) < 0)
+ panic("command pipe");
+ sprint(buf, "#s/%s.cmd", service);
+ srvfd(buf, cmdmode, p[0]);
+ close(p[0]);
+ cmdfd = p[1];
+ notify(catchalarm);
+
+ t = time(nil);
+ for(;;){
+ i = syncblock();
+ alarmed = 0;
+ alarm(i ? 1000: 10000);
+ n = read(cmdfd, buf, sizeof buf - 1);
+ if(n <= 0 && !alarmed)
+ sleep(i ? 1000: 10000);
+ alarm(0);
+ if(n > 0){
+ buf[n] = '\0';
+ if(cmd_exec(buf))
+ fprint(cmdfd, "done");
+ else
+ fprint(cmdfd, "unknown command");
+ }
+ n = time(nil);
+ d = n - t;
+ if(d < 0 || d > 5*60)
+ d = 0;
+ while(d >= 1) {
+ d -= 1;
+ for(i=0; i<f.nfilter; i++) {
+ ft = f.filters[i];
+ c0 = ft->count;
+ c1 = c0 - ft->oldcount;
+ ft->oldcount = c0;
+ ft->filter[0] = famd(ft->filter[0], c1, 59, 60);
+ ft->filter[1] = famd(ft->filter[1], c1, 599, 600);
+ ft->filter[2] = famd(ft->filter[2], c1, 5999, 6000);
+ }
+ }
+ t = n;
+ }
+}
+
+void
+dofilter(Filter *ft)
+{
+ int i;
+
+ i = f.nfilter;
+ if(i >= sizeof f.filters / sizeof f.filters[0]) {
+ print("dofilter: too many filters\n");
+ return;
+ }
+ f.filters[i] = ft;
+ f.nfilter = i+1;
+}
+
+void
+startproc(void (*f)(void), char *name)
+{
+ switch(rfork(RFMEM|RFFDG|RFPROC)){
+ case -1:
+ panic("can't fork");
+ case 0:
+ break;
+ default:
+ return;
+ }
+ procname = name;
+ f();
+ _exits(nil);
+}
+
+void
+confinit(void)
+{
+ conf.niobuf = 0;
+ conf.nuid = 600;
+ conf.nserve = 2;
+ conf.uidspace = conf.nuid*6;
+ conf.gidspace = conf.nuid*3;
+ cons.flags = 0;
+}
+
+static void
+dochaninit(Chan *cp, int fd)
+{
+ cp->chan = fd;
+ fileinit(cp);
+ wlock(&cp->reflock);
+ wunlock(&cp->reflock);
+ lock(&cp->flock);
+ unlock(&cp->flock);
+}
+
+Chan*
+chaninit(char *server)
+{
+ Chan *cp;
+ char buf[3*NAMELEN];
+ int p[2];
+
+ sprint(buf, "#s/%s", server);
+ if(sfd < 0){
+ if(pipe(p) < 0)
+ panic("can't make a pipe");
+ sfd = p[0];
+ rfd = p[1];
+ }
+ srvfd(buf, 0666, sfd);
+ close(sfd);
+ cp = ialloc(sizeof *cp);
+ cons.srvchan = cp;
+ dochaninit(cp, rfd);
+ return cp;
+}
+
+int
+netserve(char *netaddr)
+{
+ int afd, lfd, fd;
+ char adir[2*NAMELEN], ldir[2*NAMELEN];
+ Chan *netchan;
+
+ if(access("/net/tcp/clone", 0) < 0)
+ bind("#I", "/net", MAFTER);
+ if(access("/net.alt/tcp/clone", 0) < 0)
+ bind("#I1", "/net.alt", MAFTER);
+
+ afd = announce(netaddr, adir);
+ if (afd < 0)
+ return -1;
+ switch (rfork(RFMEM|RFFDG|RFPROC)) {
+ case -1:
+ return -1;
+ case 0:
+ break;
+ default:
+ return 0;
+ }
+ for (;;) {
+ lfd = listen(adir, ldir);
+ if (lfd < 0)
+ continue;
+ fd = accept(lfd, ldir);
+ if (fd < 0) {
+ close(lfd);
+ continue;
+ }
+ netchan = mallocz(sizeof(Chan), 1);
+ if(netchan == nil)
+ panic("out of memory");
+ dochaninit(netchan, fd);
+ switch (rfork(RFMEM|RFFDG|RFPROC)) {
+ case -1:
+ panic("can't fork");
+ case 0:
+ close(afd);
+ close(lfd);
+ serve(netchan);
+ free(netchan);
+ exits(0);
+ default:
+ close(fd);
+ close(lfd);
+ continue;
+ }
+ }
+}
+
+int
+srvfd(char *s, int mode, int sfd)
+{
+ int fd;
+ char buf[32];
+
+ fd = create(s, ORCLOSE|OWRITE, mode);
+ if(fd < 0){
+ remove(s);
+ fd = create(s, ORCLOSE|OWRITE, mode);
+ if(fd < 0)
+ panic(s);
+ }
+ sprint(buf, "%d", sfd);
+ if(write(fd, buf, strlen(buf)) != strlen(buf))
+ panic("srv write");
+ return sfd;
+}
+
+void
+consinit(void)
+{
+ int i;
+
+ cons.chan = ialloc(sizeof(Chan));
+ wlock(&cons.chan->reflock);
+ wunlock(&cons.chan->reflock);
+ lock(&cons.chan->flock);
+ unlock(&cons.chan->flock);
+ dofilter(&cons.work);
+ dofilter(&cons.rate);
+ dofilter(&cons.bhit);
+ dofilter(&cons.bread);
+ dofilter(&cons.binit);
+ for(i = 0; i < MAXTAG; i++)
+ dofilter(&cons.tags[i]);
+}
+
+/*
+ * always called with mainlock locked
+ */
+void
+syncall(void)
+{
+ for(;;)
+ if(!syncblock())
+ return;
+}
+
+int
+askream(Filsys *fs)
+{
+ char c;
+
+ print("File system %s inconsistent\n", fs->name);
+ print("Would you like to ream it (y/n)? ");
+ read(0, &c, 1);
+ return c == 'y';
+}
+
+ulong
+memsize(void)
+{
+ char *p, buf[128];
+ int fd, n, by2pg, secs;
+
+ by2pg = 4*1024;
+ p = getenv("cputype");
+ if(p && strcmp(p, "68020") == 0)
+ by2pg = 8*1024;
+
+ secs = 4*1024*1024;
+
+ fd = open("/dev/swap", OREAD);
+ if(fd < 0)
+ return secs;
+ n = read(fd, buf, sizeof(buf)-1);
+ close(fd);
+ if(n <= 0)
+ return secs;
+ buf[n] = 0;
+ p = strchr(buf, '/');
+ if(p)
+ secs = strtoul(p+1, 0, 0)*by2pg;
+ return secs;
+}
+
+/*
+ * init the devices
+ * wipe some of the file systems, or all if ream is set
+ * this code really assumes that only one file system exists
+ */
+int
+fsinit(int ream, int newbufsize)
+{
+ Filsys *fs;
+
+ RBUFSIZE = 4 * 1024;
+ for(fs=filesys; fs->name; fs++)
+ (*devcall[fs->dev.type].init)(fs->dev);
+ if(newbufsize == 0)
+ newbufsize = RBUFSIZE;
+
+ if(conf.niobuf == 0) {
+ conf.niobuf = memsize()/10;
+ if(conf.niobuf > 2*1024*1024)
+ conf.niobuf = 2*1024*1024;
+ conf.niobuf /= newbufsize;
+ if(conf.niobuf < 30)
+ conf.niobuf = 30;
+ }
+
+ BUFSIZE = RBUFSIZE - sizeof(Tag);
+
+ for(fs=filesys; fs->name; fs++)
+ if(ream || (*devcall[fs->dev.type].check)(fs->dev) && askream(fs)){
+ RBUFSIZE = newbufsize;
+ BUFSIZE = RBUFSIZE - sizeof(Tag);
+ (*devcall[fs->dev.type].ream)(fs->dev);
+ fs->flags |= FREAM;
+ ream = 1;
+ }
+
+ /*
+ * set up the block size dependant variables
+ */
+ BUFSIZE = RBUFSIZE - sizeof(Tag);
+ DIRPERBUF = BUFSIZE / sizeof(Dentry);
+ INDPERBUF = BUFSIZE / sizeof(long);
+ INDPERBUF2 = INDPERBUF * INDPERBUF;
+ FEPERBUF = (BUFSIZE - sizeof(Super1) - sizeof(long)) / sizeof(long);
+ return ream;
+}
+
+/*
+ * allocate rest of mem
+ * for io buffers.
+ */
+#define HWIDTH 5 /* buffers per hash */
+void
+iobufinit(void)
+{
+ long i;
+ Iobuf *p, *q;
+ Hiob *hp;
+
+ i = conf.niobuf*RBUFSIZE;
+ niob = i / (sizeof(Iobuf) + RBUFSIZE + sizeof(Hiob)/HWIDTH);
+ nhiob = niob / HWIDTH;
+ while(!prime(nhiob))
+ nhiob++;
+ if(chat)
+ print(" %ld buffers; %ld hashes\n", niob, nhiob);
+ hiob = ialloc(nhiob * sizeof(Hiob));
+ hp = hiob;
+ for(i=0; i<nhiob; i++) {
+ lock(hp);
+ unlock(hp);
+ hp++;
+ }
+ p = ialloc(niob * sizeof(Iobuf));
+ hp = hiob;
+ for(i=0; i<niob; i++) {
+ qlock(p);
+ qunlock(p);
+ if(hp == hiob)
+ hp = hiob + nhiob;
+ hp--;
+ q = hp->link;
+ if(q) {
+ p->fore = q;
+ p->back = q->back;
+ q->back = p;
+ p->back->fore = p;
+ } else {
+ hp->link = p;
+ p->fore = p;
+ p->back = p;
+ }
+ p->dev = devnone;
+ p->addr = -1;
+ p->xiobuf = ialloc(RBUFSIZE);
+ p->iobuf = (char*)-1;
+ p++;
+ }
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: kfs [-cCr] [-b bufsize] [-s infd outfd] [-f fsfile]\n");
+ exits(0);
+}
diff --git a/sys/src/cmd/disk/kfs/misc.c b/sys/src/cmd/disk/kfs/misc.c
new file mode 100755
index 000000000..75619d5f4
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/misc.c
@@ -0,0 +1,86 @@
+#include "all.h"
+
+extern int cmdfd;
+
+Float
+famd(Float a, int b, int c, int d)
+{
+ ulong x, m;
+
+ x = (a + b) * c;
+ m = x % d;
+ x /= d;
+ if(m >= d / 2)
+ x++;
+ return x;
+}
+
+ulong
+fdf(Float a, int d)
+{
+ ulong x, m;
+
+ m = a % d;
+ x = a / d;
+ if(m >= d / 2)
+ x++;
+ return x;
+}
+
+long
+belong(char *s)
+{
+ uchar *x;
+
+ x = (uchar *)s;
+ return (x[0] << 24) + (x[1] << 16) + (x[2] << 8) + x[3];
+}
+
+void
+panic(char *fmt, ...)
+{
+ char buf[8192], *s;
+ va_list arg;
+
+
+ s = buf;
+ s += sprint(s, "%s %s %d: ", progname, procname, getpid());
+ va_start(arg, fmt);
+ s = vseprint(s, buf + sizeof(buf) / sizeof(*buf), fmt, arg);
+ va_end(arg);
+ *s++ = '\n';
+ write(2, buf, s - buf);
+abort();
+ exits(buf);
+}
+
+#define SIZE 4096
+
+void
+cprint(char *fmt, ...)
+{
+ char buf[SIZE], *out;
+ va_list arg;
+
+ va_start(arg, fmt);
+ out = vseprint(buf, buf+SIZE, fmt, arg);
+ va_end(arg);
+ write(cmdfd, buf, (long)(out-buf));
+}
+
+/*
+ * print goes to fd 2 [sic] because fd 1 might be
+ * otherwise preoccupied when the -s flag is given to kfs.
+ */
+int
+print(char *fmt, ...)
+{
+ va_list arg;
+ int n;
+
+ va_start(arg, fmt);
+ n = vfprint(2, fmt, arg);
+ va_end(arg);
+ return n;
+}
+
diff --git a/sys/src/cmd/disk/kfs/mkfile b/sys/src/cmd/disk/kfs/mkfile
new file mode 100755
index 000000000..93e58ac2b
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/mkfile
@@ -0,0 +1,54 @@
+</$objtype/mkfile
+
+TARG=kfs
+
+OFILES=\
+ 9p1.$O\
+ 9p1lib.$O\
+ 9p2.$O\
+ 9p12.$O\
+ auth.$O\
+ chk.$O\
+ con.$O\
+ console.$O\
+ dat.$O\
+ dentry.$O\
+ ialloc.$O\
+ iobuf.$O\
+ main.$O\
+ misc.$O\
+ porttime.$O\
+ sub.$O\
+ uid.$O\
+ ofcallfmt.$O\
+
+HFILES=\
+ all.h\
+ dat.h\
+ errno.h\
+ fns.h\
+ portfns.h\
+ portdat.h\
+
+BIN=/$objtype/bin/disk
+
+UPDATE=mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+$O.out: devwren.$O
+
+$O.gfs: $OFILES devmulti.$O
+ $LD $LDFLAGS -o $target $prereq
+
+
+test:VQ:
+ echo rm -fr /srv/il!$sysname!11111
+ echo 'kill 8.out|rc'
+ echo rm /srv/il!$sysname!11111
+ echo 8.out -Crf /tmp/disk
+ echo disk/kfscmd ''''listen il!*!11111''''
+ echo srv il!$sysname!11111
+ echo mount /srv/il!$sysname!11111 /n/emelie
diff --git a/sys/src/cmd/disk/kfs/ofcallfmt.c b/sys/src/cmd/disk/kfs/ofcallfmt.c
new file mode 100755
index 000000000..309d70d91
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/ofcallfmt.c
@@ -0,0 +1,181 @@
+#include "all.h"
+#include "9p1.h"
+
+static void dumpsome(char*, char*, long);
+static void fdirconv(char*, Dentry*);
+
+int
+ofcallfmt(Fmt *f1)
+{
+ char buf[512];
+ Oldfcall *f;
+ int fid, type, tag, n;
+ Dentry d;
+
+ f = va_arg(f1->args, Oldfcall*);
+ type = f->type;
+ fid = f->fid;
+ tag = f->tag;
+ switch(type){
+ case Tnop9p1: /* 50 */
+ sprint(buf, "Tnop9p1 tag %ud", tag);
+ break;
+ case Rnop9p1:
+ sprint(buf, "Rnop9p1 tag %ud", tag);
+ break;
+ case Tsession9p1: /* 52 */
+ sprint(buf, "Tsession9p1 tag %ud", tag);
+ break;
+ case Rsession9p1:
+ sprint(buf, "Rsession9p1 tag %ud", tag);
+ break;
+ case Rerror9p1: /* 55 */
+ sprint(buf, "Rerror9p1 tag %ud error %.64s", tag, f->ename);
+ break;
+ case Tflush9p1: /* 56 */
+ sprint(buf, "Tflush9p1 tag %ud oldtag %d", tag, f->oldtag);
+ break;
+ case Rflush9p1:
+ sprint(buf, "Rflush9p1 tag %ud", tag);
+ break;
+ case Tattach9p1: /* 58 */
+ sprint(buf, "Tattach9p1 tag %ud fid %d uname %.28s aname %.28s auth %.28s",
+ tag, f->fid, f->uname, f->aname, f->auth);
+ break;
+ case Rattach9p1:
+ sprint(buf, "Rattach9p1 tag %ud fid %d qid 0x%lux|0x%lux",
+ tag, fid, f->qid.path, f->qid.version);
+ break;
+ case Tclone9p1: /* 60 */
+ sprint(buf, "Tclone9p1 tag %ud fid %d newfid %d", tag, fid, f->newfid);
+ break;
+ case Rclone9p1:
+ sprint(buf, "Rclone9p1 tag %ud fid %d", tag, fid);
+ break;
+ case Twalk9p1: /* 62 */
+ sprint(buf, "Twalk9p1 tag %ud fid %d name %.28s", tag, fid, f->name);
+ break;
+ case Rwalk9p1:
+ sprint(buf, "Rwalk9p1 tag %ud fid %d qid 0x%lux|0x%lux",
+ tag, fid, f->qid.path, f->qid.version);
+ break;
+ case Topen9p1: /* 64 */
+ sprint(buf, "Topen9p1 tag %ud fid %d mode %d", tag, fid, f->mode);
+ break;
+ case Ropen9p1:
+ sprint(buf, "Ropen9p1 tag %ud fid %d qid 0x%lux|0x%lux",
+ tag, fid, f->qid.path, f->qid.version);
+ break;
+ case Tcreate9p1: /* 66 */
+ sprint(buf, "Tcreate9p1 tag %ud fid %d name %.28s perm 0x%lux mode %d",
+ tag, fid, f->name, f->perm, f->mode);
+ break;
+ case Rcreate9p1:
+ sprint(buf, "Rcreate9p1 tag %ud fid %d qid 0x%lux|0x%lux",
+ tag, fid, f->qid.path, f->qid.version);
+ break;
+ case Tread9p1: /* 68 */
+ sprint(buf, "Tread9p1 tag %ud fid %d offset %ld count %ld",
+ tag, fid, f->offset, f->count);
+ break;
+ case Rread9p1:
+ n = sprint(buf, "Rread9p1 tag %ud fid %d count %ld ", tag, fid, f->count);
+ dumpsome(buf+n, f->data, f->count);
+ break;
+ case Twrite9p1: /* 70 */
+ n = sprint(buf, "Twrite9p1 tag %ud fid %d offset %ld count %ld ",
+ tag, fid, f->offset, f->count);
+ dumpsome(buf+n, f->data, f->count);
+ break;
+ case Rwrite9p1:
+ sprint(buf, "Rwrite9p1 tag %ud fid %d count %ld", tag, fid, f->count);
+ break;
+ case Tclunk9p1: /* 72 */
+ sprint(buf, "Tclunk9p1 tag %ud fid %d", tag, fid);
+ break;
+ case Rclunk9p1:
+ sprint(buf, "Rclunk9p1 tag %ud fid %d", tag, fid);
+ break;
+ case Tremove9p1: /* 74 */
+ sprint(buf, "Tremove9p1 tag %ud fid %d", tag, fid);
+ break;
+ case Rremove9p1:
+ sprint(buf, "Rremove9p1 tag %ud fid %d", tag, fid);
+ break;
+ case Tstat9p1: /* 76 */
+ sprint(buf, "Tstat9p1 tag %ud fid %d", tag, fid);
+ break;
+ case Rstat9p1:
+ n = sprint(buf, "Rstat9p1 tag %ud fid %d", tag, fid);
+ convM2D9p1(f->stat, &d);
+ sprint(buf+n, " stat ");
+ fdirconv(buf+n+6, &d);
+ break;
+ case Twstat9p1: /* 78 */
+ convM2D9p1(f->stat, &d);
+ n = sprint(buf, "Twstat9p1 tag %ud fid %d stat ", tag, fid);
+ fdirconv(buf+n, &d);
+ break;
+ case Rwstat9p1:
+ sprint(buf, "Rwstat9p1 tag %ud fid %d", tag, fid);
+ break;
+ case Tclwalk9p1: /* 81 */
+ sprint(buf, "Tclwalk9p1 tag %ud fid %d newfid %d name %.28s",
+ tag, fid, f->newfid, f->name);
+ break;
+ case Rclwalk9p1:
+ sprint(buf, "Rclwalk9p1 tag %ud fid %d qid 0x%lux|0x%lux",
+ tag, fid, f->qid.path, f->qid.version);
+ break;
+ default:
+ sprint(buf, "unknown type %d", type);
+ }
+ return fmtstrcpy(f1, buf);
+}
+
+static void
+fdirconv(char *buf, Dentry *d)
+{
+ sprint(buf, "'%s' uid=%d gid=%d "
+ "q %lux|%lux m %uo "
+ "at %ld mt %ld l %ld ",
+ d->name, d->uid, d->gid,
+ d->qid.path, d->qid.version, d->mode,
+ d->atime, d->mtime, d->size);
+}
+
+/*
+ * dump out count (or DUMPL, if count is bigger) bytes from
+ * buf to ans, as a string if they are all printable,
+ * else as a series of hex bytes
+ */
+#define DUMPL 24
+
+static void
+dumpsome(char *ans, char *buf, long count)
+{
+ int i, printable;
+ char *p;
+
+ printable = 1;
+ if(count > DUMPL)
+ count = DUMPL;
+ for(i=0; i<count && printable; i++)
+ if((buf[i]<32 && buf[i] !='\n' && buf[i] !='\t') || (uchar)buf[i]>127)
+ printable = 0;
+ p = ans;
+ *p++ = '\'';
+ if(printable){
+ memmove(p, buf, count);
+ p += count;
+ }else{
+ for(i=0; i<count; i++){
+ if(i>0 && i%4==0)
+ *p++ = ' ';
+ sprint(p, "%2.2ux", buf[i]);
+ p += 2;
+ }
+ }
+ *p++ = '\'';
+ *p = 0;
+}
diff --git a/sys/src/cmd/disk/kfs/portdat.h b/sys/src/cmd/disk/kfs/portdat.h
new file mode 100755
index 000000000..43a937e6d
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/portdat.h
@@ -0,0 +1,368 @@
+/*
+ * fundamental constants
+ */
+#define NAMELEN 28 /* size of names */
+#define NDBLOCK 6 /* number of direct blocks in Dentry */
+#define MAXDAT 8192 /* max allowable data message */
+#define NTLOCK 200 /* number of active file Tlocks */
+
+typedef struct Fbuf Fbuf;
+typedef struct Super1 Super1;
+typedef struct Superb Superb;
+// typedef struct Qid Qid;
+typedef struct Dentry Dentry;
+typedef struct Tag Tag;
+
+typedef struct Device Device;
+typedef struct Qid9p1 Qid9p1;
+typedef struct File File;
+typedef struct Filsys Filsys;
+typedef struct Filta Filta;
+typedef struct Filter Filter;
+typedef ulong Float;
+typedef struct Hiob Hiob;
+typedef struct Iobuf Iobuf;
+typedef struct P9call P9call;
+typedef struct Tlock Tlock;
+// typedef struct Tm Tm;
+typedef struct Uid Uid;
+typedef struct Wpath Wpath;
+typedef struct AuthRpc AuthRpc;
+
+/*
+ * DONT TOUCH -- data structures stored on disk
+ */
+/* DONT TOUCH, this is the disk structure */
+struct Qid9p1
+{
+ long path;
+ long version;
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct Dentry
+{
+ char name[NAMELEN];
+ short uid;
+ short gid;
+ ushort mode;
+ #define DALLOC 0x8000
+ #define DDIR 0x4000
+ #define DAPND 0x2000
+ #define DLOCK 0x1000
+ #define DREAD 0x4
+ #define DWRITE 0x2
+ #define DEXEC 0x1
+ Qid9p1 qid;
+ long size;
+ long dblock[NDBLOCK];
+ long iblock;
+ long diblock;
+ long atime;
+ long mtime;
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct Tag
+{
+ short pad;
+ short tag;
+ long path;
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct Super1
+{
+ long fstart;
+ long fsize;
+ long tfree;
+ long qidgen; /* generator for unique ids */
+
+ long fsok; /* file system ok */
+
+ /*
+ * garbage for WWC device
+ */
+ long roraddr; /* dump root addr */
+ long last; /* last super block addr */
+ long next; /* next super block addr */
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct Fbuf
+{
+ long nfree;
+ long free[1]; /* changes based on BUFSIZE */
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct Superb
+{
+ Super1;
+ Fbuf fbuf;
+};
+
+struct Device
+{
+ char type;
+ char ctrl;
+ char unit;
+ char part;
+};
+
+/*
+ * for load stats
+ */
+struct Filter
+{
+ ulong count; /* count and old count kept separate */
+ ulong oldcount; /* so interrput can read them */
+ Float filter[3]; /* filters for 1m 10m 100m */
+};
+
+struct Filta
+{
+ Filter* f;
+ int scale;
+};
+
+/*
+ * array of qids that are locked
+ */
+struct Tlock
+{
+ Device dev;
+ long time;
+ long qpath;
+ File* file;
+};
+
+struct File
+{
+ QLock;
+ Qid qid;
+ Wpath* wpath;
+ Chan* cp; /* null means a free slot */
+ Tlock* tlock; /* if file is locked */
+ File* next; /* in cp->flist */
+ File* list; /* in list of free files */
+ Filsys* fs;
+ long addr;
+ long slot;
+ long lastra; /* read ahead address */
+ short fid;
+ short uid;
+ char open;
+ #define FREAD 1
+ #define FWRITE 2
+ #define FREMOV 4
+ #define FWSTAT 8
+ long doffset; /* directory reading */
+ ulong dvers;
+ long dslot;
+
+ /* for network authentication */
+ AuthRpc *rpc;
+ short cuid;
+};
+
+struct Filsys
+{
+ char* name; /* name of filesys */
+ Device dev; /* device that filesys is on */
+ int flags;
+ #define FREAM (1<<1) /* mkfs */
+ #define FRECOVER (1<<2) /* install last dump */
+};
+
+struct Hiob
+{
+ Iobuf* link;
+ Lock;
+};
+
+struct Iobuf
+{
+ QLock;
+ Device dev;
+ Iobuf *next; /* for hash */
+ Iobuf *fore; /* for lru */
+ Iobuf *back; /* for lru */
+ char *iobuf; /* only active while locked */
+ char *xiobuf; /* "real" buffer pointer */
+ long addr;
+ int flags;
+};
+
+struct P9call
+{
+ uchar calln;
+ uchar rxflag;
+ short msize;
+ void (*func)(Chan*, int);
+};
+
+// struct Tm
+// {
+// /* see ctime(3) */
+// int sec;
+// int min;
+// int hour;
+// int mday;
+// int mon;
+// int year;
+// int wday;
+// int yday;
+// int isdst;
+// };
+
+struct Uid
+{
+ short uid; /* user id */
+ short lead; /* leader of group */
+ short offset; /* byte offset in uidspace */
+};
+
+struct Wpath
+{
+ Wpath *up; /* pointer upwards in path */
+ Wpath *list; /* link in free chain */
+ long addr; /* directory entry addr of parent */
+ long slot; /* directory entry slot of parent */
+ short refs; /* number of files using this structure */
+};
+
+#define MAXFDATA 8192
+
+/*
+ * error codes generated from the file server
+ */
+enum
+{
+ Ebadspc = 1,
+ Efid,
+ Efidinuse,
+ Echar,
+ Eopen,
+ Ecount,
+ Ealloc,
+ Eqid,
+ Eauth,
+ Eauthmsg,
+ Eaccess,
+ Eentry,
+ Emode,
+ Edir1,
+ Edir2,
+ Ephase,
+ Eexist,
+ Edot,
+ Eempty,
+ Ebadu,
+ Enotu,
+ Enotg,
+ Ename,
+ Ewalk,
+ Eronly,
+ Efull,
+ Eoffset,
+ Elocked,
+ Ebroken,
+ Etoolong,
+ Ersc,
+ Eqidmode,
+ Econvert,
+ Enotm,
+ Enotd,
+ Enotl,
+ Enotw,
+ Esystem,
+
+ MAXERR
+};
+
+/*
+ * devnone block numbers
+ */
+enum
+{
+ Cwio1 = 1,
+ Cwio2,
+ Cwxx1,
+ Cwxx2,
+ Cwxx3,
+ Cwxx4,
+ Cwdump1,
+ Cwdump2,
+ Cuidbuf,
+};
+
+/*
+ * tags on block
+ */
+enum
+{
+ Tnone = 0,
+ 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
+};
+
+/*
+ * flags to getbuf
+ */
+enum
+{
+ Bread = (1<<0), /* read the block if miss */
+ Bprobe = (1<<1), /* return null if miss */
+ Bmod = (1<<2), /* set modified bit in buffer */
+ Bimm = (1<<3), /* set immediate bit in buffer */
+ Bres = (1<<4), /* reserved, never renammed */
+};
+
+/*
+ * open modes passed into P9 open/create
+ */
+enum
+{
+ MREAD = 0,
+ MWRITE,
+ MBOTH,
+ MEXEC,
+ MTRUNC = (1<<4), /* truncate on open */
+ MCEXEC = (1<<5), /* close on exec (host) */
+ MRCLOSE = (1<<6), /* remove on close */
+};
+
+/*
+ * check flags
+ */
+enum
+{
+ Crdall = (1<<0), /* read all files */
+ Ctag = (1<<1), /* rebuild tags */
+ Cpfile = (1<<2), /* print files */
+ Cpdir = (1<<3), /* print directories */
+ Cfree = (1<<4), /* rebuild free list */
+ Cream = (1<<6), /* clear all bad tags */
+ Cbad = (1<<7), /* clear all bad blocks */
+ Ctouch = (1<<8), /* touch old dir and indir */
+ Cquiet = (1<<9), /* report just nasty things */
+};
+
+/*
+ * buffer size variables
+ */
+extern int RBUFSIZE;
+extern int BUFSIZE;
+extern int DIRPERBUF;
+extern int INDPERBUF;
+extern int INDPERBUF2;
+extern int FEPERBUF;
diff --git a/sys/src/cmd/disk/kfs/portfns.h b/sys/src/cmd/disk/kfs/portfns.h
new file mode 100755
index 000000000..3528072f7
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/portfns.h
@@ -0,0 +1,104 @@
+void accessdir(Iobuf*, Dentry*, int);
+void authfree(File*);
+void addfree(Device, long, Superb*);
+long balloc(Device, int, long);
+void bfree(Device, long, int);
+int byname(void*, void*);
+int byuid(void*, void*);
+int checkname(char*);
+int checktag(Iobuf*, int, long);
+void cmd_user(void);
+char* cname(char*);
+int con_attach(int, char*, char*);
+int con_clone(int, int);
+int con_create(int, char*, int, int, long, int);
+int con_open(int, int);
+int con_path(int, char*);
+int con_read(int, char*, long, int);
+int con_remove(int);
+int con_stat(int, char*);
+int con_swap(int, int);
+int con_clri(int);
+int con_session(void);
+int con_walk(int, char*);
+int con_write(int, char*, long, int);
+int con_wstat(int, char*);
+void cprint(char*, ...);
+void datestr(char*, long);
+void dbufread(Iobuf*, Dentry*, long);
+Qid dentryqid(Dentry*);
+int devcmp(Device, Device);
+Iobuf* dnodebuf(Iobuf*, Dentry*, long, int);
+Iobuf* dnodebuf1(Iobuf*, Dentry*, long, int);
+void dofilter(Filter*);
+int doremove(File *, int);
+void dtrunc(Iobuf*, Dentry*);
+void exit(void);
+Float famd(Float, int, int, int);
+int fchar(void);
+ulong fdf(Float, int);
+void fileinit(Chan*);
+void sublockinit(void);
+File* filep(Chan*, int, int);
+int fname(char*);
+void formatinit(void);
+void freefp(File*);
+void freewp(Wpath*);
+Filsys* fsstr(char*);
+Iobuf* getbuf(Device, long, int);
+Dentry* getdir(Iobuf*, int);
+long getraddr(Device);
+Wpath* getwp(Wpath*);
+void hexdump(void*, int);
+int iaccess(File*, Dentry*, int);
+long indfetch(Iobuf*, Dentry*, long, long , int, int);
+int ingroup(int, int);
+void iobufinit(void);
+int leadgroup(int, int);
+void mkchallenge(Chan*);
+void mkqid(Qid*, Dentry*, int);
+int mkqidcmp(Qid*, Dentry*);
+void mkqid9p1(Qid9p1*, Qid*);
+void mkqid9p2(Qid*, Qid9p1*, int);
+int netserve(char*);
+File* newfp(Chan*);
+Qid newqid(Device);
+void newstart(void);
+Wpath* newwp(void);
+int oconvD2M(Dentry*, void*);
+int oconvM2D(void*, Dentry*);
+int ofcallfmt(Fmt*);
+void panic(char*, ...);
+int prime(long);
+void putbuf(Iobuf*);
+void putwp(Wpath*);
+long qidpathgen(Device*);
+void rootream(Device, long);
+void settag(Iobuf*, int, long);
+void serve(Chan*);
+void serve9p1(Chan*, uchar*, int);
+void serve9p2(Chan*, uchar*, int);
+void strrand(void*, int);
+int strtouid(char*);
+int strtouid1(char*);
+int superok(Device, long, int);
+void superream(Device, long);
+void sync(char*);
+int syncblock(void);
+int Tfmt(Fmt*);
+Tlock* tlocked(Iobuf*, Dentry*);
+void uidtostr(char*,int);
+void uidtostr1(char*,int);
+
+#pragma varargck argpos cprint 1
+#pragma varargck argpos panic 1
+
+#pragma varargck type "C" Chan*
+#pragma varargck type "D" Device
+#pragma varargck type "A" Filta
+#pragma varargck type "G" int
+#pragma varargck type "T" long
+#pragma varargck type "F" Fcall*
+
+typedef struct Oldfcall Oldfcall; /* needed for pragma */
+#pragma varargck type "O" Oldfcall*
diff --git a/sys/src/cmd/disk/kfs/porttime.c b/sys/src/cmd/disk/kfs/porttime.c
new file mode 100755
index 000000000..f9a29f96c
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/porttime.c
@@ -0,0 +1,243 @@
+#include "all.h"
+
+static int sunday(Tm *t, int d);
+static int dysize(int);
+static void ct_numb(char*, int);
+static void klocaltime(long tim, Tm *ct);
+static void kgmtime(long tim, Tm *ct);
+
+static char dmsize[12] =
+{
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/*
+ * The following table is used for 1974 and 1975 and
+ * gives the day number of the first day after the Sunday of the
+ * change.
+ */
+static struct
+{
+ short yrfrom;
+ short yrto;
+ short daylb;
+ short dayle;
+} daytab[] =
+{
+ 87, 999, 97, 303,
+ 76, 86, 119, 303,
+ 75, 75, 58, 303,
+ 74, 74, 5, 333,
+ 0, 73, 119, 303,
+};
+
+static struct
+{
+ short minuteswest; /* minutes west of Greenwich */
+ short dsttime; /* dst correction */
+} timezone =
+{
+ 5*60, 1
+};
+
+static void
+klocaltime(long tim, Tm *ct)
+{
+ int daylbegin, daylend, dayno, i;
+ long copyt;
+
+ copyt = tim - timezone.minuteswest*60L;
+ kgmtime(copyt, ct);
+ dayno = ct->yday;
+ for(i=0;; i++)
+ if(ct->year >= daytab[i].yrfrom &&
+ ct->year <= daytab[i].yrto) {
+ daylbegin = sunday(ct, daytab[i].daylb);
+ daylend = sunday(ct, daytab[i].dayle);
+ break;
+ }
+ if(timezone.dsttime &&
+ (dayno>daylbegin || (dayno==daylbegin && ct->hour>=2)) &&
+ (dayno<daylend || (dayno==daylend && ct->hour<1))) {
+ copyt += 60L*60L;
+ kgmtime(copyt, ct);
+ }
+}
+
+/*
+ * The argument is a 0-origin day number.
+ * The value is the day number of the last
+ * Sunday before or after the day.
+ */
+static
+sunday(Tm *t, int d)
+{
+ if(d >= 58)
+ d += dysize(t->year) - 365;
+ return d - (d - t->yday + t->wday + 700) % 7;
+}
+
+static void
+kgmtime(long tim, Tm *ct)
+{
+ int d0, d1;
+ long hms, day;
+
+ /*
+ * break initial number into days
+ */
+ hms = tim % 86400L;
+ day = tim / 86400L;
+ if(hms < 0) {
+ hms += 86400L;
+ day -= 1;
+ }
+
+ /*
+ * generate hours:minutes:seconds
+ */
+ ct->sec = hms % 60;
+ d1 = hms / 60;
+ ct->min = d1 % 60;
+ d1 /= 60;
+ ct->hour = d1;
+
+ /*
+ * day is the day number.
+ * generate day of the week.
+ * The addend is 4 mod 7 (1/1/1970 was Thursday)
+ */
+
+ ct->wday = (day + 7340036L) % 7;
+
+ /*
+ * year number
+ */
+ if(day >= 0)
+ for(d1 = 70; day >= dysize(d1); d1++)
+ day -= dysize(d1);
+ else
+ for (d1 = 70; day < 0; d1--)
+ day += dysize(d1-1);
+ ct->year = d1;
+ ct->yday = d0 = day;
+
+ /*
+ * generate month
+ */
+
+ if(dysize(d1) == 366)
+ dmsize[1] = 29;
+ for(d1 = 0; d0 >= dmsize[d1]; d1++)
+ d0 -= dmsize[d1];
+ dmsize[1] = 28;
+ ct->mday = d0 + 1;
+ ct->mon = d1;
+}
+
+void
+datestr(char *s, long t)
+{
+ Tm tm;
+
+ klocaltime(t, &tm);
+ sprint(s, "%.4d%.2d%.2d", tm.year+1900, tm.mon+1, tm.mday);
+}
+
+int
+Tfmt(Fmt *f1)
+{
+ char s[30];
+ char *cp;
+ long t;
+ Tm tm;
+
+ t = va_arg(f1->args, long);
+ if(t == 0)
+ return fmtstrcpy(f1, "The Epoch");
+
+ klocaltime(t, &tm);
+ strcpy(s, "Day Mon 00 00:00:00 1900");
+ cp = &"SunMonTueWedThuFriSat"[tm.wday*3];
+ s[0] = cp[0];
+ s[1] = cp[1];
+ s[2] = cp[2];
+ cp = &"JanFebMarAprMayJunJulAugSepOctNovDec"[tm.mon*3];
+ s[4] = cp[0];
+ s[5] = cp[1];
+ s[6] = cp[2];
+ ct_numb(s+8, tm.mday);
+ ct_numb(s+11, tm.hour+100);
+ ct_numb(s+14, tm.min+100);
+ ct_numb(s+17, tm.sec+100);
+ if(tm.year >= 100) {
+ s[20] = '2';
+ s[21] = '0';
+ }
+ ct_numb(s+22, tm.year+100);
+
+ return fmtstrcpy(f1, s);
+}
+
+static
+dysize(int y)
+{
+
+ if((y%4) == 0)
+ return 366;
+ return 365;
+}
+
+static
+void
+ct_numb(char *cp, int n)
+{
+
+ if(n >= 10)
+ cp[0] = (n/10)%10 + '0';
+ else
+ cp[0] = ' ';
+ cp[1] = n%10 + '0';
+}
+
+/*
+ * compute the next time after t
+ * that has hour hr and is not on
+ * day in bitpattern --
+ * for automatic dumps
+ */
+long
+nextime(long t, int hr, int day)
+{
+ Tm tm;
+ int nhr;
+
+ if(hr < 0 || hr >= 24)
+ hr = 5;
+ if((day&0x7f) == 0x7f)
+ day = 0;
+
+loop:
+ klocaltime(t, &tm);
+ t -= tm.sec;
+ t -= tm.min*60;
+ nhr = tm.hour;
+ do {
+ t += 60*60;
+ nhr++;
+ } while(nhr%24 != hr);
+ klocaltime(t, &tm);
+ if(tm.hour != hr) {
+ t += 60*60;
+ klocaltime(t, &tm);
+ if(tm.hour != hr) {
+ t -= 60*60;
+ klocaltime(t, &tm);
+ }
+ }
+ if(day & (1<<tm.wday)) {
+ t += 12*60*60;
+ goto loop;
+ }
+ return t;
+}
diff --git a/sys/src/cmd/disk/kfs/print.c b/sys/src/cmd/disk/kfs/print.c
new file mode 100755
index 000000000..339cacda2
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/print.c
@@ -0,0 +1,288 @@
+#include "all.h"
+
+#define PTR sizeof(char*)
+#define SHORT sizeof(int)
+#define INT sizeof(int)
+#define LONG sizeof(long)
+#define IDIGIT 30
+#define MAXCON 30
+
+static int convcount = { 10 };
+
+#define PUT(o, c) if((o)->p < (o)->ep) *(o)->p++ = c
+
+static int noconv(Op*);
+static int cconv(Op*);
+static int dconv(Op*);
+static int hconv(Op*);
+static int lconv(Op*);
+static int oconv(Op*);
+static int sconv(Op*);
+static int uconv(Op*);
+static int xconv(Op*);
+static int percent(Op*);
+
+static
+int (*fmtconv[MAXCON])(Op*) =
+{
+ noconv,
+ cconv, dconv, hconv, lconv,
+ oconv, sconv, uconv, xconv,
+ percent,
+};
+static
+char fmtindex[128] =
+{
+ ['c'] 1,
+ ['d'] 2,
+ ['h'] 3,
+ ['l'] 4,
+ ['o'] 5,
+ ['s'] 6,
+ ['u'] 7,
+ ['x'] 8,
+ ['%'] 9,
+};
+
+int
+fmtinstall(char c, int (*f)(Op*))
+{
+
+ c &= 0177;
+ if(fmtindex[c] == 0) {
+ if(convcount >= MAXCON)
+ return 1;
+ fmtindex[c] = convcount++;
+ }
+ fmtconv[fmtindex[c]] = f;
+ return 0;
+}
+
+char*
+doprint(char *p, char *ep, char *fmt, void *argp)
+{
+ int sf1, c;
+ Op o;
+
+ o.p = p;
+ o.ep = ep;
+ o.argp = argp;
+
+loop:
+ c = *fmt++;
+ if(c != '%') {
+ if(c == 0) {
+ if(o.p < o.ep)
+ *o.p = 0;
+ return o.p;
+ }
+ PUT(&o, c);
+ goto loop;
+ }
+ o.f1 = 0;
+ o.f2 = -1;
+ o.f3 = 0;
+ c = *fmt++;
+ sf1 = 0;
+ if(c == '-') {
+ sf1 = 1;
+ c = *fmt++;
+ }
+ while(c >= '0' && c <= '9') {
+ o.f1 = o.f1*10 + c-'0';
+ c = *fmt++;
+ }
+ if(sf1)
+ o.f1 = -o.f1;
+ if(c != '.')
+ goto l1;
+ c = *fmt++;
+ while(c >= '0' && c <= '9') {
+ if(o.f2 < 0)
+ o.f2 = 0;
+ o.f2 = o.f2*10 + c-'0';
+ c = *fmt++;
+ }
+l1:
+ if(c == 0)
+ fmt--;
+ c = (*fmtconv[fmtindex[c&0177]])(&o);
+ if(c < 0) {
+ o.f3 |= -c;
+ c = *fmt++;
+ goto l1;
+ }
+ o.argp = (char*)o.argp + c;
+ goto loop;
+}
+
+int
+numbconv(Op *op, int base)
+{
+ char b[IDIGIT];
+ int i, f, n, r;
+ long v;
+ short h;
+
+ f = 0;
+ switch(op->f3 & (FLONG|FSHORT|FUNSIGN)) {
+ case FLONG:
+ v = *(long*)op->argp;
+ r = LONG;
+ break;
+
+ case FUNSIGN|FLONG:
+ v = *(ulong*)op->argp;
+ r = LONG;
+ break;
+
+ case FSHORT:
+ h = *(int*)op->argp;
+ v = h;
+ r = SHORT;
+ break;
+
+ case FUNSIGN|FSHORT:
+ h = *(int*)op->argp;
+ v = (ushort)h;
+ r = SHORT;
+ break;
+
+ default:
+ v = *(int*)op->argp;
+ r = INT;
+ break;
+
+ case FUNSIGN:
+ v = *(unsigned*)op->argp;
+ r = INT;
+ break;
+ }
+ if(!(op->f3 & FUNSIGN) && v < 0) {
+ v = -v;
+ f = 1;
+ }
+ b[IDIGIT-1] = 0;
+ for(i = IDIGIT-2;; i--) {
+ n = (ulong)v % base;
+ n += '0';
+ if(n > '9')
+ n += 'a' - ('9'+1);
+ b[i] = n;
+ if(i < 2)
+ break;
+ v = (ulong)v / base;
+ if(op->f2 >= 0 && i >= IDIGIT-op->f2)
+ continue;
+ if(v <= 0)
+ break;
+ }
+sout:
+ if(f)
+ b[--i] = '-';
+ strconv(b+i, op, op->f1, -1);
+ return r;
+}
+
+void
+strconv(char *o, Op *op, int f1, int f2)
+{
+ int n, c;
+ char *p;
+
+ n = strlen(o);
+ if(f1 >= 0)
+ while(n < f1) {
+ PUT(op, ' ');
+ n++;
+ }
+ for(p=o; c = *p++;)
+ if(f2 != 0) {
+ PUT(op, c);
+ f2--;
+ }
+ if(f1 < 0) {
+ f1 = -f1;
+ while(n < f1) {
+ PUT(op, ' ');
+ n++;
+ }
+ }
+}
+
+static int
+noconv(Op *op)
+{
+
+ strconv("***", op, 0, -1);
+ return 0;
+}
+
+static int
+cconv(Op *op)
+{
+ char b[2];
+
+ b[0] = *(int*)op->argp;
+ b[1] = 0;
+ strconv(b, op, op->f1, -1);
+ return INT;
+}
+
+static int
+dconv(Op *op)
+{
+
+ return numbconv(op, 10);
+}
+
+static int
+hconv(Op *op)
+{
+ USED(op);
+ return -FSHORT;
+}
+
+static int
+lconv(Op *op)
+{
+ USED(op);
+ return -FLONG;
+}
+
+static int
+oconv(Op *op)
+{
+ USED(op);
+ return numbconv(op, 8);
+}
+
+static int
+sconv(Op *op)
+{
+
+ strconv(*(char**)op->argp, op, op->f1, op->f2);
+ return PTR;
+}
+
+static int
+uconv(Op *op)
+{
+ USED(op);
+ return -FUNSIGN;
+}
+
+static int
+xconv(Op *op)
+{
+
+ return numbconv(op, 16);
+}
+
+static int
+percent(Op *op)
+{
+
+ PUT(op, '%');
+ return 0;
+}
diff --git a/sys/src/cmd/disk/kfs/sub.c b/sys/src/cmd/disk/kfs/sub.c
new file mode 100755
index 000000000..09485aa46
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/sub.c
@@ -0,0 +1,690 @@
+#include "all.h"
+
+Lock wpathlock;
+
+struct {
+ Lock flock;
+ File* ffree; /* free file structures */
+ Wpath* wfree;
+} suballoc;
+
+enum{
+ Finc= 128, /* allocation chunksize for files */
+ Fmax= 10000, /* maximum file structures to be allocated */
+
+ Winc= 8*128, /* allocation chunksize for wpath */
+ Wmax= 8*10000, /* maximum wpath structures to be allocated */
+};
+
+
+Filsys*
+fsstr(char *p)
+{
+ Filsys *fs;
+
+ for(fs=filesys; fs->name; fs++)
+ if(strcmp(fs->name, p) == 0)
+ return fs;
+ return 0;
+}
+
+void
+fileinit(Chan *cp)
+{
+ File *f;
+ Tlock *t;
+
+loop:
+ lock(&cp->flock);
+ f = cp->flist;
+ if(!f) {
+ unlock(&cp->flock);
+ return;
+ }
+ cp->flist = f->next;
+ unlock(&cp->flock);
+
+ qlock(f);
+ if(t = f->tlock) {
+ t->time = 0;
+ f->tlock = 0;
+ }
+ if(f->open & FREMOV)
+ doremove(f, 0);
+ freewp(f->wpath);
+ f->open = 0;
+ f->cp = 0;
+ qunlock(f);
+
+ goto loop;
+}
+
+/*
+ * returns a locked file structure
+ */
+File*
+filep(Chan *cp, int fid, int flag)
+{
+ File *f, *prev;
+
+ if(fid == NOF)
+ return 0;
+
+loop:
+ lock(&cp->flock);
+ for(prev=0,f=cp->flist; f; prev=f,f=f->next) {
+ if(f->fid != fid)
+ continue;
+ if(prev) {
+ prev->next = f->next;
+ f->next = cp->flist;
+ cp->flist = f;
+ }
+ goto out;
+ }
+ if(flag) {
+ f = newfp(cp);
+ if(f) {
+ f->fid = fid;
+ goto out;
+ }
+ }
+else print("cannot find %p.%d (list=%p)\n", cp, fid, cp->flist);
+ unlock(&cp->flock);
+ return 0;
+
+out:
+ unlock(&cp->flock);
+ qlock(f);
+ if(f->fid != fid) {
+ qunlock(f);
+ goto loop;
+ }
+ return f;
+}
+
+void
+sublockinit(void)
+{
+ lock(&suballoc.flock);
+ lock(&wpathlock);
+ conf.nfile = 0;
+ conf.nwpath = 0;
+ unlock(&suballoc.flock);
+ unlock(&wpathlock);
+}
+
+/*
+ * always called with cp->flock locked
+ */
+File*
+newfp(Chan *cp)
+{
+ File *f, *e;
+
+retry:
+ lock(&suballoc.flock);
+ f = suballoc.ffree;
+ if(f != nil){
+ suballoc.ffree = f->list;
+ unlock(&suballoc.flock);
+ f->list = 0;
+ f->cp = cp;
+ f->next = cp->flist;
+ f->wpath = 0;
+ f->tlock = 0;
+ f->dslot = 0;
+ f->doffset = 0;
+ f->uid = 0;
+ f->cuid = 0;
+ cp->flist = f;
+ return f;
+ }
+ unlock(&suballoc.flock);
+
+ if(conf.nfile > Fmax){
+ print("%d: out of files\n", cp->chan);
+ return 0;
+ }
+
+ /*
+ * create a few new files
+ */
+ f = malloc(Finc*sizeof(*f));
+ memset(f, 0, Finc*sizeof(*f));
+ lock(&suballoc.flock);
+ for(e = f+Finc; f < e; f++){
+ qlock(f);
+ qunlock(f);
+ f->list = suballoc.ffree;
+ suballoc.ffree = f;
+ }
+ conf.nfile += Finc;
+ unlock(&suballoc.flock);
+ goto retry;
+}
+
+void
+freefp(File *fp)
+{
+ Chan *cp;
+ File *f, *prev;
+
+ if(!fp || !(cp = fp->cp))
+ return;
+ authfree(fp);
+ lock(&cp->flock);
+ for(prev=0,f=cp->flist; f; prev=f,f=f->next) {
+ if(f != fp)
+ continue;
+ if(prev)
+ prev->next = f->next;
+ else
+ cp->flist = f->next;
+ f->cp = 0;
+ lock(&suballoc.flock);
+ f->list = suballoc.ffree;
+ suballoc.ffree = f;
+ unlock(&suballoc.flock);
+ break;
+ }
+ unlock(&cp->flock);
+}
+
+Wpath*
+newwp(void)
+{
+ Wpath *w, *e;
+
+retry:
+ lock(&wpathlock);
+ w = suballoc.wfree;
+ if(w != nil){
+ suballoc.wfree = w->list;
+ unlock(&wpathlock);
+ memset(w, 0, sizeof(*w));
+ w->refs = 1;
+ w->up = 0;
+ return w;
+ }
+ unlock(&wpathlock);
+
+ if(conf.nwpath > Wmax){
+ print("out of wpaths\n");
+ return 0;
+ }
+
+ /*
+ * create a few new wpaths
+ */
+ w = malloc(Winc*sizeof(*w));
+ memset(w, 0, Winc*sizeof(*w));
+ lock(&wpathlock);
+ for(e = w+Winc; w < e; w++){
+ w->list = suballoc.wfree;
+ suballoc.wfree = w;
+ }
+ conf.nwpath += Winc;
+ unlock(&wpathlock);
+ goto retry;
+}
+
+/*
+ * increment the references for the whole path
+ */
+Wpath*
+getwp(Wpath *w)
+{
+ Wpath *nw;
+
+ lock(&wpathlock);
+ for(nw = w; nw; nw=nw->up)
+ nw->refs++;
+ unlock(&wpathlock);
+ return w;
+}
+
+/*
+ * decrement the reference for each element of the path
+ */
+void
+freewp(Wpath *w)
+{
+ lock(&wpathlock);
+ for(; w; w=w->up){
+ w->refs--;
+ if(w->refs == 0){
+ w->list = suballoc.wfree;
+ suballoc.wfree = w;
+ }
+ }
+ unlock(&wpathlock);
+}
+
+/*
+ * decrement the reference for just this element
+ */
+void
+putwp(Wpath *w)
+{
+ lock(&wpathlock);
+ w->refs--;
+ if(w->refs == 0){
+ w->list = suballoc.wfree;
+ suballoc.wfree = w;
+ }
+ unlock(&wpathlock);
+}
+
+int
+iaccess(File *f, Dentry *d, int m)
+{
+ if(wstatallow)
+ return 0;
+
+ /*
+ * owner is next
+ */
+ if(f->uid == d->uid)
+ if((m<<6) & d->mode)
+ return 0;
+ /*
+ * group membership is hard
+ */
+ if(ingroup(f->uid, d->gid))
+ if((m<<3) & d->mode)
+ return 0;
+ /*
+ * other access for everyone except members of group 9999
+ */
+ if(m & d->mode){
+ /*
+ * walk directories regardless.
+ * otherwise its impossible to get
+ * from the root to noworld's directories.
+ */
+ if((d->mode & DDIR) && (m == DEXEC))
+ return 0;
+ if(!ingroup(f->uid, 9999))
+ return 0;
+ }
+ return 1;
+}
+
+Tlock*
+tlocked(Iobuf *p, Dentry *d)
+{
+ Tlock *t, *t1;
+ long qpath, tim;
+ Device dev;
+
+ tim = time(0);
+ qpath = d->qid.path;
+ dev = p->dev;
+ t1 = 0;
+ for(t=tlocks+NTLOCK-1; t>=tlocks; t--) {
+ if(t->qpath == qpath)
+ if(t->time >= tim)
+ if(devcmp(t->dev, dev) == 0)
+ return 0; /* its locked */
+ if(!t1 && t->time < tim)
+ t1 = t; /* steal first lock */
+ }
+ if(t1) {
+ t1->dev = dev;
+ t1->qpath = qpath;
+ t1->time = tim + TLOCK;
+ }
+ /* botch
+ * out of tlock nodes simulates
+ * a locked file
+ */
+ return t1;
+}
+
+Qid
+newqid(Device dev)
+{
+ Iobuf *p;
+ Superb *sb;
+ Qid qid;
+
+ p = getbuf(dev, superaddr(dev), Bread|Bmod);
+ if(!p || checktag(p, Tsuper, QPSUPER))
+ panic("newqid: super block");
+ sb = (Superb*)p->iobuf;
+ sb->qidgen++;
+ qid.path = sb->qidgen;
+ qid.vers = 0;
+ qid.type = 0;
+ putbuf(p);
+ return qid;
+}
+
+/*
+ * what are legal characters in a name?
+ * only disallow control characters.
+ * a) utf avoids control characters.
+ * b) '/' may not be the separator
+ */
+int
+checkname(char *n)
+{
+ int i, c;
+
+ for(i=0; i<NAMELEN; i++) {
+ c = *n & 0xff;
+ if(c == 0) {
+ if(i == 0)
+ return 1;
+ memset(n, 0, NAMELEN-i);
+ return 0;
+ }
+ if(c <= 040)
+ return 1;
+ n++;
+ }
+ return 1; /* too long */
+}
+
+void
+bfree(Device dev, long addr, int d)
+{
+ Iobuf *p;
+ long a;
+ int i;
+
+ if(!addr)
+ return;
+ if(d > 0) {
+ d--;
+ p = getbuf(dev, addr, Bread);
+ if(p) {
+ for(i=INDPERBUF-1; i>=0; i--) {
+ a = ((long*)p->iobuf)[i];
+ bfree(dev, a, d);
+ }
+ putbuf(p);
+ }
+ }
+ /*
+ * stop outstanding i/o
+ */
+ p = getbuf(dev, addr, Bprobe);
+ if(p) {
+ p->flags &= ~(Bmod|Bimm);
+ putbuf(p);
+ }
+ /*
+ * dont put written worm
+ * blocks into free list
+ */
+ if(nofree(dev, addr))
+ return;
+ p = getbuf(dev, superaddr(dev), Bread|Bmod);
+ if(!p || checktag(p, Tsuper, QPSUPER))
+ panic("bfree: super block");
+ addfree(dev, addr, (Superb*)p->iobuf);
+ putbuf(p);
+}
+
+long
+balloc(Device dev, int tag, long qid)
+{
+ Iobuf *bp, *p;
+ Superb *sb;
+ long a;
+ int n;
+
+ p = getbuf(dev, superaddr(dev), Bread|Bmod);
+ if(!p || checktag(p, Tsuper, QPSUPER))
+ panic("balloc: super block");
+ sb = (Superb*)p->iobuf;
+
+loop:
+ n = --sb->fbuf.nfree;
+ sb->tfree--;
+ if(n < 0 || n >= FEPERBUF)
+ panic("balloc: bad freelist");
+ a = sb->fbuf.free[n];
+ if(n <= 0) {
+ if(a == 0) {
+ sb->tfree = 0;
+ sb->fbuf.nfree = 1;
+ if(devgrow(dev, sb))
+ goto loop;
+ putbuf(p);
+ return 0;
+ }
+ bp = getbuf(dev, a, Bread);
+ if(!bp || checktag(bp, Tfree, QPNONE)) {
+ if(bp)
+ putbuf(bp);
+ putbuf(p);
+ return 0;
+ }
+ memmove(&sb->fbuf, bp->iobuf, (FEPERBUF+1)*sizeof(long));
+ putbuf(bp);
+ }
+ bp = getbuf(dev, a, Bmod);
+ memset(bp->iobuf, 0, RBUFSIZE);
+ settag(bp, tag, qid);
+ if(tag == Tind1 || tag == Tind2 || tag == Tdir)
+ bp->flags |= Bimm;
+ putbuf(bp);
+ putbuf(p);
+ return a;
+}
+
+void
+addfree(Device dev, long addr, Superb *sb)
+{
+ int n;
+ Iobuf *p;
+
+ if(addr >= sb->fsize){
+ print("addfree: bad addr %lux\n", addr);
+ return;
+ }
+ n = sb->fbuf.nfree;
+ if(n < 0 || n > FEPERBUF)
+ panic("addfree: bad freelist");
+ if(n >= FEPERBUF) {
+ p = getbuf(dev, addr, Bmod);
+ if(p == 0)
+ panic("addfree: getbuf");
+ memmove(p->iobuf, &sb->fbuf, (FEPERBUF+1)*sizeof(long));
+ settag(p, Tfree, QPNONE);
+ putbuf(p);
+ n = 0;
+ }
+ sb->fbuf.free[n++] = addr;
+ sb->fbuf.nfree = n;
+ sb->tfree++;
+ if(addr >= sb->fsize)
+ sb->fsize = addr+1;
+}
+
+int
+Cfmt(Fmt *f1)
+{
+ Chan *cp;
+
+ cp = va_arg(f1->args, Chan*);
+ return fmtprint(f1, "C%d.%.3d", cp->type, cp->chan);
+}
+
+int
+Dfmt(Fmt *f1)
+{
+ Device d;
+
+ d = va_arg(f1->args, Device);
+ return fmtprint(f1, "D%d.%d.%d.%d", d.type, d.ctrl, d.unit, d.part);
+}
+
+int
+Afmt(Fmt *f1)
+{
+ Filta a;
+
+ a = va_arg(f1->args, Filta);
+ return fmtprint(f1, "%6lud %6lud %6lud",
+ fdf(a.f->filter[0], a.scale*60),
+ fdf(a.f->filter[1], a.scale*600),
+ fdf(a.f->filter[2], a.scale*6000));
+}
+
+int
+Gfmt(Fmt *f1)
+{
+ int t;
+
+ t = va_arg(f1->args, int);
+ if(t >= 0 && t < MAXTAG)
+ return fmtstrcpy(f1, tagnames[t]);
+ else
+ return fmtprint(f1, "<badtag %d>", t);
+}
+
+void
+formatinit(void)
+{
+
+ fmtinstall('C', Cfmt); /* print channels */
+ fmtinstall('D', Dfmt); /* print devices */
+ fmtinstall('A', Afmt); /* print filters */
+ fmtinstall('G', Gfmt); /* print tags */
+ fmtinstall('T', Tfmt); /* print times */
+ fmtinstall('O', ofcallfmt); /* print old fcalls */
+}
+int
+devcmp(Device d1, Device d2)
+{
+
+ if(d1.type == d2.type)
+ if(d1.ctrl == d2.ctrl)
+ if(d1.unit == d2.unit)
+ if(d1.part == d2.part)
+ return 0;
+ return 1;
+}
+
+void
+rootream(Device dev, long addr)
+{
+ Iobuf *p;
+ Dentry *d;
+
+ p = getbuf(dev, addr, Bmod|Bimm);
+ memset(p->iobuf, 0, RBUFSIZE);
+ settag(p, Tdir, QPROOT);
+ d = getdir(p, 0);
+ strcpy(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 = QID9P1(QPROOT|QPDIR,0);
+ d->atime = time(0);
+ d->mtime = d->atime;
+ putbuf(p);
+}
+
+int
+superok(Device dev, long addr, int set)
+{
+ Iobuf *p;
+ Superb *s;
+ int ok;
+
+ p = getbuf(dev, addr, Bread|Bmod|Bimm);
+ s = (Superb*)p->iobuf;
+ ok = s->fsok;
+ s->fsok = set;
+ putbuf(p);
+ return ok;
+}
+
+void
+superream(Device dev, long addr)
+{
+ Iobuf *p;
+ Superb *s;
+ long i;
+
+ p = getbuf(dev, addr, Bmod|Bimm);
+ memset(p->iobuf, 0, RBUFSIZE);
+ settag(p, Tsuper, QPSUPER);
+
+ s = (Superb*)p->iobuf;
+ s->fstart = 1;
+ s->fsize = devsize(dev);
+ s->fbuf.nfree = 1;
+ s->qidgen = 10;
+ for(i=s->fsize-1; i>=addr+2; i--)
+ addfree(dev, i, s);
+ putbuf(p);
+}
+
+/*
+ * returns 1 if n is prime
+ * used for adjusting lengths
+ * of hashing things.
+ * there is no need to be clever
+ */
+int
+prime(long n)
+{
+ long i;
+
+ if((n%2) == 0)
+ return 0;
+ for(i=3;; i+=2) {
+ if((n%i) == 0)
+ return 0;
+ if(i*i >= n)
+ return 1;
+ }
+}
+
+void
+hexdump(void *a, int n)
+{
+ char s1[30], s2[4];
+ uchar *p;
+ int i;
+
+ p = a;
+ s1[0] = 0;
+ for(i=0; i<n; i++) {
+ sprint(s2, " %.2ux", p[i]);
+ strcat(s1, s2);
+ if((i&7) == 7) {
+ print("%s\n", s1);
+ s1[0] = 0;
+ }
+ }
+ if(s1[0])
+ print("%s\n", s1);
+}
+
+long
+qidpathgen(Device *dev)
+{
+ Iobuf *p;
+ Superb *sb;
+ long path;
+
+ p = getbuf(*dev, superaddr((*dev)), Bread|Bmod);
+ if(!p || checktag(p, Tsuper, QPSUPER))
+ panic("newqid: super block");
+ sb = (Superb*)p->iobuf;
+ sb->qidgen++;
+ path = sb->qidgen;
+ putbuf(p);
+ return path;
+}
+
diff --git a/sys/src/cmd/disk/kfs/uid.c b/sys/src/cmd/disk/kfs/uid.c
new file mode 100755
index 000000000..bc5489a4f
--- /dev/null
+++ b/sys/src/cmd/disk/kfs/uid.c
@@ -0,0 +1,427 @@
+#include "all.h"
+
+struct
+{
+ RWLock uidlock;
+ char* uidbuf;
+ int flen;
+ int find;
+} uidgc;
+
+int
+byuid(void *a1, void *a2)
+{
+ Uid *u1, *u2;
+
+ u1 = a1;
+ u2 = a2;
+ return u2->uid - u1->uid;
+}
+
+int
+byname(void *a1, void *a2)
+{
+ Uid *u1, *u2;
+
+ u1 = a1;
+ u2 = a2;
+ return strcmp(uidspace+u2->offset, uidspace+u1->offset);
+}
+
+int
+fchar(void)
+{
+
+ if(uidgc.find >= uidgc.flen) {
+ uidgc.find = 0;
+ uidgc.flen = con_read(FID2, uidgc.uidbuf, cons.offset, MAXDAT);
+ if(uidgc.flen <= 0)
+ return 0;
+ cons.offset += uidgc.flen;
+ }
+ return uidgc.uidbuf[uidgc.find++];
+}
+
+int
+fname(char *name)
+{
+ int i, c;
+
+ memset(name, 0, NAMELEN);
+ for(i=0;; i++) {
+ c = fchar();
+ switch(c) {
+ case ':':
+ case '\n':
+ case ',':
+ case ' ':
+ case '#':
+ case 0:
+ return c;
+ }
+ if(i < NAMELEN-1)
+ name[i] = c;
+ }
+}
+
+#ifdef sometime
+/*
+ * file format is
+ * uid:name:lead:member,member,...\n
+ */
+void
+read_user(void)
+{
+ int c;
+
+ if((c=fname(ustr))!=':' || (c=fname(name))!=':' || (c=fname(lead))!=':')
+ goto skipline;
+ n = atol(ustr);
+ if(n == 0)
+ goto skipline;
+ if(readu){
+ o -= strlen(name)+1;
+ if(o < 0) {
+ cprint("conf.uidspace(%ld) too small\n", conf.uidspace);
+ return -1;
+ }
+ strcpy(uidspace+o, name);
+ uid[u].uid = n;
+ uid[u].offset = o;
+ u++;
+ if(u >= conf.nuid) {
+ cprint("conf.nuid(%ld) too small\n", conf.nuid);
+ goto initu;
+ }
+ }else{
+ o = strtouid1(name);
+ if(o == 0 && strcmp(name, "") != 0)
+ o = n;
+ for(c=0; c<u; c++)
+ if(uid[c].uid == n) {
+ uid[c].lead = o;
+ break;
+ }
+ while(((c=fname(name))==',' || c=='\n') && name[0]){
+work here
+ if(c=='\n')
+ break;
+ }
+ }
+
+skipline:
+ while(c != '\n')
+ fname(ustr);
+}
+#endif
+
+/*
+ -1:adm:adm:
+ 0:none:adm:
+ 1:tor:tor:
+ 10000:sys::
+ 10001:map:map:
+ 10002:doc::
+ 10003:upas:upas:
+ 10004:font::
+ 10005:bootes:bootes:
+*/
+
+struct {
+ int uid;
+ char *name;
+ int leader;
+}
+admusers[] = {
+ -1, "adm", -1,
+ 0, "none", -1,
+ 1, "tor", 1,
+ 2, "glenda", 2,
+ 10000, "sys", 0,
+ 10001, "upas", 10001,
+ 10002, "bootes", 10002,
+ 0, 0, 0,
+};
+
+
+void
+cmd_user(void)
+{
+ int c, n, o, u, g, i;
+ char name[NAMELEN];
+
+ if(con_clone(FID1, FID2))
+ goto ainitu;
+ if(con_path(FID2, "/adm/users"))
+ goto ainitu;
+ if(con_open(FID2, 0)){
+ goto ainitu;
+ }
+
+ wlock(&uidgc.uidlock);
+ uidgc.uidbuf = malloc(MAXDAT);
+
+ memset(uid, 0, conf.nuid * sizeof(*uid));
+ memset(uidspace, 0, conf.uidspace * sizeof(*uidspace));
+ memset(gidspace, 0, conf.gidspace * sizeof(*gidspace));
+
+ uidgc.flen = 0;
+ uidgc.find = 0;
+ cons.offset = 0;
+ u = 0;
+ o = conf.uidspace;
+
+ul1:
+ c = fname(name);
+ if(c != ':')
+ goto uskip;
+ n = atol(name);
+ if(n == 0)
+ goto uskip;
+ c = fname(name);
+ if(c != ':')
+ goto uskip;
+ o -= strlen(name)+1;
+ if(o < 0) {
+ cprint("conf.uidspace(%ld) too small\n", conf.uidspace);
+ goto initu;
+ }
+ strcpy(uidspace+o, name);
+ uid[u].uid = n;
+ uid[u].offset = o;
+ u++;
+ if(u >= conf.nuid) {
+ cprint("conf.nuid(%ld) too small\n", conf.nuid);
+ goto initu;
+ }
+
+uskip:
+ if(c == '\n')
+ goto ul1;
+ if(c) {
+ c = fname(name);
+ goto uskip;
+ }
+/* cprint("%d uids read\n", u);/**/
+ qsort(uid, u, sizeof(uid[0]), byuid);
+ for(c=u-1; c>0; c--)
+ if(uid[c].uid == uid[c-1].uid) {
+ cprint("duplicate uids: %d\n", uid[c].uid);
+ cprint(" %s", uidspace+uid[c].offset);
+ cprint(" %s\n", uidspace+uid[c-1].offset);
+ }
+ qsort(uid, u, sizeof(uid[0]), byname);
+ for(c=u-1; c>0; c--)
+ if(!strcmp(uidspace+uid[c].offset,
+ uidspace+uid[c-1].offset)) {
+ cprint("kfs: duplicate names: %s\n", uidspace+uid[c].offset);
+ cprint(" %d", uid[c].uid);
+ cprint(" %d\n", uid[c-1].uid);
+ }
+ if(cons.flags & Fuid)
+ for(c=0; c<u; c++)
+ cprint("%6d %s\n", uid[c].uid, uidspace+uid[c].offset);
+
+ uidgc.flen = 0;
+ uidgc.find = 0;
+ cons.offset = 0;
+ g = 0;
+
+gl1:
+ c = fname(name);
+ if(c != ':')
+ goto gskip;
+ n = atol(name); /* number */
+ if(n == 0)
+ goto gskip;
+ c = fname(name); /* name */
+ if(c != ':')
+ goto gskip;
+ c = fname(name); /* leader */
+ if(c != ':')
+ goto gskip;
+ o = strtouid1(name);
+ if(o == 0 && strcmp(name, "") != 0)
+ o = n;
+ for(c=0; c<u; c++)
+ if(uid[c].uid == n) {
+ uid[c].lead = o;
+ break;
+ }
+ c = fname(name); /* list of members */
+ if(c != ',' && c != '\n')
+ goto gskip;
+ if(!name[0])
+ goto gskip;
+ gidspace[g++] = n;
+gl2:
+ n = strtouid1(name);
+ if(n)
+ gidspace[g++] = n;
+ if(g >= conf.gidspace-2) {
+ cprint("conf.gidspace(%ld) too small\n", conf.gidspace);
+ goto initu;
+ }
+ if(c == '\n')
+ goto gl3;
+ c = fname(name);
+ if(c == ',' || c == '\n')
+ goto gl2;
+ cprint("gid truncated\n");
+
+gl3:
+ gidspace[g++] = 0;
+
+gskip:
+ if(c == '\n')
+ goto gl1;
+ if(c) {
+ c = fname(name);
+ goto gskip;
+ }
+ if(cons.flags & Fuid) {
+ o = 0;
+ for(c=0; c<g; c++) {
+ n = gidspace[c];
+ if(n == 0) {
+ o = 0;
+ continue;
+ }
+ uidtostr1(name, n);
+ if(o) {
+ if(o > 6) {
+ cprint("\n %s", name);
+ o = 1;
+ } else
+ cprint(" %s", name);
+ } else
+ cprint("\n%6s", name);
+ o++;
+ }
+ cprint("\n");
+ }
+ goto out;
+
+ainitu:
+ wlock(&uidgc.uidlock);
+ uidgc.uidbuf = malloc(MAXDAT);
+
+initu:
+ cprint("initializing minimal user table\n");
+ memset(uid, 0, conf.nuid * sizeof(*uid));
+ memset(uidspace, 0, conf.uidspace * sizeof(*uidspace));
+ memset(gidspace, 0, conf.gidspace * sizeof(*gidspace));
+ o = conf.uidspace;
+ u = 0;
+
+ for(i=0; admusers[i].name; i++){
+ o -= strlen(admusers[i].name)+1;
+ strcpy(uidspace+o, admusers[i].name);
+ uid[u].uid = admusers[i].uid;
+ uid[u].lead = admusers[i].leader;
+ uid[u].offset = o;
+ u++;
+ }
+
+out:
+ free(uidgc.uidbuf);
+ writegroup = strtouid1("write");
+ wunlock(&uidgc.uidlock);
+
+}
+
+void
+uidtostr(char *name, int id)
+{
+ rlock(&uidgc.uidlock);
+ uidtostr1(name, id);
+ runlock(&uidgc.uidlock);
+}
+
+void
+uidtostr1(char *name, int id)
+{
+ Uid *u;
+ int i;
+
+ if(id == 0){
+ strncpy(name, "none", NAMELEN);
+ return;
+ }
+ for(i=0, u=uid; i<conf.nuid; i++,u++) {
+ if(u->uid == id) {
+ strncpy(name, uidspace+u->offset, NAMELEN);
+ return;
+ }
+ }
+ strncpy(name, "none", NAMELEN);
+}
+
+int
+strtouid(char *s)
+{
+ int i;
+
+ rlock(&uidgc.uidlock);
+ i = strtouid1(s);
+ runlock(&uidgc.uidlock);
+ return i;
+}
+
+int
+strtouid1(char *s)
+{
+ Uid *u;
+ int i;
+
+ for(i=0, u=uid; i<conf.nuid; i++,u++)
+ if(!strcmp(s, uidspace+u->offset))
+ return u->uid;
+ return 0;
+}
+
+int
+ingroup(int u, int g)
+{
+ short *p;
+
+ if(u == g)
+ return 1;
+ rlock(&uidgc.uidlock);
+ for(p=gidspace; *p;) {
+ if(*p != g) {
+ for(p++; *p++;)
+ ;
+ continue;
+ }
+ for(p++; *p; p++)
+ if(*p == u) {
+ runlock(&uidgc.uidlock);
+ return 1;
+ }
+ }
+ runlock(&uidgc.uidlock);
+ return 0;
+}
+
+int
+leadgroup(int ui, int gi)
+{
+ Uid *u;
+ int i;
+
+ rlock(&uidgc.uidlock);
+ for(i=0, u=uid; i<conf.nuid; i++,u++) {
+ if(u->uid == gi) {
+ i = u->lead;
+ runlock(&uidgc.uidlock);
+ if(i == ui)
+ return 1;
+ if(i == 0)
+ return ingroup(ui, gi);
+ return 0;
+ }
+ }
+ runlock(&uidgc.uidlock);
+ return 0;
+}
diff --git a/sys/src/cmd/disk/kfscmd.c b/sys/src/cmd/disk/kfscmd.c
new file mode 100755
index 000000000..05fb0820f
--- /dev/null
+++ b/sys/src/cmd/disk/kfscmd.c
@@ -0,0 +1,56 @@
+#include <u.h>
+#include <libc.h>
+
+void
+main(int argc, char *argv[])
+{
+ char *name, buf[4*1024];
+ int fd, n, i, errs;
+
+ name = 0;
+ ARGBEGIN{
+ case 'n':
+ name = ARGF();
+ break;
+ default:
+ fprint(2, "usage: kfscmd [-n server] commands\n");
+ exits("usage");
+ }ARGEND
+
+ if(name)
+ snprint(buf, sizeof buf, "/srv/kfs.%s.cmd", name);
+ else
+ strcpy(buf, "/srv/kfs.cmd");
+ fd = open(buf, ORDWR);
+ if(fd < 0){
+ fprint(2, "kfscmd: can't open commands file\n");
+ exits("commands file");
+ }
+
+ errs = 0;
+ for(i = 0; i < argc; i++){
+ if(write(fd, argv[i], strlen(argv[i])) != strlen(argv[i])){
+ fprint(2, "%s: error writing %s: %r", argv0, argv[i]);
+ errs++;
+ continue;
+ }
+ for(;;){
+ n = read(fd, buf, sizeof buf - 1);
+ if(n < 0){
+ fprint(2, "%s: error executing %s: %r", argv0, argv[i]);
+ errs++;
+ break;
+ }
+ buf[n] = '\0';
+ if(strcmp(buf, "done") == 0 || strcmp(buf, "success") == 0)
+ break;
+ if(strcmp(buf, "unknown command") == 0){
+ errs++;
+ print("kfscmd: command %s not recognized\n", argv[i]);
+ break;
+ }
+ write(1, buf, n);
+ }
+ }
+ exits(errs ? "errors" : 0);
+}
diff --git a/sys/src/cmd/disk/mbr.c b/sys/src/cmd/disk/mbr.c
new file mode 100755
index 000000000..c7494a011
--- /dev/null
+++ b/sys/src/cmd/disk/mbr.c
@@ -0,0 +1,197 @@
+/*
+ * install new master boot record boot code on PC disk.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <disk.h>
+
+typedef struct {
+ uchar active; /* active flag */
+ uchar starth; /* starting head */
+ uchar starts; /* starting sector */
+ uchar startc; /* starting cylinder */
+ uchar type; /* partition type */
+ uchar endh; /* ending head */
+ uchar ends; /* ending sector */
+ uchar endc; /* ending cylinder */
+ uchar lba[4]; /* starting LBA */
+ uchar size[4]; /* size in sectors */
+} Tentry;
+
+enum {
+ Toffset = 0x1BE, /* offset of partition table */
+
+ Type9 = 0x39,
+};
+
+/*
+ * Default boot block prints an error message and reboots.
+ */
+static int ndefmbr = Toffset;
+static char defmbr[512] = {
+[0x000] 0xEB, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+[0x03E] 0xFA, 0xFC, 0x8C, 0xC8, 0x8E, 0xD8, 0x8E, 0xD0,
+ 0xBC, 0x00, 0x7C, 0xBE, 0x77, 0x7C, 0xE8, 0x19,
+ 0x00, 0x33, 0xC0, 0xCD, 0x16, 0xBB, 0x40, 0x00,
+ 0x8E, 0xC3, 0xBB, 0x72, 0x00, 0xB8, 0x34, 0x12,
+ 0x26, 0x89, 0x07, 0xEA, 0x00, 0x00, 0xFF, 0xFF,
+ 0xEB, 0xD6, 0xAC, 0x0A, 0xC0, 0x74, 0x09, 0xB4,
+ 0x0E, 0xBB, 0x07, 0x00, 0xCD, 0x10, 0xEB, 0xF2,
+ 0xC3, 'N', 'o', 't', ' ', 'a', ' ', 'b',
+ 'o', 'o', 't', 'a', 'b', 'l', 'e', ' ',
+ 'd', 'i', 's', 'c', ' ', 'o', 'r', ' ',
+ 'd', 'i', 's', 'c', ' ', 'e', 'r', 'r',
+ 'o', 'r', '\r', '\n', 'P', 'r', 'e', 's',
+ 's', ' ', 'a', 'l', 'm', 'o', 's', 't',
+ ' ', 'a', 'n', 'y', ' ', 'k', 'e', 'y',
+ ' ', 't', 'o', ' ', 'r', 'e', 'b', 'o',
+ 'o', 't', '.', '.', '.', 0x00, 0x00, 0x00,
+};
+
+void
+usage(void)
+{
+ fprint(2, "usage: disk/mbr [-m mbrfile] disk\n");
+ exits("usage");
+}
+
+void
+fatal(char *fmt, ...)
+{
+ char err[ERRMAX];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vsnprint(err, ERRMAX, fmt, arg);
+ va_end(arg);
+ fprint(2, "mbr: %s\n", err);
+ exits(err);
+}
+
+static void
+putle32(void* v, u32int i)
+{
+ uchar *p;
+
+ p = v;
+ p[0] = i;
+ p[1] = i>>8;
+ p[2] = i>>16;
+ p[3] = i>>24;
+}
+
+static void
+writechs(Disk *disk, uchar *p, vlong lba)
+{
+ int c, h, s;
+
+ s = lba % disk->s;
+ h = (lba / disk->s) % disk->h;
+ c = lba / (disk->s * disk->h);
+
+ if(c >= 1024) {
+ c = 1023;
+ h = disk->h - 1;
+ s = disk->s - 1;
+ }
+
+ p[0] = h;
+ p[1] = ((s+1) & 0x3F) | ((c>>2) & 0xC0);
+ p[2] = c;
+}
+
+static void
+wrtentry(Disk *disk, Tentry *tp, int type, u32int base, u32int lba, u32int end)
+{
+ tp->active = 0x80; /* make this sole partition active */
+ tp->type = type;
+ writechs(disk, &tp->starth, lba);
+ writechs(disk, &tp->endh, end-1);
+ putle32(tp->lba, lba-base);
+ putle32(tp->size, end-lba);
+}
+
+void
+main(int argc, char **argv)
+{
+ Disk *disk;
+ Tentry *tp;
+ uchar *mbr, *buf;
+ char *mbrfile;
+ ulong secsize;
+ int flag9, sysfd, nmbr;
+
+ flag9 = 0;
+ mbrfile = nil;
+ ARGBEGIN {
+ case '9':
+ flag9 = 1;
+ break;
+ case 'm':
+ mbrfile = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND
+
+ if(argc < 1)
+ usage();
+
+ disk = opendisk(argv[0], 0, 0);
+ if(disk == nil)
+ fatal("opendisk %s: %r", argv[0]);
+
+ if(disk->type == Tfloppy)
+ fatal("will not install mbr on floppy");
+ if(disk->secsize != 512)
+ fatal("secsize %d invalid", disk->secsize);
+
+ secsize = disk->secsize;
+ buf = malloc(secsize*(disk->s+1));
+ mbr = malloc(secsize*disk->s);
+ if(buf == nil || mbr == nil)
+ fatal("out of memory");
+
+ /*
+ * Start with initial sector from disk.
+ */
+ if(seek(disk->fd, 0, 0) < 0)
+ fatal("seek to boot sector: %r\n");
+ if(read(disk->fd, mbr, secsize) != secsize)
+ fatal("reading boot sector: %r");
+
+ if(mbrfile == nil){
+ nmbr = ndefmbr;
+ memmove(mbr, defmbr, nmbr);
+ } else {
+ memset(buf, 0, secsize*disk->s);
+ if((sysfd = open(mbrfile, OREAD)) < 0)
+ fatal("open %s: %r", mbrfile);
+ if((nmbr = read(sysfd, buf, secsize*(disk->s+1))) < 0)
+ fatal("read %s: %r", mbrfile);
+ if(nmbr > secsize*disk->s)
+ fatal("master boot record too large %d > %d", nmbr, secsize*disk->s);
+ if(nmbr < secsize)
+ nmbr = secsize;
+ close(sysfd);
+ memmove(buf+Toffset, mbr+Toffset, secsize-Toffset);
+ memmove(mbr, buf, nmbr);
+ }
+
+ if(flag9){
+ tp = (Tentry*)(mbr+Toffset);
+ memset(tp, 0, secsize-Toffset);
+ wrtentry(disk, tp, Type9, 0, disk->s, disk->secs);
+ }
+ mbr[secsize-2] = 0x55;
+ mbr[secsize-1] = 0xAA;
+ nmbr = (nmbr+secsize-1)&~(secsize-1);
+ if(seek(disk->wfd, 0, 0) < 0)
+ fatal("seek to MBR sector: %r\n");
+ if(write(disk->wfd, mbr, nmbr) != nmbr)
+ fatal("writing MBR: %r");
+
+ exits(0);
+}
diff --git a/sys/src/cmd/disk/mkext.c b/sys/src/cmd/disk/mkext.c
new file mode 100755
index 000000000..1a2210388
--- /dev/null
+++ b/sys/src/cmd/disk/mkext.c
@@ -0,0 +1,318 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+enum{
+ LEN = 8*1024,
+ NFLDS = 6, /* filename, modes, uid, gid, mtime, bytes */
+};
+
+int selected(char*, int, char*[]);
+void mkdirs(char*, char*);
+void mkdir(char*, ulong, ulong, char*, char*);
+void extract(char*, ulong, ulong, char*, char*, uvlong);
+void seekpast(uvlong);
+void error(char*, ...);
+void warn(char*, ...);
+void usage(void);
+#pragma varargck argpos warn 1
+#pragma varargck argpos error 1
+
+Biobufhdr bin;
+uchar binbuf[2*LEN];
+char linebuf[LEN];
+int uflag;
+int hflag;
+int vflag;
+int Tflag;
+
+void
+main(int argc, char **argv)
+{
+ Biobuf bout;
+ char *fields[NFLDS], name[2*LEN], *p, *namep;
+ char *uid, *gid;
+ ulong mode, mtime;
+ uvlong bytes;
+
+ quotefmtinstall();
+ namep = name;
+ ARGBEGIN{
+ case 'd':
+ p = ARGF();
+ if(strlen(p) >= LEN)
+ error("destination fs name too long\n");
+ strcpy(name, p);
+ namep = name + strlen(name);
+ break;
+ case 'h':
+ hflag = 1;
+ Binit(&bout, 1, OWRITE);
+ break;
+ case 'u':
+ uflag = 1;
+ Tflag = 1;
+ break;
+ case 'T':
+ Tflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ Binits(&bin, 0, OREAD, binbuf, sizeof binbuf);
+ while(p = Brdline(&bin, '\n')){
+ p[Blinelen(&bin)-1] = '\0';
+ strcpy(linebuf, p);
+ p = linebuf;
+ if(strcmp(p, "end of archive") == 0){
+ Bterm(&bout);
+ fprint(2, "done\n");
+ exits(0);
+ }
+ if (gettokens(p, fields, NFLDS, " \t") != NFLDS){
+ warn("too few fields in file header");
+ continue;
+ }
+ p = unquotestrdup(fields[0]);
+ strcpy(namep, p);
+ free(p);
+ mode = strtoul(fields[1], 0, 8);
+ uid = fields[2];
+ gid = fields[3];
+ mtime = strtoul(fields[4], 0, 10);
+ bytes = strtoull(fields[5], 0, 10);
+ if(argc){
+ if(!selected(namep, argc, argv)){
+ if(bytes)
+ seekpast(bytes);
+ continue;
+ }
+ mkdirs(name, namep);
+ }
+ if(hflag){
+ Bprint(&bout, "%q %luo %q %q %lud %llud\n",
+ name, mode, uid, gid, mtime, bytes);
+ if(bytes)
+ seekpast(bytes);
+ continue;
+ }
+ if(mode & DMDIR)
+ mkdir(name, mode, mtime, uid, gid);
+ else
+ extract(name, mode, mtime, uid, gid, bytes);
+ }
+ fprint(2, "premature end of archive\n");
+ exits("premature end of archive");
+}
+
+int
+fileprefix(char *prefix, char *s)
+{
+ while(*prefix)
+ if(*prefix++ != *s++)
+ return 0;
+ if(*s && *s != '/')
+ return 0;
+ return 1;
+}
+
+int
+selected(char *s, int argc, char *argv[])
+{
+ int i;
+
+ for(i=0; i<argc; i++)
+ if(fileprefix(argv[i], s))
+ return 1;
+ return 0;
+}
+
+void
+mkdirs(char *name, char *namep)
+{
+ char buf[2*LEN], *p;
+ int fd;
+
+ strcpy(buf, name);
+ for(p = &buf[namep - name]; p = utfrune(p, '/'); p++){
+ if(p[1] == '\0')
+ return;
+ *p = 0;
+ fd = create(buf, OREAD, 0775|DMDIR);
+ close(fd);
+ *p = '/';
+ }
+}
+
+void
+mkdir(char *name, ulong mode, ulong mtime, char *uid, char *gid)
+{
+ Dir *d, xd;
+ int fd;
+ char *p;
+ char olderr[256];
+
+ fd = create(name, OREAD, mode);
+ if(fd < 0){
+ rerrstr(olderr, sizeof(olderr));
+ if((d = dirstat(name)) == nil || !(d->mode & DMDIR)){
+ free(d);
+ warn("can't make directory %q, mode %luo: %s", name, mode, olderr);
+ return;
+ }
+ free(d);
+ }
+ close(fd);
+
+ d = &xd;
+ nulldir(d);
+ p = utfrrune(name, L'/');
+ if(p)
+ p++;
+ else
+ p = name;
+ d->name = p;
+ if(uflag){
+ d->uid = uid;
+ d->gid = gid;
+ }
+ if(Tflag)
+ d->mtime = mtime;
+ d->mode = mode;
+ if(dirwstat(name, d) < 0)
+ warn("can't set modes for %q: %r", name);
+
+ if(uflag||Tflag){
+ if((d = dirstat(name)) == nil){
+ warn("can't reread modes for %q: %r", name);
+ return;
+ }
+ if(Tflag && d->mtime != mtime)
+ warn("%q: time mismatch %lud %lud\n", name, mtime, d->mtime);
+ if(uflag && strcmp(uid, d->uid))
+ warn("%q: uid mismatch %q %q", name, uid, d->uid);
+ if(uflag && strcmp(gid, d->gid))
+ warn("%q: gid mismatch %q %q", name, gid, d->gid);
+ }
+}
+
+void
+extract(char *name, ulong mode, ulong mtime, char *uid, char *gid, uvlong bytes)
+{
+ Dir d, *nd;
+ Biobuf *b;
+ char buf[LEN];
+ char *p;
+ ulong n;
+ uvlong tot;
+
+ if(vflag)
+ print("x %q %llud bytes\n", name, bytes);
+
+ b = Bopen(name, OWRITE);
+ if(!b){
+ warn("can't make file %q: %r", name);
+ seekpast(bytes);
+ return;
+ }
+ for(tot = 0; tot < bytes; tot += n){
+ n = sizeof buf;
+ if(tot + n > bytes)
+ n = bytes - tot;
+ n = Bread(&bin, buf, n);
+ if(n <= 0)
+ error("premature eof reading %q", name);
+ if(Bwrite(b, buf, n) != n)
+ warn("error writing %q: %r", name);
+ }
+
+ nulldir(&d);
+ p = utfrrune(name, '/');
+ if(p)
+ p++;
+ else
+ p = name;
+ d.name = p;
+ if(uflag){
+ d.uid = uid;
+ d.gid = gid;
+ }
+ if(Tflag)
+ d.mtime = mtime;
+ d.mode = mode;
+ Bflush(b);
+ if(dirfwstat(Bfildes(b), &d) < 0)
+ warn("can't set modes for %q: %r", name);
+ if(uflag||Tflag){
+ if((nd = dirfstat(Bfildes(b))) == nil)
+ warn("can't reread modes for %q: %r", name);
+ else{
+ if(Tflag && nd->mtime != mtime)
+ warn("%q: time mismatch %lud %lud\n",
+ name, mtime, nd->mtime);
+ if(uflag && strcmp(uid, nd->uid))
+ warn("%q: uid mismatch %q %q",
+ name, uid, nd->uid);
+ if(uflag && strcmp(gid, nd->gid))
+ warn("%q: gid mismatch %q %q",
+ name, gid, nd->gid);
+ free(nd);
+ }
+ }
+ Bterm(b);
+}
+
+void
+seekpast(uvlong bytes)
+{
+ char buf[LEN];
+ long n;
+ uvlong tot;
+
+ for(tot = 0; tot < bytes; tot += n){
+ n = sizeof buf;
+ if(tot + n > bytes)
+ n = bytes - tot;
+ n = Bread(&bin, buf, n);
+ if(n < 0)
+ error("premature eof");
+ }
+}
+
+void
+error(char *fmt, ...)
+{
+ char buf[1024];
+ va_list arg;
+
+ sprint(buf, "%q: ", argv0);
+ va_start(arg, fmt);
+ vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ fprint(2, "%s\n", buf);
+ exits(0);
+}
+
+void
+warn(char *fmt, ...)
+{
+ char buf[1024];
+ va_list arg;
+
+ sprint(buf, "%q: ", argv0);
+ va_start(arg, fmt);
+ vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ fprint(2, "%s\n", buf);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: mkext [-h] [-u] [-v] [-d dest-fs] [file ...]\n");
+ exits("usage");
+}
diff --git a/sys/src/cmd/disk/mkfile b/sys/src/cmd/disk/mkfile
new file mode 100755
index 000000000..8592af83b
--- /dev/null
+++ b/sys/src/cmd/disk/mkfile
@@ -0,0 +1,49 @@
+</$objtype/mkfile
+
+TARG=exsort\
+ format\
+ kfscmd\
+ mbr\
+ mkext\
+ mkfs\
+ partfs\
+
+DIRS=\
+ 9660\
+ kfs\
+ prep\
+# sacfs\
+
+OFILES=
+
+BIN=/$objtype/bin/disk
+
+UPDATE=\
+ mkfile\
+ ${TARG:%=%.c}\
+ /sys/man/8/prep\
+ /sys/man/8/mkfs\
+ ${TARG:%=/386/bin/disk/%}\
+
+</sys/src/cmd/mkmany
+
+all:V: all-kfs
+
+install:V: install-kfs ksync
+
+clean:V: clean-kfs
+
+nuke:V: nuke-kfs
+
+installall:V: installall-kfs
+
+%-kfs:V:
+ for(i in $DIRS) @{
+ cd $i
+ mk $MKFLAGS $stem
+ }
+
+ksync:
+ touch $BIN/ksync
+
+$O.format: /$objtype/lib/libdisk.a
diff --git a/sys/src/cmd/disk/mkfs.c b/sys/src/cmd/disk/mkfs.c
new file mode 100755
index 000000000..1412011a6
--- /dev/null
+++ b/sys/src/cmd/disk/mkfs.c
@@ -0,0 +1,858 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <bio.h>
+
+enum{
+ LEN = 8*1024,
+ HUNKS = 128,
+
+ /*
+ * types of destination file sytems
+ */
+ Kfs = 0,
+ Fs,
+ Archive,
+};
+
+typedef struct File File;
+
+struct File{
+ char *new;
+ char *elem;
+ char *old;
+ char *uid;
+ char *gid;
+ ulong mode;
+};
+
+void arch(Dir*);
+void copy(Dir*);
+int copyfile(File*, Dir*, int);
+void* emalloc(ulong);
+void error(char *, ...);
+void freefile(File*);
+File* getfile(File*);
+char* getmode(char*, ulong*);
+char* getname(char*, char**);
+char* getpath(char*);
+void kfscmd(char *);
+void mkdir(Dir*);
+int mkfile(File*);
+void mkfs(File*, int);
+char* mkpath(char*, char*);
+void mktree(File*, int);
+void mountkfs(char*);
+void printfile(File*);
+void setnames(File*);
+void setusers(void);
+void skipdir(void);
+char* strdup(char*);
+int uptodate(Dir*, char*);
+void usage(void);
+void warn(char *, ...);
+
+Biobuf *b;
+Biobufhdr bout; /* stdout when writing archive */
+uchar boutbuf[2*LEN];
+char newfile[LEN];
+char oldfile[LEN];
+char *proto;
+char *cputype;
+char *users;
+char *oldroot;
+char *newroot;
+char *prog = "mkfs";
+int lineno;
+char *buf;
+char *zbuf;
+int buflen = 1024-8;
+int indent;
+int verb;
+int modes;
+int ream;
+int debug;
+int xflag;
+int sfd;
+int fskind; /* Kfs, Fs, Archive */
+int setuid; /* on Fs: set uid and gid? */
+char *user;
+
+void
+main(int argc, char **argv)
+{
+ File file;
+ char *name;
+ int i, errs;
+
+ quotefmtinstall();
+ user = getuser();
+ name = "";
+ memset(&file, 0, sizeof file);
+ file.new = "";
+ file.old = 0;
+ oldroot = "";
+ newroot = "/n/kfs";
+ users = 0;
+ fskind = Kfs;
+ ARGBEGIN{
+ case 'a':
+ if(fskind != Kfs) {
+ fprint(2, "cannot use -a with -d\n");
+ usage();
+ }
+ fskind = Archive;
+ newroot = "";
+ Binits(&bout, 1, OWRITE, boutbuf, sizeof boutbuf);
+ break;
+ case 'd':
+ if(fskind != Kfs) {
+ fprint(2, "cannot use -d with -a\n");
+ usage();
+ }
+ fskind = Fs;
+ newroot = ARGF();
+ break;
+ case 'D':
+ debug = 1;
+ break;
+ case 'n':
+ name = EARGF(usage());
+ break;
+ case 'p':
+ modes = 1;
+ break;
+ case 'r':
+ ream = 1;
+ break;
+ case 's':
+ oldroot = ARGF();
+ break;
+ case 'u':
+ users = ARGF();
+ break;
+ case 'U':
+ setuid = 1;
+ break;
+ case 'v':
+ verb = 1;
+ break;
+ case 'x':
+ xflag = 1;
+ break;
+ case 'z':
+ buflen = atoi(ARGF())-8;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(!argc)
+ usage();
+
+ buf = emalloc(buflen);
+ zbuf = emalloc(buflen);
+ memset(zbuf, 0, buflen);
+
+ mountkfs(name);
+ kfscmd("allow");
+ proto = "users";
+ setusers();
+ cputype = getenv("cputype");
+ if(cputype == 0)
+ cputype = "68020";
+
+ errs = 0;
+ for(i = 0; i < argc; i++){
+ proto = argv[i];
+ fprint(2, "processing %q\n", proto);
+
+ b = Bopen(proto, OREAD);
+ if(!b){
+ fprint(2, "%q: can't open %q: skipping\n", prog, proto);
+ errs++;
+ continue;
+ }
+
+ lineno = 0;
+ indent = 0;
+ mkfs(&file, -1);
+ Bterm(b);
+ }
+ fprint(2, "file system made\n");
+ kfscmd("disallow");
+ kfscmd("sync");
+ if(errs)
+ exits("skipped protos");
+ if(fskind == Archive){
+ Bprint(&bout, "end of archive\n");
+ Bterm(&bout);
+ }
+ exits(0);
+}
+
+void
+mkfs(File *me, int level)
+{
+ File *child;
+ int rec;
+
+ child = getfile(me);
+ if(!child)
+ return;
+ if((child->elem[0] == '+' || child->elem[0] == '*') && child->elem[1] == '\0'){
+ rec = child->elem[0] == '+';
+ free(child->new);
+ child->new = strdup(me->new);
+ setnames(child);
+ mktree(child, rec);
+ freefile(child);
+ child = getfile(me);
+ }
+ while(child && indent > level){
+ if(mkfile(child))
+ mkfs(child, indent);
+ freefile(child);
+ child = getfile(me);
+ }
+ if(child){
+ freefile(child);
+ Bseek(b, -Blinelen(b), 1);
+ lineno--;
+ }
+}
+
+void
+mktree(File *me, int rec)
+{
+ File child;
+ Dir *d;
+ int i, n, fd;
+
+ fd = open(oldfile, OREAD);
+ if(fd < 0){
+ warn("can't open %q: %r", oldfile);
+ return;
+ }
+
+ child = *me;
+ while((n = dirread(fd, &d)) > 0){
+ for(i = 0; i < n; i++){
+ child.new = mkpath(me->new, d[i].name);
+ if(me->old)
+ child.old = mkpath(me->old, d[i].name);
+ child.elem = d[i].name;
+ setnames(&child);
+ if(copyfile(&child, &d[i], 1) && rec)
+ mktree(&child, rec);
+ free(child.new);
+ if(child.old)
+ free(child.old);
+ }
+ }
+ close(fd);
+}
+
+int
+mkfile(File *f)
+{
+ Dir *dir;
+
+ if((dir = dirstat(oldfile)) == nil){
+ warn("can't stat file %q: %r", oldfile);
+ skipdir();
+ return 0;
+ }
+ return copyfile(f, dir, 0);
+}
+
+int
+copyfile(File *f, Dir *d, int permonly)
+{
+ ulong mode;
+ Dir nd;
+
+ if(xflag){
+ Bprint(&bout, "%q\t%ld\t%lld\n", f->new, d->mtime, d->length);
+ return (d->mode & DMDIR) != 0;
+ }
+ if(verb && (fskind == Archive || ream))
+ fprint(2, "%q\n", f->new);
+ d->name = f->elem;
+ if(d->type != 'M'){
+ d->uid = "sys";
+ d->gid = "sys";
+ mode = (d->mode >> 6) & 7;
+ d->mode |= mode | (mode << 3);
+ }
+ if(strcmp(f->uid, "-") != 0)
+ d->uid = f->uid;
+ if(strcmp(f->gid, "-") != 0)
+ d->gid = f->gid;
+ if(fskind == Fs && !setuid){
+ d->uid = "";
+ d->gid = "";
+ }
+ if(f->mode != ~0){
+ if(permonly)
+ d->mode = (d->mode & ~0666) | (f->mode & 0666);
+ else if((d->mode&DMDIR) != (f->mode&DMDIR))
+ warn("inconsistent mode for %q", f->new);
+ else
+ d->mode = f->mode;
+ }
+ if(!uptodate(d, newfile)){
+ if(verb && (fskind != Archive && ream == 0))
+ fprint(2, "%q\n", f->new);
+ if(d->mode & DMDIR)
+ mkdir(d);
+ else
+ copy(d);
+ }else if(modes){
+ nulldir(&nd);
+ nd.mode = d->mode;
+ nd.gid = d->gid;
+ nd.mtime = d->mtime;
+ if(verb && (fskind != Archive && ream == 0))
+ fprint(2, "%q\n", f->new);
+ if(dirwstat(newfile, &nd) < 0)
+ warn("can't set modes for %q: %r", f->new);
+ nulldir(&nd);
+ nd.uid = d->uid;
+ dirwstat(newfile, &nd);
+ }
+ return (d->mode & DMDIR) != 0;
+}
+
+/*
+ * check if file to is up to date with
+ * respect to the file represented by df
+ */
+int
+uptodate(Dir *df, char *to)
+{
+ int ret;
+ Dir *dt;
+
+ if(fskind == Archive || ream || (dt = dirstat(to)) == nil)
+ return 0;
+ ret = dt->mtime >= df->mtime;
+ free(dt);
+ return ret;
+}
+
+void
+copy(Dir *d)
+{
+ char cptmp[LEN], *p;
+ int f, t, n, needwrite, nowarnyet = 1;
+ vlong tot, len;
+ Dir nd;
+
+ f = open(oldfile, OREAD);
+ if(f < 0){
+ warn("can't open %q: %r", oldfile);
+ return;
+ }
+ t = -1;
+ if(fskind == Archive)
+ arch(d);
+ else{
+ strcpy(cptmp, newfile);
+ p = utfrrune(cptmp, L'/');
+ if(!p)
+ error("internal temporary file error");
+ strcpy(p+1, "__mkfstmp");
+ t = create(cptmp, OWRITE, 0666);
+ if(t < 0){
+ warn("can't create %q: %r", newfile);
+ close(f);
+ return;
+ }
+ }
+
+ needwrite = 0;
+ for(tot = 0; tot < d->length; tot += n){
+ len = d->length - tot;
+ /* don't read beyond d->length */
+ if (len > buflen)
+ len = buflen;
+ n = read(f, buf, len);
+ if(n <= 0) {
+ if(n < 0 && nowarnyet) {
+ warn("can't read %q: %r", oldfile);
+ nowarnyet = 0;
+ }
+ /*
+ * don't quit: pad to d->length (in pieces) to agree
+ * with the length in the header, already emitted.
+ */
+ memset(buf, 0, len);
+ n = len;
+ }
+ if(fskind == Archive){
+ if(Bwrite(&bout, buf, n) != n)
+ error("write error: %r");
+ }else if(memcmp(buf, zbuf, n) == 0){
+ if(seek(t, n, 1) < 0)
+ error("can't write zeros to %q: %r", newfile);
+ needwrite = 1;
+ }else{
+ if(write(t, buf, n) < n)
+ error("can't write %q: %r", newfile);
+ needwrite = 0;
+ }
+ }
+ close(f);
+ if(needwrite){
+ if(seek(t, -1, 1) < 0 || write(t, zbuf, 1) != 1)
+ error("can't write zero at end of %q: %r", newfile);
+ }
+ if(tot != d->length){
+ /* this should no longer happen */
+ warn("wrong number of bytes written to %q (was %lld should be %lld)\n",
+ newfile, tot, d->length);
+ if(fskind == Archive){
+ warn("seeking to proper position\n");
+ /* does no good if stdout is a pipe */
+ Bseek(&bout, d->length - tot, 1);
+ }
+ }
+ if(fskind == Archive)
+ return;
+ remove(newfile);
+ nulldir(&nd);
+ nd.mode = d->mode;
+ nd.gid = d->gid;
+ nd.mtime = d->mtime;
+ nd.name = d->name;
+ if(dirfwstat(t, &nd) < 0)
+ error("can't move tmp file to %q: %r", newfile);
+ nulldir(&nd);
+ nd.uid = d->uid;
+ dirfwstat(t, &nd);
+ close(t);
+}
+
+void
+mkdir(Dir *d)
+{
+ Dir *d1;
+ Dir nd;
+ int fd;
+
+ if(fskind == Archive){
+ arch(d);
+ return;
+ }
+ fd = create(newfile, OREAD, d->mode);
+ nulldir(&nd);
+ nd.mode = d->mode;
+ nd.gid = d->gid;
+ nd.mtime = d->mtime;
+ if(fd < 0){
+ if((d1 = dirstat(newfile)) == nil || !(d1->mode & DMDIR)){
+ free(d1);
+ error("can't create %q", newfile);
+ }
+ free(d1);
+ if(dirwstat(newfile, &nd) < 0)
+ warn("can't set modes for %q: %r", newfile);
+ nulldir(&nd);
+ nd.uid = d->uid;
+ dirwstat(newfile, &nd);
+ return;
+ }
+ if(dirfwstat(fd, &nd) < 0)
+ warn("can't set modes for %q: %r", newfile);
+ nulldir(&nd);
+ nd.uid = d->uid;
+ dirfwstat(fd, &nd);
+ close(fd);
+}
+
+void
+arch(Dir *d)
+{
+ Bprint(&bout, "%q %luo %q %q %lud %lld\n",
+ newfile, d->mode, d->uid, d->gid, d->mtime, d->length);
+}
+
+char *
+mkpath(char *prefix, char *elem)
+{
+ char *p;
+ int n;
+
+ n = strlen(prefix) + strlen(elem) + 2;
+ p = emalloc(n);
+ sprint(p, "%s/%s", prefix, elem);
+ return p;
+}
+
+char *
+strdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s) + 1);
+ return strcpy(t, s);
+}
+
+void
+setnames(File *f)
+{
+ sprint(newfile, "%s%s", newroot, f->new);
+ if(f->old){
+ if(f->old[0] == '/')
+ sprint(oldfile, "%s%s", oldroot, f->old);
+ else
+ strcpy(oldfile, f->old);
+ }else
+ sprint(oldfile, "%s%s", oldroot, f->new);
+ if(strlen(newfile) >= sizeof newfile
+ || strlen(oldfile) >= sizeof oldfile)
+ error("name overfile");
+}
+
+void
+freefile(File *f)
+{
+ if(f->old)
+ free(f->old);
+ if(f->new)
+ free(f->new);
+ free(f);
+}
+
+/*
+ * skip all files in the proto that
+ * could be in the current dir
+ */
+void
+skipdir(void)
+{
+ char *p, c;
+ int level;
+
+ if(indent < 0 || b == nil) /* b is nil when copying adm/users */
+ return;
+ level = indent;
+ for(;;){
+ indent = 0;
+ p = Brdline(b, '\n');
+ lineno++;
+ if(!p){
+ indent = -1;
+ return;
+ }
+ while((c = *p++) != '\n')
+ if(c == ' ')
+ indent++;
+ else if(c == '\t')
+ indent += 8;
+ else
+ break;
+ if(indent <= level){
+ Bseek(b, -Blinelen(b), 1);
+ lineno--;
+ return;
+ }
+ }
+}
+
+File*
+getfile(File *old)
+{
+ File *f;
+ char *elem;
+ char *p;
+ int c;
+
+ if(indent < 0)
+ return 0;
+loop:
+ indent = 0;
+ p = Brdline(b, '\n');
+ lineno++;
+ if(!p){
+ indent = -1;
+ return 0;
+ }
+ while((c = *p++) != '\n')
+ if(c == ' ')
+ indent++;
+ else if(c == '\t')
+ indent += 8;
+ else
+ break;
+ if(c == '\n' || c == '#')
+ goto loop;
+ p--;
+ f = emalloc(sizeof *f);
+ p = getname(p, &elem);
+ if(debug)
+ fprint(2, "getfile: %q root %q\n", elem, old->new);
+ f->new = mkpath(old->new, elem);
+ f->elem = utfrrune(f->new, L'/') + 1;
+ p = getmode(p, &f->mode);
+ p = getname(p, &f->uid);
+ if(!*f->uid)
+ f->uid = "-";
+ p = getname(p, &f->gid);
+ if(!*f->gid)
+ f->gid = "-";
+ f->old = getpath(p);
+ if(f->old && strcmp(f->old, "-") == 0){
+ free(f->old);
+ f->old = 0;
+ }
+ setnames(f);
+
+ if(debug)
+ printfile(f);
+
+ return f;
+}
+
+char*
+getpath(char *p)
+{
+ char *q, *new;
+ int c, n;
+
+ while((c = *p) == ' ' || c == '\t')
+ p++;
+ q = p;
+ while((c = *q) != '\n' && c != ' ' && c != '\t')
+ q++;
+ if(q == p)
+ return 0;
+ n = q - p;
+ new = emalloc(n + 1);
+ memcpy(new, p, n);
+ new[n] = 0;
+ return new;
+}
+
+char*
+getname(char *p, char **buf)
+{
+ char *s, *start;
+ int c;
+
+ while((c = *p) == ' ' || c == '\t')
+ p++;
+
+ start = p;
+ while((c = *p) != '\n' && c != ' ' && c != '\t' && c != '\0')
+ p++;
+
+ *buf = malloc(p+1-start);
+ if(*buf == nil)
+ return nil;
+ memmove(*buf, start, p-start);
+ (*buf)[p-start] = '\0';
+
+ if(**buf == '$'){
+ s = getenv(*buf+1);
+ if(s == 0){
+ warn("can't read environment variable %q", *buf+1);
+ skipdir();
+ free(*buf);
+ return nil;
+ }
+ free(*buf);
+ *buf = s;
+ }
+ return p;
+}
+
+char*
+getmode(char *p, ulong *xmode)
+{
+ char *buf, *s;
+ ulong m;
+
+ *xmode = ~0;
+ p = getname(p, &buf);
+ if(p == nil)
+ return nil;
+
+ s = buf;
+ if(!*s || strcmp(s, "-") == 0)
+ return p;
+ m = 0;
+ if(*s == 'd'){
+ m |= DMDIR;
+ s++;
+ }
+ if(*s == 'a'){
+ m |= DMAPPEND;
+ s++;
+ }
+ if(*s == 'l'){
+ m |= DMEXCL;
+ s++;
+ }
+ if(s[0] < '0' || s[0] > '7'
+ || s[1] < '0' || s[1] > '7'
+ || s[2] < '0' || s[2] > '7'
+ || s[3]){
+ warn("bad mode specification %q", buf);
+ free(buf);
+ return p;
+ }
+ *xmode = m | strtoul(s, 0, 8);
+ free(buf);
+ return p;
+}
+
+void
+setusers(void)
+{
+ File file;
+ int m;
+
+ if(fskind != Kfs)
+ return;
+ m = modes;
+ modes = 1;
+ file.uid = "adm";
+ file.gid = "adm";
+ file.mode = DMDIR|0775;
+ file.new = "/adm";
+ file.elem = "adm";
+ file.old = 0;
+ setnames(&file);
+ strcpy(oldfile, file.new); /* Don't use root for /adm */
+ mkfile(&file);
+ file.new = "/adm/users";
+ file.old = users;
+ file.elem = "users";
+ file.mode = 0664;
+ setnames(&file);
+ if (file.old)
+ strcpy(oldfile, file.old); /* Don't use root for /adm/users */
+ mkfile(&file);
+ kfscmd("user");
+ mkfile(&file);
+ file.mode = DMDIR|0775;
+ file.new = "/adm";
+ file.old = "/adm";
+ file.elem = "adm";
+ setnames(&file);
+ strcpy(oldfile, file.old); /* Don't use root for /adm */
+ mkfile(&file);
+ modes = m;
+}
+
+void
+mountkfs(char *name)
+{
+ char kname[64];
+
+ if(fskind != Kfs)
+ return;
+ if(name[0])
+ snprint(kname, sizeof kname, "/srv/kfs.%s", name);
+ else
+ strcpy(kname, "/srv/kfs");
+ sfd = open(kname, ORDWR);
+ if(sfd < 0){
+ fprint(2, "can't open %q\n", kname);
+ exits("open /srv/kfs");
+ }
+ if(mount(sfd, -1, "/n/kfs", MREPL|MCREATE, "") < 0){
+ fprint(2, "can't mount kfs on /n/kfs\n");
+ exits("mount kfs");
+ }
+ close(sfd);
+ strcat(kname, ".cmd");
+ sfd = open(kname, ORDWR);
+ if(sfd < 0){
+ fprint(2, "can't open %q\n", kname);
+ exits("open /srv/kfs");
+ }
+}
+
+void
+kfscmd(char *cmd)
+{
+ char buf[4*1024];
+ int n;
+
+ if(fskind != Kfs)
+ return;
+ if(write(sfd, cmd, strlen(cmd)) != strlen(cmd)){
+ fprint(2, "%q: error writing %q: %r", prog, cmd);
+ return;
+ }
+ for(;;){
+ n = read(sfd, buf, sizeof buf - 1);
+ if(n <= 0)
+ return;
+ buf[n] = '\0';
+ if(strcmp(buf, "done") == 0 || strcmp(buf, "success") == 0)
+ return;
+ if(strcmp(buf, "unknown command") == 0){
+ fprint(2, "%q: command %q not recognized\n", prog, cmd);
+ return;
+ }
+ }
+}
+
+void *
+emalloc(ulong n)
+{
+ void *p;
+
+ if((p = malloc(n)) == 0)
+ error("out of memory");
+ return p;
+}
+
+void
+error(char *fmt, ...)
+{
+ char buf[1024];
+ va_list arg;
+
+ sprint(buf, "%q: %q:%d: ", prog, proto, lineno);
+ va_start(arg, fmt);
+ vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ fprint(2, "%s\n", buf);
+ kfscmd("disallow");
+ kfscmd("sync");
+ exits(0);
+}
+
+void
+warn(char *fmt, ...)
+{
+ char buf[1024];
+ va_list arg;
+
+ sprint(buf, "%q: %q:%d: ", prog, proto, lineno);
+ va_start(arg, fmt);
+ vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ fprint(2, "%s\n", buf);
+}
+
+void
+printfile(File *f)
+{
+ if(f->old)
+ fprint(2, "%q from %q %q %q %lo\n", f->new, f->old, f->uid, f->gid, f->mode);
+ else
+ fprint(2, "%q %q %q %lo\n", f->new, f->uid, f->gid, f->mode);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %q [-aprvx] [-d root] [-n name] [-s source] [-u users] [-z n] proto ...\n", prog);
+ exits("usage");
+}
diff --git a/sys/src/cmd/disk/partfs.c b/sys/src/cmd/disk/partfs.c
new file mode 100755
index 000000000..d353b73b9
--- /dev/null
+++ b/sys/src/cmd/disk/partfs.c
@@ -0,0 +1,562 @@
+/*
+ * partfs - serve an underlying file, with devsd-style partitions
+ */
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+typedef struct Part Part;
+struct Part
+{
+ int inuse;
+ int vers;
+ ulong mode;
+ char *name;
+ vlong offset; /* in sectors */
+ vlong length; /* in sectors */
+};
+
+enum
+{
+ Qroot = 0,
+ Qdir,
+ Qctl,
+ Qpart,
+};
+
+int fd = -1, ctlfd = -1;
+int rdonly;
+ulong ctlmode = 0666;
+ulong time0;
+vlong nsect, sectsize;
+
+char *inquiry = "partfs hard drive";
+char *sdname = "sdXX";
+Part tab[64];
+
+char*
+ctlstring(void)
+{
+ Part *p;
+ Fmt fmt;
+
+ fmtstrinit(&fmt);
+ fmtprint(&fmt, "inquiry %s\n", inquiry);
+ fmtprint(&fmt, "geometry %lld %lld\n", nsect, sectsize);
+ for (p = tab; p < tab + nelem(tab); p++)
+ if (p->inuse)
+ fmtprint(&fmt, "part %s %lld %lld\n",
+ p->name, p->offset, p->length);
+ return fmtstrflush(&fmt);
+}
+
+int
+addpart(char *name, vlong start, vlong end)
+{
+ Part *p;
+
+ if(start < 0 || start > end || end > nsect){
+ werrstr("bad partition boundaries");
+ return -1;
+ }
+
+ if (strcmp(name, "ctl") == 0 || strcmp(name, "data") == 0) {
+ werrstr("partition name already in use");
+ return -1;
+ }
+ for (p = tab; p < tab + nelem(tab) && p->inuse; p++)
+ if (strcmp(p->name, name) == 0) {
+ werrstr("partition name already in use");
+ return -1;
+ }
+ if(p == tab + nelem(tab)){
+ werrstr("no free partition slots");
+ return -1;
+ }
+
+ p->inuse = 1;
+ free(p->name);
+ p->name = estrdup9p(name);
+ p->offset = start;
+ p->length = end - start;
+ p->mode = ctlmode;
+ p->vers++;
+ return 0;
+}
+
+int
+delpart(char *s)
+{
+ Part *p;
+
+ for (p = tab; p < tab + nelem(tab); p++)
+ if(p->inuse && strcmp(p->name, s) == 0)
+ break;
+ if(p == tab + nelem(tab)){
+ werrstr("partition not found");
+ return -1;
+ }
+
+ p->inuse = 0;
+ free(p->name);
+ p->name = nil;
+ return 0;
+}
+
+static void
+ctlwrite0(Req *r, char *msg, Cmdbuf *cb)
+{
+ vlong start, end;
+ Part *p;
+
+ r->ofcall.count = r->ifcall.count;
+
+ if(cb->nf < 1){
+ respond(r, "empty control message");
+ return;
+ }
+
+ if(strcmp(cb->f[0], "part") == 0){
+ if(cb->nf != 4){
+ respondcmderror(r, cb, "part takes 3 args");
+ return;
+ }
+ start = strtoll(cb->f[2], 0, 0);
+ end = strtoll(cb->f[3], 0, 0);
+ if(addpart(cb->f[1], start, end) < 0){
+ respondcmderror(r, cb, "%r");
+ return;
+ }
+ }
+ else if(strcmp(cb->f[0], "delpart") == 0){
+ if(cb->nf != 2){
+ respondcmderror(r, cb, "delpart takes 1 arg");
+ return;
+ }
+ if(delpart(cb->f[1]) < 0){
+ respondcmderror(r, cb, "%r");
+ return;
+ }
+ }
+ else if(strcmp(cb->f[0], "inquiry") == 0){
+ if(cb->nf != 2){
+ respondcmderror(r, cb, "inquiry takes 1 arg");
+ return;
+ }
+ free(inquiry);
+ inquiry = estrdup9p(cb->f[1]);
+ }
+ else if(strcmp(cb->f[0], "geometry") == 0){
+ if(cb->nf != 3){
+ respondcmderror(r, cb, "geometry takes 2 args");
+ return;
+ }
+ nsect = strtoll(cb->f[1], 0, 0);
+ sectsize = strtoll(cb->f[2], 0, 0);
+ if(tab[0].inuse && strcmp(tab[0].name, "data") == 0 &&
+ tab[0].vers == 0){
+ tab[0].offset = 0;
+ tab[0].length = nsect;
+ }
+ for(p = tab; p < tab + nelem(tab); p++)
+ if(p->inuse && p->offset + p->length > nsect){
+ p->inuse = 0;
+ free(p->name);
+ p->name = nil;
+ }
+ } else
+ /* pass through to underlying ctl file, if any */
+ if (write(ctlfd, msg, r->ifcall.count) != r->ifcall.count) {
+ respondcmderror(r, cb, "%r");
+ return;
+ }
+ respond(r, nil);
+}
+
+void
+ctlwrite(Req *r)
+{
+ char *msg;
+ Cmdbuf *cb;
+
+ r->ofcall.count = r->ifcall.count;
+
+ msg = emalloc9p(r->ifcall.count+1);
+ memmove(msg, r->ifcall.data, r->ifcall.count);
+ msg[r->ifcall.count] = '\0';
+
+ cb = parsecmd(r->ifcall.data, r->ifcall.count);
+ ctlwrite0(r, msg, cb);
+
+ free(cb);
+ free(msg);
+}
+
+int
+rootgen(int off, Dir *d, void*)
+{
+ memset(d, 0, sizeof *d);
+ d->atime = time0;
+ d->mtime = time0;
+ if(off == 0){
+ d->name = estrdup9p(sdname);
+ d->mode = DMDIR|0777;
+ d->qid.path = Qdir;
+ d->qid.type = QTDIR;
+ d->uid = estrdup9p("partfs");
+ d->gid = estrdup9p("partfs");
+ d->muid = estrdup9p("");
+ return 0;
+ }
+ return -1;
+}
+
+int
+dirgen(int off, Dir *d, void*)
+{
+ int n;
+ Part *p;
+
+ memset(d, 0, sizeof *d);
+ d->atime = time0;
+ d->mtime = time0;
+ if(off == 0){
+ d->name = estrdup9p("ctl");
+ d->mode = ctlmode;
+ d->qid.path = Qctl;
+ goto Have;
+ }
+
+ off--;
+ n = 0;
+ for(p = tab; p < tab + nelem(tab); p++, n++){
+ if(!p->inuse)
+ continue;
+ if(n == off){
+ d->name = estrdup9p(p->name);
+ d->length = p->length*sectsize;
+ d->mode = p->mode;
+ d->qid.path = Qpart + p - tab;
+ d->qid.vers = p->vers;
+ goto Have;
+ }
+ }
+ return -1;
+
+Have:
+ d->uid = estrdup9p("partfs");
+ d->gid = estrdup9p("partfs");
+ d->muid = estrdup9p("");
+ return 0;
+}
+
+void*
+evommem(void *a, void *b, ulong n)
+{
+ return memmove(b, a, n);
+}
+
+int
+rdwrpart(Req *r)
+{
+ int q;
+ long count, tot;
+ vlong offset;
+ uchar *dat;
+ Part *p;
+
+ q = r->fid->qid.path - Qpart;
+ if(q < 0 || q > nelem(tab) || !tab[q].inuse ||
+ tab[q].vers != r->fid->qid.vers){
+ respond(r, "unknown partition");
+ return -1;
+ }
+ p = &tab[q];
+
+ offset = r->ifcall.offset;
+ count = r->ifcall.count;
+ if(offset < 0){
+ respond(r, "negative offset");
+ return -1;
+ }
+ if(count < 0){
+ respond(r, "negative count");
+ return -1;
+ }
+ if(offset > p->length*sectsize){
+ respond(r, "offset past end of partition");
+ return -1;
+ }
+ if(offset+count > p->length*sectsize)
+ count = p->length*sectsize - offset;
+ offset += p->offset*sectsize;
+
+ if(r->ifcall.type == Tread)
+ dat = (uchar*)r->ofcall.data;
+ else
+ dat = (uchar*)r->ifcall.data;
+
+ /* pass i/o through to underlying file */
+ seek(fd, offset, 0);
+ if (r->ifcall.type == Twrite) {
+ tot = write(fd, dat, count);
+ if (tot != count) {
+ respond(r, "%r");
+ return -1;
+ }
+ } else {
+ tot = read(fd, dat, count);
+ if (tot < 0) {
+ respond(r, "%r");
+ return -1;
+ }
+ }
+ r->ofcall.count = tot;
+ respond(r, nil);
+ return 0;
+}
+
+void
+fsread(Req *r)
+{
+ char *s;
+
+ switch((int)r->fid->qid.path){
+ case Qroot:
+ dirread9p(r, rootgen, nil);
+ break;
+ case Qdir:
+ dirread9p(r, dirgen, nil);
+ break;
+ case Qctl:
+ s = ctlstring();
+ readstr(r, s);
+ free(s);
+ break;
+ default:
+ rdwrpart(r);
+ return;
+ }
+ respond(r, nil);
+}
+
+void
+fswrite(Req *r)
+{
+ switch((int)r->fid->qid.path){
+ case Qroot:
+ case Qdir:
+ respond(r, "write to a directory?");
+ break;
+ case Qctl:
+ ctlwrite(r);
+ break;
+ default:
+ rdwrpart(r);
+ break;
+ }
+}
+
+void
+fsopen(Req *r)
+{
+ if(r->ifcall.mode&ORCLOSE)
+ respond(r, "cannot open ORCLOSE");
+
+ switch((int)r->fid->qid.path){
+ case Qroot:
+ case Qdir:
+ if(r->ifcall.mode != OREAD){
+ respond(r, "bad mode for directory open");
+ return;
+ }
+ }
+
+ respond(r, nil);
+}
+
+void
+fsstat(Req *r)
+{
+ int q;
+ Dir *d;
+ Part *p;
+
+ d = &r->d;
+ memset(d, 0, sizeof *d);
+ d->qid = r->fid->qid;
+ d->atime = d->mtime = time0;
+ q = r->fid->qid.path;
+ switch(q){
+ case Qroot:
+ d->name = estrdup9p("/");
+ d->mode = DMDIR|0777;
+ break;
+
+ case Qdir:
+ d->name = estrdup9p(sdname);
+ d->mode = DMDIR|0777;
+ break;
+
+ case Qctl:
+ d->name = estrdup9p("ctl");
+ d->mode = 0666;
+ break;
+
+ default:
+ q -= Qpart;
+ if(q < 0 || q > nelem(tab) || tab[q].inuse == 0 ||
+ r->fid->qid.vers != tab[q].vers){
+ respond(r, "partition no longer exists");
+ return;
+ }
+ p = &tab[q];
+ d->name = estrdup9p(p->name);
+ d->length = p->length * sectsize;
+ d->mode = p->mode;
+ break;
+ }
+
+ d->uid = estrdup9p("partfs");
+ d->gid = estrdup9p("partfs");
+ d->muid = estrdup9p("");
+ respond(r, nil);
+}
+
+void
+fsattach(Req *r)
+{
+ char *spec;
+
+ spec = r->ifcall.aname;
+ if(spec && spec[0]){
+ respond(r, "invalid attach specifier");
+ return;
+ }
+ r->ofcall.qid = (Qid){Qroot, 0, QTDIR};
+ r->fid->qid = r->ofcall.qid;
+ respond(r, nil);
+}
+
+char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+ Part *p;
+
+ switch((int)fid->qid.path){
+ case Qroot:
+ if(strcmp(name, sdname) == 0){
+ fid->qid.path = Qdir;
+ fid->qid.type = QTDIR;
+ *qid = fid->qid;
+ return nil;
+ }
+ break;
+ case Qdir:
+ if(strcmp(name, "ctl") == 0){
+ fid->qid.path = Qctl;
+ fid->qid.vers = 0;
+ fid->qid.type = 0;
+ *qid = fid->qid;
+ return nil;
+ }
+ for(p = tab; p < tab + nelem(tab); p++)
+ if(p->inuse && strcmp(p->name, name) == 0){
+ fid->qid.path = p - tab + Qpart;
+ fid->qid.vers = p->vers;
+ fid->qid.type = 0;
+ *qid = fid->qid;
+ return nil;
+ }
+ break;
+ }
+ return "file not found";
+}
+
+Srv fs = {
+ .attach=fsattach,
+ .open= fsopen,
+ .read= fsread,
+ .write= fswrite,
+ .stat= fsstat,
+ .walk1= fswalk1,
+};
+
+char *mtpt = "/dev";
+char *srvname;
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-Dr] [-d sdname] [-m mtpt] [-s srvname] diskimage\n",
+ argv0);
+ fprint(2, "\tdefault mtpt is /dev\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int isdir;
+ char *file, *cname;
+ Dir *dir;
+
+ quotefmtinstall();
+ time0 = time(0);
+
+ ARGBEGIN{
+ case 'D':
+ chatty9p++;
+ break;
+ case 'd':
+ sdname = EARGF(usage());
+ break;
+ case 'm':
+ mtpt = EARGF(usage());
+ break;
+ case 'r':
+ rdonly = 1;
+ break;
+ case 's':
+ srvname = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+ file = argv[0];
+ dir = dirstat(file);
+ if(!dir)
+ sysfatal("%s: %r", file);
+ isdir = (dir->mode & DMDIR) != 0;
+ free(dir);
+
+ if (isdir) {
+ cname = smprint("%s/ctl", file);
+ if ((ctlfd = open(cname, ORDWR)) < 0)
+ sysfatal("open %s: %r", cname);
+ file = smprint("%s/data", file);
+ }
+ if((fd = open(file, rdonly? OREAD: ORDWR)) < 0)
+ sysfatal("open %s: %r", file);
+
+ sectsize = 512; /* conventional */
+ dir = dirfstat(fd);
+ if (dir)
+ nsect = dir->length / sectsize;
+ free(dir);
+
+ inquiry = estrdup9p(inquiry);
+ tab[0].inuse = 1;
+ tab[0].name = estrdup9p("data");
+ tab[0].mode = 0666;
+ tab[0].length = nsect;
+
+ postmountsrv(&fs, srvname, mtpt, MBEFORE);
+ exits(nil);
+}
diff --git a/sys/src/cmd/disk/prep/calc.y b/sys/src/cmd/disk/prep/calc.y
new file mode 100755
index 000000000..0829f09df
--- /dev/null
+++ b/sys/src/cmd/disk/prep/calc.y
@@ -0,0 +1,200 @@
+%{
+typedef struct Exp Exp;
+enum {
+ NUM,
+ DOT,
+ DOLLAR,
+ ADD,
+ SUB,
+ MUL,
+ DIV,
+ FRAC,
+ NEG,
+};
+
+struct Exp {
+ int ty;
+ long long n;
+ Exp *e1;
+ Exp *e2;
+};
+
+typedef Exp* Expptr;
+#define YYSTYPE Expptr
+Exp *yyexp;
+%}
+
+%token NUMBER
+
+%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); }
+ ;
+
+%%
+
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include "disk.h"
+#include "edit.h"
+
+static Exp*
+mkNUM(vlong x)
+{
+ Exp *n;
+
+ n = emalloc(sizeof *n);
+
+ n->ty = NUM;
+ n->n = x;
+ return n;
+}
+
+static Exp*
+mkOP(int ty, Exp *e1, Exp *e2)
+{
+ Exp *n;
+
+ n = emalloc(sizeof *n);
+ n->ty = ty;
+ n->e1 = e1;
+ n->e2 = e2;
+
+ return n;
+}
+
+static char *inp;
+static jmp_buf jmp;
+static vlong dot, size, dollar;
+static char** errp;
+
+static int
+yylex(void)
+{
+ int c;
+ uvlong n;
+
+ while(isspace(*inp))
+ inp++;
+
+ if(*inp == 0)
+ return 0;
+
+ if(isdigit(*inp)) {
+ n = strtoull(inp, &inp, 0); /* default unit is sectors */
+ c = *inp++;
+ if(isascii(c) && isupper(c))
+ c = tolower(c);
+ switch(c) {
+ case 't':
+ n *= 1024;
+ /* fall through */
+ case 'g':
+ n *= 1024;
+ /* fall through */
+ case 'm':
+ n *= 1024;
+ /* fall through */
+ case 'k':
+ n *= 2;
+ break;
+ default:
+ --inp;
+ break;
+ }
+ yylval = mkNUM(n);
+ return NUMBER;
+ }
+ return *inp++;
+}
+
+static void
+yyerror(char *s)
+{
+ *errp = s;
+ longjmp(jmp, 1);
+}
+
+static vlong
+eval(Exp *e)
+{
+ vlong i;
+
+ switch(e->ty) {
+ case NUM:
+ return e->n;
+ case DOT:
+ return dot;
+ case DOLLAR:
+ return dollar;
+ case ADD:
+ return eval(e->e1)+eval(e->e2);
+ case SUB:
+ return eval(e->e1)-eval(e->e2);
+ case MUL:
+ return eval(e->e1)*eval(e->e2);
+ case DIV:
+ i = eval(e->e2);
+ if(i == 0)
+ yyerror("division by zero");
+ return eval(e->e1)/i;
+ case FRAC:
+ return (size*eval(e->e1))/100;
+ case NEG:
+ return -eval(e->e1);
+ }
+ assert(0);
+ return 0;
+}
+
+int yyparse(void);
+
+char*
+parseexpr(char *s, vlong xdot, vlong xdollar, vlong xsize, vlong *result)
+{
+ char *err;
+
+ errp = &err;
+ if(setjmp(jmp))
+ return err;
+
+ inp = s;
+ dot = xdot;
+ size = xsize;
+ dollar = xdollar;
+ yyparse();
+ if(yyexp == nil)
+ return "nil yylval?";
+ *result = eval(yyexp);
+ return nil;
+}
+
+#ifdef TEST
+void
+main(int argc, char **argv)
+{
+ int i;
+ vlong r;
+ char *e;
+
+ for(i=1; i<argc; i++)
+ if(e = parseexpr(argv[i], 1000, 1000000, 1000000, &r))
+ print("%s\n", e);
+ else
+ print("%lld\n", r);
+}
+#endif
diff --git a/sys/src/cmd/disk/prep/edit.c b/sys/src/cmd/disk/prep/edit.c
new file mode 100755
index 000000000..8a96116fa
--- /dev/null
+++ b/sys/src/cmd/disk/prep/edit.c
@@ -0,0 +1,551 @@
+/*
+ * disk partition editor
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <disk.h>
+#include "edit.h"
+
+char*
+getline(Edit *edit)
+{
+ static int inited;
+ static Biobuf bin;
+ char *p;
+ int n;
+
+ if(!inited){
+ Binit(&bin, 0, OREAD);
+ inited = 1;
+ }
+ p = Brdline(&bin, '\n');
+ n = Blinelen(&bin);
+ if(p == nil || n < 1){
+ if(edit->changed)
+ fprint(2, "?warning: changes not written\n");
+ exits(0);
+ }
+ p[n - 1] = '\0';
+ while(isspace(*p))
+ p++;
+ return p;
+}
+
+Part*
+findpart(Edit *edit, char *name)
+{
+ int i;
+
+ for(i=0; i<edit->npart; i++)
+ if(strcmp(edit->part[i]->name, name) == 0)
+ return edit->part[i];
+ return nil;
+}
+
+static char*
+okname(Edit *edit, char *name)
+{
+ int i;
+ static char msg[100];
+
+ if(name[0] == '\0')
+ return "partition has no name";
+
+// if(strlen(name) >= NAMELEN)
+// return "name too long";
+//
+ for(i=0; i<edit->npart; i++) {
+ if(strcmp(name, edit->part[i]->name) == 0) {
+ sprint(msg, "already have partition with name \"%s\"", name);
+ return msg;
+ }
+ }
+ return nil;
+}
+
+char*
+addpart(Edit *edit, Part *p)
+{
+ int i;
+ static char msg[100];
+ char *err;
+
+ if(err = okname(edit, p->name))
+ return err;
+
+ for(i=0; i<edit->npart; i++) {
+ if(p->start < edit->part[i]->end && edit->part[i]->start < p->end) {
+ sprint(msg, "\"%s\" %lld-%lld overlaps with \"%s\" %lld-%lld",
+ p->name, p->start, p->end,
+ edit->part[i]->name, edit->part[i]->start, edit->part[i]->end);
+ // return msg;
+ }
+ }
+
+ if(edit->npart >= nelem(edit->part))
+ return "too many partitions";
+
+ edit->part[i=edit->npart++] = 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;
+}
+
+char*
+delpart(Edit *edit, Part *p)
+{
+ int i;
+
+ for(i=0; i<edit->npart; i++)
+ if(edit->part[i] == p)
+ break;
+ assert(i < edit->npart);
+ edit->npart--;
+ for(; i<edit->npart; i++)
+ edit->part[i] = edit->part[i+1];
+
+ edit->changed = 1;
+ return nil;
+}
+
+static char*
+editdot(Edit *edit, int argc, char **argv)
+{
+ char *err;
+ vlong ndot;
+
+ if(argc == 1) {
+ print("\t. %lld\n", edit->dot);
+ return nil;
+ }
+
+ if(argc > 2)
+ return "args";
+
+ if(err = parseexpr(argv[1], edit->dot, edit->end, edit->end, &ndot))
+ return err;
+
+ edit->dot = ndot;
+ return nil;
+}
+
+static char*
+editadd(Edit *edit, int argc, char **argv)
+{
+ char *name, *err, *q;
+ static char msg[100];
+ vlong start, end, maxend;
+ int i;
+
+ if(argc < 2)
+ return "args";
+
+ name = estrdup(argv[1]);
+ if((err = okname(edit, name)) || (err = edit->okname(edit, name)))
+ return err;
+
+ if(argc >= 3)
+ q = argv[2];
+ else {
+ fprint(2, "start %s: ", edit->unit);
+ q = getline(edit);
+ }
+ if(err = parseexpr(q, edit->dot, edit->end, edit->end, &start))
+ return err;
+
+ if(start < 0 || start >= edit->end)
+ return "start out of range";
+
+ for(i=0; i < edit->npart; i++) {
+ if(edit->part[i]->start <= start && start < edit->part[i]->end) {
+ sprint(msg, "start %s in partition \"%s\"", edit->unit, edit->part[i]->name);
+ return msg;
+ }
+ }
+
+ maxend = edit->end;
+ for(i=0; i < edit->npart; i++)
+ if(start < edit->part[i]->start && edit->part[i]->start < maxend)
+ maxend = edit->part[i]->start;
+
+ if(argc >= 4)
+ q = argv[3];
+ else {
+ fprint(2, "end [%lld..%lld] ", start, maxend);
+ q = getline(edit);
+ }
+ if(err = parseexpr(q, edit->dot, maxend, edit->end, &end))
+ return err;
+
+ if(start == end)
+ return "size zero partition";
+
+ if(end <= start || end > maxend)
+ return "end out of range";
+
+ if(argc > 4)
+ return "args";
+
+ if(err = edit->add(edit, name, start, end))
+ return err;
+
+ edit->dot = end;
+ return nil;
+}
+
+static char*
+editdel(Edit *edit, int argc, char **argv)
+{
+ Part *p;
+
+ if(argc != 2)
+ return "args";
+
+ if((p = findpart(edit, argv[1])) == nil)
+ return "no such partition";
+
+ return edit->del(edit, p);
+}
+
+static char *helptext =
+ ". [newdot] - display or set value of dot\n"
+ "a name [start [end]] - add partition\n"
+ "d name - delete partition\n"
+ "h - print help message\n"
+ "p - print partition table\n"
+ "P - print commands to update sd(3) device\n"
+ "w - write partition table\n"
+ "q - quit\n";
+
+static char*
+edithelp(Edit *edit, int, char**)
+{
+ print("%s", helptext);
+ if(edit->help)
+ return edit->help(edit);
+ return nil;
+}
+
+static char*
+editprint(Edit *edit, int argc, char**)
+{
+ vlong lastend;
+ int i;
+ Part **part;
+
+ if(argc != 1)
+ return "args";
+
+ lastend = 0;
+ part = edit->part;
+ for(i=0; i<edit->npart; 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;
+}
+
+char*
+editwrite(Edit *edit, int argc, char**)
+{
+ int i;
+ char *err;
+
+ if(argc != 1)
+ return "args";
+
+ if(edit->disk->rdonly)
+ return "read only";
+
+ err = edit->write(edit);
+ if(err)
+ return err;
+ for(i=0; i<edit->npart; i++)
+ edit->part[i]->changed = 0;
+ edit->changed = 0;
+ return nil;
+}
+
+static char*
+editquit(Edit *edit, int argc, char**)
+{
+ static int warned;
+
+ if(argc != 1) {
+ warned = 0;
+ return "args";
+ }
+
+ if(edit->changed && (!edit->warned || edit->lastcmd != 'q')) {
+ edit->warned = 1;
+ return "changes unwritten";
+ }
+
+ exits(0);
+ return nil; /* not reached */
+}
+
+char*
+editctlprint(Edit *edit, int argc, char **)
+{
+ if(argc != 1)
+ return "args";
+
+ if(edit->printctl)
+ edit->printctl(edit, 1);
+ else
+ ctldiff(edit, 1);
+ return nil;
+}
+
+typedef struct Cmd Cmd;
+struct Cmd {
+ char c;
+ char *(*fn)(Edit*, int ,char**);
+};
+
+Cmd cmds[] = {
+ '.', editdot,
+ 'a', editadd,
+ 'd', editdel,
+ '?', edithelp,
+ 'h', edithelp,
+ 'P', editctlprint,
+ 'p', editprint,
+ 'w', editwrite,
+ 'q', editquit,
+};
+
+void
+runcmd(Edit *edit, char *cmd)
+{
+ char *f[10], *err;
+ int i, nf;
+
+ while(*cmd && isspace(*cmd))
+ cmd++;
+
+ nf = tokenize(cmd, f, nelem(f));
+ if(nf >= 10) {
+ fprint(2, "?\n");
+ return;
+ }
+
+ if(nf < 1)
+ return;
+ if(strlen(f[0]) != 1) {
+ fprint(2, "?\n");
+ return;
+ }
+
+ err = nil;
+ for(i=0; i<nelem(cmds); i++) {
+ if(cmds[i].c == f[0][0]) {
+ err = cmds[i].fn(edit, nf, f);
+ break;
+ }
+ }
+ if(i == nelem(cmds)){
+ if(edit->ext)
+ err = edit->ext(edit, nf, f);
+ else
+ err = "unknown command";
+ }
+ if(err)
+ fprint(2, "?%s\n", err);
+ edit->lastcmd = f[0][0];
+}
+
+static Part*
+ctlmkpart(char *name, vlong start, vlong end, int changed)
+{
+ Part *p;
+
+ p = emalloc(sizeof(*p));
+ p->name = estrdup(name);
+ p->ctlname = estrdup(name);
+ p->start = start;
+ p->end = end;
+ p->changed = changed;
+ return p;
+}
+
+static void
+rdctlpart(Edit *edit)
+{
+ int i, nline, nf;
+ char *line[128];
+ char buf[4096];
+ vlong a, b;
+ char *f[5];
+ Disk *disk;
+
+ disk = edit->disk;
+ edit->nctlpart = 0;
+ seek(disk->ctlfd, 0, 0);
+ if(readn(disk->ctlfd, buf, sizeof buf) <= 0) {
+ return;
+ }
+
+ nline = getfields(buf, line, nelem(line), 1, "\n");
+ for(i=0; i<nline; i++){
+ if(strncmp(line[i], "part ", 5) != 0)
+ continue;
+
+ nf = getfields(line[i], f, nelem(f), 1, " \t\r");
+ if(nf != 4 || strcmp(f[0], "part") != 0)
+ break;
+
+ a = strtoll(f[2], 0, 0);
+ b = strtoll(f[3], 0, 0);
+
+ 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(strcmp(f[1], disk->part) == 0)
+ continue;
+
+ if(edit->nctlpart >= nelem(edit->ctlpart)) {
+ fprint(2, "?too many partitions in ctl file\n");
+ exits("ctlpart");
+ }
+ edit->ctlpart[edit->nctlpart++] = ctlmkpart(f[1], a, b, 0);
+ }
+}
+
+static vlong
+ctlstart(Part *p)
+{
+ if(p->ctlstart)
+ return p->ctlstart;
+ return p->start;
+}
+
+static vlong
+ctlend(Part *p)
+{
+ if(p->ctlend)
+ return p->ctlend;
+ return p->end;
+}
+
+static int
+areequiv(Part *p, Part *q)
+{
+ if(p->ctlname[0]=='\0' || q->ctlname[0]=='\0')
+ return 0;
+
+ return strcmp(p->ctlname, q->ctlname) == 0
+ && ctlstart(p) == ctlstart(q) && ctlend(p) == ctlend(q);
+}
+
+static void
+unchange(Edit *edit, Part *p)
+{
+ int i;
+ Part *q;
+
+ for(i=0; i<edit->nctlpart; i++) {
+ q = edit->ctlpart[i];
+ if(p->start <= q->start && q->end <= p->end) {
+ q->changed = 0;
+ }
+ }
+assert(p->changed == 0);
+}
+
+int
+ctldiff(Edit *edit, int ctlfd)
+{
+ int i, j, waserr;
+ Part *p;
+ vlong offset;
+
+ rdctlpart(edit);
+
+ /* everything is bogus until we prove otherwise */
+ for(i=0; i<edit->nctlpart; i++)
+ edit->ctlpart[i]->changed = 1;
+
+ /*
+ * partitions with same info have not changed,
+ * and neither have partitions inside them.
+ */
+ for(i=0; i<edit->nctlpart; i++)
+ for(j=0; j<edit->npart; 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<edit->nctlpart; i++) {
+ p = edit->ctlpart[i];
+ if(p->changed)
+ if(fprint(ctlfd, "delpart %s\n", p->ctlname)<0) {
+ fprint(2, "delpart failed: %s: %r\n", p->ctlname);
+ waserr = -1;
+ }
+ }
+
+ /*
+ * add all the partitions from the real list;
+ * this is okay since adding a parition with
+ * information identical to what is there is a no-op.
+ */
+ offset = edit->disk->offset;
+ for(i=0; i<edit->npart; i++) {
+ p = edit->part[i];
+ if(p->ctlname[0]) {
+ if(fprint(ctlfd, "part %s %lld %lld\n", p->ctlname, offset+ctlstart(p), offset+ctlend(p)) < 0) {
+ fprint(2, "adding part failed: %s: %r\n", p->ctlname);
+ waserr = -1;
+ }
+ }
+ }
+ return waserr;
+}
+
+void*
+emalloc(ulong sz)
+{
+ void *v;
+
+ v = malloc(sz);
+ if(v == nil)
+ sysfatal("malloc %lud fails", sz);
+ memset(v, 0, sz);
+ return v;
+}
+
+char*
+estrdup(char *s)
+{
+ s = strdup(s);
+ if(s == nil)
+ sysfatal("strdup (%.10s) fails", s);
+ return s;
+}
+
diff --git a/sys/src/cmd/disk/prep/edit.h b/sys/src/cmd/disk/prep/edit.h
new file mode 100755
index 000000000..8751adc4f
--- /dev/null
+++ b/sys/src/cmd/disk/prep/edit.h
@@ -0,0 +1,54 @@
+typedef struct Part Part;
+struct Part {
+ char *name;
+ char *ctlname;
+ vlong start;
+ vlong end;
+ vlong ctlstart;
+ vlong ctlend;
+ int changed;
+};
+
+enum {
+ Maxpart = 32
+};
+
+typedef struct Edit Edit;
+struct Edit {
+ Disk *disk;
+
+ Part *ctlpart[Maxpart];
+ int nctlpart;
+
+ Part *part[Maxpart];
+ int npart;
+
+ char *(*add)(Edit*, char*, vlong, vlong);
+ char *(*del)(Edit*, Part*);
+ char *(*ext)(Edit*, int, char**);
+ char *(*help)(Edit*);
+ char *(*okname)(Edit*, char*);
+ void (*sum)(Edit*, Part*, vlong, vlong);
+ char *(*write)(Edit*);
+ void (*printctl)(Edit*, int);
+
+ char *unit;
+ void *aux;
+ vlong dot;
+ vlong end;
+
+ /* do not use fields below this line */
+ int changed;
+ int warned;
+ int lastcmd;
+};
+
+char *getline(Edit*);
+void runcmd(Edit*, char*);
+Part *findpart(Edit*, char*);
+char *addpart(Edit*, Part*);
+char *delpart(Edit*, Part*);
+char *parseexpr(char *s, vlong xdot, vlong xdollar, vlong xsize, vlong *result);
+int ctldiff(Edit *edit, int ctlfd);
+void *emalloc(ulong);
+char *estrdup(char*);
diff --git a/sys/src/cmd/disk/prep/fdisk.c b/sys/src/cmd/disk/prep/fdisk.c
new file mode 100755
index 000000000..6903d5adc
--- /dev/null
+++ b/sys/src/cmd/disk/prep/fdisk.c
@@ -0,0 +1,1118 @@
+/*
+ * fdisk - edit dos disk partition table
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <disk.h>
+#include "edit.h"
+
+typedef struct Dospart Dospart;
+enum {
+ NTentry = 4,
+ Mpart = 64,
+};
+
+static void rdpart(Edit*, uvlong, uvlong);
+static void findmbr(Edit*);
+static void autopart(Edit*);
+static void wrpart(Edit*);
+static void blankpart(Edit*);
+static void cmdnamectl(Edit*);
+static void recover(Edit*);
+static int Dfmt(Fmt*);
+static int blank;
+static int dowrite;
+static int file;
+static int rdonly;
+static int doauto;
+static vlong mbroffset;
+static int printflag;
+static int printchs;
+static int sec2cyl;
+static int written;
+
+static void cmdsum(Edit*, Part*, vlong, vlong);
+static char *cmdadd(Edit*, char*, vlong, vlong);
+static char *cmddel(Edit*, Part*);
+static char *cmdext(Edit*, int, char**);
+static char *cmdhelp(Edit*);
+static char *cmdokname(Edit*, char*);
+static char *cmdwrite(Edit*);
+static void cmdprintctl(Edit*, int);
+
+#pragma varargck type "D" uchar*
+
+Edit edit = {
+ .add= cmdadd,
+ .del= cmddel,
+ .ext= cmdext,
+ .help= cmdhelp,
+ .okname= cmdokname,
+ .sum= cmdsum,
+ .write= cmdwrite,
+ .printctl= cmdprintctl,
+
+ .unit= "cylinder",
+};
+
+/*
+ * Catch the obvious error routines to fix up the disk.
+ */
+void
+sysfatal(char *fmt, ...)
+{
+ char buf[1024];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ if(argv0)
+ fprint(2, "%s: %s\n", argv0, buf);
+ else
+ fprint(2, "%s\n", buf);
+
+ if(written)
+ recover(&edit);
+
+ exits(buf);
+}
+
+void
+abort(void)
+{
+ fprint(2, "abort\n");
+ recover(&edit);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: disk/fdisk [-abfprvw] [-s sectorsize] /dev/sdC0/data\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ vlong secsize;
+
+ secsize = 0;
+ ARGBEGIN{
+ case 'a':
+ doauto++;
+ break;
+ case 'b':
+ blank++;
+ break;
+ case 'f':
+ file++;
+ break;
+ case 'p':
+ printflag++;
+ break;
+ case 'r':
+ rdonly++;
+ break;
+ case 's':
+ secsize = atoi(ARGF());
+ break;
+ case 'v':
+ printchs++;
+ break;
+ case 'w':
+ dowrite++;
+ break;
+ }ARGEND;
+
+ fmtinstall('D', Dfmt);
+
+ if(argc != 1)
+ usage();
+
+ edit.disk = opendisk(argv[0], rdonly, file);
+ if(edit.disk == nil) {
+ fprint(2, "cannot open disk: %r\n");
+ exits("opendisk");
+ }
+
+ if(secsize != 0) {
+ edit.disk->secsize = secsize;
+ edit.disk->secs = edit.disk->size / secsize;
+ }
+
+ sec2cyl = edit.disk->h * edit.disk->s;
+ edit.end = edit.disk->secs / sec2cyl;
+
+ findmbr(&edit);
+
+ if(blank)
+ blankpart(&edit);
+ else
+ rdpart(&edit, 0, 0);
+
+ if(doauto)
+ autopart(&edit);
+
+ if(dowrite)
+ runcmd(&edit, "w");
+
+ if(printflag)
+ runcmd(&edit, "P");
+
+ if(dowrite || printflag)
+ exits(0);
+
+ fprint(2, "cylinder = %lld bytes\n", sec2cyl*edit.disk->secsize);
+ runcmd(&edit, "p");
+ for(;;) {
+ fprint(2, ">>> ");
+ runcmd(&edit, getline(&edit));
+ }
+}
+
+typedef struct Tentry Tentry;
+typedef struct Table Table;
+typedef struct Type Type;
+typedef struct Tab Tab;
+typedef struct Recover Recover;
+
+struct Tentry {
+ uchar active; /* active flag */
+ uchar starth; /* starting head */
+ uchar starts; /* starting sector */
+ uchar startc; /* starting cylinder */
+ uchar type; /* partition type */
+ uchar endh; /* ending head */
+ uchar ends; /* ending sector */
+ uchar endc; /* ending cylinder */
+ uchar xlba[4]; /* starting LBA from beginning of disc or ext. partition */
+ uchar xsize[4]; /* size in sectors */
+};
+
+enum {
+ Active = 0x80, /* partition is active */
+ Primary = 0x01, /* internal flag */
+
+ TypeBB = 0xFF,
+
+ TypeEMPTY = 0x00,
+ TypeFAT12 = 0x01,
+ TypeXENIX = 0x02, /* root */
+ TypeXENIXUSR = 0x03, /* usr */
+ TypeFAT16 = 0x04,
+ TypeEXTENDED = 0x05,
+ TypeFATHUGE = 0x06,
+ TypeHPFS = 0x07,
+ TypeAIXBOOT = 0x08,
+ TypeAIXDATA = 0x09,
+ TypeOS2BOOT = 0x0A, /* OS/2 Boot Manager */
+ TypeFAT32 = 0x0B, /* FAT 32 */
+ TypeFAT32LBA = 0x0C, /* FAT 32 needing LBA support */
+ TypeFAT16X = 0x0E, /* FAT 16 needing LBA support */
+ TypeEXTHUGE = 0x0F, /* FAT 32 extended partition */
+ TypeUNFORMATTED = 0x16, /* unformatted primary partition (OS/2 FDISK)? */
+ TypeHPFS2 = 0x17,
+ TypeIBMRecovery = 0x1C, /* really hidden fat */
+ TypeCPM0 = 0x52,
+ TypeDMDDO = 0x54, /* Disk Manager Dynamic Disk Overlay */
+ TypeGB = 0x56, /* ???? */
+ TypeSPEEDSTOR = 0x61,
+ TypeSYSV386 = 0x63, /* also HURD? */
+ TypeNETWARE = 0x64,
+ TypePCIX = 0x75,
+ TypeMINIX13 = 0x80, /* Minix v1.3 and below */
+ TypeMINIX = 0x81, /* Minix v1.5+ */
+ TypeLINUXSWAP = 0x82,
+ TypeLINUX = 0x83,
+ TypeLINUXEXT = 0x85,
+ TypeLINUXLVM = 0x8E, /* logical volume manager */
+ TypeAMOEBA = 0x93,
+ TypeAMOEBABB = 0x94,
+ TypeBSD386 = 0xA5,
+ TypeNETBSD = 0XA9,
+ TypeBSDI = 0xB7,
+ TypeBSDISWAP = 0xB8,
+ TypeOTHER = 0xDA,
+ TypeCPM = 0xDB,
+ TypeDellRecovery= 0xDE,
+ TypeSPEEDSTOR12 = 0xE1,
+ TypeSPEEDSTOR16 = 0xE4,
+ TypeLANSTEP = 0xFE,
+
+ Type9 = 0x39,
+
+ Toffset = 446, /* offset of partition table in sector */
+ Magic0 = 0x55,
+ Magic1 = 0xAA,
+};
+
+struct Table {
+ Tentry entry[NTentry];
+ uchar magic[2];
+};
+
+struct Type {
+ char *desc;
+ char *name;
+};
+
+struct Dospart {
+ Part;
+ Tentry;
+
+ u32int lba;
+ u32int size;
+ int primary;
+};
+
+struct Recover {
+ Table table;
+ ulong lba;
+};
+
+static Type types[256] = {
+ [TypeEMPTY] { "EMPTY", "" },
+ [TypeFAT12] { "FAT12", "dos" },
+ [TypeFAT16] { "FAT16", "dos" },
+ [TypeFAT32] { "FAT32", "dos" },
+ [TypeFAT32LBA] { "FAT32LBA", "dos" },
+ [TypeFAT16X] { "FAT16X", "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", "" },
+ [TypeLINUXLVM] { "LINUXLVM", "linuxlvm" },
+ [TypeAMOEBA] { "AMOEBA", "amoeba" },
+ [TypeAMOEBABB] { "AMOEBABB", "amoebaboot" },
+ [TypeBSD386] { "BSD386", "bsd386" },
+ [TypeNETBSD] { "NETBSD", "netbsd" },
+ [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" },
+};
+
+static Dospart part[Mpart];
+static int npart;
+
+static char*
+typestr0(int type)
+{
+ static char buf[100];
+
+ sprint(buf, "type %d", type);
+ if(type < 0 || type >= 256)
+ return buf;
+ if(types[type].desc == nil)
+ return buf;
+ return types[type].desc;
+}
+
+static u32int
+getle32(void* v)
+{
+ uchar *p;
+
+ p = v;
+ return (p[3]<<24)|(p[2]<<16)|(p[1]<<8)|p[0];
+}
+
+static void
+putle32(void* v, u32int i)
+{
+ uchar *p;
+
+ p = v;
+ p[0] = i;
+ p[1] = i>>8;
+ p[2] = i>>16;
+ p[3] = i>>24;
+}
+
+static void
+diskread(Disk *disk, void *data, int ndata, u32int sec, u32int off)
+{
+ if(seek(disk->fd, (vlong)sec*disk->secsize+off, 0) != (vlong)sec*disk->secsize+off)
+ sysfatal("diskread seek %lud.%lud: %r", (ulong)sec, (ulong)off);
+ if(readn(disk->fd, data, ndata) != ndata)
+ sysfatal("diskread %lud at %lud.%lud: %r", (ulong)ndata, (ulong)sec, (ulong)off);
+}
+
+static int
+diskwrite(Disk *disk, void *data, int ndata, u32int sec, u32int off)
+{
+ written = 1;
+ if(seek(disk->wfd, (vlong)sec*disk->secsize+off, 0) != (vlong)sec*disk->secsize+off)
+ goto Error;
+ if(write(disk->wfd, data, ndata) != ndata)
+ goto Error;
+ return 0;
+
+Error:
+ fprint(2, "write %d bytes at %lud.%lud failed: %r\n", ndata, (ulong)sec, (ulong)off);
+ return -1;
+}
+
+static Dospart*
+mkpart(char *name, int primary, vlong lba, vlong size, Tentry *t)
+{
+ static int n;
+ Dospart *p;
+
+ p = emalloc(sizeof(*p));
+ if(name)
+ p->name = estrdup(name);
+ else{
+ p->name = emalloc(20);
+ sprint(p->name, "%c%d", primary ? 'p' : 's', ++n);
+ }
+
+ if(t)
+ p->Tentry = *t;
+ else
+ memset(&p->Tentry, 0, sizeof(Tentry));
+
+ p->changed = 0;
+ p->start = lba/sec2cyl;
+ p->end = (lba+size)/sec2cyl;
+ p->ctlstart = lba;
+ p->ctlend = lba+size;
+ p->lba = lba;
+ if (p->lba != lba)
+ fprint(2, "%s: start of partition (%lld) won't fit in MBR table\n", argv0, lba);
+ p->size = size;
+ if (p->size != size)
+ fprint(2, "%s: size of partition (%lld) won't fit in MBR table\n", argv0, size);
+ p->primary = primary;
+ return p;
+}
+
+/*
+ * Recovery takes care of remembering what the various tables
+ * looked like when we started, attempting to restore them when
+ * we are finished.
+ */
+static Recover *rtab;
+static int nrtab;
+
+static void
+addrecover(Table t, ulong lba)
+{
+ if((nrtab%8) == 0) {
+ rtab = realloc(rtab, (nrtab+8)*sizeof(rtab[0]));
+ if(rtab == nil)
+ sysfatal("out of memory");
+ }
+ rtab[nrtab] = (Recover){t, lba};
+ nrtab++;
+}
+
+static void
+recover(Edit *edit)
+{
+ int err, i, ctlfd;
+ vlong offset;
+
+ err = 0;
+ for(i=0; i<nrtab; i++)
+ if(diskwrite(edit->disk, &rtab[i].table, sizeof(Table), rtab[i].lba, Toffset) < 0)
+ err = 1;
+ if(err) {
+ fprint(2, "warning: some writes failed during restoration of old partition tables\n");
+ exits("inconsistent");
+ } else {
+ fprint(2, "restored old partition tables\n");
+ }
+
+ ctlfd = edit->disk->ctlfd;
+ offset = edit->disk->offset;
+ if(ctlfd >= 0){
+ for(i=0; i<edit->npart; i++)
+ if(edit->part[i]->ctlname && fprint(ctlfd, "delpart %s", edit->part[i]->ctlname)<0)
+ fprint(2, "delpart failed: %s: %r", edit->part[i]->ctlname);
+ for(i=0; i<edit->nctlpart; i++)
+ if(edit->part[i]->name && fprint(ctlfd, "delpart %s", edit->ctlpart[i]->name)<0)
+ fprint(2, "delpart failed: %s: %r", edit->ctlpart[i]->name);
+ for(i=0; i<edit->nctlpart; i++){
+ if(fprint(ctlfd, "part %s %lld %lld", edit->ctlpart[i]->name,
+ edit->ctlpart[i]->start+offset, edit->ctlpart[i]->end+offset) < 0){
+ fprint(2, "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.
+ */
+static void
+rdpart(Edit *edit, uvlong lba, uvlong xbase)
+{
+ char *err;
+ Table table;
+ Tentry *tp, *ep;
+ Dospart *p;
+
+ if(xbase == 0)
+ xbase = lba;
+
+ diskread(edit->disk, &table, sizeof table, mbroffset+lba, Toffset);
+ addrecover(table, mbroffset+lba);
+
+ if(table.magic[0] != Magic0 || table.magic[1] != Magic1) {
+ assert(lba != 0);
+ return;
+ }
+
+ for(tp=table.entry, ep=tp+NTentry; tp<ep && npart < Mpart; tp++) {
+ switch(tp->type) {
+ case TypeEMPTY:
+ break;
+ case TypeEXTENDED:
+ case TypeEXTHUGE:
+ case TypeLINUXEXT:
+ rdpart(edit, xbase+getle32(tp->xlba), xbase);
+ break;
+ default:
+ p = mkpart(nil, lba==0, lba+getle32(tp->xlba), getle32(tp->xsize), tp);
+ if(err = addpart(edit, p))
+ fprint(2, "adding partition: %s\n", err);
+ break;
+ }
+ }
+}
+
+static void
+blankpart(Edit *edit)
+{
+ edit->changed = 1;
+}
+
+static void
+findmbr(Edit *edit)
+{
+ Table table;
+ Tentry *tp;
+
+ diskread(edit->disk, &table, sizeof(Table), 0, Toffset);
+ if(table.magic[0] != Magic0 || table.magic[1] != Magic1)
+ sysfatal("did not find master boot record");
+
+ for(tp = table.entry; tp < &table.entry[NTentry]; tp++)
+ if(tp->type == TypeDMDDO)
+ mbroffset = edit->disk->s;
+}
+
+static int
+haveroom(Edit *edit, int primary, vlong start)
+{
+ int i, lastsec, n;
+ Dospart *p, *q;
+ ulong pend, qstart;
+
+ 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<edit->npart; i++) {
+ p = (Dospart*)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<edit->npart; i++){
+ p = (Dospart*)edit->part[i];
+ if(p->primary)
+ n++;
+ pend = p->end;
+ if(i+1<edit->npart){
+ q = (Dospart*)edit->part[i+1];
+ qstart = q->start;
+ }else{
+ qstart = edit->end;
+ q = nil;
+ }
+ if(start < pend || start >= qstart)
+ continue;
+ /* we go between these two */
+ if(p->primary==0 || (q && q->primary==0))
+ return 1;
+ }
+ /* not next to a secondary, need a new primary */
+ return n<4;
+}
+
+static void
+autopart(Edit *edit)
+{
+ char *err;
+ int active, i;
+ vlong bigstart, bigsize, start;
+ Dospart *p;
+
+ for(i=0; i<edit->npart; i++)
+ if(((Dospart*)edit->part[i])->type == Type9)
+ return;
+
+ /* look for the biggest gap in which we can put a primary partition */
+ start = 0;
+ bigsize = 0;
+ SET(bigstart);
+ for(i=0; i<edit->npart; i++) {
+ p = (Dospart*)edit->part[i];
+ if(p->start > start && p->start - start > bigsize && haveroom(edit, 1, start)) {
+ bigsize = p->start - start;
+ bigstart = start;
+ }
+ start = p->end;
+ }
+
+ if(edit->end - start > bigsize && haveroom(edit, 1, start)) {
+ bigsize = edit->end - start;
+ bigstart = start;
+ }
+ if(bigsize < 1) {
+ fprint(2, "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<edit->npart; i++)
+ if(((Dospart*)edit->part[i])->primary && (((Dospart*)edit->part[i])->active & Active))
+ active = 0;
+
+ /* add new plan 9 partition */
+ bigsize *= sec2cyl;
+ bigstart *= sec2cyl;
+ if(bigstart == 0) {
+ bigstart += edit->disk->s;
+ bigsize -= edit->disk->s;
+ }
+ p = mkpart(nil, 1, bigstart, bigsize, nil);
+ p->active = active;
+ p->changed = 1;
+ p->type = Type9;
+ edit->changed = 1;
+ if(err = addpart(edit, p)) {
+ fprint(2, "error adding plan9 partition: %s\n", err);
+ return;
+ }
+}
+
+typedef struct Name Name;
+struct Name {
+ char *name;
+ Name *link;
+};
+Name *namelist;
+static void
+plan9print(Dospart *part, int fd)
+{
+ int i, ok;
+ char *name, *vname;
+ Name *n;
+ vlong start, end;
+ char *sep;
+
+ vname = types[part->type].name;
+ if(vname==nil || strcmp(vname, "")==0) {
+ part->ctlname = "";
+ return;
+ }
+
+ start = mbroffset+part->lba;
+ end = start+part->size;
+
+ /* avoid names like plan90 */
+ i = strlen(vname) - 1;
+ if(vname[i] >= '0' && vname[i] <= '9')
+ sep = ".";
+ else
+ sep = "";
+
+ i = 0;
+ name = emalloc(strlen(vname)+10);
+
+ sprint(name, "%s", vname);
+ do {
+ ok = 1;
+ for(n=namelist; n; n=n->link) {
+ if(strcmp(name, n->name) == 0) {
+ i++;
+ sprint(name, "%s%s%d", vname, sep, i);
+ ok = 0;
+ }
+ }
+ } while(ok == 0);
+
+ n = emalloc(sizeof(*n));
+ n->name = name;
+ n->link = namelist;
+ namelist = n;
+ part->ctlname = name;
+
+ if(fd >= 0)
+ print("part %s %lld %lld\n", name, start, end);
+}
+
+static void
+freenamelist(void)
+{
+ Name *n, *next;
+
+ for(n=namelist; n; n=next) {
+ next = n->link;
+ free(n);
+ }
+ namelist = nil;
+}
+
+static void
+cmdprintctl(Edit *edit, int ctlfd)
+{
+ int i;
+
+ freenamelist();
+ for(i=0; i<edit->npart; i++)
+ plan9print((Dospart*)edit->part[i], -1);
+ ctldiff(edit, ctlfd);
+}
+
+static char*
+cmdokname(Edit*, char *name)
+{
+ char *q;
+
+ if(name[0] != 'p' && name[0] != 's')
+ return "name must be pN or sN";
+
+ strtol(name+1, &q, 10);
+ if(*q != '\0')
+ return "name must be pN or sN";
+
+ return nil;
+}
+
+#define TB (1024LL*GB)
+#define GB (1024*1024*1024)
+#define MB (1024*1024)
+#define KB (1024)
+
+static void
+cmdsum(Edit *edit, Part *vp, vlong a, vlong b)
+{
+ char *name, *ty;
+ char buf[3];
+ char *suf;
+ Dospart *p;
+ vlong sz, div;
+
+ p = (Dospart*)vp;
+
+ buf[0] = p && p->changed ? '\'' : ' ';
+ buf[1] = p && (p->active & Active) ? '*' : ' ';
+ buf[2] = '\0';
+
+ name = p ? p->name : "empty";
+ ty = p ? typestr0(p->type) : "";
+
+ sz = (b-a)*edit->disk->secsize*sec2cyl;
+ if(sz >= 1*TB){
+ suf = "TB";
+ div = TB;
+ }else if(sz >= 1*GB){
+ suf = "GB";
+ div = GB;
+ }else if(sz >= 1*MB){
+ suf = "MB";
+ div = MB;
+ }else if(sz >= 1*KB){
+ suf = "KB";
+ div = KB;
+ }else{
+ suf = "B ";
+ div = 1;
+ }
+
+ if(div == 1)
+ print("%s %-12s %*lld %-*lld (%lld cylinders, %lld %s) %s\n", buf, name,
+ edit->disk->width, a, edit->disk->width, b, b-a, sz, suf, ty);
+ else
+ print("%s %-12s %*lld %-*lld (%lld cylinders, %lld.%.2d %s) %s\n", buf, name,
+ edit->disk->width, a, edit->disk->width, b, b-a,
+ sz/div, (int)(((sz%div)*100)/div), suf, ty);
+}
+
+static char*
+cmdadd(Edit *edit, char *name, vlong start, vlong end)
+{
+ Dospart *p;
+
+ if(!haveroom(edit, name[0]=='p', start))
+ return "no room for partition";
+ start *= sec2cyl;
+ end *= sec2cyl;
+ if(start == 0 || name[0] != 'p')
+ start += edit->disk->s;
+ p = mkpart(name, name[0]=='p', start, end-start, nil);
+ p->changed = 1;
+ p->type = Type9;
+ return addpart(edit, p);
+}
+
+static char*
+cmddel(Edit *edit, Part *p)
+{
+ return delpart(edit, p);
+}
+
+static char*
+cmdwrite(Edit *edit)
+{
+ wrpart(edit);
+ return nil;
+}
+
+static char *help =
+ "A name - set partition active\n"
+ "P - 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";
+
+static char*
+cmdhelp(Edit*)
+{
+ print("%s\n", help);
+ return nil;
+}
+
+static char*
+cmdactive(Edit *edit, int nf, char **f)
+{
+ int i;
+ Dospart *p, *ip;
+
+ if(nf != 2)
+ return "args";
+
+ if(f[1][0] != 'p')
+ return "cannot set secondary partition active";
+
+ if((p = (Dospart*)findpart(edit, f[1])) == nil)
+ return "unknown partition";
+
+ for(i=0; i<edit->npart; i++) {
+ ip = (Dospart*)edit->part[i];
+ if(ip->active & Active) {
+ ip->active &= ~Active;
+ ip->changed = 1;
+ edit->changed = 1;
+ }
+ }
+
+ if((p->active & Active) == 0) {
+ p->active |= Active;
+ p->changed = 1;
+ edit->changed = 1;
+ }
+
+ return nil;
+}
+
+static char*
+strupr(char *s)
+{
+ char *p;
+
+ for(p=s; *p; p++)
+ *p = toupper(*p);
+ return s;
+}
+
+static void
+dumplist(void)
+{
+ int i, n;
+
+ n = 0;
+ for(i=0; i<256; i++) {
+ if(types[i].desc) {
+ print("%-16s", types[i].desc);
+ if(n++%4 == 3)
+ print("\n");
+ }
+ }
+ if(n%4)
+ print("\n");
+}
+
+static char*
+cmdtype(Edit *edit, int nf, char **f)
+{
+ char *q;
+ Dospart *p;
+ int i;
+
+ if(nf < 2)
+ return "args";
+
+ if((p = (Dospart*)findpart(edit, f[1])) == nil)
+ return "unknown partition";
+
+ if(nf == 2) {
+ for(;;) {
+ fprint(2, "new partition type [? for list]: ");
+ q = getline(edit);
+ if(q[0] == '?')
+ dumplist();
+ else
+ break;
+ }
+ } else
+ q = f[2];
+
+ strupr(q);
+ for(i=0; i<256; i++)
+ if(types[i].desc && strcmp(types[i].desc, q) == 0)
+ break;
+ if(i < 256 && p->type != i) {
+ p->type = i;
+ p->changed = 1;
+ edit->changed = 1;
+ }
+ return nil;
+}
+
+static char*
+cmdext(Edit *edit, int nf, char **f)
+{
+ switch(f[0][0]) {
+ case 'A':
+ return cmdactive(edit, nf, f);
+ case 't':
+ return cmdtype(edit, nf, f);
+ case 'R':
+ recover(edit);
+ return nil;
+ default:
+ return "unknown command";
+ }
+}
+
+static int
+Dfmt(Fmt *f)
+{
+ char buf[60];
+ uchar *p;
+ int c, h, s;
+
+ p = va_arg(f->args, uchar*);
+ h = p[0];
+ c = p[2];
+ c |= (p[1]&0xC0)<<2;
+ s = (p[1] & 0x3F);
+
+ sprint(buf, "%d/%d/%d", c, h, s);
+ return fmtstrcpy(f, buf);
+}
+
+static void
+writechs(Disk *disk, uchar *p, vlong lba)
+{
+ int c, h, s;
+
+ s = lba % disk->s;
+ h = (lba / disk->s) % disk->h;
+ c = lba / (disk->s * disk->h);
+
+ if(c >= 1024) {
+ c = 1023;
+ h = disk->h - 1;
+ s = disk->s - 1;
+ }
+
+ p[0] = h;
+ p[1] = ((s+1) & 0x3F) | ((c>>2) & 0xC0);
+ p[2] = c;
+}
+
+static void
+wrtentry(Disk *disk, Tentry *tp, int type, u32int xbase, u32int lba, u32int end)
+{
+ tp->type = type;
+ writechs(disk, &tp->starth, lba);
+ writechs(disk, &tp->endh, end-1);
+ putle32(tp->xlba, lba-xbase);
+ putle32(tp->xsize, end-lba);
+}
+
+static int
+wrextend(Edit *edit, int i, vlong xbase, vlong startlba, vlong *endlba)
+{
+ int ni;
+ Table table;
+ Tentry *tp, *ep;
+ Dospart *p;
+ Disk *disk;
+
+ if(i == edit->npart){
+ *endlba = edit->disk->secs;
+ Finish:
+ if(startlba < *endlba){
+ disk = edit->disk;
+ diskread(disk, &table, sizeof table, mbroffset+startlba, Toffset);
+ tp = table.entry;
+ ep = tp+NTentry;
+ for(; tp<ep; tp++)
+ memset(tp, 0, sizeof *tp);
+ table.magic[0] = Magic0;
+ table.magic[1] = Magic1;
+
+ if(diskwrite(edit->disk, &table, sizeof table, mbroffset+startlba, Toffset) < 0)
+ recover(edit);
+ }
+ return i;
+ }
+
+ p = (Dospart*)edit->part[i];
+ if(p->primary){
+ *endlba = (vlong)p->start*sec2cyl;
+ goto Finish;
+ }
+
+ disk = edit->disk;
+ diskread(disk, &table, sizeof table, mbroffset+startlba, Toffset);
+ tp = table.entry;
+ ep = tp+NTentry;
+
+ ni = wrextend(edit, i+1, xbase, p->end*sec2cyl, endlba);
+
+ *tp = p->Tentry;
+ wrtentry(disk, tp, p->type, startlba, startlba+disk->s, p->end*sec2cyl);
+ tp++;
+
+ if(p->end*sec2cyl != *endlba){
+ memset(tp, 0, sizeof *tp);
+ wrtentry(disk, tp, TypeEXTENDED, xbase, p->end*sec2cyl, *endlba);
+ tp++;
+ }
+
+ for(; tp<ep; tp++)
+ memset(tp, 0, sizeof *tp);
+
+ table.magic[0] = Magic0;
+ table.magic[1] = Magic1;
+
+ if(diskwrite(edit->disk, &table, sizeof table, mbroffset+startlba, Toffset) < 0)
+ recover(edit);
+ return ni;
+}
+
+static void
+wrpart(Edit *edit)
+{
+ int i, ni, t;
+ Table table;
+ Tentry *tp, *ep;
+ Disk *disk;
+ vlong s, endlba;
+ Dospart *p;
+
+ disk = edit->disk;
+
+ diskread(disk, &table, sizeof table, mbroffset, Toffset);
+
+ tp = table.entry;
+ ep = tp+NTentry;
+ for(i=0; i<edit->npart && tp<ep; ) {
+ p = (Dospart*)edit->part[i];
+ if(p->start == 0)
+ s = disk->s;
+ else
+ s = p->start*sec2cyl;
+ if(p->primary) {
+ *tp = p->Tentry;
+ wrtentry(disk, tp, p->type, 0, s, p->end*sec2cyl);
+ tp++;
+ i++;
+ } else {
+ ni = wrextend(edit, i, p->start*sec2cyl, p->start*sec2cyl, &endlba);
+ memset(tp, 0, sizeof *tp);
+ if(endlba >= 1024*sec2cyl)
+ t = TypeEXTHUGE;
+ else
+ t = TypeEXTENDED;
+ wrtentry(disk, tp, t, 0, s, endlba);
+ tp++;
+ i = ni;
+ }
+ }
+ for(; tp<ep; tp++)
+ memset(tp, 0, sizeof(*tp));
+
+ if(i != edit->npart)
+ sysfatal("cannot happen #1");
+
+ if(diskwrite(disk, &table, sizeof table, mbroffset, Toffset) < 0)
+ recover(edit);
+
+ /* bring parts up to date */
+ freenamelist();
+ for(i=0; i<edit->npart; i++)
+ plan9print((Dospart*)edit->part[i], -1);
+
+ if(ctldiff(edit, disk->ctlfd) < 0)
+ fprint(2, "?warning: partitions could not be updated in devsd\n");
+}
diff --git a/sys/src/cmd/disk/prep/mkfile b/sys/src/cmd/disk/prep/mkfile
new file mode 100755
index 000000000..5557c9b59
--- /dev/null
+++ b/sys/src/cmd/disk/prep/mkfile
@@ -0,0 +1,22 @@
+</$objtype/mkfile
+
+TARG=fdisk prep
+
+YFILES=calc.y
+HFILES=edit.h
+OFILES=\
+ edit.$O\
+ y.tab.$O\
+
+BIN=/$objtype/bin/disk
+
+UPDATE=\
+ mkfile\
+ /sys/man/8/prep\
+ edit.c\
+ ${TARG:%=%.c}\
+ $HFILES\
+ $YFILES\
+ ${TARG:%=/386/bin/disk/%}\
+
+</sys/src/cmd/mkmany
diff --git a/sys/src/cmd/disk/prep/prep.c b/sys/src/cmd/disk/prep/prep.c
new file mode 100755
index 000000000..b7848188d
--- /dev/null
+++ b/sys/src/cmd/disk/prep/prep.c
@@ -0,0 +1,523 @@
+/*
+ * prep - prepare plan9 disk partition
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <disk.h>
+#include "edit.h"
+
+enum {
+ Maxpath = 128,
+};
+
+static int blank;
+static int file;
+static int doautox;
+static int printflag;
+static Part **opart;
+static int nopart;
+static char *osecbuf;
+static char *secbuf;
+static int rdonly;
+static int dowrite;
+static int docache;
+static int donvram;
+
+static void autoxpart(Edit*);
+static Part *mkpart(char*, vlong, vlong, int);
+static void rdpart(Edit*);
+static void wrpart(Edit*);
+static void checkfat(Disk*);
+
+static void cmdsum(Edit*, Part*, vlong, vlong);
+static char *cmdadd(Edit*, char*, vlong, vlong);
+static char *cmddel(Edit*, Part*);
+static char *cmdokname(Edit*, char*);
+static char *cmdwrite(Edit*);
+static char *cmdctlprint(Edit*, int, char**);
+
+Edit edit = {
+ .add= cmdadd,
+ .del= cmddel,
+ .okname=cmdokname,
+ .sum= cmdsum,
+ .write= cmdwrite,
+
+ .unit= "sector",
+};
+
+typedef struct Auto Auto;
+struct Auto
+{
+ char *name;
+ uvlong min;
+ uvlong max;
+ uint weight;
+ uchar alloc;
+ uvlong size;
+};
+
+#define TB (1024LL*GB)
+#define GB (1024*1024*1024)
+#define MB (1024*1024)
+#define KB (1024)
+
+/*
+ * Order matters -- this is the layout order on disk.
+ */
+Auto autox[] =
+{
+ { "9fat", 10*MB, 100*MB, 10, },
+ { "nvram", 512, 512, 1, },
+ { "fscfg", 512, 512, 1, },
+ { "fs", 200*MB, 0, 10, },
+ { "fossil", 200*MB, 0, 4, },
+ { "arenas", 500*MB, 0, 20, },
+ { "isect", 25*MB, 0, 1, },
+ { "bloom", 4*MB, 512*MB, 1, },
+
+ { "other", 200*MB, 0, 4, },
+ { "swap", 100*MB, 512*MB, 1, },
+ { "cache", 50*MB, 1*GB, 2, },
+};
+
+void
+usage(void)
+{
+ fprint(2, "usage: disk/prep [-bcfprw] [-a partname]... [-s sectorsize] /dev/sdC0/plan9\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int i;
+ char *p;
+ Disk *disk;
+ vlong secsize;
+
+ secsize = 0;
+ ARGBEGIN{
+ case 'a':
+ p = EARGF(usage());
+ for(i=0; i<nelem(autox); i++){
+ if(strcmp(p, autox[i].name) == 0){
+ if(autox[i].alloc){
+ fprint(2, "you said -a %s more than once.\n", p);
+ usage();
+ }
+ autox[i].alloc = 1;
+ break;
+ }
+ }
+ if(i == nelem(autox)){
+ fprint(2, "don't know how to create automatic partition %s\n", p);
+ usage();
+ }
+ doautox = 1;
+ break;
+ case 'b':
+ blank++;
+ break;
+ case 'c':
+ docache++;
+ break;
+ case 'f':
+ file++;
+ break;
+ case 'n':
+ donvram++;
+ break;
+ case 'p':
+ printflag++;
+ rdonly++;
+ break;
+ case 'r':
+ rdonly++;
+ break;
+ case 's':
+ secsize = atoi(ARGF());
+ break;
+ case 'w':
+ dowrite++;
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc != 1)
+ usage();
+
+ disk = opendisk(argv[0], rdonly, file);
+ if(disk == nil)
+ sysfatal("cannot open disk: %r");
+
+ if(secsize != 0) {
+ disk->secsize = secsize;
+ disk->secs = disk->size / secsize;
+ }
+ edit.end = disk->secs;
+
+ checkfat(disk);
+
+ secbuf = emalloc(disk->secsize+1);
+ osecbuf = emalloc(disk->secsize+1);
+ edit.disk = disk;
+
+ if(blank == 0)
+ rdpart(&edit);
+
+ opart = emalloc(edit.npart*sizeof(opart[0]));
+
+ /* save old partition table */
+ for(i=0; i<edit.npart; i++)
+ opart[i] = edit.part[i];
+ nopart = edit.npart;
+
+ if(printflag) {
+ runcmd(&edit, "P");
+ exits(0);
+ }
+
+ if(doautox)
+ autoxpart(&edit);
+
+ if(dowrite) {
+ runcmd(&edit, "w");
+ exits(0);
+ }
+
+ runcmd(&edit, "p");
+ for(;;) {
+ fprint(2, ">>> ");
+ runcmd(&edit, getline(&edit));
+ }
+}
+
+static void
+cmdsum(Edit *edit, Part *p, vlong a, vlong b)
+{
+ vlong sz, div;
+ char *suf, *name;
+ char c;
+
+ c = p && p->changed ? '\'' : ' ';
+ name = p ? p->name : "empty";
+
+ sz = (b-a)*edit->disk->secsize;
+ if(sz >= 1*TB){
+ suf = "TB";
+ div = TB;
+ }else if(sz >= 1*GB){
+ suf = "GB";
+ div = GB;
+ }else if(sz >= 1*MB){
+ suf = "MB";
+ div = MB;
+ }else if(sz >= 1*KB){
+ suf = "KB";
+ div = KB;
+ }else{
+ if (sz < 0)
+ fprint(2, "%s: negative size!\n", argv0);
+ suf = "B ";
+ div = 1;
+ }
+
+ if(div == 1)
+ print("%c %-12s %*lld %-*lld (%lld sectors, %lld %s)\n", c, name,
+ edit->disk->width, a, edit->disk->width, b, b-a, sz, suf);
+ else
+ print("%c %-12s %*lld %-*lld (%lld sectors, %lld.%.2d %s)\n", c, name,
+ edit->disk->width, a, edit->disk->width, b, b-a,
+ sz/div, (int)(((sz%div)*100)/div), suf);
+}
+
+static char*
+cmdadd(Edit *edit, char *name, vlong start, vlong end)
+{
+ if(start < 2 && strcmp(name, "9fat") != 0)
+ return "overlaps with the pbs and/or the partition table";
+
+ return addpart(edit, mkpart(name, start, end, 1));
+}
+
+static char*
+cmddel(Edit *edit, Part *p)
+{
+ return delpart(edit, p);
+}
+
+static char*
+cmdwrite(Edit *edit)
+{
+ wrpart(edit);
+ return nil;
+}
+
+static char isfrog[256]={
+ /*NUL*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ /*BKS*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ /*DLE*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ /*CAN*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ [' '] 1,
+ ['/'] 1,
+ [0x7f] 1,
+};
+
+static char*
+cmdokname(Edit*, char *elem)
+{
+ for(; *elem; elem++)
+ if(isfrog[*(uchar*)elem])
+ return "bad character in name";
+ return nil;
+}
+
+static Part*
+mkpart(char *name, vlong start, vlong end, int changed)
+{
+ Part *p;
+
+ p = emalloc(sizeof(*p));
+ p->name = estrdup(name);
+ p->ctlname = estrdup(name);
+ p->start = start;
+ p->end = end;
+ p->changed = changed;
+ return p;
+}
+
+/* plan9 partition is first sector of the disk */
+static void
+rdpart(Edit *edit)
+{
+ int i, nline, nf, waserr;
+ vlong a, b;
+ char *line[128];
+ char *f[5];
+ char *err;
+ Disk *disk;
+
+ disk = edit->disk;
+ seek(disk->fd, disk->secsize, 0);
+ if(readn(disk->fd, osecbuf, disk->secsize) != disk->secsize)
+ return;
+ osecbuf[disk->secsize] = '\0';
+ memmove(secbuf, osecbuf, disk->secsize+1);
+
+ if(strncmp(secbuf, "part", 4) != 0){
+ fprint(2, "no plan9 partition table found\n");
+ return;
+ }
+
+ waserr = 0;
+ nline = getfields(secbuf, line, nelem(line), 1, "\n");
+ for(i=0; i<nline; i++){
+ if(strncmp(line[i], "part", 4) != 0) {
+ Error:
+ if(waserr == 0)
+ fprint(2, "syntax error reading partition\n");
+ waserr = 1;
+ continue;
+ }
+
+ nf = getfields(line[i], f, nelem(f), 1, " \t\r");
+ if(nf != 4 || strcmp(f[0], "part") != 0)
+ goto Error;
+
+ a = strtoll(f[2], 0, 0);
+ b = strtoll(f[3], 0, 0);
+ if(a >= b)
+ goto Error;
+
+ if(err = addpart(edit, mkpart(f[1], a, b, 0))) {
+ fprint(2, "?%s: not continuing\n", err);
+ exits("partition");
+ }
+ }
+}
+
+static vlong
+min(vlong a, vlong b)
+{
+ if(a < b)
+ return a;
+ return b;
+}
+
+static void
+autoxpart(Edit *edit)
+{
+ int i, totw, futz;
+ vlong secs, secsize, s;
+ char *err;
+
+ if(edit->npart > 0) {
+ if(doautox)
+ fprint(2, "partitions already exist; not repartitioning\n");
+ return;
+ }
+
+ secs = edit->disk->secs;
+ secsize = edit->disk->secsize;
+ for(;;){
+ /* compute total weights */
+ totw = 0;
+ for(i=0; i<nelem(autox); i++){
+ if(autox[i].alloc==0 || autox[i].size)
+ continue;
+ totw += autox[i].weight;
+ }
+ if(totw == 0)
+ break;
+
+ if(secs <= 0){
+ fprint(2, "ran out of disk space during autoxpartition.\n");
+ return;
+ }
+
+ /* assign any minimums for small disks */
+ futz = 0;
+ for(i=0; i<nelem(autox); i++){
+ if(autox[i].alloc==0 || autox[i].size)
+ continue;
+ s = (secs*autox[i].weight)/totw;
+ if(s < autox[i].min/secsize){
+ autox[i].size = autox[i].min/secsize;
+ secs -= autox[i].size;
+ futz = 1;
+ break;
+ }
+ }
+ if(futz)
+ continue;
+
+ /* assign any maximums for big disks */
+ futz = 0;
+ for(i=0; i<nelem(autox); i++){
+ if(autox[i].alloc==0 || autox[i].size)
+ continue;
+ s = (secs*autox[i].weight)/totw;
+ if(autox[i].max && s > autox[i].max/secsize){
+ autox[i].size = autox[i].max/secsize;
+ secs -= autox[i].size;
+ futz = 1;
+ break;
+ }
+ }
+ if(futz)
+ continue;
+
+ /* finally, assign partition sizes according to weights */
+ for(i=0; i<nelem(autox); i++){
+ if(autox[i].alloc==0 || autox[i].size)
+ continue;
+ s = (secs*autox[i].weight)/totw;
+ autox[i].size = s;
+
+ /* use entire disk even in face of rounding errors */
+ secs -= autox[i].size;
+ totw -= autox[i].weight;
+ }
+ }
+
+ for(i=0; i<nelem(autox); i++)
+ if(autox[i].alloc)
+ print("%s %llud\n", autox[i].name, autox[i].size);
+
+ s = 0;
+ for(i=0; i<nelem(autox); i++){
+ if(autox[i].alloc == 0)
+ continue;
+ if(err = addpart(edit, mkpart(autox[i].name, s, s+autox[i].size, 1)))
+ fprint(2, "addpart %s: %s\n", autox[i].name, err);
+ s += autox[i].size;
+ }
+}
+
+static void
+restore(Edit *edit, int ctlfd)
+{
+ int i;
+ vlong offset;
+
+ offset = edit->disk->offset;
+ fprint(2, "attempting to restore partitions to previous state\n");
+ if(seek(edit->disk->wfd, edit->disk->secsize, 0) != 0){
+ fprint(2, "cannot restore: error seeking on disk\n");
+ exits("inconsistent");
+ }
+
+ if(write(edit->disk->wfd, osecbuf, edit->disk->secsize) != edit->disk->secsize){
+ fprint(2, "cannot restore: couldn't write old partition table to disk\n");
+ exits("inconsistent");
+ }
+
+ if(ctlfd >= 0){
+ for(i=0; i<edit->npart; i++)
+ fprint(ctlfd, "delpart %s", edit->part[i]->name);
+ for(i=0; i<nopart; i++){
+ if(fprint(ctlfd, "part %s %lld %lld", opart[i]->name, opart[i]->start+offset, opart[i]->end+offset) < 0){
+ fprint(2, "restored disk partition table but not kernel; reboot\n");
+ exits("inconsistent");
+ }
+ }
+ }
+ exits("restored");
+}
+
+static void
+wrpart(Edit *edit)
+{
+ int i, n;
+ Disk *disk;
+
+ disk = edit->disk;
+
+ memset(secbuf, 0, disk->secsize);
+ n = 0;
+ for(i=0; i<edit->npart; i++)
+ n += snprint(secbuf+n, disk->secsize-n, "part %s %lld %lld\n",
+ edit->part[i]->name, edit->part[i]->start, edit->part[i]->end);
+
+ if(seek(disk->wfd, disk->secsize, 0) != disk->secsize){
+ fprint(2, "error seeking %d %lld on disk: %r\n", disk->wfd, disk->secsize);
+ exits("seek");
+ }
+
+ if(write(disk->wfd, secbuf, disk->secsize) != disk->secsize){
+ fprint(2, "error writing partition table to disk\n");
+ restore(edit, -1);
+ }
+
+ if(ctldiff(edit, disk->ctlfd) < 0)
+ fprint(2, "?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.
+ */
+static void
+checkfat(Disk *disk)
+{
+ uchar buf[32];
+
+ if(seek(disk->fd, disk->secsize, 0) < 0
+ || read(disk->fd, buf, sizeof(buf)) < sizeof(buf))
+ return;
+
+ if(buf[0] != 0xEB || buf[1] != 0x3C || buf[2] != 0x90)
+ return;
+
+ fprint(2,
+ "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");
+}
diff --git a/sys/src/cmd/disk/rd9660.c b/sys/src/cmd/disk/rd9660.c
new file mode 100755
index 000000000..868c43290
--- /dev/null
+++ b/sys/src/cmd/disk/rd9660.c
@@ -0,0 +1,377 @@
+/*
+ * dump a 9660 cd image for a little while.
+ * for debugging.
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <disk.h>
+
+Biobuf *b;
+
+#pragma varargck type "s" uchar*
+#pragma varargck type "L" uchar*
+#pragma varargck type "B" uchar*
+#pragma varargck type "N" uchar*
+#pragma varargck type "T" uchar*
+#pragma varargck type "D" uchar*
+
+typedef struct Voldesc Voldesc;
+struct Voldesc {
+ uchar magic[8]; /* 0x01, "CD001", 0x01, 0x00 */
+ uchar systemid[32]; /* system identifier */
+ uchar volumeid[32]; /* volume identifier */
+ uchar unused[8]; /* character set in secondary desc */
+ uchar volsize[8]; /* volume size */
+ uchar charset[32];
+ uchar volsetsize[4]; /* volume set size = 1 */
+ uchar volseqnum[4]; /* volume sequence number = 1 */
+ uchar blocksize[4]; /* logical block size */
+ uchar pathsize[8]; /* path table size */
+ uchar lpathloc[4]; /* Lpath */
+ uchar olpathloc[4]; /* optional Lpath */
+ uchar mpathloc[4]; /* Mpath */
+ uchar ompathloc[4]; /* optional Mpath */
+ uchar rootdir[34]; /* root directory */
+ uchar volsetid[128]; /* volume set identifier */
+ uchar publisher[128];
+ uchar prepid[128]; /* data preparer identifier */
+ uchar applid[128]; /* application identifier */
+ uchar notice[37]; /* copyright notice file */
+ uchar abstract[37]; /* abstract file */
+ uchar biblio[37]; /* bibliographic file */
+ uchar cdate[17]; /* creation date */
+ uchar mdate[17]; /* modification date */
+ uchar xdate[17]; /* expiration date */
+ uchar edate[17]; /* effective date */
+ uchar fsvers; /* file system version = 1 */
+};
+
+void
+dumpbootvol(void *a)
+{
+ Voldesc *v;
+
+ v = a;
+ print("magic %.2ux %.5s %.2ux %2ux\n",
+ v->magic[0], v->magic+1, v->magic[6], v->magic[7]);
+ if(v->magic[0] == 0xFF)
+ return;
+
+ print("system %.32T\n", v->systemid);
+ print("volume %.32T\n", v->volumeid);
+ print("volume size %.4N\n", v->volsize);
+ print("charset %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux\n",
+ v->charset[0], v->charset[1], v->charset[2], v->charset[3],
+ v->charset[4], v->charset[5], v->charset[6], v->charset[7]);
+ print("volume set size %.2N\n", v->volsetsize);
+ print("volume sequence number %.2N\n", v->volseqnum);
+ print("logical block size %.2N\n", v->blocksize);
+ print("path size %.4L\n", v->pathsize);
+ print("lpath loc %.4L\n", v->lpathloc);
+ print("opt lpath loc %.4L\n", v->olpathloc);
+ print("mpath loc %.4B\n", v->mpathloc);
+ print("opt mpath loc %.4B\n", v->ompathloc);
+ print("rootdir %D\n", v->rootdir);
+ print("volume set identifier %.128T\n", v->volsetid);
+ print("publisher %.128T\n", v->publisher);
+ print("preparer %.128T\n", v->prepid);
+ print("application %.128T\n", v->applid);
+ print("notice %.37T\n", v->notice);
+ print("abstract %.37T\n", v->abstract);
+ print("biblio %.37T\n", v->biblio);
+ print("creation date %.17s\n", v->cdate);
+ print("modification date %.17s\n", v->mdate);
+ print("expiration date %.17s\n", v->xdate);
+ print("effective date %.17s\n", v->edate);
+ print("fs version %d\n", v->fsvers);
+}
+
+typedef struct Cdir Cdir;
+struct Cdir {
+ uchar len;
+ uchar xlen;
+ uchar dloc[8];
+ uchar dlen[8];
+ uchar date[7];
+ uchar flags;
+ uchar unitsize;
+ uchar gapsize;
+ uchar volseqnum[4];
+ uchar namelen;
+ uchar name[1]; /* chumminess */
+};
+#pragma varargck type "D" Cdir*
+
+int
+Dfmt(Fmt *fmt)
+{
+ char buf[128];
+ Cdir *c;
+
+ c = va_arg(fmt->args, Cdir*);
+ if(c->namelen == 1 && c->name[0] == '\0' || c->name[0] == '\001') {
+ snprint(buf, sizeof buf, ".%s dloc %.4N dlen %.4N",
+ c->name[0] ? "." : "", c->dloc, c->dlen);
+ } else {
+ snprint(buf, sizeof buf, "%.*T dloc %.4N dlen %.4N", c->namelen, c->name,
+ c->dloc, c->dlen);
+ }
+ fmtstrcpy(fmt, buf);
+ return 0;
+}
+
+typedef struct Path Path;
+struct Path {
+ uchar namelen;
+ uchar xlen;
+ uchar dloc[4];
+ uchar parent[2];
+ uchar name[1]; /* chumminess */
+};
+#pragma varargck type "P" Path*
+
+char longc, shortc;
+void
+bigend(void)
+{
+ longc = 'B';
+}
+
+void
+littleend(void)
+{
+ longc = 'L';
+}
+
+int
+Pfmt(Fmt *fmt)
+{
+ char xfmt[128], buf[128];
+ Path *p;
+
+ p = va_arg(fmt->args, Path*);
+ sprint(xfmt, "data=%%.4%c up=%%.2%c name=%%.*T (%%d)", longc, longc);
+ snprint(buf, sizeof buf, xfmt, p->dloc, p->parent, p->namelen, p->name, p->namelen);
+ fmtstrcpy(fmt, buf);
+ return 0;
+}
+
+ulong
+big(void *a, int n)
+{
+ uchar *p;
+ ulong v;
+ int i;
+
+ p = a;
+ v = 0;
+ for(i=0; i<n; i++)
+ v = (v<<8) | *p++;
+ return v;
+}
+
+ulong
+little(void *a, int n)
+{
+ uchar *p;
+ ulong v;
+ int i;
+
+ p = a;
+ v = 0;
+ for(i=0; i<n; i++)
+ v |= (*p++<<(i*8));
+ return v;
+}
+
+/* numbers in big or little endian. */
+int
+BLfmt(Fmt *fmt)
+{
+ ulong v;
+ uchar *p;
+ char buf[20];
+
+ p = va_arg(fmt->args, uchar*);
+
+ if(!(fmt->flags&FmtPrec)) {
+ fmtstrcpy(fmt, "*BL*");
+ return 0;
+ }
+
+ if(fmt->r == 'B')
+ v = big(p, fmt->prec);
+ else
+ v = little(p, fmt->prec);
+
+ sprint(buf, "0x%.*lux", fmt->prec*2, v);
+ fmt->flags &= ~FmtPrec;
+ fmtstrcpy(fmt, buf);
+ return 0;
+}
+
+/* numbers in both little and big endian */
+int
+Nfmt(Fmt *fmt)
+{
+ char buf[100];
+ uchar *p;
+
+ p = va_arg(fmt->args, uchar*);
+
+ sprint(buf, "%.*L %.*B", fmt->prec, p, fmt->prec, p+fmt->prec);
+ fmt->flags &= ~FmtPrec;
+ fmtstrcpy(fmt, buf);
+ return 0;
+}
+
+int
+asciiTfmt(Fmt *fmt)
+{
+ char *p, buf[256];
+ int i;
+
+ p = va_arg(fmt->args, char*);
+ for(i=0; i<fmt->prec; i++)
+ buf[i] = *p++;
+ buf[i] = '\0';
+ for(p=buf+strlen(buf); p>buf && p[-1]==' '; p--)
+ ;
+ p[0] = '\0';
+ fmt->flags &= ~FmtPrec;
+ fmtstrcpy(fmt, buf);
+ return 0;
+}
+
+int
+runeTfmt(Fmt *fmt)
+{
+ Rune buf[256], *r;
+ int i;
+ uchar *p;
+
+ p = va_arg(fmt->args, uchar*);
+ for(i=0; i*2+2<=fmt->prec; i++, p+=2)
+ buf[i] = (p[0]<<8)|p[1];
+ buf[i] = L'\0';
+ for(r=buf+i; r>buf && r[-1]==L' '; r--)
+ ;
+ r[0] = L'\0';
+ fmt->flags &= ~FmtPrec;
+ return fmtprint(fmt, "%S", buf);
+}
+
+void
+ascii(void)
+{
+ fmtinstall('T', asciiTfmt);
+}
+
+void
+joliet(void)
+{
+ fmtinstall('T', runeTfmt);
+}
+
+void
+getsect(uchar *buf, int n)
+{
+ if(Bseek(b, n*2048, 0) != n*2048 || Bread(b, buf, 2048) != 2048)
+ sysfatal("reading block %ux", n);
+}
+
+void
+pathtable(Voldesc *v, int islittle)
+{
+ int i, j, n, sz, addr;
+ ulong (*word)(void*, int);
+ uchar x[2048], *p, *ep;
+ Path *pt;
+
+ print(">>> entire %s path table\n", islittle ? "little" : "big");
+ littleend();
+ if(islittle) {
+ littleend();
+ word = little;
+ }else{
+ bigend();
+ word = big;
+ }
+ sz = little(v->pathsize, 4); /* little, not word */
+ addr = word(islittle ? v->lpathloc : v->mpathloc, 4);
+ j = 0;
+ n = 1;
+ while(sz > 0){
+ getsect(x, addr);
+ p = x;
+ ep = x+sz;
+ if(ep > x+2048)
+ ep = x+2048;
+ for(i=0; p<ep; i++) {
+ pt = (Path*)p;
+ if(pt->namelen==0)
+ break;
+ print("0x%.4x %4d+%-4ld %P\n", n, j, p-x, pt);
+ n++;
+ p += 8+pt->namelen+(pt->namelen&1);
+ }
+ sz -= 2048;
+ addr++;
+ j++;
+ }
+}
+
+void
+dump(void *root)
+{
+ Voldesc *v;
+ Cdir *c;
+ long rootdirloc;
+ uchar x[2048];
+ int i;
+ uchar *p;
+
+ dumpbootvol(root);
+ v = (Voldesc*)root;
+ c = (Cdir*)v->rootdir;
+ rootdirloc = little(c->dloc, 4);
+ print(">>> sed 5q root directory\n");
+ getsect(x, rootdirloc);
+ p = x;
+ for(i=0; i<5 && (p-x)<little(c->dlen, 4); i++) {
+ print("%D\n", p);
+ p += ((Cdir*)p)->len;
+ }
+
+ pathtable(v, 1);
+ pathtable(v, 0);
+}
+
+void
+main(int argc, char **argv)
+{
+ uchar root[2048], jroot[2048];
+
+ if(argc != 2)
+ sysfatal("usage: %s file", argv[0]);
+
+ b = Bopen(argv[1], OREAD);
+ if(b == nil)
+ sysfatal("bopen %r");
+
+ fmtinstall('L', BLfmt);
+ fmtinstall('B', BLfmt);
+ fmtinstall('N', Nfmt);
+ fmtinstall('D', Dfmt);
+ fmtinstall('P', Pfmt);
+
+ getsect(root, 16);
+ ascii();
+ dump(root);
+
+ getsect(jroot, 17);
+ joliet();
+ dump(jroot);
+ exits(0);
+}
diff --git a/sys/src/cmd/disk/sacfs/mkfile b/sys/src/cmd/disk/sacfs/mkfile
new file mode 100755
index 000000000..3a6a9436b
--- /dev/null
+++ b/sys/src/cmd/disk/sacfs/mkfile
@@ -0,0 +1,20 @@
+</$objtype/mkfile
+
+HFILES=sac.h\
+ ssort.h\
+ sacfs.h\
+
+TARG=mksacfs\
+ sacfs\
+
+OFILES=
+
+PROGS=${TARG:%=$O.%}
+
+BIN=/$objtype/bin/disk
+
+< /sys/src/cmd/mkmany
+
+$O.mksacfs: sac.$O ssort6.$O
+
+$O.sacfs: unsac.$O
diff --git a/sys/src/cmd/disk/sacfs/mksacfs.c b/sys/src/cmd/disk/sacfs/mksacfs.c
new file mode 100755
index 000000000..c0c28dd57
--- /dev/null
+++ b/sys/src/cmd/disk/sacfs/mksacfs.c
@@ -0,0 +1,295 @@
+#include <u.h>
+#include <libc.h>
+#include "sac.h"
+#include "sacfs.h"
+
+enum {
+ NCACHE = 1024, /* must be power of two */
+ OffsetSize = 4, /* size of block offset */
+};
+
+int warn(char *);
+int seen(Dir *);
+void usage(void);
+void outwrite(void *buf, int n, long off);
+void putl(void *p, uint v);
+void *emalloc(int size);
+void sacfs(char *root);
+void sacfile(char *name, Dir *dir, SacDir *sd);
+void sacdir(char *name, Dir *dir, SacDir *sd);
+
+int uflag=0;
+int fflag=0;
+long blocksize = 4*1024;
+
+struct Out
+{
+ int fd;
+ long size;
+} out;
+
+typedef struct Cache Cache;
+struct Cache
+{
+ Dir* cache;
+ int n;
+ int max;
+} cache[NCACHE];
+
+void
+main(int argc, char *argv[])
+{
+ char *s, *ss;
+ char *outfile = nil;
+
+ ARGBEGIN {
+ case 'u':
+ uflag=1;
+ break;
+ case 'o':
+ outfile = ARGF();
+ break;
+ case 'b':
+ s = ARGF();
+ if(s) {
+ blocksize = strtoul(s, &ss, 0);
+ if(s == ss)
+ usage();
+ if(*ss == 'k')
+ blocksize *= 1024;
+ }
+ if(blocksize < sizeof(SacDir))
+ sysfatal("blocksize too small");
+ break;
+ } ARGEND
+
+ if(outfile == nil) {
+ sysfatal("still to do: need to create temp file");
+ } else {
+ out.fd = create(outfile, OWRITE|OTRUNC, 0664);
+ if(out.fd < 0)
+ sysfatal("could not create file: %s: %r", outfile);
+ }
+
+ if(argc==0)
+ sacfs(".");
+ else
+ sacfs(argv[0]);
+
+ close(out.fd);
+
+ exits(0);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-u] [-b blocksize] -o output [root]\n", argv0);
+ exits("usage");
+}
+
+void
+sacfs(char *root)
+{
+ Dir *dir;
+ long offset;
+ SacHeader hdr;
+ SacDir sd;
+
+ dir = dirstat(root);
+ if(dir == nil)
+ sysfatal("could not stat root: %s: %r", root);
+ offset = out.size;
+ out.size += sizeof(SacHeader) + sizeof(SacDir);
+ memset(&hdr, 0, sizeof(hdr));
+ putl(hdr.magic, Magic);
+ putl(hdr.blocksize, blocksize);
+ if(dir->mode & DMDIR)
+ sacdir(root, dir, &sd);
+ else
+ sacfile(root, dir, &sd);
+ putl(hdr.length, out.size);
+ outwrite(&hdr, sizeof(hdr), offset);
+ outwrite(&sd, sizeof(sd), offset+sizeof(SacHeader));
+ free(dir);
+}
+
+void
+setsd(SacDir *sd, Dir *dir, long length, long blocks)
+{
+ static qid = 1;
+
+ memset(sd, 0, sizeof(SacDir));
+ strncpy(sd->name, dir->name, NAMELEN);
+ strncpy(sd->uid, dir->uid, NAMELEN);
+ strncpy(sd->gid, dir->gid, NAMELEN);
+ putl(sd->qid, qid++|(dir->mode&DMDIR));
+ putl(sd->mode, dir->mode);
+ putl(sd->atime, dir->atime);
+ putl(sd->mtime, dir->mtime);
+ putl(sd->length, length);
+ putl(sd->blocks, blocks);
+}
+
+
+void
+sacfile(char *name, Dir *dir, SacDir *sd)
+{
+ int fd, i, n, nn;
+ long nblock;
+ uchar *blocks;
+ uchar *buf, *cbuf;
+ long offset;
+ long block;
+
+ fd = open(name, OREAD);
+ if(fd < 0)
+ sysfatal("could not open file: %s: %r", name);
+ nblock = (dir->length + blocksize-1)/blocksize;
+ blocks = emalloc((nblock+1)*OffsetSize);
+ buf = emalloc(blocksize);
+ cbuf = emalloc(blocksize);
+ offset = out.size;
+ out.size += (nblock+1)*OffsetSize;
+ for(i=0; i<nblock; i++) {
+ n = read(fd, buf, blocksize);
+ if(n < 0)
+ sysfatal("read failed: %s: %r", name);
+ if(n == 0)
+ sysfatal("unexpected eof: %s", name);
+ if(n < blocksize && i != nblock-1)
+ sysfatal("short read: %s: got %d", name, n);
+ block = out.size;
+ nn = sac(cbuf, buf, n);
+ if(nn < 0 || uflag) {
+ outwrite(buf, n, out.size);
+ out.size += n;
+ } else {
+ block = -block;
+ outwrite(cbuf, nn, out.size);
+ out.size += nn;
+ }
+ putl(blocks+i*OffsetSize, block);
+ }
+ putl(blocks+i*OffsetSize, out.size);
+ outwrite(blocks, (nblock+1)*OffsetSize, offset);
+ setsd(sd, dir, dir->length, offset);
+ close(fd);
+ free(buf);
+ free(cbuf);
+ free(blocks);
+}
+
+void
+sacdir(char *name, Dir *dir, SacDir *sd)
+{
+ Dir *dirs, *p;
+ int i, n, nn, per;
+ SacDir *sds;
+ int ndir, fd, nblock;
+ long offset, block;
+ uchar *blocks, *cbuf;
+ char file[512];
+
+ fd = open(name, OREAD);
+ if(fd < 0)
+ sysfatal("could not open directory: %s: %r", name);
+ ndir = dirreadall(fd, &dirs);
+ if(ndir < 0)
+ sysfatal("could not read directory: %s: %r", name);
+ close(fd);
+ per = blocksize/sizeof(SacDir);
+ nblock = (ndir+per-1)/per;
+ sds = emalloc(nblock*per*sizeof(SacDir));
+ p = dirs;
+ for(i=0; i<ndir; i++,p++) {
+ sprint(file, "%s/%s", name, p->name);
+ if(p->mode & DMDIR)
+ sacdir(file, p, sds+i);
+ else
+ sacfile(file, p, sds+i);
+ }
+ free(dirs);
+ blocks = emalloc((nblock+1)*OffsetSize);
+ offset = out.size;
+ out.size += (nblock+1)*OffsetSize;
+ n = per*sizeof(SacDir);
+ cbuf = emalloc(n);
+ for(i=0; i<nblock; i++) {
+ block = out.size;
+ if(n > (ndir-i*per)*sizeof(SacDir))
+ n = (ndir-i*per)*sizeof(SacDir);
+ nn = sac(cbuf, (uchar*)(sds+i*per), n);
+ if(nn < 0 || uflag) {
+ outwrite(sds+i*per, n, out.size);
+ out.size += n;
+ } else {
+ block = -block;
+ outwrite(cbuf, nn, out.size);
+ out.size += nn;
+ }
+ putl(blocks+i*OffsetSize, block);
+ }
+ free(cbuf);
+ putl(blocks+i*OffsetSize, out.size);
+ outwrite(blocks, (nblock+1)*OffsetSize, offset);
+ setsd(sd, dir, ndir, offset);
+ free(sds);
+ free(blocks);
+}
+
+int
+seen(Dir *dir)
+{
+ Dir *dp;
+ int i;
+ Cache *c;
+
+ c = &cache[dir->qid.path&(NCACHE-1)];
+ dp = c->cache;
+ for(i=0; i<c->n; i++, dp++)
+ if(dir->qid.path == dp->qid.path &&
+ dir->type == dp->type &&
+ dir->dev == dp->dev)
+ return 1;
+ if(c->n == c->max){
+ c->cache = realloc(c->cache, (c->max+=20)*sizeof(Dir));
+ if(cache == 0)
+ sysfatal("malloc failure");
+ }
+ c->cache[c->n++] = *dir;
+ return 0;
+}
+
+void
+outwrite(void *buf, int n, long offset)
+{
+ if(seek(out.fd, offset, 0) < 0)
+ sysfatal("seek failed: %r");
+ if(write(out.fd, buf, n) < n)
+ sysfatal("write failed: %r");
+}
+
+void
+putl(void *p, uint v)
+{
+ uchar *a;
+
+ a = p;
+ a[0] = v>>24;
+ a[1] = v>>16;
+ a[2] = v>>8;
+ a[3] = v;
+}
+
+void *
+emalloc(int size)
+{
+ void *p;
+
+ p = malloc(size);
+ if(p == nil)
+ sysfatal("malloc failed");
+ memset(p, 0, size);
+ return p;
+}
diff --git a/sys/src/cmd/disk/sacfs/sac.c b/sys/src/cmd/disk/sacfs/sac.c
new file mode 100755
index 000000000..abe775a7a
--- /dev/null
+++ b/sys/src/cmd/disk/sacfs/sac.c
@@ -0,0 +1,792 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "sac.h"
+#include "ssort.h"
+
+typedef struct Chain Chain;
+typedef struct Chains Chains;
+typedef struct Huff Huff;
+
+enum
+{
+ ZBase = 2, /* base of code to encode 0 runs */
+ LitBase = ZBase-1, /* base of literal values */
+ MaxLit = 256,
+
+ MaxLeaf = MaxLit+LitBase,
+
+ MaxHuffBits = 16, /* limit of bits in a huffman code */
+ ChainMem = 2 * (MaxHuffBits - 1) * MaxHuffBits,
+};
+
+/*
+ * huffman code table
+ */
+struct Huff
+{
+ short bits; /* length of the code */
+ ulong encode; /* the code */
+};
+
+struct Chain
+{
+ ulong count; /* occurances of everything in the chain */
+ ushort leaf; /* leaves to the left of chain, or leaf value */
+ char col; /* ref count for collecting unused chains */
+ char gen; /* need to generate chains for next lower level */
+ Chain *up; /* Chain up in the lists */
+};
+
+struct Chains
+{
+ Chain *lists[(MaxHuffBits - 1) * 2];
+ ulong leafcount[MaxLeaf]; /* sorted list of leaf counts */
+ ushort leafmap[MaxLeaf]; /* map to actual leaf number */
+ int nleaf; /* number of leaves */
+ Chain chains[ChainMem];
+ Chain *echains;
+ Chain *free;
+ char col;
+ int nlists;
+};
+
+static jmp_buf errjmp;
+
+static uchar *bout;
+static int bmax;
+static int bpos;
+
+static ulong leafcount[MaxLeaf];
+static ulong nzero;
+static ulong *hbuf;
+static int hbpos;
+static ulong nzero;
+static ulong maxblocksym;
+
+static void henc(int);
+static void hbflush(void);
+
+static void mkprecode(Huff*, ulong *, int, int);
+static ulong sendtab(Huff *tab, int maxleaf, ushort *map);
+static int elimBits(int b, ulong *bused, char *tmtf, int maxbits);
+static void elimBit(int b, char *tmtf, int maxbits);
+static void nextchain(Chains*, int);
+static void hufftabinit(Huff*, int, ulong*, int);
+static void leafsort(ulong*, ushort*, int, int);
+
+static void bitput(int, int);
+
+static int mtf(uchar*, int);
+static void fatal(char*, ...);
+
+static ulong headerbits;
+static ulong charmapbits;
+static ulong hufftabbits;
+static ulong databits;
+static ulong nbits;
+static ulong bits;
+
+int
+sac(uchar *dst, uchar *src, int n)
+{
+ uchar front[256];
+ ulong *perm, cmap[256];
+ long i, c, rev;
+
+ rev = 0;
+
+ perm = nil;
+
+ if(setjmp(errjmp)){
+ free(perm);
+ if(rev){
+ for(i = 0; i < n/2; i++){
+ c = src[i];
+ src[i] = src[n-i-1];
+ src[n-i-1] = c;
+ }
+ }
+ return -1;
+ }
+
+ /*
+ * set up the output buffer
+ */
+ bout = dst;
+ bmax = n;
+ bpos = 0;
+
+ perm = malloc((n+1)*sizeof *perm);
+ if(perm == nil)
+ return -1;
+
+ hbuf = perm;
+ hbpos = 0;
+
+ nbits = 0;
+ nzero = 0;
+ for(i = 0; i < MaxLeaf; i++)
+ leafcount[i] = 0;
+
+ if(rev){
+ for(i = 0; i < n/2; i++){
+ c = src[i];
+ src[i] = src[n-i-1];
+ src[n-i-1] = c;
+ }
+ }
+
+ i = ssortbyte(src, (int*)perm, nil, n);
+
+ headerbits += 16;
+ bitput(i, 16);
+
+ /*
+ * send the map of chars which occur in this block
+ */
+ for(i = 0; i < 256; i++)
+ cmap[i] = 0;
+ for(i = 0; i < n; i++)
+ cmap[src[i]] = 1;
+ charmapbits += 1;
+ bitput(cmap[0] != 0, 1);
+ for(i = 0; i < 256; i = c){
+ for(c = i + 1; c < 256 && (cmap[c] != 0) == (cmap[i] != 0); c++)
+ ;
+ charmapbits += 8;
+ bitput(c - i - 1, 8);
+ }
+
+ /*
+ * initialize mtf state
+ */
+ c = 0;
+ for(i = 0; i < 256; i++){
+ if(cmap[i]){
+ cmap[i] = c;
+ front[c] = i;
+ c++;
+ }
+ }
+ maxblocksym = c + LitBase;
+
+ for(i = 0; i <= n; i++){
+ c = perm[i] - 1;
+ if(c < 0)
+ continue;
+ henc(mtf(front, src[c]));
+ }
+
+ hbflush();
+ bitput(0, 24);
+ bitput(0, 8 - nbits);
+
+ free(perm);
+ return bpos;
+}
+
+static void
+bitput(int c, int n)
+{
+ bits <<= n;
+ bits |= c;
+ for(nbits += n; nbits >= 8; nbits -= 8){
+ c = bits << (32 - nbits);
+
+ if(bpos >= bmax)
+ longjmp(errjmp, 1);
+ bout[bpos++] = c >> 24;
+ }
+}
+
+static int
+mtf(uchar *front, int c)
+{
+ int i, v;
+
+ for(i = 0; front[i] != c; i++)
+ ;
+ for(v = i; i > 0; i--)
+ front[i] = front[i - 1];
+ front[0] = c;
+ return v;
+}
+
+/*
+ * encode a run of zeros
+ * convert to funny run length coding:
+ * add one, omit implicit top 1 bit.
+ */
+static void
+zenc(void)
+{
+ int m;
+
+ nzero++;
+ while(nzero != 1){
+ m = nzero & 1;
+ hbuf[hbpos++] = m;
+ leafcount[m]++;
+ nzero >>= 1;
+ }
+ nzero = 0;
+}
+
+/*
+ * encode one symbol
+ */
+static void
+henc(int c)
+{
+ if(c == 0){
+ nzero++;
+ return;
+ }
+
+ if(nzero)
+ zenc();
+ c += LitBase;
+ leafcount[c]++;
+
+ hbuf[hbpos++] = c;
+}
+
+static void
+hbflush(void)
+{
+ Huff tab[MaxLeaf];
+ int i, b, s;
+
+ if(nzero)
+ zenc();
+ if(hbpos == 0)
+ return;
+
+ mkprecode(tab, leafcount, maxblocksym, MaxHuffBits);
+
+ hufftabbits += sendtab(tab, maxblocksym, nil);
+
+ /*
+ * send the data
+ */
+ for(i = 0; i < hbpos; i++){
+ s = hbuf[i];
+ b = tab[s].bits;
+ if(b == 0)
+ fatal("bad tables %d", i);
+ databits += b;
+ bitput(tab[s].encode, b);
+ }
+}
+
+static ulong
+sendtab(Huff *tab, int maxleaf, ushort *map)
+{
+ ulong ccount[MaxHuffBits+1], bused[MaxHuffBits+1];
+ Huff codetab[MaxHuffBits+1];
+ char tmtf[MaxHuffBits+1];
+ ulong bits;
+ int i, b, max, ttb, s, elim;
+
+ /*
+ * make up the table table
+ * over cleverness: the data is known to be a huffman table
+ * check for fullness at each bit level, and wipe it out from
+ * the possibilities; when nothing exists except 0, stop.
+ */
+ for(i = 0; i <= MaxHuffBits; i++){
+ tmtf[i] = i;
+ ccount[i] = 0;
+ bused[i] = 0;
+ }
+ tmtf[0] = -1;
+ tmtf[MaxHuffBits] = 0;
+
+ elim = 0;
+ for(i = 0; i < maxleaf && elim != MaxHuffBits; i++){
+ b = i;
+ if(map)
+ b = map[b];
+ b = tab[b].bits;
+ for(s = 0; tmtf[s] != b; s++)
+ if(s >= MaxHuffBits)
+ fatal("bitlength not found");
+ ccount[s]++;
+ for(; s > 0; s--)
+ tmtf[s] = tmtf[s-1];
+ tmtf[0] = b;
+
+ elim += elimBits(b, bused, tmtf, MaxHuffBits);
+ }
+ if(elim != MaxHuffBits && elim != 0)
+ fatal("incomplete huffman table");
+
+ /*
+ * make up and send the table table
+ */
+ max = 8;
+ mkprecode(codetab, ccount, MaxHuffBits+1, max);
+ for(i = 0; i <= max; i++){
+ tmtf[i] = i;
+ bused[i] = 0;
+ }
+ tmtf[0] = -1;
+ tmtf[max] = 0;
+ elim = 0;
+ bits = 0;
+ for(i = 0; i <= MaxHuffBits && elim != max; i++){
+ b = codetab[i].bits;
+ for(s = 0; tmtf[s] != b; s++)
+ if(s > max)
+ fatal("bitlength not found");
+ ttb = 4;
+ while(max - elim < (1 << (ttb-1)))
+ ttb--;
+ bits += ttb;
+ bitput(s, ttb);
+
+ elim += elimBits(b, bused, tmtf, max);
+ }
+ if(elim != max)
+ fatal("incomplete huffman table table");
+
+ /*
+ * send the table
+ */
+ for(i = 0; i <= MaxHuffBits; i++){
+ tmtf[i] = i;
+ bused[i] = 0;
+ }
+ tmtf[0] = -1;
+ tmtf[MaxHuffBits] = 0;
+ elim = 0;
+ for(i = 0; i < maxleaf && elim != MaxHuffBits; i++){
+ b = i;
+ if(map)
+ b = map[b];
+ b = tab[b].bits;
+ for(s = 0; tmtf[s] != b; s++)
+ if(s >= MaxHuffBits)
+ fatal("bitlength not found");
+ ttb = codetab[s].bits;
+ if(ttb < 0)
+ fatal("bad huffman table table");
+ bits += ttb;
+ bitput(codetab[s].encode, ttb);
+ for(; s > 0; s--)
+ tmtf[s] = tmtf[s-1];
+ tmtf[0] = b;
+
+ elim += elimBits(b, bused, tmtf, MaxHuffBits);
+ }
+ if(elim != MaxHuffBits && elim != 0)
+ fatal("incomplete huffman table");
+
+ return bits;
+}
+
+static int
+elimBits(int b, ulong *bused, char *tmtf, int maxbits)
+{
+ int bb, elim;
+
+ if(b < 0)
+ return 0;
+
+ elim = 0;
+
+ /*
+ * increase bits counts for all descendants
+ */
+ for(bb = b + 1; bb < maxbits; bb++){
+ bused[bb] += 1 << (bb - b);
+ if(bused[bb] == (1 << bb)){
+ elim++;
+ elimBit(bb, tmtf, maxbits);
+ }
+ }
+
+ /*
+ * steal bits from parent & check for fullness
+ */
+ for(; b >= 0; b--){
+ bused[b]++;
+ if(bused[b] == (1 << b)){
+ elim++;
+ elimBit(b, tmtf, maxbits);
+ }
+ if((bused[b] & 1) == 0)
+ break;
+ }
+ return elim;
+}
+
+static void
+elimBit(int b, char *tmtf, int maxbits)
+{
+ int bb;
+
+ for(bb = 0; bb < maxbits; bb++)
+ if(tmtf[bb] == b)
+ break;
+ if(tmtf[bb] != b)
+ fatal("elim bit not found");
+ while(++bb <= maxbits)
+ tmtf[bb - 1] = tmtf[bb];
+}
+
+/*
+ * fast?, in-place huffman codes
+ *
+ * A. Moffat, J. Katajainen, "In-Place Calculation of Minimum-Redundancy Codes",
+ *
+ * counts must be sorted, and may be aliased to bitlens
+ */
+static int
+fmkprecode(ulong *bitcount, ulong *bitlen, ulong *counts, int n, int maxbits)
+{
+ int leaf, tree, treet, depth, nodes, internal;
+
+ /*
+ * form the internal tree structure:
+ * [0, tree) are parent pointers,
+ * [tree, treet) are weights of internal nodes,
+ * [leaf, n) are weights of remaining leaves.
+ *
+ * note that at the end, there are n-1 internal nodes
+ */
+ leaf = 0;
+ tree = 0;
+ for(treet = 0; treet != n - 1; treet++){
+ if(leaf >= n || tree < treet && bitlen[tree] < counts[leaf]){
+ bitlen[treet] = bitlen[tree];
+ bitlen[tree++] = treet;
+ }else
+ bitlen[treet] = counts[leaf++];
+ if(leaf >= n || tree < treet && bitlen[tree] < counts[leaf]){
+ bitlen[treet] += bitlen[tree];
+ bitlen[tree++] = treet;
+ }else
+ bitlen[treet] += counts[leaf++];
+ }
+ if(tree != treet - 1)
+ fatal("bad fast mkprecode");
+
+ /*
+ * calculate depth of internal nodes
+ */
+ bitlen[n - 2] = 0;
+ for(tree = n - 3; tree >= 0; tree--)
+ bitlen[tree] = bitlen[bitlen[tree]] + 1;
+
+ /*
+ * calculate depth of leaves:
+ * at each depth, leaves(depth) = nodes(depth) - internal(depth)
+ * and nodes(depth+1) = 2 * internal(depth)
+ */
+ leaf = n;
+ tree = n - 2;
+ depth = 0;
+ for(nodes = 1; nodes > 0; nodes = 2 * internal){
+ internal = 0;
+ while(tree >= 0 && bitlen[tree] == depth){
+ tree--;
+ internal++;
+ }
+ nodes -= internal;
+ if(depth < maxbits)
+ bitcount[depth] = nodes;
+ while(nodes-- > 0)
+ bitlen[--leaf] = depth;
+ depth++;
+ }
+ if(leaf != 0 || tree != -1)
+ fatal("bad leaves in fast mkprecode");
+
+ return depth - 1;
+}
+
+/*
+ * fast, low space overhead algorithm for max depth huffman type codes
+ *
+ * J. Katajainen, A. Moffat and A. Turpin, "A fast and space-economical
+ * algorithm for length-limited coding," Proc. Intl. Symp. on Algorithms
+ * and Computation, Cairns, Australia, Dec. 1995, Lecture Notes in Computer
+ * Science, Vol 1004, J. Staples, P. Eades, N. Katoh, and A. Moffat, eds.,
+ * pp 12-21, Springer Verlag, New York, 1995.
+ */
+static void
+mkprecode(Huff *tab, ulong *count, int n, int maxbits)
+{
+ Chains cs;
+ Chain *c;
+ ulong bitcount[MaxHuffBits];
+ int i, m, em, bits;
+
+ /*
+ * set up the sorted list of leaves
+ */
+ m = 0;
+ for(i = 0; i < n; i++){
+ tab[i].bits = -1;
+ tab[i].encode = 0;
+ if(count[i] != 0){
+ cs.leafcount[m] = count[i];
+ cs.leafmap[m] = i;
+ m++;
+ }
+ }
+ if(m < 2){
+ if(m != 0){
+ m = cs.leafmap[0];
+ tab[m].bits = 0;
+ tab[m].encode = 0;
+ }
+ return;
+ }
+ cs.nleaf = m;
+ leafsort(cs.leafcount, cs.leafmap, 0, m);
+
+ /*
+ * try fast code
+ */
+ bits = fmkprecode(bitcount, cs.leafcount, cs.leafcount, m, maxbits);
+ if(bits < maxbits){
+ for(i = 0; i < m; i++)
+ tab[cs.leafmap[i]].bits = cs.leafcount[i];
+ bitcount[0] = 0;
+ }else{
+ for(i = 0; i < m; i++)
+ cs.leafcount[i] = count[cs.leafmap[i]];
+
+ /*
+ * set up free list
+ */
+ cs.free = &cs.chains[2];
+ cs.echains = &cs.chains[ChainMem];
+ cs.col = 1;
+
+ /*
+ * initialize chains for each list
+ */
+ c = &cs.chains[0];
+ c->count = cs.leafcount[0];
+ c->leaf = 1;
+ c->col = cs.col;
+ c->up = nil;
+ c->gen = 0;
+ cs.chains[1] = cs.chains[0];
+ cs.chains[1].leaf = 2;
+ cs.chains[1].count = cs.leafcount[1];
+ for(i = 0; i < maxbits-1; i++){
+ cs.lists[i * 2] = &cs.chains[0];
+ cs.lists[i * 2 + 1] = &cs.chains[1];
+ }
+
+ cs.nlists = 2 * (maxbits - 1);
+ m = 2 * m - 2;
+ for(i = 2; i < m; i++)
+ nextchain(&cs, cs.nlists - 2);
+
+ bits = 0;
+ bitcount[0] = cs.nleaf;
+ for(c = cs.lists[cs.nlists - 1]; c != nil; c = c->up){
+ m = c->leaf;
+ bitcount[bits++] -= m;
+ bitcount[bits] = m;
+ }
+ m = 0;
+ for(i = bits; i >= 0; i--)
+ for(em = m + bitcount[i]; m < em; m++)
+ tab[cs.leafmap[m]].bits = i;
+ }
+ hufftabinit(tab, n, bitcount, bits);
+}
+
+/*
+ * calculate the next chain on the list
+ * we can always toss out the old chain
+ */
+static void
+nextchain(Chains *cs, int list)
+{
+ Chain *c, *oc;
+ int i, nleaf, sumc;
+
+ oc = cs->lists[list + 1];
+ cs->lists[list] = oc;
+ if(oc == nil)
+ return;
+
+ /*
+ * make sure we have all chains needed to make sumc
+ * note it is possible to generate only one of these,
+ * use twice that value for sumc, and then generate
+ * the second if that preliminary sumc would be chosen.
+ * however, this appears to be slower on current tests
+ */
+ if(oc->gen){
+ nextchain(cs, list - 2);
+ nextchain(cs, list - 2);
+ oc->gen = 0;
+ }
+
+ /*
+ * pick up the chain we're going to add;
+ * collect unused chains no free ones are left
+ */
+ for(c = cs->free; ; c++){
+ if(c >= cs->echains){
+ cs->col++;
+ for(i = 0; i < cs->nlists; i++)
+ for(c = cs->lists[i]; c != nil; c = c->up)
+ c->col = cs->col;
+ c = cs->chains;
+ }
+ if(c->col != cs->col)
+ break;
+ }
+
+ /*
+ * pick the cheapest of
+ * 1) the next package from the previous list
+ * 2) the next leaf
+ */
+ nleaf = oc->leaf;
+ sumc = 0;
+ if(list > 0 && cs->lists[list-1] != nil)
+ sumc = cs->lists[list-2]->count + cs->lists[list-1]->count;
+ if(sumc != 0 && (nleaf >= cs->nleaf || cs->leafcount[nleaf] > sumc)){
+ c->count = sumc;
+ c->leaf = oc->leaf;
+ c->up = cs->lists[list-1];
+ c->gen = 1;
+ }else if(nleaf >= cs->nleaf){
+ cs->lists[list + 1] = nil;
+ return;
+ }else{
+ c->leaf = nleaf + 1;
+ c->count = cs->leafcount[nleaf];
+ c->up = oc->up;
+ c->gen = 0;
+ }
+ cs->free = c + 1;
+
+ cs->lists[list + 1] = c;
+ c->col = cs->col;
+}
+
+static void
+hufftabinit(Huff *tab, int n, ulong *bitcount, int nbits)
+{
+ ulong code, nc[MaxHuffBits];
+ int i, bits;
+
+ code = 0;
+ for(bits = 1; bits <= nbits; bits++){
+ code = (code + bitcount[bits-1]) << 1;
+ nc[bits] = code;
+ }
+
+ for(i = 0; i < n; i++){
+ bits = tab[i].bits;
+ if(bits > 0)
+ tab[i].encode = nc[bits]++;
+ }
+}
+
+static int
+pivot(ulong *c, int a, int n)
+{
+ int j, pi, pj, pk;
+
+ j = n/6;
+ pi = a + j; /* 1/6 */
+ j += j;
+ pj = pi + j; /* 1/2 */
+ pk = pj + j; /* 5/6 */
+ if(c[pi] < c[pj]){
+ if(c[pi] < c[pk]){
+ if(c[pj] < c[pk])
+ return pj;
+ return pk;
+ }
+ return pi;
+ }
+ if(c[pj] < c[pk]){
+ if(c[pi] < c[pk])
+ return pi;
+ return pk;
+ }
+ return pj;
+}
+
+static void
+leafsort(ulong *leafcount, ushort *leafmap, int a, int n)
+{
+ ulong t;
+ int j, pi, pj, pn;
+
+ while(n > 1){
+ if(n > 10){
+ pi = pivot(leafcount, a, n);
+ }else
+ pi = a + (n>>1);
+
+ t = leafcount[pi];
+ leafcount[pi] = leafcount[a];
+ leafcount[a] = t;
+ t = leafmap[pi];
+ leafmap[pi] = leafmap[a];
+ leafmap[a] = t;
+ pi = a;
+ pn = a + n;
+ pj = pn;
+ for(;;){
+ do
+ pi++;
+ while(pi < pn && (leafcount[pi] < leafcount[a] || leafcount[pi] == leafcount[a] && leafmap[pi] > leafmap[a]));
+ do
+ pj--;
+ while(pj > a && (leafcount[pj] > leafcount[a] || leafcount[pj] == leafcount[a] && leafmap[pj] < leafmap[a]));
+ if(pj < pi)
+ break;
+ t = leafcount[pi];
+ leafcount[pi] = leafcount[pj];
+ leafcount[pj] = t;
+ t = leafmap[pi];
+ leafmap[pi] = leafmap[pj];
+ leafmap[pj] = t;
+ }
+ t = leafcount[a];
+ leafcount[a] = leafcount[pj];
+ leafcount[pj] = t;
+ t = leafmap[a];
+ leafmap[a] = leafmap[pj];
+ leafmap[pj] = t;
+ j = pj - a;
+
+ n = n-j-1;
+ if(j >= n){
+ leafsort(leafcount, leafmap, a, j);
+ a += j+1;
+ }else{
+ leafsort(leafcount, leafmap, a + (j+1), n);
+ n = j;
+ }
+ }
+}
+
+static void
+fatal(char *fmt, ...)
+{
+ char buf[512];
+ va_list arg;
+
+ va_start(arg, fmt);
+ doprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+
+ longjmp(errjmp, 1);
+}
diff --git a/sys/src/cmd/disk/sacfs/sac.h b/sys/src/cmd/disk/sacfs/sac.h
new file mode 100755
index 000000000..7caaa476c
--- /dev/null
+++ b/sys/src/cmd/disk/sacfs/sac.h
@@ -0,0 +1,2 @@
+int sac(uchar *dst, uchar *src, int blocksize);
+int unsac(uchar *dst, uchar *src, int n, int nsrc);
diff --git a/sys/src/cmd/disk/sacfs/sacfs.c b/sys/src/cmd/disk/sacfs/sacfs.c
new file mode 100755
index 000000000..0824143a4
--- /dev/null
+++ b/sys/src/cmd/disk/sacfs/sacfs.c
@@ -0,0 +1,882 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include "sac.h"
+#include "sacfs.h"
+
+/*
+ * Rather than reading /adm/users, which is a lot of work for
+ * a toy program, we assume all groups have the form
+ * NNN:user:user:
+ * meaning that each user is the leader of his own group.
+ */
+
+enum
+{
+ OPERM = 0x3, /* mask of all permission types in open mode */
+ Nram = 512,
+ OffsetSize = 4, /* size in bytes of an offset */
+ CacheSize = 20,
+};
+
+typedef struct Fid Fid;
+typedef struct Path Path;
+typedef struct Sac Sac;
+typedef struct Cache Cache;
+
+struct Fid
+{
+ short busy;
+ short open;
+ int fid;
+ char *user;
+ Qid qid;
+ Sac *sac;
+ Fid *next;
+};
+
+struct Sac
+{
+ SacDir;
+ Path *path;
+};
+
+struct Path
+{
+ int ref;
+ Path *up;
+ long blocks;
+ int entry;
+ int nentry;
+};
+
+struct Cache
+{
+ long block;
+ ulong age;
+ uchar *data;
+};
+
+enum
+{
+ Pexec = 1,
+ Pwrite = 2,
+ Pread = 4,
+ Pother = 1,
+ Pgroup = 8,
+ Powner = 64,
+};
+
+Fid *fids;
+uchar *data;
+int mfd[2];
+char user[NAMELEN];
+char mdata[MAXMSG+MAXFDATA];
+Fcall rhdr;
+Fcall thdr;
+int blocksize;
+Sac root;
+Cache cache[CacheSize];
+ulong cacheage;
+
+Fid * newfid(int);
+void sacstat(SacDir*, char*);
+void error(char*);
+void io(void);
+void *erealloc(void*, ulong);
+void *emalloc(ulong);
+void usage(void);
+int perm(Fid*, Sac*, int);
+ulong getl(void *p);
+void init(char*);
+Sac *saccpy(Sac *s);
+Sac *saclookup(Sac *s, char *name);
+int sacdirread(Sac *s, char *p, long off, long cnt);
+void loadblock(void *buf, uchar *offset, int blocksize);
+void sacfree(Sac*);
+
+char *rflush(Fid*), *rnop(Fid*), *rsession(Fid*),
+ *rattach(Fid*), *rclone(Fid*), *rwalk(Fid*),
+ *rclwalk(Fid*), *ropen(Fid*), *rcreate(Fid*),
+ *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
+ *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*);
+
+char *(*fcalls[])(Fid*) = {
+ [Tflush] rflush,
+ [Tsession] rsession,
+ [Tnop] rnop,
+ [Tattach] rattach,
+ [Tclone] rclone,
+ [Twalk] rwalk,
+ [Tclwalk] rclwalk,
+ [Topen] ropen,
+ [Tcreate] rcreate,
+ [Tread] rread,
+ [Twrite] rwrite,
+ [Tclunk] rclunk,
+ [Tremove] rremove,
+ [Tstat] rstat,
+ [Twstat] rwstat,
+};
+
+char Eperm[] = "permission denied";
+char Enotdir[] = "not a directory";
+char Enoauth[] = "no authentication in ramfs";
+char Enotexist[] = "file does not exist";
+char Einuse[] = "file in use";
+char Eexist[] = "file exists";
+char Enotowner[] = "not owner";
+char Eisopen[] = "file already open for I/O";
+char Excl[] = "exclusive use file already open";
+char Ename[] = "illegal name";
+char Erdonly[] = "read only file system";
+
+int debug;
+
+void
+notifyf(void *a, char *s)
+{
+ USED(a);
+ if(strncmp(s, "interrupt", 9) == 0)
+ noted(NCONT);
+ noted(NDFLT);
+}
+
+void
+main(int argc, char *argv[])
+{
+ char *defmnt;
+ int p[2];
+ char buf[12];
+ int fd;
+ int stdio = 0;
+
+ defmnt = "/n/c:";
+ ARGBEGIN{
+ case 'd':
+ debug = 1;
+ break;
+ case 'i':
+ defmnt = 0;
+ stdio = 1;
+ mfd[0] = 0;
+ mfd[1] = 1;
+ break;
+ case 's':
+ defmnt = 0;
+ break;
+ case 'm':
+ defmnt = ARGF();
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+
+ init(argv[0]);
+
+ if(pipe(p) < 0)
+ error("pipe failed");
+ if(!stdio){
+ mfd[0] = p[0];
+ mfd[1] = p[0];
+ if(defmnt == 0){
+ fd = create("#s/sacfs", OWRITE, 0666);
+ if(fd < 0)
+ error("create of /srv/sacfs failed");
+ sprint(buf, "%d", p[1]);
+ if(write(fd, buf, strlen(buf)) < 0)
+ error("writing /srv/sacfs");
+ }
+ }
+
+ if(debug)
+ fmtinstall('F', fcallconv);
+ switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){
+ case -1:
+ error("fork");
+ case 0:
+ close(p[1]);
+ io();
+ break;
+ default:
+ close(p[0]); /* don't deadlock if child fails */
+ if(defmnt && mount(p[1], defmnt, MREPL|MCREATE, "") < 0)
+ error("mount failed");
+ }
+ exits(0);
+}
+
+char*
+rnop(Fid *f)
+{
+ USED(f);
+ return 0;
+}
+
+char*
+rsession(Fid *unused)
+{
+ Fid *f;
+
+ USED(unused);
+
+ for(f = fids; f; f = f->next)
+ if(f->busy)
+ rclunk(f);
+ memset(thdr.authid, 0, sizeof(thdr.authid));
+ memset(thdr.authdom, 0, sizeof(thdr.authdom));
+ memset(thdr.chal, 0, sizeof(thdr.chal));
+ return 0;
+}
+
+char*
+rflush(Fid *f)
+{
+ USED(f);
+ return 0;
+}
+
+char*
+rattach(Fid *f)
+{
+ /* no authentication! */
+ f->busy = 1;
+ f->qid = (Qid){getl(root.qid), 0};
+ f->sac = saccpy(&root);
+ thdr.qid = f->qid;
+ if(rhdr.uname[0])
+ f->user = strdup(rhdr.uname);
+ else
+ f->user = "none";
+ return 0;
+}
+
+char*
+rclone(Fid *f)
+{
+ Fid *nf;
+
+ if(f->open)
+ return Eisopen;
+ if(f->busy == 0)
+ return Enotexist;
+ nf = newfid(rhdr.newfid);
+ nf->busy = 1;
+ nf->open = 0;
+ nf->qid = f->qid;
+ nf->sac = saccpy(f->sac);
+ nf->user = strdup(f->user);
+ return 0;
+}
+
+char*
+rwalk(Fid *f)
+{
+ char *name;
+ Sac *sac;
+
+ if((f->qid.path & CHDIR) == 0)
+ return Enotdir;
+ if(f->busy == 0)
+ return Enotexist;
+ name = rhdr.name;
+ sac = f->sac;
+ if(strcmp(name, ".") == 0){
+ thdr.qid = f->qid;
+ return 0;
+ }
+ if(!perm(f, sac, Pexec))
+ return Eperm;
+ sac = saclookup(sac, name);
+ if(sac == nil)
+ return Enotexist;
+ f->sac = sac;
+ f->qid = (Qid){getl(sac->qid), 0};
+ thdr.qid = f->qid;
+ return 0;
+}
+
+char *
+rclwalk(Fid *f)
+{
+ Fid *nf;
+ char *err;
+
+ nf = newfid(rhdr.newfid);
+ nf->busy = 1;
+ nf->sac = saccpy(f->sac);
+ nf->user = strdup(f->user);
+ if(err = rwalk(nf))
+ rclunk(nf);
+ return err;
+}
+
+char *
+ropen(Fid *f)
+{
+ int mode, trunc;
+
+ if(f->open)
+ return Eisopen;
+ if(f->busy == 0)
+ return Enotexist;
+ mode = rhdr.mode;
+ if(f->qid.path & CHDIR){
+ if(mode != OREAD)
+ return Eperm;
+ thdr.qid = f->qid;
+ return 0;
+ }
+ if(mode & ORCLOSE)
+ return Erdonly;
+ trunc = mode & OTRUNC;
+ mode &= OPERM;
+ if(mode==OWRITE || mode==ORDWR || trunc)
+ return Erdonly;
+ if(mode==OREAD)
+ if(!perm(f, f->sac, Pread))
+ return Eperm;
+ if(mode==OEXEC)
+ if(!perm(f, f->sac, Pexec))
+ return Eperm;
+ thdr.qid = f->qid;
+ f->open = 1;
+ return 0;
+}
+
+char *
+rcreate(Fid *f)
+{
+ if(f->open)
+ return Eisopen;
+ if(f->busy == 0)
+ return Enotexist;
+ return Erdonly;
+}
+
+char*
+rread(Fid *f)
+{
+ Sac *sac;
+ char *buf, *buf2;
+ long off;
+ int n, cnt, i, j;
+ uchar *blocks;
+ long length;
+
+ if(f->busy == 0)
+ return Enotexist;
+ sac = f->sac;
+ thdr.count = 0;
+ off = rhdr.offset;
+ buf = thdr.data;
+ cnt = rhdr.count;
+ if(f->qid.path & CHDIR){
+ cnt = (rhdr.count/DIRLEN)*DIRLEN;
+ if(off%DIRLEN)
+ return "i/o error";
+ thdr.count = sacdirread(sac, buf, off, cnt);
+ return 0;
+ }
+ length = getl(sac->length);
+ if(off >= length) {
+ rhdr.count = 0;
+ return 0;
+ }
+ if(cnt > length-off)
+ cnt = length-off;
+ thdr.count = cnt;
+ if(cnt == 0)
+ return 0;
+ blocks = data + getl(sac->blocks);
+ buf2 = malloc(blocksize);
+ if(buf2 == nil)
+ sysfatal("malloc failed");
+ while(cnt > 0) {
+ i = off/blocksize;
+ n = blocksize;
+ if(n > length-i*blocksize)
+ n = length-i*blocksize;
+ loadblock(buf2, blocks+i*OffsetSize, n);
+ j = off-i*blocksize;
+ n = blocksize-j;
+ if(n > cnt)
+ n = cnt;
+ memmove(buf, buf2+j, n);
+ cnt -= n;
+ off += n;
+ buf += n;
+ }
+ free(buf2);
+ return 0;
+}
+
+char*
+rwrite(Fid *f)
+{
+ if(f->busy == 0)
+ return Enotexist;
+ return Erdonly;
+}
+
+char *
+rclunk(Fid *f)
+{
+ f->busy = 0;
+ f->open = 0;
+ free(f->user);
+ sacfree(f->sac);
+ return 0;
+}
+
+char *
+rremove(Fid *f)
+{
+ f->busy = 0;
+ f->open = 0;
+ free(f->user);
+ sacfree(f->sac);
+ return Erdonly;
+}
+
+char *
+rstat(Fid *f)
+{
+ if(f->busy == 0)
+ return Enotexist;
+ sacstat(f->sac, thdr.stat);
+ return 0;
+}
+
+char *
+rwstat(Fid *f)
+{
+ if(f->busy == 0)
+ return Enotexist;
+ return Erdonly;
+}
+
+Sac*
+saccpy(Sac *s)
+{
+ Sac *ss;
+
+ ss = emalloc(sizeof(Sac));
+ *ss = *s;
+ if(ss->path)
+ ss->path->ref++;
+ return ss;
+}
+
+Path *
+pathalloc(Path *p, long blocks, int entry, int nentry)
+{
+ Path *pp = emalloc(sizeof(Path));
+
+ pp->ref = 1;
+ pp->blocks = blocks;
+ pp->entry = entry;
+ pp->nentry = nentry;
+ pp->up = p;
+ return pp;
+}
+
+void
+pathfree(Path *p)
+{
+ if(p == nil)
+ return;
+ p->ref--;
+ if(p->ref > 0)
+ return;
+
+ pathfree(p->up);
+ free(p);
+}
+
+
+void
+sacfree(Sac *s)
+{
+ pathfree(s->path);
+ free(s);
+}
+
+void
+sacstat(SacDir *s, char *buf)
+{
+ Dir dir;
+
+ memmove(dir.name, s->name, NAMELEN);
+ dir.qid = (Qid){getl(s->qid), 0};
+ dir.mode = getl(s->mode);
+ dir.length = getl(s->length);
+ if(dir.mode &CHDIR)
+ dir.length *= DIRLEN;
+ memmove(dir.uid, s->uid, NAMELEN);
+ memmove(dir.gid, s->gid, NAMELEN);
+ dir.atime = getl(s->atime);
+ dir.mtime = getl(s->mtime);
+ convD2M(&dir, buf);
+}
+
+void
+loadblock(void *buf, uchar *offset, int blocksize)
+{
+ long block, n;
+ ulong age;
+ int i, j;
+
+ block = getl(offset);
+ if(block < 0) {
+ block = -block;
+ cacheage++;
+ // age has wraped
+ if(cacheage == 0) {
+ for(i=0; i<CacheSize; i++)
+ cache[i].age = 0;
+ }
+ j = 0;
+ age = cache[0].age;
+ for(i=0; i<CacheSize; i++) {
+ if(cache[i].age < age) {
+ age = cache[i].age;
+ j = i;
+ }
+ if(cache[i].block != block)
+ continue;
+ memmove(buf, cache[i].data, blocksize);
+ cache[i].age = cacheage;
+ return;
+ }
+
+ n = getl(offset+OffsetSize);
+ if(n < 0)
+ n = -n;
+ n -= block;
+ if(unsac(buf, data+block, blocksize, n)<0)
+ sysfatal("unsac failed!");
+ memmove(cache[j].data, buf, blocksize);
+ cache[j].age = cacheage;
+ cache[j].block = block;
+ } else {
+ memmove(buf, data+block, blocksize);
+ }
+}
+
+Sac*
+sacparent(Sac *s)
+{
+ uchar *blocks;
+ SacDir *buf;
+ int per, i, n;
+ Path *p;
+
+ p = s->path;
+ if(p == nil || p->up == nil) {
+ pathfree(p);
+ *s = root;
+ return s;
+ }
+ p = p->up;
+
+ blocks = data + p->blocks;
+ per = blocksize/sizeof(SacDir);
+ i = p->entry/per;
+ n = per;
+ if(n > p->nentry-i*per)
+ n = p->nentry-i*per;
+ buf = emalloc(per*sizeof(SacDir));
+ loadblock(buf, blocks + i*OffsetSize, n*sizeof(SacDir));
+ s->SacDir = buf[p->entry-i*per];
+ free(buf);
+ p->ref++;
+ pathfree(s->path);
+ s->path = p;
+ return s;
+}
+
+int
+sacdirread(Sac *s, char *p, long off, long cnt)
+{
+ uchar *blocks;
+ SacDir *buf;
+ int iblock, per, i, j, ndir, n;
+
+ blocks = data + getl(s->blocks);
+ per = blocksize/sizeof(SacDir);
+ ndir = getl(s->length);
+ off /= DIRLEN;
+ cnt /= DIRLEN;
+ if(off >= ndir)
+ return 0;
+ if(cnt > ndir-off)
+ cnt = ndir-off;
+ iblock = -1;
+ buf = emalloc(per*sizeof(SacDir));
+ for(i=off; i<off+cnt; i++) {
+ j = i/per;
+ if(j != iblock) {
+ n = per;
+ if(n > ndir-j*per)
+ n = ndir-j*per;
+ loadblock(buf, blocks + j*OffsetSize, n*sizeof(SacDir));
+ iblock = j;
+ }
+ sacstat(buf+i-j*per, p);
+ p += DIRLEN;
+ }
+ free(buf);
+ return cnt*DIRLEN;
+}
+
+Sac*
+saclookup(Sac *s, char *name)
+{
+ int ndir;
+ int top, bot, i, j, k, n, per;
+ uchar *blocks;
+ SacDir *buf;
+ int iblock;
+ SacDir *sd;
+
+ if(strcmp(name, "..") == 0)
+ return sacparent(s);
+ blocks = data + getl(s->blocks);
+ per = blocksize/sizeof(SacDir);
+ ndir = getl(s->length);
+ buf = malloc(per*sizeof(SacDir));
+ if(buf == nil)
+ sysfatal("malloc failed");
+ iblock = -1;
+
+ if(1) {
+ // linear search
+ for(i=0; i<ndir; i++) {
+ j = i/per;
+ if(j != iblock) {
+ n = per;
+ if(n > ndir-j*per)
+ n = ndir-j*per;
+ loadblock(buf, blocks + j*OffsetSize, n*sizeof(SacDir));
+ iblock = j;
+ }
+ sd = buf+i-j*per;
+ k = strcmp(name, sd->name);
+ if(k == 0) {
+ s->path = pathalloc(s->path, getl(s->blocks), i, ndir);
+ s->SacDir = *sd;
+ free(buf);
+ return s;
+ }
+ }
+ free(buf);
+ return 0;
+ }
+
+ // binary search
+ top = ndir;
+ bot = 0;
+ while(bot != top){
+ i = (bot+top)>>1;
+ j = i/per;
+ if(j != iblock) {
+ n = per;
+ if(n > ndir-j*per)
+ n = ndir-j*per;
+ loadblock(buf, blocks + j*OffsetSize, n*sizeof(SacDir));
+ iblock = j;
+ }
+ j *= per;
+ sd = buf+i-j;
+ k = strcmp(name, sd->name);
+ if(k == 0) {
+ s->path = pathalloc(s->path, getl(s->blocks), i, ndir);
+ s->SacDir = *sd;
+ free(buf);
+ }
+ if(k < 0) {
+ top = i;
+ sd = buf;
+ if(strcmp(name, sd->name) < 0)
+ top = j;
+ } else {
+ bot = i+1;
+ if(ndir-j < per)
+ i = ndir-j;
+ else
+ i = per;
+ sd = buf+i-1;
+ if(strcmp(name, sd->name) > 0)
+ bot = j+i;
+ }
+ }
+ return 0;
+}
+
+Fid *
+newfid(int fid)
+{
+ Fid *f, *ff;
+
+ ff = 0;
+ for(f = fids; f; f = f->next)
+ if(f->fid == fid)
+ return f;
+ else if(!ff && !f->busy)
+ ff = f;
+ if(ff){
+ ff->fid = fid;
+ return ff;
+ }
+ f = emalloc(sizeof *f);
+ memset(f, 0 , sizeof(Fid));
+ f->fid = fid;
+ f->next = fids;
+ fids = f;
+ return f;
+}
+
+void
+io(void)
+{
+ char *err;
+ int n;
+
+ for(;;){
+ /*
+ * reading from a pipe or a network device
+ * will give an error after a few eof reads
+ * however, we cannot tell the difference
+ * between a zero-length read and an interrupt
+ * on the processes writing to us,
+ * so we wait for the error
+ */
+ n = read(mfd[0], mdata, sizeof mdata);
+ if(n == 0)
+ continue;
+ if(n < 0)
+ error("mount read");
+ if(convM2S(mdata, &rhdr, n) == 0)
+ continue;
+
+ if(debug)
+ fprint(2, "sacfs:<-%F\n", &rhdr);
+
+ thdr.data = mdata + MAXMSG;
+ if(!fcalls[rhdr.type])
+ err = "bad fcall type";
+ else
+ err = (*fcalls[rhdr.type])(newfid(rhdr.fid));
+ if(err){
+ thdr.type = Rerror;
+ strncpy(thdr.ename, err, ERRLEN);
+ }else{
+ thdr.type = rhdr.type + 1;
+ thdr.fid = rhdr.fid;
+ }
+ thdr.tag = rhdr.tag;
+ if(debug)
+ fprint(2, "ramfs:->%F\n", &thdr);/**/
+ n = convS2M(&thdr, mdata);
+ if(write(mfd[1], mdata, n) != n)
+ error("mount write");
+ }
+}
+
+int
+perm(Fid *f, Sac *s, int p)
+{
+ ulong perm = getl(s->mode);
+ if((p*Pother) & perm)
+ return 1;
+ if(strcmp(f->user, s->gid)==0 && ((p*Pgroup) & perm))
+ return 1;
+ if(strcmp(f->user, s->uid)==0 && ((p*Powner) & perm))
+ return 1;
+ return 0;
+}
+
+void
+init(char *file)
+{
+ SacHeader *hdr;
+ Dir dir;
+ int fd;
+ int i;
+ uchar *p;
+
+ notify(notifyf);
+ strcpy(user, getuser());
+
+
+ if(dirstat(file, &dir) < 0)
+ error("bad file");
+ data = emalloc(dir.length);
+ fd = open(file, OREAD);
+ if(fd < 0)
+ error("opening file");
+ if(read(fd, data, dir.length) < dir.length)
+ error("reading file");
+ hdr = (SacHeader*)data;
+ if(getl(hdr->magic) != Magic)
+ error("bad magic");
+ if(getl(hdr->length) != (ulong)(dir.length))
+ error("bad length");
+ blocksize = getl(hdr->blocksize);
+ root.SacDir = *(SacDir*)(data + sizeof(SacHeader));
+ p = malloc(CacheSize*blocksize);
+ if(p == nil)
+ error("allocating cache");
+ for(i=0; i<CacheSize; i++) {
+ cache[i].data = p;
+ p += blocksize;
+ }
+}
+
+void
+error(char *s)
+{
+ fprint(2, "%s: %s: %r\n", argv0, s);
+ exits(s);
+}
+
+void *
+emalloc(ulong n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(!p)
+ error("out of memory");
+ return p;
+}
+
+void *
+erealloc(void *p, ulong n)
+{
+ p = realloc(p, n);
+ if(!p)
+ error("out of memory");
+ return p;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-i infd outfd] [-s] [-m mountpoint] sacfsfile\n", argv0);
+ exits("usage");
+}
+
+ulong
+getl(void *p)
+{
+ uchar *a = p;
+
+ return (a[0]<<24) | (a[1]<<16) | (a[2]<<8) | a[3];
+}
+
diff --git a/sys/src/cmd/disk/sacfs/sacfs.h b/sys/src/cmd/disk/sacfs/sacfs.h
new file mode 100755
index 000000000..90c8b6ee8
--- /dev/null
+++ b/sys/src/cmd/disk/sacfs/sacfs.h
@@ -0,0 +1,29 @@
+typedef struct SacHeader SacHeader;
+typedef struct SacDir SacDir;
+
+enum {
+ Magic = 0x5acf5,
+ NAMELEN = 28,
+};
+
+struct SacDir
+{
+ char name[NAMELEN];
+ char uid[NAMELEN];
+ char gid[NAMELEN];
+ uchar qid[4];
+ uchar mode[4];
+ uchar atime[4];
+ uchar mtime[4];
+ uchar length[8];
+ uchar blocks[8];
+};
+
+struct SacHeader
+{
+ uchar magic[4];
+ uchar length[8];
+ uchar blocksize[4];
+ uchar md5[16];
+};
+
diff --git a/sys/src/cmd/disk/sacfs/ssort.h b/sys/src/cmd/disk/sacfs/ssort.h
new file mode 100755
index 000000000..3f9193942
--- /dev/null
+++ b/sys/src/cmd/disk/sacfs/ssort.h
@@ -0,0 +1,2 @@
+int ssortbyte(uchar a[], int p[], int shared[], int n);
+int ssort(int a[], int s[], int);
diff --git a/sys/src/cmd/disk/sacfs/ssort6.c b/sys/src/cmd/disk/sacfs/ssort6.c
new file mode 100755
index 000000000..a5032ec07
--- /dev/null
+++ b/sys/src/cmd/disk/sacfs/ssort6.c
@@ -0,0 +1,419 @@
+#include <u.h>
+#include <libc.h>
+#include "ssort.h"
+
+#define pred(i, h) ((t=(i)-(h))<0? t+n: t)
+#define succ(i, h) ((t=(i)+(h))>=n? t-n: t)
+
+enum
+{
+ BUCK = ~(~0u>>1), /* high bit */
+ MAXI = ~0u>>1, /* biggest int */
+};
+
+typedef int Elem;
+static void qsort2(Elem*, Elem*, int n);
+static int ssortit(int a[], int p[], int s[], int q[], int n, int h, int *pe, int nbuck);
+static void lift(int si, int q[], int i);
+int sharedlen(int i, int j, int s[], int q[]);
+
+int
+ssort(int a[], int s[], int n)
+{
+ int i, l;
+ int c, cc, ncc, lab, cum, nbuck;
+ int k;
+ int *p = 0;
+ int result = 0;
+ int *q = 0;
+ int *al;
+ int *pl;
+
+# define finish(r) if(1){result=r; goto out;}else
+
+ for(k=0,i=0; i<n; i++)
+ if(a[i] > k)
+ k = a[i]; /* max element */
+ k++;
+ if(k>n)
+ finish(2);
+
+ nbuck = 0;
+ p = malloc(n*sizeof(int));
+ if(p == 0)
+ finish(1);
+
+ if(s) { /* shared lengths */
+ q = malloc(((n+1)>>1)*sizeof(int));
+ if(q == 0)
+ finish(1);
+ for(i=0; i<n; i++)
+ s[i] = q[i>>1] = MAXI;
+ q[i>>1] = MAXI;
+ }
+
+ pl = p + n - k;
+ al = a;
+ memset(pl, -1, k*sizeof(int));
+
+ for(i=0; i<n; i++) { /* (1) link */
+ l = a[i];
+ al[i] = pl[l];
+ pl[l] = i;
+ }
+
+ for(i=0; i<k; i++) /* check input - no holes */
+ if(pl[i]<0)
+ finish(2);
+
+
+ lab = 0; /* (2) create p and label a */
+ cum = 0;
+ i = 0;
+ for(c = 0; c < k; c++){
+ for(cc = pl[c]; cc != -1; cc = ncc){
+ ncc = al[cc];
+ al[cc] = lab;
+ cum++;
+ p[i++] = cc;
+ }
+ if(lab + 1 == cum) {
+ i--;
+ } else {
+ p[i-1] |= BUCK;
+ nbuck++;
+ }
+ if(s) {
+ s[lab] = 0;
+ lift(0, q, lab);
+ }
+ lab = cum;
+ }
+
+ ssortit(a, p, s, q, n, 1, p+i, nbuck);
+ memcpy(a, p, n*sizeof(int));
+
+out:
+ free(p);
+ free(q);
+ return result;
+}
+
+/*
+ * calculate the suffix array for the bytes in buf,
+ * terminated by a unique end marker less than any character in buf
+ * returns the index of the identity permutation,
+ * and -1 if there was an error.
+ */
+int
+ssortbyte(uchar buf[], int p[], int s[], int n)
+{
+ int *a, *q, buckets[256*256];
+ int i, last, lastc, cum, c, cc, ncc, lab, id, nbuck;
+
+ a = malloc((n+1)*sizeof(int));
+ if(a == nil)
+ return -1;
+
+ q = nil;
+ if(s) { /* shared lengths */
+ q = malloc(((n+2)>>1)*sizeof(int));
+ if(q == nil){
+ free(a);
+ return -1;
+ }
+ for(i=0; i<n+1; i++)
+ s[i] = q[i>>1] = MAXI;
+ q[i>>1] = MAXI;
+ }
+
+ memset(buckets, -1, sizeof(buckets));
+ c = buf[n-1] << 8;
+ last = c;
+ for(i = n - 2; i >= 0; i--){
+ c = (buf[i] << 8) | (c >> 8);
+ a[i] = buckets[c];
+ buckets[c] = i;
+ }
+
+ /*
+ * end of string comes before anything else
+ */
+ a[n] = 0;
+ if(s) {
+ s[0] = 0;
+ lift(0, q, 0);
+ }
+
+ lab = 1;
+ cum = 1;
+ i = 0;
+ lastc = 1; /* won't match c & 0xff00 for any c */
+ nbuck = 0;
+ for(c = 0; c < 256*256; c++) {
+ /*
+ * last character is followed by unique end of string
+ */
+ if(c == last) {
+ a[n-1] = lab;
+ if(s) {
+ s[lab] = 0;
+ lift(0, q, lab);
+ }
+ cum++;
+ lab++;
+ lastc = c & 0xff00;
+ }
+
+ for(cc = buckets[c]; cc != -1; cc = ncc) {
+ ncc = a[cc];
+ a[cc] = lab;
+ cum++;
+ p[i++] = cc;
+ }
+ if(lab == cum)
+ continue;
+ if(lab + 1 == cum)
+ i--;
+ else {
+ p[i - 1] |= BUCK;
+ nbuck++;
+ }
+ if(s) {
+ cc = (c & 0xff00) == lastc;
+ s[lab] = cc;
+ lift(cc, q, lab);
+ }
+ lastc = c & 0xff00;
+ lab = cum;
+ }
+
+ id = ssortit(a, p, s, q, n+1, 2, p+i, nbuck);
+ free(a);
+ free(q);
+ return id;
+}
+
+/*
+ * can get an interval for the shared lengths from [h,3h) by recording h
+ * rather than h + sharedlen(..) when relabelling. if so, no calls to lift are needed.
+ */
+static int
+ssortit(int a[], int p[], int shared[], int q[], int n, int h, int *pe, int nbuck)
+{
+ int *s, *ss, *packing, *sorting;
+ int v, sv, vv, packed, lab, t, i;
+
+ for(; h < n && p < pe; h=2*h) {
+ packing = p;
+ nbuck = 0;
+
+ for(sorting = p; sorting < pe; sorting = s){
+ /*
+ * find length of stuff to sort
+ */
+ lab = a[*sorting];
+ for(s = sorting; ; s++) {
+ sv = *s;
+ v = a[succ(sv & ~BUCK, h)];
+ if(v & BUCK)
+ v = lab;
+ a[sv & ~BUCK] = v | BUCK;
+ if(sv & BUCK)
+ break;
+ }
+ *s++ &= ~BUCK;
+ nbuck++;
+
+ qsort2(sorting, a, s - sorting);
+
+ v = a[*sorting];
+ a[*sorting] = lab;
+ packed = 0;
+ for(ss = sorting + 1; ss < s; ss++) {
+ sv = *ss;
+ vv = a[sv];
+ if(vv == v) {
+ *packing++ = ss[-1];
+ packed++;
+ } else {
+ if(packed) {
+ *packing++ = ss[-1] | BUCK;
+ }
+ lab += packed + 1;
+ if(shared) {
+ v = h + sharedlen(v&~BUCK, vv&~BUCK, shared, q);
+ shared[lab] = v;
+ lift(v, q, lab);
+ }
+ packed = 0;
+ v = vv;
+ }
+ a[sv] = lab;
+ }
+ if(packed) {
+ *packing++ = ss[-1] | BUCK;
+ }
+ }
+ pe = packing;
+ }
+
+ /*
+ * reconstuct the permutation matrix
+ * return index of the entire string
+ */
+ v = a[0];
+ for(i = 0; i < n; i++)
+ p[a[i]] = i;
+
+ return v;
+}
+
+/* Propagate a new entry s[i], with value si, into q[]. */
+static void
+lift(int si, int q[], int i)
+{
+ for(i >>= 1; q[i] > si; i &= ~-i) /* squash the low 1-bit */
+ q[i] = si;
+}
+
+/*
+ * Find in s[] the minimum value over the interval i<=k<=j, using
+ * tree q[] to do logarithmic, rather than linear search
+ */
+int
+sharedlen(int i, int j, int s[], int q[])
+{
+ int k, v;
+ int min = MAXI;
+ int max = 0;
+
+ if(i > j) { /* swap i & j */
+ i ^= j;
+ j ^= i;
+ i ^= j;
+ }
+ i++;
+ while(i <= j && min > max) {
+ k = i & -i;
+ if(i & 1)
+ v = s[i];
+ else
+ v = q[i>>1];
+ if(i+k > j+1) {
+ if(v > max)
+ max = v;
+ if(s[i] < min)
+ min = s[i];
+ i++;
+ } else {
+ if(v < min)
+ min = v;
+ i += k;
+ }
+ }
+ return min;
+}
+
+/*
+ * qsort specialized for sorting permutations based on successors
+ */
+static void
+vecswap2(Elem *a, Elem *b, int n)
+{
+ while (n-- > 0) {
+ Elem t = *a;
+ *a++ = *b;
+ *b++ = t;
+ }
+}
+
+#define swap2(a, b) { t = *(a); *(a) = *(b); *(b) = t; }
+#define ptr2char(i, asucc) (asucc[*(i)])
+
+static Elem*
+med3(Elem *a, Elem *b, Elem *c, Elem *asucc)
+{
+ Elem va, vb, vc;
+
+ if ((va=ptr2char(a, asucc)) == (vb=ptr2char(b, asucc)))
+ return a;
+ if ((vc=ptr2char(c, asucc)) == va || vc == vb)
+ return c;
+ return va < vb ?
+ (vb < vc ? b : (va < vc ? c : a))
+ : (vb > vc ? b : (va < vc ? a : c));
+}
+
+static void
+inssort(Elem *a, Elem *asucc, int n)
+{
+ Elem *pi, *pj, t;
+
+ for (pi = a + 1; --n > 0; pi++)
+ for (pj = pi; pj > a; pj--) {
+ if(ptr2char(pj-1, asucc) <= ptr2char(pj, asucc))
+ break;
+ swap2(pj, pj-1);
+ }
+}
+
+static void
+qsort2(Elem *a, Elem *asucc, int n)
+{
+ Elem d, r, partval;
+ Elem *pa, *pb, *pc, *pd, *pl, *pm, *pn, t;
+
+ if (n < 15) {
+ inssort(a, asucc, n);
+ return;
+ }
+ pl = a;
+ pm = a + (n >> 1);
+ pn = a + (n-1);
+ if (n > 30) { /* On big arrays, pseudomedian of 9 */
+ d = (n >> 3);
+ pl = med3(pl, pl+d, pl+2*d, asucc);
+ pm = med3(pm-d, pm, pm+d, asucc);
+ pn = med3(pn-2*d, pn-d, pn, asucc);
+ }
+ pm = med3(pl, pm, pn, asucc);
+ swap2(a, pm);
+ partval = ptr2char(a, asucc);
+ pa = pb = a + 1;
+ pc = pd = a + n-1;
+ for (;;) {
+ while (pb <= pc && (r = ptr2char(pb, asucc)-partval) <= 0) {
+ if (r == 0) {
+ swap2(pa, pb);
+ pa++;
+ }
+ pb++;
+ }
+ while (pb <= pc && (r = ptr2char(pc, asucc)-partval) >= 0) {
+ if (r == 0) {
+ swap2(pc, pd);
+ pd--;
+ }
+ pc--;
+ }
+ if (pb > pc)
+ break;
+ swap2(pb, pc);
+ pb++;
+ pc--;
+ }
+ pn = a + n;
+ r = pa-a;
+ if(pb-pa < r)
+ r = pb-pa;
+ vecswap2(a, pb-r, r);
+ r = pn-pd-1;
+ if(pd-pc < r)
+ r = pd-pc;
+ vecswap2(pb, pn-r, r);
+ if ((r = pb-pa) > 1)
+ qsort2(a, asucc, r);
+ if ((r = pd-pc) > 1)
+ qsort2(a + n-r, asucc, r);
+}
diff --git a/sys/src/cmd/disk/sacfs/unsac.c b/sys/src/cmd/disk/sacfs/unsac.c
new file mode 100755
index 000000000..e3f1ab09c
--- /dev/null
+++ b/sys/src/cmd/disk/sacfs/unsac.c
@@ -0,0 +1,620 @@
+#include <u.h>
+#include <libc.h>
+#include "sac.h"
+
+typedef struct Huff Huff;
+typedef struct Mtf Mtf;
+typedef struct Decode Decode;
+
+enum
+{
+ ZBase = 2, /* base of code to encode 0 runs */
+ LitBase = ZBase-1, /* base of literal values */
+ MaxLit = 256,
+
+ MaxLeaf = MaxLit+LitBase,
+ MaxHuffBits = 16, /* max bits in a huffman code */
+ MaxFlatbits = 5, /* max bits decoded in flat table */
+
+ CombLog = 4,
+ CombSpace = 1 << CombLog, /* mtf speedup indices spacing */
+ CombMask = CombSpace - 1,
+};
+
+struct Mtf
+{
+ int maxcomb; /* index of last valid comb */
+ uchar prev[MaxLit];
+ uchar next[MaxLit];
+ uchar comb[MaxLit / CombSpace + 1];
+};
+
+struct Huff
+{
+ int maxbits;
+ int flatbits;
+ ulong flat[1<<MaxFlatbits];
+ ulong maxcode[MaxHuffBits];
+ ulong last[MaxHuffBits];
+ ulong decode[MaxLeaf];
+};
+
+struct Decode{
+ Huff tab;
+ Mtf mtf;
+ int nbits;
+ ulong bits;
+ int nzero;
+ int base;
+ ulong maxblocksym;
+
+ jmp_buf errjmp;
+
+ uchar *src; /* input buffer */
+ uchar *smax; /* limit */
+};
+
+static void fatal(Decode *dec, char*);
+
+static int hdec(Decode*);
+static void recvtab(Decode*, Huff*, int, ushort*);
+static ulong bitget(Decode*, int);
+static int mtf(uchar*, int);
+
+#define FORWARD 0
+
+static void
+mtflistinit(Mtf *m, uchar *front, int n)
+{
+ int last, me, f, i, comb;
+
+ if(n == 0)
+ return;
+
+ /*
+ * add all entries to free list
+ */
+ last = MaxLit - 1;
+ for(i = 0; i < MaxLit; i++){
+ m->prev[i] = last;
+ m->next[i] = i + 1;
+ last = i;
+ }
+ m->next[last] = 0;
+ f = 0;
+
+ /*
+ * pull valid entries off free list and enter into mtf list
+ */
+ comb = 0;
+ last = front[0];
+ for(i = 0; i < n; i++){
+ me = front[i];
+
+ f = m->next[me];
+ m->prev[f] = m->prev[me];
+ m->next[m->prev[f]] = f;
+
+ m->next[last] = me;
+ m->prev[me] = last;
+ last = me;
+ if((i & CombMask) == 0)
+ m->comb[comb++] = me;
+ }
+
+ /*
+ * pad out the list with dummies to the next comb,
+ * using free entries
+ */
+ for(; i & CombMask; i++){
+ me = f;
+
+ f = m->next[me];
+ m->prev[f] = m->prev[me];
+ m->next[m->prev[f]] = f;
+
+ m->next[last] = me;
+ m->prev[me] = last;
+ last = me;
+ }
+ me = front[0];
+ m->next[last] = me;
+ m->prev[me] = last;
+ m->comb[comb] = me;
+ m->maxcomb = comb;
+}
+
+static int
+mtflist(Mtf *m, int pos)
+{
+ uchar *next, *prev, *mycomb;
+ int c, c0, pc, nc, off;
+
+ if(pos == 0)
+ return m->comb[0];
+
+ next = m->next;
+ prev = m->prev;
+ mycomb = &m->comb[pos >> CombLog];
+ off = pos & CombMask;
+ if(off >= CombSpace / 2){
+ c = mycomb[1];
+ for(; off < CombSpace; off++)
+ c = prev[c];
+ }else{
+ c = *mycomb;
+ for(; off; off--)
+ c = next[c];
+ }
+
+ nc = next[c];
+ pc = prev[c];
+ prev[nc] = pc;
+ next[pc] = nc;
+
+ for(; mycomb > m->comb; mycomb--)
+ *mycomb = prev[*mycomb];
+ c0 = *mycomb;
+ *mycomb = c;
+ mycomb[m->maxcomb] = c;
+
+ next[c] = c0;
+ pc = prev[c0];
+ prev[c] = pc;
+ prev[c0] = c;
+ next[pc] = c;
+ return c;
+}
+
+static void
+hdecblock(Decode *dec, ulong n, ulong I, uchar *buf, ulong *sums, ulong *prev)
+{
+ ulong i, nn, sum;
+ int m, z, zz, c;
+
+ nn = I;
+ n--;
+ i = 0;
+again:
+ for(; i < nn; i++){
+ while((m = hdec(dec)) == 0 && i + dec->nzero < n)
+ ;
+ if(z = dec->nzero){
+ dec->nzero = 0;
+ c = dec->mtf.comb[0];
+ sum = sums[c];
+ sums[c] = sum + z;
+
+ z += i;
+ zz = z;
+ if(i < I && z > I){
+ zz = I;
+ z++;
+ }
+
+ zagain:
+ for(; i < zz; i++){
+ buf[i] = c;
+ prev[i] = sum++;
+ }
+ if(i != z){
+ zz = z;
+ nn = ++n;
+ i++;
+ goto zagain;
+ }
+ if(i == nn){
+ if(i == n)
+ return;
+ nn = ++n;
+ i++;
+ }
+ }
+
+ c = mtflist(&dec->mtf, m);
+
+ buf[i] = c;
+ sum = sums[c];
+ prev[i] = sum++;
+ sums[c] = sum;
+
+ }
+ if(i == n)
+ return;
+ nn = ++n;
+ i++;
+ goto again;
+}
+
+int
+unsac(uchar *dst, uchar *src, int n, int nsrc)
+{
+ Decode *dec;
+ uchar *buf, *front;
+ ulong *prev, *sums;
+ ulong sum, i, I;
+ int m, j, c;
+
+ dec = malloc(sizeof *dec);
+ buf = malloc(n+2);
+ prev = malloc((n+2) * sizeof *prev);
+ front = malloc(MaxLit * sizeof *front);
+ sums = malloc(MaxLit * sizeof *sums);
+
+ if(dec == nil || buf == nil || prev == nil || front == nil || sums == nil || setjmp(dec->errjmp)){
+ free(dec);
+ free(buf);
+ free(prev);
+ free(front);
+ free(sums);
+ return -1;
+ }
+
+ dec->src = src;
+ dec->smax = src + nsrc;
+
+ dec->nbits = 0;
+ dec->bits = 0;
+ dec->nzero = 0;
+ for(i = 0; i < MaxLit; i++)
+ front[i] = i;
+
+ n++;
+ I = bitget(dec, 16);
+ if(I >= n)
+ fatal(dec, "corrupted input");
+
+ /*
+ * decode the character usage map
+ */
+ for(i = 0; i < MaxLit; i++)
+ sums[i] = 0;
+ c = bitget(dec, 1);
+ for(i = 0; i < MaxLit; ){
+ m = bitget(dec, 8) + 1;
+ while(m--){
+ if(i >= MaxLit)
+ fatal(dec, "corrupted char map");
+ front[i++] = c;
+ }
+ c = c ^ 1;
+ }
+
+ /*
+ * initialize mtf state
+ */
+ c = 0;
+ for(i = 0; i < MaxLit; i++)
+ if(front[i])
+ front[c++] = i;
+ mtflistinit(&dec->mtf, front, c);
+ dec->maxblocksym = c + LitBase;
+
+ /*
+ * huffman decoding, move to front decoding,
+ * along with character counting
+ */
+ dec->base = 1;
+ recvtab(dec, &dec->tab, MaxLeaf, nil);
+ hdecblock(dec, n, I, buf, sums, prev);
+
+ sum = 1;
+ for(i = 0; i < MaxLit; i++){
+ c = sums[i];
+ sums[i] = sum;
+ sum += c;
+ }
+
+ i = 0;
+ for(j = n - 2; j >= 0; j--){
+ if(i > n || i < 0 || i == I)
+ fatal(dec, "corrupted data");
+ c = buf[i];
+ dst[j] = c;
+ i = prev[i] + sums[c];
+ }
+
+ free(dec);
+ free(buf);
+ free(prev);
+ free(front);
+ free(sums);
+ return n;
+}
+
+static ulong
+bitget(Decode *dec, int nb)
+{
+ int c;
+
+ while(dec->nbits < nb){
+ if(dec->src >= dec->smax)
+ fatal(dec, "premature eof 1");
+ c = *dec->src++;
+ dec->bits <<= 8;
+ dec->bits |= c;
+ dec->nbits += 8;
+ }
+ dec->nbits -= nb;
+ return (dec->bits >> dec->nbits) & ((1 << nb) - 1);
+}
+
+static void
+fillbits(Decode *dec)
+{
+ int c;
+
+ while(dec->nbits < 24){
+ if(dec->src >= dec->smax)
+ fatal(dec, "premature eof 2");
+ c = *dec->src++;
+ dec->bits <<= 8;
+ dec->bits |= c;
+ dec->nbits += 8;
+ }
+}
+
+/*
+ * decode one symbol
+ */
+static int
+hdecsym(Decode *dec, Huff *h, int b)
+{
+ long c;
+ ulong bits;
+ int nbits;
+
+ bits = dec->bits;
+ nbits = dec->nbits;
+ for(; (c = bits >> (nbits - b)) > h->maxcode[b]; b++)
+ ;
+ if(b > h->maxbits)
+ fatal(dec, "too many bits consumed");
+ dec->nbits = nbits - b;
+ return h->decode[h->last[b] - c];
+}
+
+static int
+hdec(Decode *dec)
+{
+ ulong c;
+ int nbits, nb;
+
+ if(dec->nbits < dec->tab.maxbits)
+ fillbits(dec);
+ nbits = dec->nbits;
+ dec->bits &= (1 << nbits) - 1;
+ c = dec->tab.flat[dec->bits >> (nbits - dec->tab.flatbits)];
+ nb = c & 0xff;
+ c >>= 8;
+ if(nb == 0xff)
+ c = hdecsym(dec, &dec->tab, c);
+ else
+ dec->nbits = nbits - nb;
+
+ /*
+ * reverse funny run-length coding
+ */
+ if(c < ZBase){
+ dec->nzero += dec->base << c;
+ dec->base <<= 1;
+ return 0;
+ }
+
+ dec->base = 1;
+ c -= LitBase;
+ return c;
+}
+
+static void
+hufftab(Decode *dec, Huff *h, char *hb, ulong *bitcount, int maxleaf, int maxbits, int flatbits)
+{
+ ulong c, mincode, code, nc[MaxHuffBits];
+ int i, b, ec;
+
+ h->maxbits = maxbits;
+ if(maxbits < 0)
+ return;
+
+ code = 0;
+ c = 0;
+ for(b = 0; b <= maxbits; b++){
+ h->last[b] = c;
+ c += bitcount[b];
+ mincode = code << 1;
+ nc[b] = mincode;
+ code = mincode + bitcount[b];
+ if(code > (1 << b))
+ fatal(dec, "corrupted huffman table");
+ h->maxcode[b] = code - 1;
+ h->last[b] += code - 1;
+ }
+ if(code != (1 << maxbits))
+ fatal(dec, "huffman table not full");
+ if(flatbits > maxbits)
+ flatbits = maxbits;
+ h->flatbits = flatbits;
+
+ b = 1 << flatbits;
+ for(i = 0; i < b; i++)
+ h->flat[i] = ~0;
+
+ /*
+ * initialize the flat table to include the minimum possible
+ * bit length for each code prefix
+ */
+ for(b = maxbits; b > flatbits; b--){
+ code = h->maxcode[b];
+ if(code == -1)
+ break;
+ mincode = code + 1 - bitcount[b];
+ mincode >>= b - flatbits;
+ code >>= b - flatbits;
+ for(; mincode <= code; mincode++)
+ h->flat[mincode] = (b << 8) | 0xff;
+ }
+
+ for(i = 0; i < maxleaf; i++){
+ b = hb[i];
+ if(b == -1)
+ continue;
+ c = nc[b]++;
+ if(b <= flatbits){
+ code = (i << 8) | b;
+ ec = (c + 1) << (flatbits - b);
+ if(ec > (1<<flatbits))
+ fatal(dec, "flat code too big");
+ for(c <<= (flatbits - b); c < ec; c++)
+ h->flat[c] = code;
+ }else{
+ c = h->last[b] - c;
+ if(c >= maxleaf)
+ fatal(dec, "corrupted huffman table");
+ h->decode[c] = i;
+ }
+ }
+}
+
+static void
+elimBit(int b, char *tmtf, int maxbits)
+{
+ int bb;
+
+ for(bb = 0; bb < maxbits; bb++)
+ if(tmtf[bb] == b)
+ break;
+ while(++bb <= maxbits)
+ tmtf[bb - 1] = tmtf[bb];
+}
+
+static int
+elimBits(int b, ulong *bused, char *tmtf, int maxbits)
+{
+ int bb, elim;
+
+ if(b < 0)
+ return 0;
+
+ elim = 0;
+
+ /*
+ * increase bits counts for all descendants
+ */
+ for(bb = b + 1; bb < maxbits; bb++){
+ bused[bb] += 1 << (bb - b);
+ if(bused[bb] == (1 << bb)){
+ elim++;
+ elimBit(bb, tmtf, maxbits);
+ }
+ }
+
+ /*
+ * steal bits from parent & check for fullness
+ */
+ for(; b >= 0; b--){
+ bused[b]++;
+ if(bused[b] == (1 << b)){
+ elim++;
+ elimBit(b, tmtf, maxbits);
+ }
+ if((bused[b] & 1) == 0)
+ break;
+ }
+ return elim;
+}
+
+static void
+recvtab(Decode *dec, Huff *tab, int maxleaf, ushort *map)
+{
+ ulong bitcount[MaxHuffBits+1], bused[MaxHuffBits+1];
+ char tmtf[MaxHuffBits+1], *hb;
+ int i, b, ttb, m, maxbits, max, elim;
+
+ hb = malloc(MaxLeaf * sizeof *hb);
+ if(hb == nil)
+ fatal(dec, "out of memory");
+
+ /*
+ * read the tables for the tables
+ */
+ max = 8;
+ for(i = 0; i <= MaxHuffBits; i++){
+ bitcount[i] = 0;
+ tmtf[i] = i;
+ bused[i] = 0;
+ }
+ tmtf[0] = -1;
+ tmtf[max] = 0;
+ elim = 0;
+ maxbits = -1;
+ for(i = 0; i <= MaxHuffBits && elim != max; i++){
+ ttb = 4;
+ while(max - elim < (1 << (ttb-1)))
+ ttb--;
+ b = bitget(dec, ttb);
+ if(b > max - elim)
+ fatal(dec, "corrupted huffman table table");
+ b = tmtf[b];
+ hb[i] = b;
+ bitcount[b]++;
+ if(b > maxbits)
+ maxbits = b;
+
+ elim += elimBits(b, bused, tmtf, max);
+ }
+ if(elim != max)
+ fatal(dec, "incomplete huffman table table");
+ hufftab(dec, tab, hb, bitcount, i, maxbits, MaxFlatbits);
+ for(i = 0; i <= MaxHuffBits; i++){
+ tmtf[i] = i;
+ bitcount[i] = 0;
+ bused[i] = 0;
+ }
+ tmtf[0] = -1;
+ tmtf[MaxHuffBits] = 0;
+ elim = 0;
+ maxbits = -1;
+ for(i = 0; i < maxleaf && elim != MaxHuffBits; i++){
+ if(dec->nbits <= tab->maxbits)
+ fillbits(dec);
+ dec->bits &= (1 << dec->nbits) - 1;
+ m = tab->flat[dec->bits >> (dec->nbits - tab->flatbits)];
+ b = m & 0xff;
+ m >>= 8;
+ if(b == 0xff)
+ m = hdecsym(dec, tab, m);
+ else
+ dec->nbits -= b;
+ b = tmtf[m];
+ for(; m > 0; m--)
+ tmtf[m] = tmtf[m-1];
+ tmtf[0] = b;
+
+ if(b > MaxHuffBits)
+ fatal(dec, "bit length too big");
+ m = i;
+ if(map != nil)
+ m = map[m];
+ hb[m] = b;
+ bitcount[b]++;
+ if(b > maxbits)
+ maxbits = b;
+ elim += elimBits(b, bused, tmtf, MaxHuffBits);
+ }
+ if(elim != MaxHuffBits && elim != 0)
+ fatal(dec, "incomplete huffman table");
+ if(map != nil)
+ for(; i < maxleaf; i++)
+ hb[map[i]] = -1;
+
+ hufftab(dec, tab, hb, bitcount, i, maxbits, MaxFlatbits);
+
+ free(hb);
+}
+
+static void
+fatal(Decode *dec, char *msg)
+{
+ print("%s: %s\n", argv0, msg);
+ longjmp(dec->errjmp, 1);
+}