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