1implement Dbfs; 2 3# 4# Copyright © 1999, 2002 Vita Nuova Limited. All rights reserved. 5# 6 7# Enhanced to include record locking, index field generation and update notification 8 9# TO DO: 10# make writing & reading more like real files; don't ignore offsets. 11# open with OTRUNC should work. 12# provide some way of compacting a dbfs file. 13 14include "sys.m"; 15 sys: Sys; 16 Qid: import Sys; 17 18include "draw.m"; 19 20include "arg.m"; 21 22include "styx.m"; 23 styx: Styx; 24 Rmsg, Tmsg: import styx; 25 26include "styxservers.m"; 27 styxservers: Styxservers; 28 Styxserver, Fid, Navigator, Navop: import styxservers; 29 Enotfound, Eperm, Ebadfid, Ebadarg: import styxservers; 30 31include "string.m"; 32 str: String; 33 34include "bufio.m"; 35 bufio: Bufio; 36 Iobuf: import bufio; 37 38include "sh.m"; 39 sh: Sh; 40 41Record: adt { 42 id: int; # file number in directory (if block is allocated) 43 offset: int; # start of data 44 count: int; # length of block (excluding header) 45 datalen: int; # length of data (-1 if block is free) 46 vers: int; # version 47 48 new: fn(offset: int, length: int): ref Record; 49 qid: fn(r: self ref Record): Sys->Qid; 50}; 51 52# Record lock 53Lock: adt { 54 qpath: big; 55 fid: int; 56}; 57 58HEADLEN: con 10; 59MINSIZE: con 20; 60 61Database: adt { 62 file: ref Iobuf; 63 records: array of ref Record; 64 maxid: int; 65 locking: int; 66 locklist: list of Lock; 67 indexing: int; 68 stats: int; 69 index: int; 70 s_reads: int; 71 s_writes: int; 72 s_creates: int; 73 s_removes: int; 74 updcmd: string; 75 vers: int; 76 77 build: fn(f: ref Iobuf, locking, indexing: int, stats: int, updcmd: string): (ref Database, string); 78 write: fn(db: self ref Database, n: int, data: array of byte): int; 79 read: fn(db: self ref Database, n: int): array of byte; 80 remove: fn(db: self ref Database, n: int); 81 create: fn(db: self ref Database, data: array of byte): ref Record; 82 updated: fn(db: self ref Database); 83 lock: fn(db: self ref Database, c: ref Styxservers->Fid): int; 84 unlock: fn(db: self ref Database, c: ref Styxservers->Fid); 85 ownlock: fn(db: self ref Database, c: ref Styxservers->Fid): int; 86}; 87 88Dbfs: module 89{ 90 init: fn(ctxt: ref Draw->Context, nil: list of string); 91}; 92 93Qdir, Qnew, Qdata, Qindex, Qstats: con iota; 94 95stderr: ref Sys->FD; 96database: ref Database; 97context: ref Draw->Context; 98user: string; 99Eremoved: con "file removed"; 100Egreg: con "thermal problems"; 101Elocked: con "open/create -- file is locked"; 102 103usage() 104{ 105 sys->fprint(stderr, "Usage: dbfs [-abcelrxD][-u cmd] file mountpoint\n"); 106 raise "fail:usage"; 107} 108 109nomod(s: string) 110{ 111 sys->fprint(stderr, "dbfs: can't load %s: %r\n", s); 112 raise "fail:load"; 113} 114 115init(ctxt: ref Draw->Context, args: list of string) 116{ 117 sys = load Sys Sys->PATH; 118 stderr = sys->fildes(2); 119 context = ctxt; 120 sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); 121 styx = load Styx Styx->PATH; 122 if(styx == nil) 123 nomod(Styx->PATH); 124 styx->init(); 125 styxservers = load Styxservers Styxservers->PATH; 126 if(styxservers == nil) 127 nomod(Styxservers->PATH); 128 styxservers->init(styx); 129 str = load String String->PATH; 130 if(str == nil) 131 nomod(String->PATH); 132 bufio = load Bufio Bufio->PATH; 133 if(bufio == nil) 134 nomod(Bufio->PATH); 135 136 arg := load Arg Arg->PATH; 137 if(arg == nil) 138 nomod(Arg->PATH); 139 arg->init(args); 140 flags := Sys->MREPL; 141 copt := 0; 142 empty := 0; 143 locking := 0; 144 stats := 0; 145 indexing := 0; 146 updcmd := ""; 147 while((o := arg->opt()) != 0) 148 case o { 149 'a' => flags = Sys->MAFTER; 150 'b' => flags = Sys->MBEFORE; 151 'r' => flags = Sys->MREPL; 152 'c' => copt = 1; 153 'e' => empty = 1; 154 'l' => locking = 1; 155 'u' => updcmd = arg->arg(); 156 if(updcmd == nil) 157 usage(); 158 'x' => indexing = 1; 159 stats = 1; 160 'D' => styxservers->traceset(1); 161 * => usage(); 162 } 163 args = arg->argv(); 164 arg = nil; 165 166 if(len args != 2) 167 usage(); 168 if(copt) 169 flags |= Sys->MCREATE; 170 file := hd args; 171 args = tl args; 172 mountpt := hd args; 173 174 if(updcmd != nil){ 175 sh = load Sh Sh->PATH; 176 if(sh == nil) 177 nomod(Sh->PATH); 178 } 179 180 df := bufio->open(file, Sys->ORDWR); 181 if(df == nil && empty){ 182 (rc, nil) := sys->stat(file); 183 if(rc < 0) 184 df = bufio->create(file, Sys->ORDWR, 8r600); 185 } 186 if(df == nil){ 187 sys->fprint(stderr, "dbfs: can't open %s: %r\n", file); 188 raise "fail:cannot open file"; 189 } 190 (db, err) := Database.build(df, locking, indexing, stats, updcmd); 191 if(db == nil){ 192 sys->fprint(stderr, "dbfs: can't read %s: %s\n", file, err); 193 raise "fail:cannot read db"; 194 } 195 database = db; 196 197 sys->pctl(Sys->FORKFD, nil); 198 199 user = rf("/dev/user"); 200 if(user == nil) 201 user = "inferno"; 202 203 fds := array[2] of ref Sys->FD; 204 if(sys->pipe(fds) < 0){ 205 sys->fprint(stderr, "dbfs: can't create pipe: %r\n"); 206 raise "fail:pipe"; 207 } 208 209 navops := chan of ref Navop; 210 spawn navigator(navops); 211 212 (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qdir); 213 fds[0] = nil; 214 215 pidc := chan of int; 216 spawn serveloop(tchan, srv, pidc, navops); 217 <-pidc; 218 219 if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) { 220 sys->fprint(stderr, "dbfs: mount failed: %r\n"); 221 raise "fail:bad mount"; 222 } 223} 224 225rf(f: string): string 226{ 227 fd := sys->open(f, Sys->OREAD); 228 if(fd == nil) 229 return nil; 230 b := array[Sys->NAMEMAX] of byte; 231 n := sys->read(fd, b, len b); 232 if(n < 0) 233 return nil; 234 return string b[0:n]; 235} 236 237serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop) 238{ 239 pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, stderr.fd::1::2::database.file.fd.fd::srv.fd.fd::nil); 240# stderr = sys->fildes(stderr.fd); 241 database.file.fd = sys->fildes(database.file.fd.fd); 242Serve: 243 while((gm := <-tchan) != nil){ 244 pick m := gm { 245 Readerror => 246 sys->fprint(stderr, "dbfs: fatal read error: %s\n", m.error); 247 break Serve; 248 Open => 249 open(srv, m); 250 Read => 251 (c, err) := srv.canread(m); 252 if(c == nil) { 253 srv.reply(ref Rmsg.Error(m.tag, err)); 254 break; 255 } 256 if(c.qtype & Sys->QTDIR){ 257 srv.read(m); 258 break; 259 } 260 case TYPE(c.path) { 261 Qindex => 262 if(database.index < 0) { 263 srv.reply(ref Rmsg.Error(m.tag, Eperm)); 264 break; 265 } 266 if (m.offset > big 0) { 267 srv.reply(ref Rmsg.Read(m.tag, nil)); 268 break; 269 } 270 reply := array of byte string ++database.index; 271 if(m.count < len reply) 272 reply = reply[:m.count]; 273 srv.reply(ref Rmsg.Read(m.tag, reply)); 274 Qstats => 275 if (m.offset > big 0) { 276 srv.reply(ref Rmsg.Read(m.tag, nil)); 277 break; 278 } 279 reply := array of byte sys->sprint("%d %d %d %d", database.s_reads, database.s_writes, 280 database.s_creates, database.s_removes); 281 if(m.count < len reply) reply = reply[:m.count]; 282 srv.reply(ref Rmsg.Read(m.tag, reply)); 283 Qdata => 284 recno := id2recno(FILENO(c.path)); 285 if(recno == -1) 286 srv.reply(ref Rmsg.Error(m.tag, Eremoved)); 287 else 288 srv.reply(styxservers->readbytes(m, database.read(recno))); 289 * => 290 srv.reply(ref Rmsg.Error(m.tag, Egreg)); 291 } 292 Write => 293 (c, err) := srv.canwrite(m); 294 if(c == nil){ 295 srv.reply(ref Rmsg.Error(m.tag, err)); 296 break; 297 } 298 if(!database.ownlock(c)) { 299 # shouldn't happen: open checks 300 srv.reply(ref Rmsg.Error(m.tag, Elocked)); 301 break; 302 } 303 case TYPE(c.path) { 304 Qindex => 305 if(database.index >= 0) { 306 srv.reply(ref Rmsg.Error(m.tag, Eperm)); 307 break; 308 } 309 database.index = int string m.data; 310 srv.reply(ref Rmsg.Write(m.tag, len m.data)); 311 Qdata => 312 recno := id2recno(FILENO(c.path)); 313 if(recno == -1) 314 srv.reply(ref Rmsg.Error(m.tag, "phase error")); 315 else { 316 changed := 1; 317 if(database.updcmd != nil){ 318 oldrec := database.read(recno); 319 changed = !eqbytes(m.data, oldrec); 320 } 321 if(changed && database.write(recno, m.data) == -1){ 322 srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); 323 break; 324 } 325 if(changed) 326 database.updated(); # run the command before reply 327 srv.reply(ref Rmsg.Write(m.tag, len m.data)); 328 } 329 * => 330 srv.reply(ref Rmsg.Error(m.tag, Eperm)); 331 } 332 Clunk => 333 c := srv.getfid(m.fid); 334 if(c != nil) 335 database.unlock(c); 336 srv.clunk(m); 337 Remove => 338 c := srv.getfid(m.fid); 339 database.unlock(c); 340 if(c == nil || c.qtype & Sys->QTDIR || TYPE(c.path) != Qdata){ 341 # let it diagnose all the errors 342 srv.remove(m); 343 break; 344 } 345 recno := id2recno(FILENO(c.path)); 346 if(recno == -1) 347 srv.reply(ref Rmsg.Error(m.tag, "phase error")); 348 else { 349 database.remove(recno); 350 database.updated(); 351 srv.reply(ref Rmsg.Remove(m.tag)); 352 } 353 srv.delfid(c); 354 * => 355 srv.default(gm); 356 } 357 } 358 navops <-= nil; # shut down navigator 359} 360 361eqbytes(a, b: array of byte): int 362{ 363 if(len a != len b) 364 return 0; 365 for(i := 0; i < len a; i++) 366 if(a[i] != b[i]) 367 return 0; 368 return 1; 369} 370 371id2recno(id: int): int 372{ 373 recs := database.records; 374 for(i := 0; i < len recs; i++) 375 if(recs[i].datalen >= 0 && recs[i].id == id) 376 return i; 377 return -1; 378} 379 380open(srv: ref Styxserver, m: ref Tmsg.Open): ref Fid 381{ 382 (c, mode, d, err) := srv.canopen(m); 383 if(c == nil){ 384 srv.reply(ref Rmsg.Error(m.tag, err)); 385 return nil; 386 } 387 if(TYPE(c.path) == Qnew){ 388 # generate new file 389 if(c.uname != user){ 390 srv.reply(ref Rmsg.Error(m.tag, Eperm)); 391 return nil; 392 } 393 r := database.create(array[0] of byte); 394 if(r == nil) { 395 srv.reply(ref Rmsg.Error(m.tag, "create -- i/o error")); 396 return nil; 397 } 398 (d, nil) = dirgen(QPATH(r.id, Qdata)); 399 } 400 if(m.mode & Sys->OTRUNC) { 401 # TO DO 402 } 403 c.open(mode, d.qid); 404 if(database.locking && TYPE(c.path) == Qdata && (m.mode & (Sys->OWRITE|Sys->ORDWR))) { 405 if(!database.lock(c)) { 406 srv.reply(ref Rmsg.Error(m.tag, Elocked)); 407 return nil; 408 } 409 } 410 srv.reply(ref Rmsg.Open(m.tag, d.qid, srv.iounit())); 411 return c; 412} 413 414dirslot(n: int): int 415{ 416 for(i := 0; i < len database.records; i++){ 417 r := database.records[i]; 418 if(r != nil && r.datalen >= 0){ 419 if(n == 0) 420 return i; 421 n--; 422 } 423 } 424 return -1; 425} 426 427dir(qid: Sys->Qid, name: string, length: big, uid: string, perm: int): ref Sys->Dir 428{ 429 d := ref sys->zerodir; 430 d.qid = qid; 431 if(qid.qtype & Sys->QTDIR) 432 perm |= Sys->DMDIR; 433 d.mode = perm; 434 d.name = name; 435 d.uid = uid; 436 d.gid = uid; 437 d.length = length; 438 return d; 439} 440 441dirgen(p: big): (ref Sys->Dir, string) 442{ 443 case TYPE(p) { 444 Qdir => 445 return (dir(Qid(QPATH(0, Qdir),database.vers,Sys->QTDIR), "/", big 0, user, 8r700), nil); 446 Qnew => 447 return (dir(Qid(QPATH(0, Qnew),0,Sys->QTFILE), "new", big 0, user, 8r600), nil); 448 Qindex => 449 return (dir(Qid(QPATH(0, Qindex),0,Sys->QTFILE), "index", big 0, user, 8r600), nil); 450 Qstats => 451 return (dir(Qid(QPATH(0, Qstats),0,Sys->QTFILE), "stats", big 0, user, 8r400), nil); 452 * => 453 n := id2recno(FILENO(p)); 454 if(n < 0 || n >= len database.records) 455 return (nil, nil); 456 r := database.records[n]; 457 if(r == nil || r.datalen < 0) 458 return (nil, Enotfound); 459 l := r.datalen; 460 if(l < 0) 461 l = 0; 462 return (dir(r.qid(), sys->sprint("%d", r.id), big l, user, 8r600), nil); 463 } 464} 465 466navigator(navops: chan of ref Navop) 467{ 468 while((m := <-navops) != nil){ 469 pick n := m { 470 Stat => 471 n.reply <-= dirgen(n.path); 472 Walk => 473 if(int n.path != Qdir){ 474 n.reply <-= (nil, "not a directory"); 475 break; 476 } 477 case n.name { 478 ".." => 479 ; # nop 480 "new" => 481 n.path = QPATH(0, Qnew); 482 "stats" => 483 if(!database.indexing){ 484 n.reply <-= (nil, Enotfound); 485 continue; 486 } 487 n.path = QPATH(0, Qstats); 488 "index" => 489 if(!database.indexing){ 490 n.reply <-= (nil, Enotfound); 491 continue; 492 } 493 n.path = QPATH(0, Qindex); 494 * => 495 if(len n.name < 1 || !(n.name[0]>='0' && n.name[0]<='9')){ # weak test for now 496 n.reply <-= (nil, Enotfound); 497 continue; 498 } 499 n.path = QPATH(int n.name, Qdata); 500 } 501 n.reply <-= dirgen(n.path); 502 Readdir => 503 if(int m.path != Qdir){ 504 n.reply <-= (nil, "not a directory"); 505 break; 506 } 507 o := 1; # Qnew; 508 stats := -1; 509 indexing := -1; 510 if(database.indexing) 511 indexing = o++; 512 if(database.stats) 513 stats = o++; 514 Dread: 515 for(i := n.offset; --n.count >= 0; i++){ 516 case i { 517 0 => 518 n.reply <-= dirgen(QPATH(0,Qnew)); 519 * => 520 if(i == indexing) 521 n.reply <-= dirgen(QPATH(0, Qindex)); 522 if(i == stats) 523 n.reply <-= dirgen(QPATH(0, Qstats)); 524 j := dirslot(i-o); # n² but fine if the file will be small 525 if(j < 0) 526 break Dread; 527 r := database.records[j]; 528 n.reply <-= dirgen(QPATH(r.id,Qdata)); 529 } 530 } 531 n.reply <-= (nil, nil); 532 } 533 } 534} 535 536QPATH(w, q: int): big 537{ 538 return big ((w<<8)|q); 539} 540 541TYPE(path: big): int 542{ 543 return int path & 16rFF; 544} 545 546FILENO(path: big): int 547{ 548 return (int path >> 8) & 16rFFFFFF; 549} 550 551Database.build(f: ref Iobuf, locking, indexing, stats: int, updcmd: string): (ref Database, string) 552{ 553 rl: list of ref Record; 554 offset := 0; 555 maxid := 0; 556 for(;;) { 557 d := array[HEADLEN] of byte; 558 n := f.read(d, HEADLEN); 559 if(n < HEADLEN) 560 break; 561 orig := s := string d; 562 if(len s != HEADLEN) 563 return (nil, "found bad header"); 564 r := ref Record; 565 r.vers = 0; 566 (r.count, s) = str->toint(s, 10); 567 (r.datalen, s) = str->toint(s, 10); 568 if(s != "\n") 569 return (nil, sys->sprint("found bad header '%s'\n", orig)); 570 r.offset = offset + HEADLEN; 571 offset += r.count + HEADLEN; 572 f.seek(big offset, Bufio->SEEKSTART); 573 r.id = maxid++; 574 rl = r :: rl; 575 } 576 db := ref Database(f, array[maxid] of ref Record, maxid, locking, nil, indexing, stats, -1, 0, 0, 0, 0, updcmd, 0); 577 for(i := len db.records - 1; i >= 0; i--) { 578 db.records[i] = hd rl; 579 rl = tl rl; 580 } 581 return (db, nil); 582} 583 584Database.write(db: self ref Database, recno: int, data: array of byte): int 585{ 586 db.s_writes++; 587 r := db.records[recno]; 588 r.vers++; 589 if(len data <= r.count) { 590 if(r.count - len data >= HEADLEN + MINSIZE) 591 splitrec(db, recno, len data); 592 writerec(db, recno, data); 593 db.file.flush(); 594 } else { 595 freerec(db, recno); 596 n := allocrec(db, len data); 597 if(n == -1) 598 return -1; # BUG: we lose the original data in this case. 599 db.records[n].id = r.id; 600 db.write(n, data); 601 } 602 return 0; 603} 604 605Database.create(db: self ref Database, data: array of byte): ref Record 606{ 607 db.s_creates++; 608 db.vers++; 609 n := allocrec(db, len data); 610 if(n < 0) 611 return nil; 612 if(db.write(n, data) < 0){ 613 freerec(db, n); 614 return nil; 615 } 616 r := db.records[n]; 617 r.id = db.maxid++; 618 return r; 619} 620 621Database.read(db: self ref Database, recno: int): array of byte 622{ 623 db.s_reads++; 624 r := db.records[recno]; 625 if(r.datalen <= 0) 626 return nil; 627 db.file.seek(big r.offset, Bufio->SEEKSTART); 628 d := array[r.datalen] of byte; 629 n := db.file.read(d, r.datalen); 630 if(n != r.datalen) { 631 sys->fprint(stderr, "dbfs: only read %d bytes (expected %d)\n", n, r.datalen); 632 return nil; 633 } 634 return d; 635} 636 637Database.remove(db: self ref Database, recno: int) 638{ 639 db.s_removes++; 640 db.vers++; 641 freerec(db, recno); 642 db.file.flush(); 643} 644 645Database.updated(db: self ref Database) 646{ 647 if(db.updcmd != nil) 648 sh->system(context, db.updcmd); 649} 650 651# Locking - try to lock a record 652 653Database.lock(db: self ref Database, c: ref Styxservers->Fid): int 654{ 655 if(TYPE(c.path) != Qdata || !db.locking) 656 return 1; 657 for(ll := db.locklist; ll != nil; ll = tl ll) { 658 lock := hd ll; 659 if(lock.qpath == c.path) 660 return lock.fid == c.fid; 661 } 662 db.locklist = (c.path, c.fid) :: db.locklist; 663 return 1; 664} 665 666 667# Locking - unlock a record 668 669Database.unlock(db: self ref Database, c: ref Styxservers->Fid) 670{ 671 if(TYPE(c.path) != Qdata || !db.locking) 672 return; 673 ll := db.locklist; 674 db.locklist = nil; 675 for(; ll != nil; ll = tl ll){ 676 lock := hd ll; 677 if(lock.qpath == c.path && lock.fid == c.fid){ 678 # not replaced on list 679 }else 680 db.locklist = hd ll :: db.locklist; 681 } 682} 683 684 685# Locking - check if Fid c has the lock on its record 686 687Database.ownlock(db: self ref Database, c: ref Styxservers->Fid): int 688{ 689 if(TYPE(c.path) != Qdata || !db.locking) 690 return 1; 691 for(ll := db.locklist; ll != nil; ll = tl ll) { 692 lock := hd ll; 693 if(lock.qpath == c.path) 694 return lock.fid == c.fid; 695 } 696 return 0; 697} 698 699Record.new(offset: int, length: int): ref Record 700{ 701 return ref Record(-1, offset, length, -1, 0); 702} 703 704Record.qid(r: self ref Record): Qid 705{ 706 return Qid(QPATH(r.id,Qdata), r.vers, Sys->QTFILE); 707} 708 709freerec(db: ref Database, recno: int) 710{ 711 nr := len db.records; 712 db.records[recno].datalen = -1; 713 for(i := recno; i >= 0; i--) 714 if(db.records[i].datalen != -1) 715 break; 716 f := i + 1; 717 nb := 0; 718 for(i = f; i < nr; i++) { 719 if(db.records[i].datalen != -1) 720 break; 721 nb += db.records[i].count + HEADLEN; 722 } 723 db.records[f].count = nb - HEADLEN; 724 writeheader(db.file, db.records[f]); 725 # could blank out freed entries here if we cared. 726 if(i < nr && f < i) 727 db.records[f+1:] = db.records[i:]; 728 db.records = db.records[0:nr - (i - f - 1)]; 729} 730 731splitrec(db: ref Database, recno: int, pos: int) 732{ 733 a := array[len db.records + 1] of ref Record; 734 a[0:] = db.records[0:recno+1]; 735 if(recno < len db.records - 1) 736 a[recno+2:] = db.records[recno+1:]; 737 db.records = a; 738 r := a[recno]; 739 a[recno+1] = Record.new(r.offset + pos + HEADLEN, r.count - HEADLEN - pos); 740 r.count = pos; 741 writeheader(db.file, a[recno+1]); 742} 743 744writerec(db: ref Database, recno: int, data: array of byte): int 745{ 746 db.records[recno].datalen = len data; 747 if(writeheader(db.file, db.records[recno]) == -1) 748 return -1; 749 if(db.file.write(data, len data) == Bufio->ERROR) 750 return -1; 751 return 0; 752} 753 754writeheader(f: ref Iobuf, r: ref Record): int 755{ 756 f.seek(big r.offset - big HEADLEN, Bufio->SEEKSTART); 757 if(f.puts(sys->sprint("%4d %4d\n", r.count, r.datalen)) == Bufio->ERROR) { 758 sys->fprint(stderr, "dbfs: error writing header (id %d, offset %d, count %d, datalen %d): %r\n", 759 r.id, r.offset, r.count, r.datalen); 760 return -1; 761 } 762 return 0; 763} 764 765# finds or creates a record of the requisite size; does not mark it as allocated. 766allocrec(db: ref Database, nb: int): int 767{ 768 if(nb < MINSIZE) 769 nb = MINSIZE; 770 best := -1; 771 n := -1; 772 for(i := 0; i < len db.records; i++) { 773 r := db.records[i]; 774 if(r.datalen == -1) { 775 avail := r.count - nb; 776 if(avail >= 0 && (n == -1 || avail < best)) { 777 best = avail; 778 n = i; 779 } 780 } 781 } 782 if(n != -1) 783 return n; 784 nr := len db.records; 785 a := array[nr + 1] of ref Record; 786 a[0:] = db.records[0:]; 787 offset := 0; 788 if(nr > 0) 789 offset = a[nr-1].offset + a[nr-1].count; 790 db.file.seek(big offset, Bufio->SEEKSTART); 791 if(db.file.write(array[nb + HEADLEN] of {* => byte(0)}, nb + HEADLEN) == Bufio->ERROR 792 || db.file.flush() == Bufio->ERROR) { 793 sys->fprint(stderr, "dbfs: write of new entry failed: %r\n"); 794 return -1; 795 } 796 a[nr] = Record.new(offset + HEADLEN, nb); 797 db.records = a; 798 return nr; 799} 800 801now(fd: ref Sys->FD): int 802{ 803 if(fd == nil) 804 return 0; 805 buf := array[128] of byte; 806 sys->seek(fd, big 0, 0); 807 n := sys->read(fd, buf, len buf); 808 if(n < 0) 809 return 0; 810 t := (big string buf[0:n]) / big 1000000; 811 return int t; 812} 813