1implement Dbfs; 2 3# 4# Copyright © 1999 Vita Nuova Limited. All rights reserved. 5# Revisions copyright © 2002 Vita Nuova Holdings Limited. All rights reserved. 6# 7 8include "sys.m"; 9 sys: Sys; 10 Qid: import Sys; 11 12include "draw.m"; 13 14include "arg.m"; 15 16include "styx.m"; 17 styx: Styx; 18 Tmsg, Rmsg: import styx; 19 20include "styxservers.m"; 21 styxservers: Styxservers; 22 Fid, Styxserver, Navigator, Navop: import styxservers; 23 Enotfound, Eperm, Ebadarg: import styxservers; 24 25include "bufio.m"; 26 bufio: Bufio; 27 Iobuf: import bufio; 28 29Record: adt { 30 id: int; # file number in directory 31 x: int; # index in file 32 dirty: int; # modified but not written 33 vers: int; # version 34 data: array of byte; 35 36 new: fn(x: array of byte): ref Record; 37 print: fn(r: self ref Record, fd: ref Sys->FD); 38 qid: fn(r: self ref Record): Sys->Qid; 39}; 40 41Database: adt { 42 name: string; 43 file: ref Iobuf; 44 records: array of ref Record; 45 dirty: int; 46 vers: int; 47 nextid: int; 48 49 findrec: fn(db: self ref Database, id: int): ref Record; 50}; 51 52Dbfs: module 53{ 54 init: fn(nil: ref Draw->Context, nil: list of string); 55}; 56 57Qdir, Qnew, Qdata: con iota; 58 59clockfd: ref Sys->FD; 60stderr: ref Sys->FD; 61database: ref Database; 62user: string; 63Eremoved: con "file removed"; 64 65usage() 66{ 67 sys->fprint(stderr, "Usage: dbfs [-a|-b|-ac|-bc] [-D] file mountpoint\n"); 68 raise "fail:usage"; 69} 70 71nomod(s: string) 72{ 73 sys->fprint(stderr, "dbfs: can't load %s: %r\n", s); 74 raise "fail:load"; 75} 76 77init(nil: ref Draw->Context, args: list of string) 78{ 79 sys = load Sys Sys->PATH; 80 sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); 81 stderr = sys->fildes(2); 82 styx = load Styx Styx->PATH; 83 if(styx == nil) 84 nomod(Styx->PATH); 85 styx->init(); 86 styxservers = load Styxservers Styxservers->PATH; 87 if(styxservers == nil) 88 nomod(Styxservers->PATH); 89 styxservers->init(styx); 90 bufio = load Bufio Bufio->PATH; 91 if(bufio == nil) 92 nomod(Bufio->PATH); 93 94 arg := load Arg Arg->PATH; 95 if(arg == nil) 96 nomod(Arg->PATH); 97 arg->init(args); 98 flags := Sys->MREPL; 99 copt := 0; 100 empty := 0; 101 while((o := arg->opt()) != 0) 102 case o { 103 'a' => flags = Sys->MAFTER; 104 'b' => flags = Sys->MBEFORE; 105 'c' => copt = 1; 106 'e' => empty = 1; 107 'D' => styxservers->traceset(1); 108 * => usage(); 109 } 110 args = arg->argv(); 111 arg = nil; 112 113 if(len args != 2) 114 usage(); 115 if(copt) 116 flags |= Sys->MCREATE; 117 file := hd args; 118 args = tl args; 119 mountpt := hd args; 120 121 df := bufio->open(file, Sys->OREAD); 122 if(df == nil && empty){ 123 (rc, nil) := sys->stat(file); 124 if(rc < 0) 125 df = bufio->create(file, Sys->OREAD, 8r600); 126 } 127 if(df == nil){ 128 sys->fprint(stderr, "dbfs: can't open %s: %r\n", file); 129 raise "fail:open"; 130 } 131 (db, err) := dbread(ref Database(file, df, nil, 0, 0, 0)); 132 if(db == nil){ 133 sys->fprint(stderr, "dbfs: can't read %s: %s\n", file, err); 134 raise "fail:dbread"; 135 } 136 db.file = nil; 137# dbprint(db); 138 database = db; 139 140 sys->pctl(Sys->FORKFD, nil); 141 142 user = rf("/dev/user"); 143 if(user == nil) 144 user = "inferno"; 145 146 fds := array[2] of ref Sys->FD; 147 if(sys->pipe(fds) < 0){ 148 sys->fprint(stderr, "dbfs: can't create pipe: %r\n"); 149 raise "fail:pipe"; 150 } 151 152 navops := chan of ref Navop; 153 spawn navigator(navops); 154 155 (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qdir); 156 fds[0] = nil; 157 158 pidc := chan of int; 159 spawn serveloop(tchan, srv, pidc, navops); 160 <-pidc; 161 162 if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) { 163 sys->fprint(stderr, "dbfs: mount failed: %r\n"); 164 raise "fail:mount"; 165 } 166} 167 168rf(f: string): string 169{ 170 fd := sys->open(f, Sys->OREAD); 171 if(fd == nil) 172 return nil; 173 b := array[Sys->NAMEMAX] of byte; 174 n := sys->read(fd, b, len b); 175 if(n < 0) 176 return nil; 177 return string b[0:n]; 178} 179 180dbread(db: ref Database): (ref Database, string) 181{ 182 db.file.seek(big 0, Sys->SEEKSTART); 183 rl: list of ref Record; 184 n := 0; 185 for(;;){ 186 (r, err) := getrec(db); 187 if(err != nil) 188 return (nil, err); # could press on without it, or make it the `file' contents 189 if(r == nil) 190 break; 191 rl = r :: rl; 192 n++; 193 } 194 db.nextid = n; 195 db.records = array[n] of ref Record; 196 for(; rl != nil; rl = tl rl){ 197 r := hd rl; 198 n--; 199 r.id = n; 200 r.x = n; 201 db.records[n] = r; 202 } 203 return (db, nil); 204} 205 206# 207# a record is (.+\n)*\n 208# 209getrec(db: ref Database): (ref Record, string) 210{ 211 r := ref Record(-1, -1, 0, 0, nil); 212 data := ""; 213 for(;;){ 214 s := db.file.gets('\n'); 215 if(s == nil){ 216 if(data == nil) 217 return (nil, nil); # BUG: distinguish i/o error from EOF? 218 break; 219 } 220 if(s[len s - 1] != '\n') 221# return (nil, "file missing newline"); # possibly truncated 222 s += "\n"; 223 if(s == "\n") 224 break; 225 data += s; 226 } 227 r.data = array of byte data; 228 return (r, nil); 229} 230 231dbsync(db: ref Database): int 232{ 233 if(db.dirty){ 234 db.file = bufio->create(db.name, Sys->OWRITE, 8r666); 235 if(db.file == nil) 236 return -1; 237 for(i := 0; i < len db.records; i++){ 238 r := db.records[i]; 239 if(r != nil && r.data != nil){ 240 if(db.file.write(r.data, len r.data) != len r.data) 241 return -1; 242 db.file.putc('\n'); 243 } 244 } 245 if(db.file.flush()) 246 return -1; 247 db.file = nil; 248 db.dirty = 0; 249 } 250 return 0; 251} 252 253dbprint(db: ref Database) 254{ 255 stdout := sys->fildes(1); 256 for(i := 0; i < len db.records; i++){ 257 db.records[i].print(stdout); 258 sys->print("\n"); 259 } 260} 261 262Database.findrec(db: self ref Database, id: int): ref Record 263{ 264 for(i:=0; i<len db.records; i++) 265 if((r := db.records[i]) != nil && r.id == id) 266 return r; 267 return nil; 268} 269 270Record.new(fields: array of byte): ref Record 271{ 272 n := len database.records; 273 r := ref Record(n, n, 0, 0, fields); 274 a := array[n+1] of ref Record; 275 if(n) 276 a[0:] = database.records[0:]; 277 a[n] = r; 278 database.records = a; 279 database.vers++; 280 return r; 281} 282 283Record.print(r: self ref Record, fd: ref Sys->FD) 284{ 285 if(r.data != nil) 286 sys->write(fd, r.data, len r.data); 287} 288 289Record.qid(r: self ref Record): Sys->Qid 290{ 291 return Sys->Qid(QPATH(r.x, Qdata), r.vers, Sys->QTFILE); 292} 293 294serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop) 295{ 296 pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::nil); 297Serve: 298 while((gm := <-tchan) != nil){ 299 pick m := gm { 300 Readerror => 301 sys->fprint(stderr, "dbfs: fatal read error: %s\n", m.error); 302 break Serve; 303 Open => 304 c := srv.getfid(m.fid); 305 if(c == nil || TYPE(c.path) != Qnew){ 306 srv.open(m); # default action 307 break; 308 } 309 if(c.uname != user) { 310 srv.reply(ref Rmsg.Error(m.tag, Eperm)); 311 break; 312 } 313 mode := styxservers->openmode(m.mode); 314 if(mode < 0) { 315 srv.reply(ref Rmsg.Error(m.tag, Ebadarg)); 316 break; 317 } 318 # generate new file, change Fid's qid to match 319 r := Record.new(array[0] of byte); 320 qid := r.qid(); 321 c.open(mode, qid); 322 srv.reply(ref Rmsg.Open(m.tag, qid, srv.iounit())); 323 Read => 324 (c, err) := srv.canread(m); 325 if(c == nil){ 326 srv.reply(ref Rmsg.Error(m.tag, err)); 327 break; 328 } 329 if(c.qtype & Sys->QTDIR){ 330 srv.read(m); # does readdir 331 break; 332 } 333 r := database.records[FILENO(c.path)]; 334 if(r == nil) 335 srv.reply(ref Rmsg.Error(m.tag, Eremoved)); 336 else 337 srv.reply(styxservers->readbytes(m, r.data)); 338 Write => 339 (c, merr) := srv.canwrite(m); 340 if(c == nil){ 341 srv.reply(ref Rmsg.Error(m.tag, merr)); 342 break; 343 } 344 (value, err) := data2rec(m.data); 345 if(err != nil){ 346 srv.reply(ref Rmsg.Error(m.tag, err)); 347 break; 348 } 349 fno := FILENO(c.path); 350 r := database.records[fno]; 351 if(r == nil){ 352 srv.reply(ref Rmsg.Error(m.tag, Eremoved)); 353 break; 354 } 355 r.data = value; 356 r.vers++; 357 database.dirty++; 358 if(dbsync(database) == 0) 359 srv.reply(ref Rmsg.Write(m.tag, len m.data)); 360 else 361 srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); 362 Clunk => 363 # a transaction-oriented dbfs could delay updating the record until clunk 364 srv.clunk(m); 365 Remove => 366 c := srv.getfid(m.fid); 367 if(c == nil || c.qtype & Sys->QTDIR || TYPE(c.path) != Qdata){ 368 # let it diagnose all the errors 369 srv.remove(m); 370 break; 371 } 372 r := database.records[FILENO(c.path)]; 373 if(r != nil) 374 r.data = nil; 375 database.dirty++; 376 srv.delfid(c); 377 if(dbsync(database) == 0) 378 srv.reply(ref Rmsg.Remove(m.tag)); 379 else 380 srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); 381 Wstat => 382 srv.default(gm); # TO DO? 383 * => 384 srv.default(gm); 385 } 386 } 387 navops <-= nil; # shut down navigator 388} 389 390dirslot(n: int): int 391{ 392 for(i := 0; i < len database.records; i++){ 393 r := database.records[i]; 394 if(r != nil && r.data != nil){ 395 if(n == 0) 396 return i; 397 n--; 398 } 399 } 400 return -1; 401} 402 403dir(qid: Sys->Qid, name: string, length: big, uid: string, perm: int): ref Sys->Dir 404{ 405 d := ref sys->zerodir; 406 d.qid = qid; 407 if(qid.qtype & Sys->QTDIR) 408 perm |= Sys->DMDIR; 409 d.mode = perm; 410 d.name = name; 411 d.uid = uid; 412 d.gid = uid; 413 d.length = length; 414 return d; 415} 416 417dirgen(p: big): (ref Sys->Dir, string) 418{ 419 case TYPE(p) { 420 Qdir => 421 return (dir(Qid(QPATH(0, Qdir),database.vers,Sys->QTDIR), "/", big 0, user, 8r700), nil); 422 Qnew => 423 return (dir(Qid(QPATH(0, Qnew),0,Sys->QTFILE), "new", big 0, user, 8r600), nil); 424 * => 425 n := FILENO(p); 426 if(n < 0 || n >= len database.records) 427 return (nil, nil); 428 r := database.records[n]; 429 if(r == nil || r.data == nil) 430 return (nil, Enotfound); 431 return (dir(r.qid(), sys->sprint("%d", r.id), big len r.data, user, 8r600), nil); 432 } 433} 434 435navigator(navops: chan of ref Navop) 436{ 437 while((m := <-navops) != nil){ 438 pick n := m { 439 Stat => 440 n.reply <-= dirgen(n.path); 441 Walk => 442 if(int n.path != Qdir){ 443 n.reply <-= (nil, "not a directory"); 444 break; 445 } 446 case n.name { 447 ".." => 448 ; # nop 449 "new" => 450 n.path = QPATH(0, Qnew); 451 * => 452 if(len n.name < 1 || !(n.name[0]>='0' && n.name[0]<='9')){ # weak test for now 453 n.reply <-= (nil, Enotfound); 454 continue; 455 } 456 r := database.findrec(int n.name); 457 if(r == nil){ 458 n.reply <-= (nil, Enotfound); 459 continue; 460 } 461 n.path = QPATH(r.x, Qdata); 462 } 463 n.reply <-= dirgen(n.path); 464 Readdir => 465 if(int m.path != Qdir){ 466 n.reply <-= (nil, "not a directory"); 467 break; 468 } 469 i := n.offset; 470 if(i == 0) 471 n.reply <-= dirgen(QPATH(0,Qnew)); 472 for(; --n.count >= 0 && (j := dirslot(i)) >= 0; i++) 473 n.reply <-= dirgen(QPATH(j,Qdata)); # n² but the file will be small 474 n.reply <-= (nil, nil); 475 } 476 } 477} 478 479QPATH(w, q: int): big 480{ 481 return big ((w<<8)|q); 482} 483 484TYPE(path: big): int 485{ 486 return int path & 16rFF; 487} 488 489FILENO(path: big) : int 490{ 491 return (int path >> 8) & 16rFFFFFF; 492} 493 494# 495# a record is (.+\n)*, without final empty line 496# 497data2rec(data: array of byte): (array of byte, string) 498{ 499 s: string; 500 for(b := data; len b > 0;){ 501 (b, s) = getline(b); 502 if(s == nil || s[len s - 1] != '\n' || s == "\n") 503 return (nil, "partial or malformed record"); # possibly truncated 504 } 505 return (data, nil); 506} 507 508getline(b: array of byte): (array of byte, string) 509{ 510 n := len b; 511 for(i := 0; i < n; i++){ 512 (ch, l, nil) := sys->byte2char(b, i); 513 i += l; 514 if(l == 0 || ch == '\n') 515 break; 516 } 517 return (b[i:], string b[0:i]); 518} 519