Browse Source

Plan 9 from Bell Labs 2007-03-26

David du Colombier 17 years ago
parent
commit
98584bfd83
75 changed files with 19598 additions and 125 deletions
  1. 81 6
      dist/replica/_plan9.db
  2. 81 6
      dist/replica/plan9.db
  3. 81 0
      dist/replica/plan9.log
  4. 14 0
      rc/bin/diskparts
  5. 8 6
      rc/bin/man
  6. 2 7
      rc/bin/termrc
  7. 153 97
      sys/lib/lp/process/generic
  8. 13 1
      sys/lib/tmac/tmac.an
  9. 75 0
      sys/man/2/lock
  10. 298 0
      sys/man/4/cwfs
  11. 20 0
      sys/src/cmd/cwfs/32bit.h
  12. 22 0
      sys/src/cmd/cwfs/64bit.h
  13. 31 0
      sys/src/cmd/cwfs/9netics32.16k/conf.c
  14. 36 0
      sys/src/cmd/cwfs/9netics32.16k/dat.h
  15. 2 0
      sys/src/cmd/cwfs/9netics32.16k/mkfile
  16. 31 0
      sys/src/cmd/cwfs/9netics64.8k/conf.c
  17. 36 0
      sys/src/cmd/cwfs/9netics64.8k/dat.h
  18. 2 0
      sys/src/cmd/cwfs/9netics64.8k/mkfile
  19. 1636 0
      sys/src/cmd/cwfs/9p1.c
  20. 116 0
      sys/src/cmd/cwfs/9p1.h
  21. 523 0
      sys/src/cmd/cwfs/9p1lib.c
  22. 1856 0
      sys/src/cmd/cwfs/9p2.c
  23. 8 0
      sys/src/cmd/cwfs/README
  24. 112 0
      sys/src/cmd/cwfs/all.h
  25. 326 0
      sys/src/cmd/cwfs/auth.c
  26. 838 0
      sys/src/cmd/cwfs/chk.c
  27. 31 0
      sys/src/cmd/cwfs/choline/conf.c
  28. 36 0
      sys/src/cmd/cwfs/choline/dat.h
  29. 2 0
      sys/src/cmd/cwfs/choline/mkfile
  30. 844 0
      sys/src/cmd/cwfs/con.c
  31. 1037 0
      sys/src/cmd/cwfs/config.c
  32. 309 0
      sys/src/cmd/cwfs/console.c
  33. 2259 0
      sys/src/cmd/cwfs/cw.c
  34. 32 0
      sys/src/cmd/cwfs/cwfs/conf.c
  35. 36 0
      sys/src/cmd/cwfs/cwfs/dat.h
  36. 2 0
      sys/src/cmd/cwfs/cwfs/mkfile
  37. 150 0
      sys/src/cmd/cwfs/data.c
  38. 337 0
      sys/src/cmd/cwfs/dentry.c
  39. 68 0
      sys/src/cmd/cwfs/doc/changes
  40. 181 0
      sys/src/cmd/cwfs/doc/emelie.boot
  41. 29 0
      sys/src/cmd/cwfs/doc/user.mode.boot
  42. 25 0
      sys/src/cmd/cwfs/doc/words
  43. 128 0
      sys/src/cmd/cwfs/doc/worm.fs
  44. 81 0
      sys/src/cmd/cwfs/doc/worm.fs64
  45. 150 0
      sys/src/cmd/cwfs/doc/worms.32-bit
  46. 33 0
      sys/src/cmd/cwfs/emelie/conf.c
  47. 36 0
      sys/src/cmd/cwfs/emelie/dat.h
  48. 1 0
      sys/src/cmd/cwfs/emelie/map.w2-w0
  49. 2 0
      sys/src/cmd/cwfs/emelie/mkfile
  50. 32 0
      sys/src/cmd/cwfs/fs/conf.c
  51. 36 0
      sys/src/cmd/cwfs/fs/dat.h
  52. 2 0
      sys/src/cmd/cwfs/fs/mkfile
  53. 31 0
      sys/src/cmd/cwfs/fs64/conf.c
  54. 36 0
      sys/src/cmd/cwfs/fs64/dat.h
  55. 2 0
      sys/src/cmd/cwfs/fs64/mkfile
  56. 105 0
      sys/src/cmd/cwfs/fworm.c
  57. 38 0
      sys/src/cmd/cwfs/io.h
  58. 289 0
      sys/src/cmd/cwfs/iobuf.c
  59. 1329 0
      sys/src/cmd/cwfs/juke.c
  60. 83 0
      sys/src/cmd/cwfs/lrand.c
  61. 573 0
      sys/src/cmd/cwfs/main.c
  62. 130 0
      sys/src/cmd/cwfs/malloc.c
  63. 8 0
      sys/src/cmd/cwfs/mkfile
  64. 285 0
      sys/src/cmd/cwfs/mworm.c
  65. 454 0
      sys/src/cmd/cwfs/net.c
  66. 92 0
      sys/src/cmd/cwfs/pc.c
  67. 773 0
      sys/src/cmd/cwfs/portdat.h
  68. 246 0
      sys/src/cmd/cwfs/portfns.h
  69. 61 0
      sys/src/cmd/cwfs/portmkfile
  70. 423 0
      sys/src/cmd/cwfs/scsi.c
  71. 1537 0
      sys/src/cmd/cwfs/sub.c
  72. 117 0
      sys/src/cmd/cwfs/time.c
  73. 582 0
      sys/src/cmd/cwfs/uidgid.c
  74. 122 0
      sys/src/cmd/cwfs/wren.c
  75. 2 2
      sys/src/libauthsrv/readnvram.c

+ 81 - 6
dist/replica/_plan9.db

@@ -5444,6 +5444,7 @@ rc/bin/delkey - 775 sys sys 1109429137 643
 rc/bin/dial - 20000000775 sys sys 1059180057 0
 rc/bin/diffy - 775 sys sys 1140694870 277
 rc/bin/dircp - 775 sys sys 1169490692 234
+rc/bin/diskparts - 775 sys sys 1174849973 392
 rc/bin/doc2ps - 775 sys sys 1069793831 244
 rc/bin/doc2txt - 775 sys sys 1025197880 563
 rc/bin/doctype - 775 sys sys 1162921755 1727
@@ -5469,7 +5470,7 @@ rc/bin/leak - 775 sys sys 1172760642 1921
 rc/bin/lookman - 775 sys sys 1017679344 686
 rc/bin/lp - 775 sys sys 1162105982 5201
 rc/bin/mail - 775 sys sys 1045504003 138
-rc/bin/man - 775 sys sys 1173755398 2504
+rc/bin/man - 775 sys sys 1174803269 2639
 rc/bin/map - 775 sys sys 945617207 1576
 rc/bin/mapdemo - 775 sys sys 945617207 4075
 rc/bin/membername - 775 sys sys 945617207 89
@@ -5559,7 +5560,7 @@ rc/bin/start - 775 sys sys 945617209 120
 rc/bin/stock - 775 sys sys 1143126371 292
 rc/bin/stop - 775 sys sys 945617209 110
 rc/bin/tel - 775 sys sys 1161209756 128
-rc/bin/termrc - 775 sys sys 1164658821 2647
+rc/bin/termrc - 775 sys sys 1174850048 2434
 rc/bin/thesaurus - 775 sys sys 1068054167 246
 rc/bin/tlsclienttunnel - 775 sys sys 1024375633 153
 rc/bin/tlssrvtunnel - 775 sys sys 1024375634 175
@@ -6568,7 +6569,7 @@ sys/lib/lp/process - 20000000775 sys sys 1077670465 0
 sys/lib/lp/process/dpost - 775 sys sys 1015012079 2412
 sys/lib/lp/process/dvipost - 775 sys sys 954037459 3123
 sys/lib/lp/process/g3post - 775 sys sys 954037459 2348
-sys/lib/lp/process/generic - 775 sys sys 1077715118 4805
+sys/lib/lp/process/generic - 775 sys sys 1174846328 4108
 sys/lib/lp/process/gifpost - 775 sys sys 1015012079 2317
 sys/lib/lp/process/gspipe - 775 sys sys 1015012079 434
 sys/lib/lp/process/gspipeijs - 775 sys sys 1077670465 949
@@ -7066,7 +7067,7 @@ sys/lib/tmac/mmt - 664 sys sys 964454718 40915
 sys/lib/tmac/name.sed - 664 sys sys 944956202 62
 sys/lib/tmac/sendcover - 775 sys sys 944956202 50
 sys/lib/tmac/strings.mm - 664 sys sys 964455723 2146
-sys/lib/tmac/tmac.an - 664 sys sys 1161484650 8228
+sys/lib/tmac/tmac.an - 664 sys sys 1174803319 8439
 sys/lib/tmac/tmac.anhtml - 664 sys sys 984696197 105
 sys/lib/tmac/tmac.antimes - 664 sys sys 964454718 7809
 sys/lib/tmac/tmac.bits - 664 sys sys 944956202 1089
@@ -7508,7 +7509,7 @@ sys/man/2/iounit - 664 sys sys 1015091523 1001
 sys/man/2/ip - 664 sys sys 1173239323 7249
 sys/man/2/isalpharune - 664 sys sys 1015091523 1059
 sys/man/2/keyboard - 664 sys sys 950892860 2065
-sys/man/2/lock - 664 sys sys 1172959628 4693
+sys/man/2/lock - 664 sys sys 1174844415 5704
 sys/man/2/mach - 664 sys sys 1080179298 7995
 sys/man/2/malloc - 664 sys sys 1102093069 4763
 sys/man/2/matrix - 664 sys sys 950892861 6090
@@ -7618,6 +7619,7 @@ sys/man/4/archfs - 664 sys sys 960000712 533
 sys/man/4/cdfs - 664 sys sys 1026846913 3638
 sys/man/4/cfs - 664 sys sys 1172762903 1813
 sys/man/4/consolefs - 664 sys sys 1144424854 4245
+sys/man/4/cwfs - 664 sys sys 1174799917 6329
 sys/man/4/dossrv - 664 sys sys 1168307403 4334
 sys/man/4/execnet - 664 sys sys 1019866708 1069
 sys/man/4/exportfs - 664 sys sys 1145881912 4655
@@ -9941,6 +9943,79 @@ sys/src/cmd/cpp/test.c - 664 sys sys 944960879 47
 sys/src/cmd/cpp/tokens.c - 664 sys sys 944960879 6755
 sys/src/cmd/cpu.c - 664 sys sys 1164133664 21154
 sys/src/cmd/crop.c - 664 sys sys 1134557476 4137
+sys/src/cmd/cwfs - 20000000775 sys sys 1174799196 0
+sys/src/cmd/cwfs/32bit.h - 664 sys sys 1171162581 594
+sys/src/cmd/cwfs/64bit.h - 664 sys sys 1171162523 634
+sys/src/cmd/cwfs/9netics32.16k - 20000000775 sys sys 1174799194 0
+sys/src/cmd/cwfs/9netics32.16k/conf.c - 664 sys sys 1171171267 503
+sys/src/cmd/cwfs/9netics32.16k/dat.h - 664 sys sys 1171171041 794
+sys/src/cmd/cwfs/9netics32.16k/mkfile - 664 sys sys 1171519744 29
+sys/src/cmd/cwfs/9netics64.8k - 20000000775 sys sys 1174799194 0
+sys/src/cmd/cwfs/9netics64.8k/conf.c - 664 sys sys 1171171458 564
+sys/src/cmd/cwfs/9netics64.8k/dat.h - 664 sys sys 1171171387 791
+sys/src/cmd/cwfs/9netics64.8k/mkfile - 664 sys sys 1171519752 28
+sys/src/cmd/cwfs/9p1.c - 664 sys sys 1174716878 29747
+sys/src/cmd/cwfs/9p1.h - 664 sys sys 1171159776 2128
+sys/src/cmd/cwfs/9p1lib.c - 664 sys sys 1171145882 7894
+sys/src/cmd/cwfs/9p2.c - 664 sys sys 1174716894 35170
+sys/src/cmd/cwfs/README - 664 sys sys 1171520084 344
+sys/src/cmd/cwfs/all.h - 664 sys sys 1174716764 2186
+sys/src/cmd/cwfs/auth.c - 664 sys sys 1174797532 5840
+sys/src/cmd/cwfs/chk.c - 664 sys sys 1174716979 14579
+sys/src/cmd/cwfs/choline - 20000000775 sys sys 1174799194 0
+sys/src/cmd/cwfs/choline/conf.c - 664 sys sys 1171170952 412
+sys/src/cmd/cwfs/choline/dat.h - 664 sys sys 1171170751 791
+sys/src/cmd/cwfs/choline/mkfile - 664 sys sys 1171519759 26
+sys/src/cmd/cwfs/con.c - 664 sys sys 1174717615 15713
+sys/src/cmd/cwfs/config.c - 664 sys sys 1174717895 20243
+sys/src/cmd/cwfs/console.c - 664 sys sys 1174716894 4671
+sys/src/cmd/cwfs/cw.c - 664 sys sys 1174716894 42798
+sys/src/cmd/cwfs/cwfs - 20000000775 sys sys 1174799195 0
+sys/src/cmd/cwfs/cwfs/conf.c - 664 sys sys 1171485877 452
+sys/src/cmd/cwfs/cwfs/dat.h - 664 sys sys 1171485890 796
+sys/src/cmd/cwfs/cwfs/mkfile - 664 sys sys 1174798645 21
+sys/src/cmd/cwfs/data.c - 664 sys sys 1171158553 4515
+sys/src/cmd/cwfs/dentry.c - 664 sys sys 1174716894 6265
+sys/src/cmd/cwfs/doc - 20000000775 sys sys 1174799195 0
+sys/src/cmd/cwfs/doc/changes - 664 sys sys 1146795434 2967
+sys/src/cmd/cwfs/doc/emelie.boot - 664 sys sys 1171304297 8751
+sys/src/cmd/cwfs/doc/user.mode.boot - 664 sys sys 1171326690 747
+sys/src/cmd/cwfs/doc/words - 664 sys sys 1171172525 1096
+sys/src/cmd/cwfs/doc/worm.fs - 664 sys sys 1146795434 4057
+sys/src/cmd/cwfs/doc/worm.fs64 - 664 sys sys 1146795434 1554
+sys/src/cmd/cwfs/doc/worms.32-bit - 664 sys sys 1146795434 2806
+sys/src/cmd/cwfs/emelie - 20000000775 sys sys 1174799195 0
+sys/src/cmd/cwfs/emelie/conf.c - 664 sys sys 1174370534 500
+sys/src/cmd/cwfs/emelie/dat.h - 664 sys sys 1171165543 790
+sys/src/cmd/cwfs/emelie/map.w2-w0 - 664 sys sys 1171864578 6
+sys/src/cmd/cwfs/emelie/mkfile - 664 sys sys 1174799340 26
+sys/src/cmd/cwfs/fs - 20000000775 sys sys 1174799195 0
+sys/src/cmd/cwfs/fs/conf.c - 664 sys sys 1171171939 554
+sys/src/cmd/cwfs/fs/dat.h - 664 sys sys 1171171640 784
+sys/src/cmd/cwfs/fs/mkfile - 664 sys sys 1171519772 21
+sys/src/cmd/cwfs/fs64 - 20000000775 sys sys 1174799195 0
+sys/src/cmd/cwfs/fs64/conf.c - 664 sys sys 1171172030 559
+sys/src/cmd/cwfs/fs64/dat.h - 664 sys sys 1171172076 786
+sys/src/cmd/cwfs/fs64/mkfile - 664 sys sys 1174799404 24
+sys/src/cmd/cwfs/fworm.c - 664 sys sys 1174716894 1814
+sys/src/cmd/cwfs/io.h - 664 sys sys 1174280312 866
+sys/src/cmd/cwfs/iobuf.c - 664 sys sys 1174716894 4713
+sys/src/cmd/cwfs/juke.c - 664 sys sys 1174799503 28859
+sys/src/cmd/cwfs/lrand.c - 664 sys sys 1171160167 1070
+sys/src/cmd/cwfs/main.c - 664 sys sys 1174799729 9976
+sys/src/cmd/cwfs/malloc.c - 664 sys sys 1174281557 2360
+sys/src/cmd/cwfs/mkfile - 664 sys sys 1174799125 170
+sys/src/cmd/cwfs/mworm.c - 664 sys sys 1174370308 4311
+sys/src/cmd/cwfs/net.c - 664 sys sys 1174521421 9939
+sys/src/cmd/cwfs/pc.c - 664 sys sys 1174716747 1718
+sys/src/cmd/cwfs/portdat.h - 664 sys sys 1174716952 15241
+sys/src/cmd/cwfs/portfns.h - 664 sys sys 1174798778 6701
+sys/src/cmd/cwfs/portmkfile - 664 sys sys 1174798711 791
+sys/src/cmd/cwfs/scsi.c - 664 sys sys 1174282908 8865
+sys/src/cmd/cwfs/sub.c - 664 sys sys 1174798849 24353
+sys/src/cmd/cwfs/time.c - 664 sys sys 1171500176 1696
+sys/src/cmd/cwfs/uidgid.c - 664 sys sys 1174717700 9421
+sys/src/cmd/cwfs/wren.c - 664 sys sys 1174458303 2737
 sys/src/cmd/date.c - 664 sys sys 944961351 449
 sys/src/cmd/db - 20000000775 sys sys 1046363143 0
 sys/src/cmd/db/command.c - 664 sys sys 1166823791 4376
@@ -14655,7 +14730,7 @@ sys/src/libauthsrv/mkfile - 664 sys sys 1035389776 409
 sys/src/libauthsrv/nvcsum.c - 664 sys sys 1015091654 192
 sys/src/libauthsrv/opasstokey.c - 664 sys sys 1015091654 448
 sys/src/libauthsrv/passtokey.c - 664 sys sys 1143695654 488
-sys/src/libauthsrv/readnvram.c - 664 sys sys 1171777600 10170
+sys/src/libauthsrv/readnvram.c - 664 sys sys 1174807437 10155
 sys/src/libbin - 20000000775 sys sys 1045502972 0
 sys/src/libbin/bin.c - 664 sys sys 1135487932 1829
 sys/src/libbin/mkfile - 664 sys sys 1035389776 197

+ 81 - 6
dist/replica/plan9.db

@@ -5444,6 +5444,7 @@ rc/bin/delkey - 775 sys sys 1109429137 643
 rc/bin/dial - 20000000775 sys sys 1059180057 0
 rc/bin/diffy - 775 sys sys 1140694870 277
 rc/bin/dircp - 775 sys sys 1169490692 234
+rc/bin/diskparts - 775 sys sys 1174849973 392
 rc/bin/doc2ps - 775 sys sys 1069793831 244
 rc/bin/doc2txt - 775 sys sys 1025197880 563
 rc/bin/doctype - 775 sys sys 1162921755 1727
@@ -5469,7 +5470,7 @@ rc/bin/leak - 775 sys sys 1172760642 1921
 rc/bin/lookman - 775 sys sys 1017679344 686
 rc/bin/lp - 775 sys sys 1162105982 5201
 rc/bin/mail - 775 sys sys 1045504003 138
-rc/bin/man - 775 sys sys 1173755398 2504
+rc/bin/man - 775 sys sys 1174803269 2639
 rc/bin/map - 775 sys sys 945617207 1576
 rc/bin/mapdemo - 775 sys sys 945617207 4075
 rc/bin/membername - 775 sys sys 945617207 89
@@ -5559,7 +5560,7 @@ rc/bin/start - 775 sys sys 945617209 120
 rc/bin/stock - 775 sys sys 1143126371 292
 rc/bin/stop - 775 sys sys 945617209 110
 rc/bin/tel - 775 sys sys 1161209756 128
-rc/bin/termrc - 775 sys sys 1164658821 2647
+rc/bin/termrc - 775 sys sys 1174850048 2434
 rc/bin/thesaurus - 775 sys sys 1068054167 246
 rc/bin/tlsclienttunnel - 775 sys sys 1024375633 153
 rc/bin/tlssrvtunnel - 775 sys sys 1024375634 175
@@ -6568,7 +6569,7 @@ sys/lib/lp/process - 20000000775 sys sys 1077670465 0
 sys/lib/lp/process/dpost - 775 sys sys 1015012079 2412
 sys/lib/lp/process/dvipost - 775 sys sys 954037459 3123
 sys/lib/lp/process/g3post - 775 sys sys 954037459 2348
-sys/lib/lp/process/generic - 775 sys sys 1077715118 4805
+sys/lib/lp/process/generic - 775 sys sys 1174846328 4108
 sys/lib/lp/process/gifpost - 775 sys sys 1015012079 2317
 sys/lib/lp/process/gspipe - 775 sys sys 1015012079 434
 sys/lib/lp/process/gspipeijs - 775 sys sys 1077670465 949
@@ -7066,7 +7067,7 @@ sys/lib/tmac/mmt - 664 sys sys 964454718 40915
 sys/lib/tmac/name.sed - 664 sys sys 944956202 62
 sys/lib/tmac/sendcover - 775 sys sys 944956202 50
 sys/lib/tmac/strings.mm - 664 sys sys 964455723 2146
-sys/lib/tmac/tmac.an - 664 sys sys 1161484650 8228
+sys/lib/tmac/tmac.an - 664 sys sys 1174803319 8439
 sys/lib/tmac/tmac.anhtml - 664 sys sys 984696197 105
 sys/lib/tmac/tmac.antimes - 664 sys sys 964454718 7809
 sys/lib/tmac/tmac.bits - 664 sys sys 944956202 1089
@@ -7508,7 +7509,7 @@ sys/man/2/iounit - 664 sys sys 1015091523 1001
 sys/man/2/ip - 664 sys sys 1173239323 7249
 sys/man/2/isalpharune - 664 sys sys 1015091523 1059
 sys/man/2/keyboard - 664 sys sys 950892860 2065
-sys/man/2/lock - 664 sys sys 1172959628 4693
+sys/man/2/lock - 664 sys sys 1174844415 5704
 sys/man/2/mach - 664 sys sys 1080179298 7995
 sys/man/2/malloc - 664 sys sys 1102093069 4763
 sys/man/2/matrix - 664 sys sys 950892861 6090
@@ -7618,6 +7619,7 @@ sys/man/4/archfs - 664 sys sys 960000712 533
 sys/man/4/cdfs - 664 sys sys 1026846913 3638
 sys/man/4/cfs - 664 sys sys 1172762903 1813
 sys/man/4/consolefs - 664 sys sys 1144424854 4245
+sys/man/4/cwfs - 664 sys sys 1174799917 6329
 sys/man/4/dossrv - 664 sys sys 1168307403 4334
 sys/man/4/execnet - 664 sys sys 1019866708 1069
 sys/man/4/exportfs - 664 sys sys 1145881912 4655
@@ -9941,6 +9943,79 @@ sys/src/cmd/cpp/test.c - 664 sys sys 944960879 47
 sys/src/cmd/cpp/tokens.c - 664 sys sys 944960879 6755
 sys/src/cmd/cpu.c - 664 sys sys 1164133664 21154
 sys/src/cmd/crop.c - 664 sys sys 1134557476 4137
+sys/src/cmd/cwfs - 20000000775 sys sys 1174799196 0
+sys/src/cmd/cwfs/32bit.h - 664 sys sys 1171162581 594
+sys/src/cmd/cwfs/64bit.h - 664 sys sys 1171162523 634
+sys/src/cmd/cwfs/9netics32.16k - 20000000775 sys sys 1174799194 0
+sys/src/cmd/cwfs/9netics32.16k/conf.c - 664 sys sys 1171171267 503
+sys/src/cmd/cwfs/9netics32.16k/dat.h - 664 sys sys 1171171041 794
+sys/src/cmd/cwfs/9netics32.16k/mkfile - 664 sys sys 1171519744 29
+sys/src/cmd/cwfs/9netics64.8k - 20000000775 sys sys 1174799194 0
+sys/src/cmd/cwfs/9netics64.8k/conf.c - 664 sys sys 1171171458 564
+sys/src/cmd/cwfs/9netics64.8k/dat.h - 664 sys sys 1171171387 791
+sys/src/cmd/cwfs/9netics64.8k/mkfile - 664 sys sys 1171519752 28
+sys/src/cmd/cwfs/9p1.c - 664 sys sys 1174716878 29747
+sys/src/cmd/cwfs/9p1.h - 664 sys sys 1171159776 2128
+sys/src/cmd/cwfs/9p1lib.c - 664 sys sys 1171145882 7894
+sys/src/cmd/cwfs/9p2.c - 664 sys sys 1174716894 35170
+sys/src/cmd/cwfs/README - 664 sys sys 1171520084 344
+sys/src/cmd/cwfs/all.h - 664 sys sys 1174716764 2186
+sys/src/cmd/cwfs/auth.c - 664 sys sys 1174797532 5840
+sys/src/cmd/cwfs/chk.c - 664 sys sys 1174716979 14579
+sys/src/cmd/cwfs/choline - 20000000775 sys sys 1174799194 0
+sys/src/cmd/cwfs/choline/conf.c - 664 sys sys 1171170952 412
+sys/src/cmd/cwfs/choline/dat.h - 664 sys sys 1171170751 791
+sys/src/cmd/cwfs/choline/mkfile - 664 sys sys 1171519759 26
+sys/src/cmd/cwfs/con.c - 664 sys sys 1174717615 15713
+sys/src/cmd/cwfs/config.c - 664 sys sys 1174717895 20243
+sys/src/cmd/cwfs/console.c - 664 sys sys 1174716894 4671
+sys/src/cmd/cwfs/cw.c - 664 sys sys 1174716894 42798
+sys/src/cmd/cwfs/cwfs - 20000000775 sys sys 1174799195 0
+sys/src/cmd/cwfs/cwfs/conf.c - 664 sys sys 1171485877 452
+sys/src/cmd/cwfs/cwfs/dat.h - 664 sys sys 1171485890 796
+sys/src/cmd/cwfs/cwfs/mkfile - 664 sys sys 1174798645 21
+sys/src/cmd/cwfs/data.c - 664 sys sys 1171158553 4515
+sys/src/cmd/cwfs/dentry.c - 664 sys sys 1174716894 6265
+sys/src/cmd/cwfs/doc - 20000000775 sys sys 1174799195 0
+sys/src/cmd/cwfs/doc/changes - 664 sys sys 1146795434 2967
+sys/src/cmd/cwfs/doc/emelie.boot - 664 sys sys 1171304297 8751
+sys/src/cmd/cwfs/doc/user.mode.boot - 664 sys sys 1171326690 747
+sys/src/cmd/cwfs/doc/words - 664 sys sys 1171172525 1096
+sys/src/cmd/cwfs/doc/worm.fs - 664 sys sys 1146795434 4057
+sys/src/cmd/cwfs/doc/worm.fs64 - 664 sys sys 1146795434 1554
+sys/src/cmd/cwfs/doc/worms.32-bit - 664 sys sys 1146795434 2806
+sys/src/cmd/cwfs/emelie - 20000000775 sys sys 1174799195 0
+sys/src/cmd/cwfs/emelie/conf.c - 664 sys sys 1174370534 500
+sys/src/cmd/cwfs/emelie/dat.h - 664 sys sys 1171165543 790
+sys/src/cmd/cwfs/emelie/map.w2-w0 - 664 sys sys 1171864578 6
+sys/src/cmd/cwfs/emelie/mkfile - 664 sys sys 1174799340 26
+sys/src/cmd/cwfs/fs - 20000000775 sys sys 1174799195 0
+sys/src/cmd/cwfs/fs/conf.c - 664 sys sys 1171171939 554
+sys/src/cmd/cwfs/fs/dat.h - 664 sys sys 1171171640 784
+sys/src/cmd/cwfs/fs/mkfile - 664 sys sys 1171519772 21
+sys/src/cmd/cwfs/fs64 - 20000000775 sys sys 1174799195 0
+sys/src/cmd/cwfs/fs64/conf.c - 664 sys sys 1171172030 559
+sys/src/cmd/cwfs/fs64/dat.h - 664 sys sys 1171172076 786
+sys/src/cmd/cwfs/fs64/mkfile - 664 sys sys 1174799404 24
+sys/src/cmd/cwfs/fworm.c - 664 sys sys 1174716894 1814
+sys/src/cmd/cwfs/io.h - 664 sys sys 1174280312 866
+sys/src/cmd/cwfs/iobuf.c - 664 sys sys 1174716894 4713
+sys/src/cmd/cwfs/juke.c - 664 sys sys 1174799503 28859
+sys/src/cmd/cwfs/lrand.c - 664 sys sys 1171160167 1070
+sys/src/cmd/cwfs/main.c - 664 sys sys 1174799729 9976
+sys/src/cmd/cwfs/malloc.c - 664 sys sys 1174281557 2360
+sys/src/cmd/cwfs/mkfile - 664 sys sys 1174799125 170
+sys/src/cmd/cwfs/mworm.c - 664 sys sys 1174370308 4311
+sys/src/cmd/cwfs/net.c - 664 sys sys 1174521421 9939
+sys/src/cmd/cwfs/pc.c - 664 sys sys 1174716747 1718
+sys/src/cmd/cwfs/portdat.h - 664 sys sys 1174716952 15241
+sys/src/cmd/cwfs/portfns.h - 664 sys sys 1174798778 6701
+sys/src/cmd/cwfs/portmkfile - 664 sys sys 1174798711 791
+sys/src/cmd/cwfs/scsi.c - 664 sys sys 1174282908 8865
+sys/src/cmd/cwfs/sub.c - 664 sys sys 1174798849 24353
+sys/src/cmd/cwfs/time.c - 664 sys sys 1171500176 1696
+sys/src/cmd/cwfs/uidgid.c - 664 sys sys 1174717700 9421
+sys/src/cmd/cwfs/wren.c - 664 sys sys 1174458303 2737
 sys/src/cmd/date.c - 664 sys sys 944961351 449
 sys/src/cmd/db - 20000000775 sys sys 1046363143 0
 sys/src/cmd/db/command.c - 664 sys sys 1166823791 4376
@@ -14655,7 +14730,7 @@ sys/src/libauthsrv/mkfile - 664 sys sys 1035389776 409
 sys/src/libauthsrv/nvcsum.c - 664 sys sys 1015091654 192
 sys/src/libauthsrv/opasstokey.c - 664 sys sys 1015091654 448
 sys/src/libauthsrv/passtokey.c - 664 sys sys 1143695654 488
-sys/src/libauthsrv/readnvram.c - 664 sys sys 1171777600 10170
+sys/src/libauthsrv/readnvram.c - 664 sys sys 1174807437 10155
 sys/src/libbin - 20000000775 sys sys 1045502972 0
 sys/src/libbin/bin.c - 664 sys sys 1135487932 1829
 sys/src/libbin/mkfile - 664 sys sys 1035389776 197

+ 81 - 0
dist/replica/plan9.log

@@ -47963,3 +47963,84 @@
 1174793406 0 c sys/man/8/cpurc - 664 sys sys 1174792133 1541
 1174793406 1 c sys/man/8/listen - 664 sys sys 1174792129 3826
 1174793406 2 c sys/src/cmd/ndb/dblookup.c - 664 sys sys 1174792290 20940
+1174800606 0 a sys/man/4/cwfs - 664 sys sys 1174799917 6329
+1174800606 1 a sys/src/cmd/cwfs - 20000000775 sys sys 1174799196 0
+1174800606 2 a sys/src/cmd/cwfs/32bit.h - 664 sys sys 1171162581 594
+1174800606 3 a sys/src/cmd/cwfs/64bit.h - 664 sys sys 1171162523 634
+1174800606 4 a sys/src/cmd/cwfs/9netics32.16k - 20000000775 sys sys 1174799194 0
+1174800606 5 a sys/src/cmd/cwfs/9netics32.16k/conf.c - 664 sys sys 1171171267 503
+1174800606 6 a sys/src/cmd/cwfs/9netics32.16k/dat.h - 664 sys sys 1171171041 794
+1174800606 7 a sys/src/cmd/cwfs/9netics32.16k/mkfile - 664 sys sys 1171519744 29
+1174800606 8 a sys/src/cmd/cwfs/9netics64.8k - 20000000775 sys sys 1174799194 0
+1174800606 9 a sys/src/cmd/cwfs/9netics64.8k/conf.c - 664 sys sys 1171171458 564
+1174800606 10 a sys/src/cmd/cwfs/9netics64.8k/dat.h - 664 sys sys 1171171387 791
+1174800606 11 a sys/src/cmd/cwfs/9netics64.8k/mkfile - 664 sys sys 1171519752 28
+1174800606 12 a sys/src/cmd/cwfs/9p1.c - 664 sys sys 1174716878 29747
+1174800606 13 a sys/src/cmd/cwfs/9p1.h - 664 sys sys 1171159776 2128
+1174800606 14 a sys/src/cmd/cwfs/9p1lib.c - 664 sys sys 1171145882 7894
+1174800606 15 a sys/src/cmd/cwfs/9p2.c - 664 sys sys 1174716894 35170
+1174800606 16 a sys/src/cmd/cwfs/README - 664 sys sys 1171520084 344
+1174800606 17 a sys/src/cmd/cwfs/all.h - 664 sys sys 1174716764 2186
+1174800606 18 a sys/src/cmd/cwfs/auth.c - 664 sys sys 1174797532 5840
+1174800606 19 a sys/src/cmd/cwfs/chk.c - 664 sys sys 1174716979 14579
+1174800606 20 a sys/src/cmd/cwfs/choline - 20000000775 sys sys 1174799194 0
+1174800606 21 a sys/src/cmd/cwfs/choline/conf.c - 664 sys sys 1171170952 412
+1174800606 22 a sys/src/cmd/cwfs/choline/dat.h - 664 sys sys 1171170751 791
+1174800606 23 a sys/src/cmd/cwfs/choline/mkfile - 664 sys sys 1171519759 26
+1174800606 24 a sys/src/cmd/cwfs/con.c - 664 sys sys 1174717615 15713
+1174800606 25 a sys/src/cmd/cwfs/config.c - 664 sys sys 1174717895 20243
+1174800606 26 a sys/src/cmd/cwfs/console.c - 664 sys sys 1174716894 4671
+1174800606 27 a sys/src/cmd/cwfs/cw.c - 664 sys sys 1174716894 42798
+1174800606 28 a sys/src/cmd/cwfs/cwfs - 20000000775 sys sys 1174799195 0
+1174800606 29 a sys/src/cmd/cwfs/cwfs/conf.c - 664 sys sys 1171485877 452
+1174800606 30 a sys/src/cmd/cwfs/cwfs/dat.h - 664 sys sys 1171485890 796
+1174800606 31 a sys/src/cmd/cwfs/cwfs/mkfile - 664 sys sys 1174798645 21
+1174800606 32 a sys/src/cmd/cwfs/data.c - 664 sys sys 1171158553 4515
+1174800606 33 a sys/src/cmd/cwfs/dentry.c - 664 sys sys 1174716894 6265
+1174800606 34 a sys/src/cmd/cwfs/doc - 20000000775 sys sys 1174799195 0
+1174800606 35 a sys/src/cmd/cwfs/doc/changes - 664 sys sys 1146795434 2967
+1174800606 36 a sys/src/cmd/cwfs/doc/emelie.boot - 664 sys sys 1171304297 8751
+1174800606 37 a sys/src/cmd/cwfs/doc/user.mode.boot - 664 sys sys 1171326690 747
+1174800606 38 a sys/src/cmd/cwfs/doc/words - 664 sys sys 1171172525 1096
+1174800606 39 a sys/src/cmd/cwfs/doc/worm.fs - 664 sys sys 1146795434 4057
+1174800606 40 a sys/src/cmd/cwfs/doc/worm.fs64 - 664 sys sys 1146795434 1554
+1174800606 41 a sys/src/cmd/cwfs/doc/worms.32-bit - 664 sys sys 1146795434 2806
+1174800606 42 a sys/src/cmd/cwfs/emelie - 20000000775 sys sys 1174799195 0
+1174800606 43 a sys/src/cmd/cwfs/emelie/conf.c - 664 sys sys 1174370534 500
+1174800606 44 a sys/src/cmd/cwfs/emelie/dat.h - 664 sys sys 1171165543 790
+1174800606 45 a sys/src/cmd/cwfs/emelie/map.w2-w0 - 664 sys sys 1171864578 6
+1174800606 46 a sys/src/cmd/cwfs/emelie/mkfile - 664 sys sys 1174799340 26
+1174800606 47 a sys/src/cmd/cwfs/fs - 20000000775 sys sys 1174799195 0
+1174800606 48 a sys/src/cmd/cwfs/fs/conf.c - 664 sys sys 1171171939 554
+1174800606 49 a sys/src/cmd/cwfs/fs/dat.h - 664 sys sys 1171171640 784
+1174800606 50 a sys/src/cmd/cwfs/fs/mkfile - 664 sys sys 1171519772 21
+1174800606 51 a sys/src/cmd/cwfs/fs64 - 20000000775 sys sys 1174799195 0
+1174800606 52 a sys/src/cmd/cwfs/fs64/conf.c - 664 sys sys 1171172030 559
+1174800606 53 a sys/src/cmd/cwfs/fs64/dat.h - 664 sys sys 1171172076 786
+1174800606 54 a sys/src/cmd/cwfs/fs64/mkfile - 664 sys sys 1174799404 24
+1174800606 55 a sys/src/cmd/cwfs/fworm.c - 664 sys sys 1174716894 1814
+1174800606 56 a sys/src/cmd/cwfs/io.h - 664 sys sys 1174280312 866
+1174800606 57 a sys/src/cmd/cwfs/iobuf.c - 664 sys sys 1174716894 4713
+1174800606 58 a sys/src/cmd/cwfs/juke.c - 664 sys sys 1174799503 28859
+1174800606 59 a sys/src/cmd/cwfs/lrand.c - 664 sys sys 1171160167 1070
+1174800606 60 a sys/src/cmd/cwfs/main.c - 664 sys sys 1174799729 9976
+1174800606 61 a sys/src/cmd/cwfs/malloc.c - 664 sys sys 1174281557 2360
+1174800606 62 a sys/src/cmd/cwfs/mkfile - 664 sys sys 1174799125 170
+1174800606 63 a sys/src/cmd/cwfs/mworm.c - 664 sys sys 1174370308 4311
+1174800606 64 a sys/src/cmd/cwfs/net.c - 664 sys sys 1174521421 9939
+1174800606 65 a sys/src/cmd/cwfs/pc.c - 664 sys sys 1174716747 1718
+1174800606 66 a sys/src/cmd/cwfs/portdat.h - 664 sys sys 1174716952 15241
+1174800606 67 a sys/src/cmd/cwfs/portfns.h - 664 sys sys 1174798778 6701
+1174800606 68 a sys/src/cmd/cwfs/portmkfile - 664 sys sys 1174798711 791
+1174800606 69 a sys/src/cmd/cwfs/scsi.c - 664 sys sys 1174282908 8865
+1174800606 70 a sys/src/cmd/cwfs/sub.c - 664 sys sys 1174798849 24353
+1174800606 71 a sys/src/cmd/cwfs/time.c - 664 sys sys 1171500176 1696
+1174800606 72 a sys/src/cmd/cwfs/uidgid.c - 664 sys sys 1174717700 9421
+1174800606 73 a sys/src/cmd/cwfs/wren.c - 664 sys sys 1174458303 2737
+1174804206 0 c rc/bin/man - 775 sys sys 1174803269 2639
+1174804206 1 c sys/lib/tmac/tmac.an - 664 sys sys 1174803319 8439
+1174807805 0 c sys/src/libauthsrv/readnvram.c - 664 sys sys 1174807437 10155
+1174845606 0 c sys/man/2/lock - 664 sys sys 1174844415 5704
+1174847405 0 c sys/lib/lp/process/generic - 775 sys sys 1174846328 4108
+1174851005 0 c rc/bin/termrc - 775 sys sys 1174850048 2434
+1174851005 1 a rc/bin/diskparts - 775 sys sys 1174849973 392

+ 14 - 0
rc/bin/diskparts

@@ -0,0 +1,14 @@
+#!/bin/rc
+# set up any /dev/sd partitions
+rfork e
+for(disk in /dev/sd[0-9A-Zabd-z]*) {
+	if(test -f $disk/data && test -f $disk/ctl)
+		disk/fdisk -p $disk/data >$disk/ctl >[2]/dev/null
+	for(part in $disk/plan9*)
+		if(test -f $part)
+			disk/prep -p $part >$disk/ctl >[2]/dev/null
+}
+
+# set up any fs(3) partitions
+if (test -r	/cfg/$sysname/fsconfig)
+	read -m /cfg/$sysname/fsconfig >/dev/fs/ctl

+ 8 - 6
rc/bin/man

@@ -21,7 +21,9 @@ fn roff {
 		Nflag=-Tutf
 	}
 	if not {
-		Nflag=-N
+		Nflag='-N'
+		Lflag='-rL1000i'
+		# setting L changes page length to infinity (sed script removes empty lines)
 		if (grep -s '^\.2C' $2)
 			postproc=col
 	}
@@ -32,17 +34,17 @@ fn roff {
 	{echo -n $FONTS; cat $2 </dev/null} |
 		switch($#preproc) {
 		case 0
-			troff $Nflag -$MAN 
+			troff $Nflag $Lflag -$MAN 
 		case 1
-			$preproc | troff $Nflag -$MAN
+			$preproc | troff $Nflag $Lflag -$MAN
 		case 2
-			$preproc(1) | $preproc(2) | troff $Nflag -$MAN
+			$preproc(1) | $preproc(2) | troff $Nflag $Lflag -$MAN
 		case 3
 			$preproc(1) | $preproc(2) | $preproc(3) |
-				troff $Nflag -$MAN
+				troff $Nflag $Lflag -$MAN
 		case *
 			$preproc(1) | $preproc(2) | $preproc(3) |
-				$preproc(4) | troff $Nflag -$MAN
+				$preproc(4) | troff $Nflag $Lflag -$MAN
 		} | $postproc
 }
 

+ 2 - 7
rc/bin/termrc

@@ -56,13 +56,8 @@ case *' indigo'*
 case NCR* 'AT&TNSX'* generic* _MP_* 'alpha apc'*
 	for(i in H w f t m v L S P U '$' Σ κ)
 		/bin/bind -a '#'^$i /dev >/dev/null >[2=1]
-	for(disk in /dev/sd??) {
-		if(test -f $disk/data && test -f $disk/ctl)
-			disk/fdisk -p $disk/data >$disk/ctl >[2]/dev/null
-		for(part in $disk/plan9*)
-			if(test -f $part)
-				disk/prep -p $part >$disk/ctl >[2]/dev/null
-	}
+
+	diskparts
 
 	if(! ~ `{cat /dev/user} none)
 		aux/vmware

+ 153 - 97
sys/lib/lp/process/generic

@@ -2,134 +2,190 @@
 # Tries to determine what type of file you are printing and do the correct
 # thing with it.
 # It currently knows about images, troff intermediate, and ascii files.
-TMPFILE=/tmp/lp$pid
-fn sigexit { rm -f $TMPFILE; }
-if (! ~ $DEBUG '') flag x +
-if (~ $LPCLASS *nohead*) NOHEAD=1
-if (~ $LPCLASS *duplex*) DUPLEX=1
-cat >$TMPFILE
-FILETYPE=`{file $TMPFILE}
-switch ($FILETYPE(2)) {
-case troff;
-	switch ($LPCLASS) {
-	case *Latin1* *post* *opost*;	switch ($FILETYPE(5)) {
-					# Latin1 is for compatibility with old research UNIX systems, doesn't work on Plan 9
-					case Latin1 post; tcs -s -f utf -t latin1 < $TMPFILE |$LPLIB/process/dpost
-
-					case UTF; $LPLIB/process/tr2post < $TMPFILE
-					}
+rfork e
+temp=/tmp/lp$pid
+fn sigexit { rm -f $temp }
+proc=$LPLIB/process
 
-	case *gs!* *gsijs!*;	switch ($FILETYPE(5)) {
-					# Latin1 is for compatibility with old research UNIX systems, doesn't work on Plan 9
-					case Latin1 post; tcs -s -f utf -t latin1 < $TMPFILE |$LPLIB/process/dpost |$LPLIB/process/gspipe
+if (! ~ $DEBUG '')
+	flag x +
+if (~ $LPCLASS *nohead*)
+	NOHEAD=1
+if (~ $LPCLASS *duplex*)
+	DUPLEX=1
+cat >$temp
 
-					case UTF; $LPLIB/process/tr2post < $TMPFILE |$LPLIB/process/gspipe
-					}
+type=`{file $temp}
+switch ($type(2)) {
+case troff
+	switch ($LPCLASS) {
+	case *Latin1* *post* *opost*
+		switch ($type(5)) {
+		# Latin1 is for compatibility with old research UNIX systems,
+		# doesn't work on Plan 9
+		case Latin1 post
+			tcs -s -f utf -t latin1 <$temp | $proc/dpost
+		case UTF
+			$proc/tr2post <$temp
+		}
+	case *gs!* *gsijs!*
+		switch ($type(5)) {
+		# Latin1 is for compatibility with old research UNIX systems,
+		# doesn't work on Plan 9
+		case Latin1 post
+			tcs -s -f utf -t latin1 <$temp | $proc/dpost |
+				$proc/gspipe
 
-	case *;		echo $FILETYPE(2) -T$FILETYPE(5) output is improper for $LPDEST >[1=2]
+		case UTF
+			$proc/tr2post <$temp | $proc/gspipe
+		}
+	case *
+		echo $type(2) -T$type(5) output is improper for $LPDEST >[1=2]
 	}
-case special;
-	switch ($FILETYPE(4)) {
-	case '#b';		switch ($LPCLASS) {
-				case *post*;	$LPLIB/process/p9bitpost < $TMPFILE
-				case *gs!*;	$LPLIB/process/p9bitpost < $TMPFILE |$LPLIB/process/gspipe
-				case *gsijs!*;	$LPLIB/process/p9bitpost < $TMPFILE |$LPLIB/process/gspipeijs
-				}
-
-	case *;		echo $FILETYPE file is improper for $LPDEST >[1=2]
+case special
+	switch ($type(4)) {
+	case '#b'
+		switch ($LPCLASS) {
+		case *post*
+			$proc/p9bitpost <$temp
+		case *gs!*
+			$proc/p9bitpost <$temp | $proc/gspipe
+		case *gsijs!*
+			$proc/p9bitpost <$temp | $proc/gspipeijs
+		}
+	case *
+		echo $type file is improper for $LPDEST >[1=2]
 	}
-case Compressed plan old;	# type is really 'Compressed image' or 'plan 9 image' 
-				# or 'old plan 9 image'
+case Compressed plan old
+	# type is really 'Compressed image' or 'plan 9 image' or
+	# 'old plan 9 image'
 	switch ($LPCLASS) {
-	case *post*;	$LPLIB/process/p9bitpost < $TMPFILE
-	case *gs!*;	$LPLIB/process/p9bitpost < $TMPFILE |$LPLIB/process/gspipe
-	case *gsijs!*;	$LPLIB/process/p9bitpost < $TMPFILE |$LPLIB/process/gspipeijs
+	case *post*
+		$proc/p9bitpost <$temp
+	case *gs!*
+		$proc/p9bitpost <$temp | $proc/gspipe
+	case *gsijs!*
+		$proc/p9bitpost <$temp | $proc/gspipeijs
 	}
-case jpeg;
+case jpeg
 	switch ($LPCLASS) {
-	case *post*;	$LPLIB/process/jpgpost < $TMPFILE
-	case *gs!*;	$LPLIB/process/jpgpost < $TMPFILE |$LPLIB/process/gspipe
-	case *gsijs!*;	$LPLIB/process/jpgpost < $TMPFILE |$LPLIB/process/gspipeijs
+	case *post*
+		$proc/jpgpost <$temp
+	case *gs!*
+		$proc/jpgpost <$temp | $proc/gspipe
+	case *gsijs!*
+		$proc/jpgpost <$temp | $proc/gspipeijs
 	}
-
-case GIF;
+case GIF
 	switch ($LPCLASS) {
-	case *post*;	$LPLIB/process/gifpost < $TMPFILE
-	case *gs!*;	$LPLIB/process/gifpost < $TMPFILE |$LPLIB/process/gspipe
-	case *gsijs!*;	$LPLIB/process/gifpost < $TMPFILE |$LPLIB/process/gspipeijs
+	case *post*
+		$proc/gifpost <$temp
+	case *gs!*
+		$proc/gifpost <$temp | $proc/gspipe
+	case *gsijs!*
+		$proc/gifpost <$temp | $proc/gspipeijs
 	}
-
 case ccitt-g31;
 	switch ($LPCLASS) {
-	case *post*;	$LPLIB/process/g3post < $TMPFILE
-	case *gs!*;	$LPLIB/process/g3post < $TMPFILE |$LPLIB/process/gspipe
-	case *gsijs!*;	$LPLIB/process/g3post < $TMPFILE |$LPLIB/process/gspipeijs
+	case *post*
+		$proc/g3post <$temp
+	case *gs!*
+		$proc/g3post <$temp | $proc/gspipe
+	case *gsijs!*
+		$proc/g3post <$temp | $proc/gspipeijs
 	}
-
-# bitmap for research UNIX compatibility, does not work on Plan 9.
-case bitmap;
+case bitmap
+	# bitmap for research UNIX compatibility, does not work on Plan 9.
 	switch ($LPCLASS) {
-	case *post*;	$LPLIB/process/bpost < $TMPFILE
-	case *mhcc*;	$LPLIB/process/bpost < $TMPFILE | $LPLIB/process/mhcc
-	case *;		echo $FILETYPE(2) file is improper for $LPDEST >[1=2]
+	case *post*
+		$proc/bpost <$temp
+	case *mhcc*
+		$proc/bpost <$temp | $proc/mhcc
+	case *
+		echo $type(2) file is improper for $LPDEST >[1=2]
 	}
-case tex;
-	mv $TMPFILE $TMPFILE.dvi
-	TMPFILE=$TMPFILE.dvi
+case tex
+	mv $temp $temp.dvi
+	temp=$temp.dvi
 	switch ($LPCLASS) {
-	case *post*;	$LPLIB/process/dvipost $TMPFILE
-	case *gs!*;	$LPLIB/process/dvipost $TMPFILE |$LPLIB/process/gspipe
-	case *gsijs!*;	$LPLIB/process/dvipost $TMPFILE |$LPLIB/process/gspipeijs
-	case *;		echo $FILETYPE(2) file is improper for $LPDEST >[1=2]
+	case *post*
+		$proc/dvipost $temp
+	case *gs!*
+		$proc/dvipost $temp | $proc/gspipe
+	case *gsijs!*
+		$proc/dvipost $temp | $proc/gspipeijs
+	case *
+		echo $type(2) file is improper for $LPDEST >[1=2]
 	}
-case postscript;
+case postscript
 	switch ($LPCLASS) {
-	case *post*;		$LPLIB/process/post < $TMPFILE
-	case *gs!*;		$LPLIB/process/post < $TMPFILE |$LPLIB/process/gspipe
-	case *gsijs!*;		$LPLIB/process/post < $TMPFILE |$LPLIB/process/gspipeijs
-	case *;			echo $FILETYPE(2) file is improper for $LPDEST >[1=2]
+	case *post*
+		$proc/post <$temp
+	case *gs!*
+		$proc/post <$temp | $proc/gspipe
+	case *gsijs!*
+		$proc/post <$temp | $proc/gspipeijs
+	case *
+		echo $type(2) file is improper for $LPDEST >[1=2]
 	}
-case HPJCL;
+case HPJCL
 	switch ($LPCLASS) {
-	case *HPJCL*;		$LPLIB/process/noproc < $TMPFILE
-	case *;			echo $FILETYPE(2) file is improper for $LPDEST >[1=2]
+	case *HPJCL*
+		$proc/noproc <$temp
+	case *
+		echo $type(2) file is improper for $LPDEST >[1=2]
 	}
-case daisy;
+case daisy
 	switch ($LPDEST) {
-	case *;		echo $FILETYPE(2) file is improper for $LPDEST >[1=2]
+	case *
+		echo $type(2) file is improper for $LPDEST >[1=2]
 	}
-case English short extended alef limbo [Aa]scii assembler c latin rc sh as mail email message/rfc822;
+case tiff
 	switch ($LPCLASS) {
-	case *post*;	$LPLIB/process/ppost < $TMPFILE
-	case *gs!*;	$LPLIB/process/ppost < $TMPFILE |$LPLIB/process/gspipe
-	case *gsijs!*;	$LPLIB/process/ppost < $TMPFILE |$LPLIB/process/gspipeijs
-	case *canon*;	$LPLIB/process/can $* < $TMPFILE
-	case *;		echo Unrecognized class of line printer for $LPDEST >[1=2]
+	case *post*
+		$proc/tiffpost $temp
+	case *gs!*
+		$proc/tiffpost $temp | $proc/gspipe
+	case *gsijs!*
+		$proc/tiffpost $temp | $proc/gspipeijs
+	case *
+		echo Unrecognized class of line printer for $LPDEST >[1=2]
 	}
-
-case tiff;
-	switch ($LPCLASS) {
-	case *post*;	$LPLIB/process/tiffpost $TMPFILE
-	case *gs!*;	$LPLIB/process/tiffpost $TMPFILE |$LPLIB/process/gspipe
-	case *gsijs!*;	$LPLIB/process/tiffpost $TMPFILE |$LPLIB/process/gspipeijs
-	case *;		echo Unrecognized class of line printer for $LPDEST >[1=2]
-	}
-case PDF;
+case PDF
 	switch ($LPCLASS) {
-	case *post*;	$LPLIB/process/pdfpost $TMPFILE
-	case *gs!*;	$LPLIB/process/pdfgs $TMPFILE
-	case *gsijs!*;	$LPLIB/process/pdfgsijs $TMPFILE
-	case *;		echo Unrecognized class of line printer for $LPDEST >[1=2]
+	case *post*
+		$proc/pdfpost $temp
+	case *gs!*
+		$proc/pdfgs $temp
+	case *gsijs!*
+		$proc/pdfgsijs $temp
+	case *
+		echo Unrecognized class of line printer for $LPDEST >[1=2]
 	}
-case empty;
+case empty
 	echo file is empty >[1=2]
-case cannot;
+case cannot
 	echo cannot open file >[1=2]
-case *;
-	echo $FILETYPE(2) file is improper for $LPDEST >[1=2]
+case English short extended alef limbo [Aa]scii assembler c latin rc sh \
+    as mail email message/rfc822 manual
+	switch ($LPCLASS) {
+	case *post*
+		$proc/ppost <$temp
+	case *gs!*
+		$proc/ppost <$temp | $proc/gspipe
+	case *gsijs!*
+		$proc/ppost <$temp | $proc/gspipeijs
+	case *canon*
+		$proc/can $* <$temp
+	case *
+		echo Unrecognized class of line printer for $LPDEST >[1=2]
+	}
+case *
+	echo $type(2) file is improper for $LPDEST >[1=2]
 }
+
 wait
 rv=$status
-rm -f $TMPFILE
-#exit $status
+rm -f $temp
+# exit $rv
 exit

+ 13 - 1
sys/lib/tmac/tmac.an

@@ -447,6 +447,9 @@
 .tlNot for use or disclosure outside the
 .tlBell System except under written agreement. \}
 ..
+.\" end of macros
+.
+.\" choose page dimensions
 .nr)s 0
 .ift .if \ns .nr )s 1
 .nr)t 0
@@ -464,7 +467,12 @@
 .ift \{.ds R ®
 .dsS \s\n()S
 ..\}
-.ifn \{.nr )L 1000i
+.ifn \{.if \nL<=0 .nr )L 11i
+.ie \{
+.nr )L \nLu
+.\" Changed the above from .nr )L 1000i
+.\" (doesn't work for invocation outside of man) [sape]
+.nr V 0\}
 .nrLL 6.5i
 .nr)O .463i
 .if '\*(.T'think' \{.nrLL 80n
@@ -480,6 +488,7 @@
 .nrLL 82n
 .nr)L 84v
 .rmul \}
+.
 .nr)p 0 1
 .ds]I \\\\n(]
 .ds]R \\\\n()
@@ -506,10 +515,13 @@
 .if\n()t \{.ie \nd .ds ]W \*(]m \nd, 20\ny
 .el.ds ]W \*(]m \n(dy, 20\n(yr
 ..\}
+.
+.\" set page dimensions
 .pl\n()Lu
 .ll\n(LLu
 .lt\n(LLu
 .po\n()Ou
+.
 .ift .tr \``\''
 .}f
 .if\n()s .nr :m 3.5v

+ 75 - 0
sys/man/2/lock

@@ -184,6 +184,9 @@ pointer, which should point at the
 .B QLock
 that will guard
 .IR r .
+It is important that this
+.B QLock
+is the same one that protects the rendezvous condition; see the example.
 .PP
 A
 .B Ref
@@ -198,6 +201,78 @@ in one atomic operation.
 atomically decrements the
 .B Ref
 and returns zero if the resulting value is zero, non-zero otherwise.
+.SH EXAMPLE
+Implement a buffered single-element channel using 
+.I rsleep
+and
+.IR rwakeup :
+.IP
+.EX
+.ta +4n +4n +4n
+typedef struct Chan
+{
+	QLock l;
+	Rendez full, empty;
+	int val, haveval;
+} Chan;
+.EE
+.IP
+.EX
+.ta +4n +4n +4n
+Chan*
+mkchan(void)
+{
+	Chan *c;
+
+	c = mallocz(sizeof *c, 1);
+	c->full.l = &c->l;
+	c->empty.l = &c->l;
+	return c;
+}
+.EE
+.IP
+.EX
+.ta +4n +4n +4n
+void
+send(Chan *c, int val)
+{
+	qlock(&c->l);
+	while(c->haveval)
+		rsleep(&c->full);
+	c->haveval = 1;
+	c->val = val;
+	rwakeup(&c->empty);  /* no longer empty */
+	qunlock(&c->l);
+}
+.EE
+.IP
+.EX
+.ta +4n +4n +4n
+int
+recv(Chan *c)
+{
+	int v;
+
+	qlock(&c->l);
+	while(!c->haveval)
+		rsleep(&c->empty);
+	c->haveval = 0;
+	v = c->val;
+	rwakeup(&c->full);  /* no longer full */
+	qunlock(&c->l);
+	return v;
+}
+.EE
+.LP
+Note that the 
+.B QLock
+protecting the
+.B Chan
+is the same
+.B QLock
+used for the 
+.B Rendez ;
+this ensures that wakeups are not missed.
 .SH SOURCE
 .B /sys/src/libc/port/lock.c
 .br

+ 298 - 0
sys/man/4/cwfs

@@ -0,0 +1,298 @@
+.TH CWFS 4
+.SH NAME
+cwfs \- cached-worm file server, dump
+.SH SYNOPSIS
+.B cwfs
+[
+.B -f
+] [
+.B -a
+.I announce-string
+] ... [
+.B -m
+.I device-map
+]
+.I config-device
+.SH DESCRIPTION
+.I Cwfs
+is a cached-worm file server that runs
+as a user-mode program and can
+maintain file systems created by
+.IR fs (4),
+the original Plan 9 file server
+that had its own kernel and operated
+a standalone system with disks and
+optical-disc jukebox attached.
+Unlike
+.IR fs (4),
+which could only accept 9P connections over IL/IPv4 on Ethernets
+(or over Datakit and Cyclones, long ago),
+.I cwfs
+accepts 9P connections over any network medium and protocol
+that it can announce on,
+by default TCP (over IPv4 or IPv6).
+Given suitable 9P clients,
+one could even run 9P over
+.IR aan (8)
+or
+.IR tls (3).
+.PP
+The stock
+.I cwfs
+implements a 16K file system block size
+and 32-bit disk addresses,
+in order to be compatible with some existing file systems, notably
+.IR emelie 's.
+These parameters can be changed by recompilation.
+.PP
+.I Cwfs
+expects to find the configuration block on
+.IR config-device .
+.PP
+Options are:
+.TF -m
+.TP
+.B -a
+announce on
+.I announce-string
+instead of
+.LR tcp!*!9fs .
+.TP
+.B -f
+enter the file server's configuration mode
+before starting normal operation.
+.TP
+.B -m
+the file
+.I device-map
+contains a simple device name
+(e.g.,
+.LR w9 )
+and a replacement per line.
+The device name is in the usual
+.I filsys
+notation of
+.IR fsconfig (8).
+The replacement can be the name of an existing file
+or another such device name.
+For example, the file
+.RS
+.PD
+.IP
+.EX
+w0 /tmp/w0
+h1 w2
+.EE
+.PP
+.PD 0.3v
+would map accesses to device
+.L w0
+to existing file
+.LR /tmp/w0
+and accesses to device
+.L h1
+to device
+.LR w2 ,
+if no file named
+.L w2
+exists.
+.RE
+.PD
+.PP
+The file server normally requires all users except
+.L none
+to provide authentication tickets on each
+.IR attach (5).
+This can be disabled using the
+.B noauth
+configuration command (see
+.IR fsconfig (8)).
+.PP
+The group numbered 9999, normally called
+.BR noworld ,
+is special
+on the file server.  Any user belonging to that group has
+attenuated access privileges.  Specifically, when checking such
+a user's access to files, the file's permission bits are first ANDed
+with 0770 for normal files or 0771 for directories.  The effect is
+to deny world access permissions to
+.B noworld
+users, except
+when walking directories.
+.PP
+The user
+.B none
+is always allowed to attach to
+.B emelie
+without authentication but has minimal permissions.
+.PP
+.B Emelie
+maintains three file systems
+on a combination of disks and
+write-once-read-many (WORM) magneto-optical disks.
+.TP
+.B other
+is a simple disk-based file system similar to
+.IR kfs (4) .
+.TP
+.B main
+is a worm-based file system with a disk-based
+look-aside cache.
+The disk cache holds
+modified worm blocks
+to overcome the write-once property of the worm.
+The cache also holds recently accessed
+non-modified blocks to
+speed up the effective access time of the worm.
+Occasionally
+(usually daily at 5AM) the modified blocks in the
+disk cache are
+.IR dumped .
+At this time,
+traffic to the file system is halted and the
+modified blocks are relabeled to the unwritten
+portion of the worm.
+After the dump,
+the file system traffic is continued and
+the relabeled blocks are copied to the worm by
+a background process.
+.TP
+.B dump
+Each time the main file system is dumped,
+its root is appended to a subdirectory of the dump file system.
+Since the dump file system is not mirrored with a disk
+cache,
+it is read-only.
+The name of the newly added root is created from the date
+of the dump:
+.BI / yyyy / mmdds\f1.
+Here
+.I yyyy
+is the full year,
+.I mm
+is the month number,
+.I dd
+is the day number and
+.I s
+is a sequence number if more than
+one dump is done in a day.
+For the first dump,
+.I s
+is null.
+For the subsequent dumps
+.I s
+is 1, 2, 3, etc.
+.sp
+The root of the main file system
+that is frozen on the first dump
+of March 1, 1992
+will be named
+.B /1992/0301/
+in the dump file system.
+.SS "Changes from fs(4)"
+.IR fs (4)'s
+IP configuration is ignored and the underlying system's is used.
+.PP
+Various other
+.IR fs (4)
+commands have been omitted since they (or equivalents) can now be
+executed directly on the underlying CPU server,
+notably
+.I date
+and
+.I passwd
+(see
+.IR auth/wrkey ).
+.PP
+.IR fs (4)'s
+device names
+.L h
+for IDE disks and
+.L m
+for Marvell SATA disks are not supported; use
+.B -m
+to map wren devices to appropriate names under
+.BR /dev/sd* .
+.PP
+The file server kernel seems to have scanned PCI buses
+in reverse order from the other Plan 9 kernels,
+so systems with multiple SCSI cards may find controller
+numbering reversed.
+.B -m
+can be used to compensate for this if you don't want to change
+.I filsys
+declarations.
+.PP
+The file server kernel's
+.I config
+field in NVRAM was overloaded in recent times to hold a
+.IR secstore (1)
+key for the CPU hostowner.
+Since
+.I cwfs
+runs on a CPU kernel,
+the location of its configuration block must be supplied on the command line.
+.PP
+Disk labels are now implemented for
+.B l
+devices.
+At the first access of a side,
+.I cwfs
+will attempt to read the label and verify that it has the correct side
+number and byte order; if either is wrong, it will issue a warning.
+If the label cannot be read,
+.I cwfs
+will attempt to write a new label.
+.SH EXAMPLES
+Place the root of the
+.B dump
+file system on
+.B /n/dump
+and show the modified times of the MIPS C compiler
+over all dumps in February, 1992:
+.IP
+.EX
+9fs dump
+ls -l /n/dump/1992/02??/mips/bin/vc
+.EE
+.PP
+To get only one line of output for each version of the compiler:
+.IP
+.EX
+ls -lp /n/dump/1992/02??/mips/bin/vc | uniq
+.EE
+.PP
+Make the
+.B other
+file system available in directory
+.BR /n/emelieother :
+.IP
+.EX
+mount -c /srv/boot /n/emelieother other
+.EE
+.SH SOURCE
+.B /sys/src/cmd/cwfs
+.SH SEE ALSO
+.IR yesterday (1),
+.IR fs (3),
+.IR sd (3),
+.IR fs (4),
+.IR srv (4),
+.IR fs (8)
+.br
+Sean Quinlan,
+``A Cached WORM File System'',
+.I
+Software \- Practice and Experience,
+December, 1991
+.br
+Ken Thompson,
+Geoff Collyer,
+``The 64-bit Standalone Plan 9 File Server''
+.SH BUGS
+For the moment,
+the file server serves both the old (9P1) and new (9P2000) versions of 9P,
+deciding which to serve by sniffing the first packet on each connection.
+.PP
+File system block size and disk address size (32- or 64-bit) are fixed
+at compilation time, and this is not easily changed.

+ 20 - 0
sys/src/cmd/cwfs/32bit.h

@@ -0,0 +1,20 @@
+/*
+ * fundamental constants and types of the implementation
+ * changing any of these changes the layout on disk
+ */
+
+/*
+ * compatible on disk with the old 32-bit file server and can also speak 9P1.
+ * this lets people run this file server on their old file systems.
+ * DON'T TOUCH or you'll break compatibility.
+ */
+enum {
+	NAMELEN		= 28,		/* max size of file name components */
+	NDBLOCK		= 6,		/* number of direct blocks in Dentry */
+	NIBLOCK		= 2,		/* max depth of indirect blocks */
+};
+
+typedef long Off;	/* file offsets & sizes, in bytes & blocks */
+
+#define COMPAT32
+#define swaboff swab4

+ 22 - 0
sys/src/cmd/cwfs/64bit.h

@@ -0,0 +1,22 @@
+/*
+ * fundamental constants and types of the implementation
+ * changing any of these changes the layout on disk
+ */
+
+/* the glorious new, incompatible (on disk) 64-bit world */
+
+/* keeping NAMELEN ≤ 50 bytes permits 3 Dentrys per mag disk sector */
+enum {
+	NAMELEN		= 56,		/* max size of file name components */
+	NDBLOCK		= 6,		/* number of direct blocks in Dentry */
+	NIBLOCK		= 4,		/* max depth of indirect blocks */
+};
+
+/*
+ * file offsets & sizes, in bytes & blocks.  typically long or vlong.
+ * vlong is used in the code where would be needed if Off were just long.
+ */
+typedef vlong Off;
+
+#undef COMPAT32
+#define swaboff swab8

+ 31 - 0
sys/src/cmd/cwfs/9netics32.16k/conf.c

@@ -0,0 +1,31 @@
+/* 9net32.16k-specific configuration */
+
+#include "all.h"
+
+#ifndef	DATE
+#define	DATE 1170808167L
+#endif
+
+Timet	fs_mktime = DATE;			/* set by mkfile */
+
+Startsb	startsb[] = {
+	"main",		2,
+	nil,
+};
+
+void
+localconfinit(void)
+{
+	conf.nodump = 0;
+	conf.dumpreread = 0;
+	conf.firstsb = 0;	/* time- & jukebox-dependent optimisation */
+	conf.recovsb = 0;
+	conf.nlgmsg = 1100;	/* @8576 bytes, for packets */
+	conf.nsmmsg = 500;	/* @128 bytes */
+}
+
+int (*fsprotocol[])(Msgbuf*) = {
+	serve9p1,
+	serve9p2,
+	nil,
+};

+ 36 - 0
sys/src/cmd/cwfs/9netics32.16k/dat.h

@@ -0,0 +1,36 @@
+/* 9net32.16k's configuration: 16K blocks, 32-bit sizes */
+
+/*
+ * The most fundamental constant.
+ * The code will not compile with RBUFSIZE made a variable;
+ * for one thing, RBUFSIZE determines FEPERBUF, which determines
+ * the number of elements in a free-list-block array.
+ */
+#ifndef RBUFSIZE
+#define RBUFSIZE	(16*1024)	/* raw buffer size */
+#endif
+#include "32bit.h"
+/*
+ * setting this to zero permits the use of discs of different sizes, but
+ * can make jukeinit() quite slow while the robotics work through each disc
+ * twice (once per side).
+ */
+enum { FIXEDSIZE = 1 };
+
+
+#include "portdat.h"
+
+enum { MAXBANK = 2 };
+
+typedef struct Mbank {
+	ulong	base;
+	ulong	limit;
+} Mbank;
+
+typedef struct Mconf {
+	Lock;
+	Mbank	bank[MAXBANK];
+	int	nbank;
+	ulong	memsize;
+} Mconf;
+extern Mconf mconf;

+ 2 - 0
sys/src/cmd/cwfs/9netics32.16k/mkfile

@@ -0,0 +1,2 @@
+FS=9net32.16k
+<../portmkfile

+ 31 - 0
sys/src/cmd/cwfs/9netics64.8k/conf.c

@@ -0,0 +1,31 @@
+/* 9net64.8k-specific configuration */
+
+#include "all.h"
+
+#ifndef	DATE
+#define	DATE 1170808167L
+#endif
+
+Timet	fs_mktime = DATE;			/* set by mkfile */
+
+Startsb	startsb[] = {
+	"main",		2,
+	nil,
+};
+
+void
+localconfinit(void)
+{
+	conf.nodump = 0;
+	conf.dumpreread = 0;
+	conf.firstsb = 0;	/* time- & jukebox-dependent optimisation */
+	conf.recovsb = 0;
+	conf.nlgmsg = 1100;	/* @8576 bytes, for packets */
+	conf.nsmmsg = 500;	/* @128 bytes */
+}
+
+int (*fsprotocol[])(Msgbuf*) = {
+	/* 64-bit file servers can't serve 9P1 correctly: NAMELEN is too big */
+	serve9p2,
+	nil,
+};

+ 36 - 0
sys/src/cmd/cwfs/9netics64.8k/dat.h

@@ -0,0 +1,36 @@
+/* 9net64.8k's configuration: 8K blocks, 64-bit sizes */
+
+/*
+ * The most fundamental constant.
+ * The code will not compile with RBUFSIZE made a variable;
+ * for one thing, RBUFSIZE determines FEPERBUF, which determines
+ * the number of elements in a free-list-block array.
+ */
+#ifndef RBUFSIZE
+#define RBUFSIZE	(8*1024)	/* raw buffer size */
+#endif
+#include "64bit.h"
+/*
+ * setting this to zero permits the use of discs of different sizes, but
+ * can make jukeinit() quite slow while the robotics work through each disc
+ * twice (once per side).
+ */
+enum { FIXEDSIZE = 1 };
+
+
+#include "portdat.h"
+
+enum { MAXBANK = 2 };
+
+typedef struct Mbank {
+	ulong	base;
+	ulong	limit;
+} Mbank;
+
+typedef struct Mconf {
+	Lock;
+	Mbank	bank[MAXBANK];
+	int	nbank;
+	ulong	memsize;
+} Mconf;
+extern Mconf mconf;

+ 2 - 0
sys/src/cmd/cwfs/9netics64.8k/mkfile

@@ -0,0 +1,2 @@
+FS=9net64.8k
+<../portmkfile

+ 1636 - 0
sys/src/cmd/cwfs/9p1.c

@@ -0,0 +1,1636 @@
+#include "all.h"
+#include "9p1.h"
+
+extern Nvrsafe	nvr;
+
+typedef struct {
+	uchar	chal[CHALLEN];		/* locally generated challenge */
+	uchar	rchal[CHALLEN];		/* remotely generated challenge */
+	Lock	idlock;
+	ulong	idoffset;		/* offset of id vector */
+	ulong	idvec;			/* vector of acceptable id's */
+} Authinfo;
+
+static void
+f_nop(Chan *cp, Fcall*, Fcall*)
+{
+	if(CHAT(cp))
+		print("c_nop %d\n", cp->chan);
+}
+
+static void
+f_flush(Chan *cp, Fcall*, Fcall*)
+{
+	if(CHAT(cp))
+		print("c_flush %d\n", cp->chan);
+	runlock(&cp->reflock);
+	wlock(&cp->reflock);
+	wunlock(&cp->reflock);
+	rlock(&cp->reflock);
+}
+
+/*
+ *  create a challenge for a fid space
+ */
+static void
+mkchallenge(Authinfo *aip)
+{
+	int i;
+
+	srand((ulong)aip + time(nil));
+	for(i = 0; i < CHALLEN; i++)
+		aip->chal[i] = nrand(256);
+
+	aip->idoffset = 0;
+	aip->idvec = 0;
+}
+
+static void
+f_session(Chan *cp, Fcall *in, Fcall *ou)
+{
+	Authinfo *aip;
+
+	aip = (Authinfo*)cp->authinfo;
+
+	if(CHAT(cp))
+		print("c_session %d\n", cp->chan);
+	memmove(aip->rchal, in->chal, sizeof(aip->rchal));
+	mkchallenge(aip);
+	memmove(ou->chal, aip->chal, sizeof(ou->chal));
+	if(noauth || wstatallow)
+		memset(ou->authid, 0, sizeof(ou->authid));
+	else
+		memmove(ou->authid, nvr.authid, sizeof(ou->authid));
+
+	sprint(ou->authdom, "%s.%s", service, nvr.authdom);
+	fileinit(cp);
+}
+
+/*
+ *  match a challenge from an attach
+ */
+static int
+authorize(Chan *cp, Fcall *in, Fcall *ou)
+{
+	Ticket t;
+	Authenticator a;
+	int x;
+	ulong bit;
+	Authinfo *aip;
+
+	if(noauth || wstatallow)	/* set to allow entry during boot */
+		return 1;
+
+	if(strcmp(in->uname, "none") == 0)
+		return 1;
+
+	if(in->type == Toattach)
+		return 0;
+
+	/* decrypt and unpack ticket */
+	convM2T9p1(in->ticket, &t, nvr.machkey);
+	if(t.num != AuthTs){
+		print("9p1: bad AuthTs num\n");
+		return 0;
+	}
+
+	/* decrypt and unpack authenticator */
+	convM2A9p1(in->auth, &a, t.key);
+	if(a.num != AuthAc){
+		print("9p1: bad AuthAc num\n");
+		return 0;
+	}
+
+	/* challenges must match */
+	aip = (Authinfo*)cp->authinfo;
+	if(memcmp(a.chal, aip->chal, sizeof(a.chal)) != 0){
+		print("9p1: bad challenge\n");
+		return 0;
+	}
+
+	/*
+	 *  the id must be in a valid range.  the range is specified by a
+	 *  lower bound (idoffset) and a bit vector (idvec) where a
+	 *  bit set to 1 means unusable
+	 */
+	lock(&aip->idlock);
+	x = a.id - aip->idoffset;
+	bit = 1<<x;
+	if(x < 0 || x > 31 || (bit&aip->idvec)){
+		unlock(&aip->idlock);
+		print("9p1: id out of range: idoff %ld idvec %lux id %ld\n",
+		   aip->idoffset, aip->idvec, a.id);
+		return 0;
+	}
+	aip->idvec |= bit;
+
+	/* normalize the vector */
+	while(aip->idvec&0xffff0001){
+		aip->idvec >>= 1;
+		aip->idoffset++;
+	}
+	unlock(&aip->idlock);
+
+	/* ticket name and attach name must match */
+	if(memcmp(in->uname, t.cuid, sizeof(in->uname)) != 0){
+		print("9p1: names don't match\n");
+		return 0;
+	}
+
+	/* copy translated name into input record */
+	memmove(in->uname, t.suid, sizeof(in->uname));
+
+	/* craft a reply */
+	a.num = AuthAs;
+	memmove(a.chal, aip->rchal, CHALLEN);
+	convA2M9p1(&a, ou->rauth, t.key);
+
+	return 1;
+}
+
+/*
+ * buggery to give false qid for
+ * the top 2 levels of the dump fs
+ */
+void
+mkqid(Qid* qid, Dentry *d, int buggery)
+{
+	int c;
+
+	if(buggery && d->qid.path == (QPDIR|QPROOT)){
+		c = d->name[0];
+		if(isascii(c) && isdigit(c)){
+			qid->path = 3;
+			qid->vers = d->qid.version;
+			qid->type = QTDIR;
+
+			c = (c-'0')*10 + (d->name[1]-'0');
+			if(c >= 1 && c <= 12)
+				qid->path = 4;
+			return;
+		}
+	}
+
+	mkqid9p2(qid, &d->qid, d->mode);
+}
+
+int
+mkqidcmp(Qid* qid, Dentry *d)
+{
+	Qid tmp;
+
+	mkqid(&tmp, d, 1);
+	if(qid->path == tmp.path && qid->type == tmp.type)
+		return 0;
+	return Eqid;
+}
+
+static void
+f_attach(Chan *cp, Fcall *in, Fcall *ou)
+{
+	Iobuf *p;
+	Dentry *d;
+	File *f;
+	int u;
+	Filsys *fs;
+	Off raddr;
+
+	if(CHAT(cp)) {
+		print("c_attach %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+		print("\tuid = %s\n", in->uname);
+		print("\targ = %s\n", in->aname);
+	}
+
+	ou->qid = QID9P1(0,0);
+	ou->fid = in->fid;
+	if(!in->aname[0])	/* default */
+		strncpy(in->aname, "main", sizeof(in->aname));
+	p = 0;
+	f = filep(cp, in->fid, 1);
+	if(!f) {
+		ou->err = Efid;
+		goto out;
+	}
+
+	u = -1;
+	if(cp != cons.chan) {
+		if(noattach && strcmp(in->uname, "none")) {
+			ou->err = Enoattach;
+			goto out;
+		}
+		if(authorize(cp, in, ou) == 0 || strcmp(in->uname, "adm") == 0) {
+			ou->err = Eauth;
+			goto out;
+		}
+		u = strtouid(in->uname);
+		if(u < 0) {
+			ou->err = Ebadu;
+			goto out;
+		}
+	}
+	f->uid = u;
+
+	fs = fsstr(in->aname);
+	if(fs == 0) {
+		ou->err = Ebadspc;
+		goto out;
+	}
+	raddr = getraddr(fs->dev);
+	p = getbuf(fs->dev, raddr, Brd);
+	d = getdir(p, 0);
+	if(!d || checktag(p, Tdir, QPROOT) || !(d->mode & DALLOC)) {
+		ou->err = Ealloc;
+		goto out;
+	}
+	if (iaccess(f, d, DEXEC) ||
+	    f->uid == 0 && fs->dev->type == Devro) {
+		/*
+		 * 'none' not allowed on dump
+		 */
+		ou->err = Eaccess;
+		goto out;
+	}
+	accessdir(p, d, FREAD, f->uid);
+	mkqid(&f->qid, d, 1);
+	f->fs = fs;
+	f->addr = raddr;
+	f->slot = 0;
+	f->open = 0;
+	freewp(f->wpath);
+	f->wpath = 0;
+
+	mkqid9p1(&ou->qid, &f->qid);
+
+	strncpy(cp->whoname, in->uname, sizeof(cp->whoname));
+	cp->whotime = time(nil);
+	if(cons.flags & attachflag)
+		print("9p1: attach %s %T to \"%s\" C%d\n",
+			cp->whoname, cp->whotime, fs->name, cp->chan);
+
+out:
+	if((cons.flags & attachflag) && ou->err)
+		print("9p1: attach %s %T SUCK EGGS --- %s\n",
+			in->uname, time(nil), errstr9p[ou->err]);
+	if(p)
+		putbuf(p);
+	if(f) {
+		qunlock(f);
+		if(ou->err)
+			freefp(f);
+	}
+}
+
+static void
+f_clone(Chan *cp, Fcall *in, Fcall *ou)
+{
+	File *f1, *f2;
+	Wpath *p;
+	int fid, fid1;
+
+	if(CHAT(cp)) {
+		print("c_clone %d\n", cp->chan);
+		print("\told fid = %d\n", in->fid);
+		print("\tnew fid = %d\n", in->newfid);
+	}
+
+	fid = in->fid;
+	fid1 = in->newfid;
+
+	f1 = 0;
+	f2 = 0;
+	if(fid < fid1) {
+		f1 = filep(cp, fid, 0);
+		f2 = filep(cp, fid1, 1);
+	} else
+	if(fid1 < fid) {
+		f2 = filep(cp, fid1, 1);
+		f1 = filep(cp, fid, 0);
+	}
+	if(!f1 || !f2) {
+		ou->err = Efid;
+		goto out;
+	}
+
+
+	f2->fs = f1->fs;
+	f2->addr = f1->addr;
+	f2->open = f1->open & ~FREMOV;
+	f2->uid = f1->uid;
+	f2->slot = f1->slot;
+	f2->qid = f1->qid;
+
+	freewp(f2->wpath);
+	lock(&wpathlock);
+	f2->wpath = f1->wpath;
+	for(p = f2->wpath; p; p = p->up)
+		p->refs++;
+	unlock(&wpathlock);
+
+out:
+	ou->fid = fid;
+	if(f1)
+		qunlock(f1);
+	if(f2) {
+		qunlock(f2);
+		if(ou->err)
+			freefp(f2);
+	}
+}
+
+static void
+f_walk(Chan *cp, Fcall *in, Fcall *ou)
+{
+	Iobuf *p, *p1;
+	Dentry *d, *d1;
+	File *f;
+	Wpath *w;
+	int slot;
+	Off addr, qpath;
+
+	if(CHAT(cp)) {
+		print("c_walk %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+		print("\tname = %s\n", in->name);
+	}
+
+	ou->fid = in->fid;
+	ou->qid = QID9P1(0,0);
+	p = 0;
+	f = filep(cp, in->fid, 0);
+	if(!f) {
+		ou->err = Efid;
+		goto out;
+	}
+	p = getbuf(f->fs->dev, f->addr, Brd);
+	d = getdir(p, f->slot);
+	if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+		ou->err = Ealloc;
+		goto out;
+	}
+	if(!(d->mode & DDIR)) {
+		ou->err = Edir1;
+		goto out;
+	}
+	if(ou->err = mkqidcmp(&f->qid, d))
+		goto out;
+	if(cp != cons.chan && iaccess(f, d, DEXEC)) {
+		ou->err = Eaccess;
+		goto out;
+	}
+	accessdir(p, d, FREAD, f->uid);
+	if(strcmp(in->name, ".") == 0)
+		goto setdot;
+	if(strcmp(in->name, "..") == 0) {
+		if(f->wpath == 0)
+			goto setdot;
+		putbuf(p);
+		p = 0;
+		addr = f->wpath->addr;
+		slot = f->wpath->slot;
+		p1 = getbuf(f->fs->dev, addr, Brd);
+		d1 = getdir(p1, slot);
+		if(!d1 || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)) {
+			if(p1)
+				putbuf(p1);
+			ou->err = Ephase;
+			goto out;
+		}
+		lock(&wpathlock);
+		f->wpath->refs--;
+		f->wpath = f->wpath->up;
+		unlock(&wpathlock);
+		goto found;
+	}
+	for(addr=0;; addr++) {
+		if(p == 0) {
+			p = getbuf(f->fs->dev, f->addr, Brd);
+			d = getdir(p, f->slot);
+			if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+				ou->err = Ealloc;
+				goto out;
+			}
+		}
+		qpath = d->qid.path;
+		p1 = dnodebuf1(p, d, addr, 0, f->uid);
+		p = 0;
+		if(!p1 || checktag(p1, Tdir, qpath) ) {
+			if(p1)
+				putbuf(p1);
+			ou->err = Eentry;
+			goto out;
+		}
+		for(slot=0; slot<DIRPERBUF; slot++) {
+			d1 = getdir(p1, slot);
+			if(!(d1->mode & DALLOC))
+				continue;
+			if(strncmp(in->name, d1->name, sizeof(in->name)) != 0)
+				continue;
+			/*
+			 * update walk path
+			 */
+			w = newwp();
+			if(!w) {
+				ou->err = Ewalk;
+				putbuf(p1);
+				goto out;
+			}
+			w->addr = f->addr;
+			w->slot = f->slot;
+			w->up = f->wpath;
+			f->wpath = w;
+			slot += DIRPERBUF*addr;
+			goto found;
+		}
+		putbuf(p1);
+	}
+
+found:
+	f->addr = p1->addr;
+	mkqid(&f->qid, d1, 1);
+	putbuf(p1);
+	f->slot = slot;
+
+setdot:
+	mkqid9p1(&ou->qid, &f->qid);
+	f->open = 0;
+
+out:
+	if(p)
+		putbuf(p);
+	if(f)
+		qunlock(f);
+}
+
+static void
+f_open(Chan *cp, Fcall *in, Fcall *ou)
+{
+	Iobuf *p;
+	Dentry *d;
+	File *f;
+	Tlock *t;
+	Qid qid;
+	int ro, fmod, wok;
+
+	if(CHAT(cp)) {
+		print("c_open %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+		print("\tmode = %o\n", in->mode);
+	}
+
+	wok = 0;
+	if(cp == cons.chan || writeallow)
+		wok = 1;
+
+	p = 0;
+	f = filep(cp, in->fid, 0);
+	if(!f) {
+		ou->err = Efid;
+		goto out;
+	}
+
+	/*
+	 * if remove on close, check access here
+	 */
+	ro = f->fs->dev->type == Devro;
+	if(in->mode & ORCLOSE) {
+		if(ro) {
+			ou->err = Eronly;
+			goto out;
+		}
+		/*
+		 * check on parent directory of file to be deleted
+		 */
+		if(f->wpath == 0 || f->wpath->addr == f->addr) {
+			ou->err = Ephase;
+			goto out;
+		}
+		p = getbuf(f->fs->dev, f->wpath->addr, Brd);
+		d = getdir(p, f->wpath->slot);
+		if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+			ou->err = Ephase;
+			goto out;
+		}
+		if(iaccess(f, d, DWRITE)) {
+			ou->err = Eaccess;
+			goto out;
+		}
+		putbuf(p);
+	}
+	p = getbuf(f->fs->dev, f->addr, Brd);
+	d = getdir(p, f->slot);
+	if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+		ou->err = Ealloc;
+		goto out;
+	}
+	if(ou->err = mkqidcmp(&f->qid, d))
+		goto out;
+	mkqid(&qid, d, 1);
+	switch(in->mode & 7) {
+
+	case OREAD:
+		if(iaccess(f, d, DREAD) && !wok)
+			goto badaccess;
+		fmod = FREAD;
+		break;
+
+	case OWRITE:
+		if((d->mode & DDIR) ||
+		   (iaccess(f, d, DWRITE) && !wok))
+			goto badaccess;
+		if(ro) {
+			ou->err = Eronly;
+			goto out;
+		}
+		fmod = FWRITE;
+		break;
+
+	case ORDWR:
+		if((d->mode & DDIR) ||
+		   (iaccess(f, d, DREAD) && !wok) ||
+		   (iaccess(f, d, DWRITE) && !wok))
+			goto badaccess;
+		if(ro) {
+			ou->err = Eronly;
+			goto out;
+		}
+		fmod = FREAD+FWRITE;
+		break;
+
+	case OEXEC:
+		if((d->mode & DDIR) ||
+		   (iaccess(f, d, DEXEC) && !wok))
+			goto badaccess;
+		fmod = FREAD;
+		break;
+
+	default:
+		ou->err = Emode;
+		goto out;
+	}
+	if(in->mode & OTRUNC) {
+		if((d->mode & DDIR) ||
+		   (iaccess(f, d, DWRITE) && !wok))
+			goto badaccess;
+		if(ro) {
+			ou->err = Eronly;
+			goto out;
+		}
+	}
+	t = 0;
+	if(d->mode & DLOCK) {
+		t = tlocked(p, d);
+		if(t == nil) {
+			ou->err = Elocked;
+			goto out;
+		}
+	}
+	if(in->mode & ORCLOSE)
+		fmod |= FREMOV;
+	f->open = fmod;
+	if(in->mode & OTRUNC)
+		if(!(d->mode & DAPND)) {
+			dtrunc(p, d, f->uid);
+			qid.vers = d->qid.version;
+		}
+	f->tlock = t;
+	if(t)
+		t->file = f;
+	f->lastra = 1;
+	mkqid9p1(&ou->qid, &qid);
+	goto out;
+
+badaccess:
+	ou->err = Eaccess;
+	f->open = 0;
+
+out:
+	if(p)
+		putbuf(p);
+	if(f)
+		qunlock(f);
+	ou->fid = in->fid;
+}
+
+static void
+f_create(Chan *cp, Fcall *in, Fcall *ou)
+{
+	Iobuf *p, *p1;
+	Dentry *d, *d1;
+	File *f;
+	int slot, slot1, fmod, wok;
+	Off addr, addr1, path;
+	Qid qid;
+	Tlock *t;
+	Wpath *w;
+
+	if(CHAT(cp)) {
+		print("c_create %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+		print("\tname = %s\n", in->name);
+		print("\tperm = %lx+%lo\n", (in->perm>>28)&0xf,
+				in->perm&0777);
+		print("\tmode = %o\n", in->mode);
+	}
+
+	wok = 0;
+	if(cp == cons.chan || writeallow)
+		wok = 1;
+
+	p = 0;
+	f = filep(cp, in->fid, 0);
+	if(!f) {
+		ou->err = Efid;
+		goto out;
+	}
+	if(f->fs->dev->type == Devro) {
+		ou->err = Eronly;
+		goto out;
+	}
+
+	p = getbuf(f->fs->dev, f->addr, Brd);
+	d = getdir(p, f->slot);
+	if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+		ou->err = Ealloc;
+		goto out;
+	}
+	if(ou->err = mkqidcmp(&f->qid, d))
+		goto out;
+	if(!(d->mode & DDIR)) {
+		ou->err = Edir2;
+		goto out;
+	}
+	if(iaccess(f, d, DWRITE) && !wok) {
+		ou->err = Eaccess;
+		goto out;
+	}
+	accessdir(p, d, FREAD, f->uid);
+	if(!strncmp(in->name, ".", sizeof(in->name)) ||
+	   !strncmp(in->name, "..", sizeof(in->name))) {
+		ou->err = Edot;
+		goto out;
+	}
+	if(checkname(in->name)) {
+		ou->err = Ename;
+		goto out;
+	}
+	addr1 = 0;
+	slot1 = 0;	/* set */
+	for(addr=0;; addr++) {
+		p1 = dnodebuf(p, d, addr, 0, f->uid);
+		if(!p1) {
+			if(addr1)
+				break;
+			p1 = dnodebuf(p, d, addr, Tdir, f->uid);
+		}
+		if(p1 == 0) {
+			ou->err = Efull;
+			goto out;
+		}
+		if(checktag(p1, Tdir, d->qid.path)) {
+			putbuf(p1);
+			goto phase;
+		}
+		for(slot=0; slot<DIRPERBUF; slot++) {
+			d1 = getdir(p1, slot);
+			if(!(d1->mode & DALLOC)) {
+				if(!addr1) {
+					addr1 = p1->addr;
+					slot1 = slot + addr*DIRPERBUF;
+				}
+				continue;
+			}
+			if(!strncmp(in->name, d1->name, sizeof(in->name))) {
+				putbuf(p1);
+				ou->err = Eexist;
+				goto out;
+			}
+		}
+		putbuf(p1);
+	}
+	switch(in->mode & 7) {
+	case OEXEC:
+	case OREAD:		/* seems only useful to make directories */
+		fmod = FREAD;
+		break;
+
+	case OWRITE:
+		fmod = FWRITE;
+		break;
+
+	case ORDWR:
+		fmod = FREAD+FWRITE;
+		break;
+
+	default:
+		ou->err = Emode;
+		goto out;
+	}
+	if(in->perm & PDIR)
+		if((in->mode & OTRUNC) || (in->perm & PAPND) || (fmod & FWRITE))
+			goto badaccess;
+	/*
+	 * do it
+	 */
+	path = qidpathgen(f->fs->dev);
+	p1 = getbuf(f->fs->dev, addr1, Brd|Bimm|Bmod);
+	d1 = getdir(p1, slot1);
+	if(!d1 || checktag(p1, Tdir, d->qid.path)) {
+		if(p1)
+			putbuf(p1);
+		goto phase;
+	}
+	if(d1->mode & DALLOC) {
+		putbuf(p1);
+		goto phase;
+	}
+
+	strncpy(d1->name, in->name, sizeof(in->name));
+	if(cp == cons.chan) {
+		d1->uid = cons.uid;
+		d1->gid = cons.gid;
+	} else {
+		d1->uid = f->uid;
+		d1->gid = d->gid;
+		in->perm &= d->mode | ~0666;
+		if(in->perm & PDIR)
+			in->perm &= d->mode | ~0777;
+	}
+	d1->qid.path = path;
+	d1->qid.version = 0;
+	d1->mode = DALLOC | (in->perm & 0777);
+	if(in->perm & PDIR) {
+		d1->mode |= DDIR;
+		d1->qid.path |= QPDIR;
+	}
+	if(in->perm & PAPND)
+		d1->mode |= DAPND;
+	t = 0;
+	if(in->perm & PLOCK) {
+		d1->mode |= DLOCK;
+		t = tlocked(p1, d1);
+		/* if nil, out of tlock structures */
+	}
+	accessdir(p1, d1, FWRITE, f->uid);
+	mkqid(&qid, d1, 0);
+	putbuf(p1);
+	accessdir(p, d, FWRITE, f->uid);
+
+	/*
+	 * do a walk to new directory entry
+	 */
+	w = newwp();
+	if(!w) {
+		ou->err = Ewalk;
+		goto out;
+	}
+	w->addr = f->addr;
+	w->slot = f->slot;
+	w->up = f->wpath;
+	f->wpath = w;
+	f->qid = qid;
+	f->tlock = t;
+	if(t)
+		t->file = f;
+	f->lastra = 1;
+	if(in->mode & ORCLOSE)
+		fmod |= FREMOV;
+	f->open = fmod;
+	f->addr = addr1;
+	f->slot = slot1;
+	mkqid9p1(&ou->qid, &qid);
+	goto out;
+
+badaccess:
+	ou->err = Eaccess;
+	goto out;
+
+phase:
+	ou->err = Ephase;
+
+out:
+	if(p)
+		putbuf(p);
+	if(f)
+		qunlock(f);
+	ou->fid = in->fid;
+}
+
+static void
+f_read(Chan *cp, Fcall *in, Fcall *ou)
+{
+	Iobuf *p, *p1;
+	File *f;
+	Dentry *d, *d1;
+	Tlock *t;
+	Off addr, offset;
+	Timet tim;
+	int nread, count, n, o, slot;
+
+	if(CHAT(cp)) {
+		print("c_read %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+		print("\toffset = %lld\n", (Wideoff)in->offset);
+		print("\tcount = %ld\n", in->count);
+	}
+
+	p = 0;
+	count = in->count;
+	offset = in->offset;
+	nread = 0;
+	f = filep(cp, in->fid, 0);
+	if(!f) {
+		ou->err = Efid;
+		goto out;
+	}
+	if(!(f->open & FREAD)) {
+		ou->err = Eopen;
+		goto out;
+	}
+	if(count < 0 || count > MAXDAT) {
+		ou->err = Ecount;
+		goto out;
+	}
+	if(offset < 0) {
+		ou->err = Eoffset;
+		goto out;
+	}
+	p = getbuf(f->fs->dev, f->addr, Brd);
+	d = getdir(p, f->slot);
+	if(!d || !(d->mode & DALLOC)) {
+		ou->err = Ealloc;
+		goto out;
+	}
+	if(ou->err = mkqidcmp(&f->qid, d))
+		goto out;
+	if(t = f->tlock) {
+		tim = toytime();
+		if(t->time < tim || t->file != f) {
+			ou->err = Ebroken;
+			goto out;
+		}
+		/* renew the lock */
+		t->time = tim + TLOCK;
+	}
+	accessdir(p, d, FREAD, f->uid);
+	if(d->mode & DDIR) {
+		addr = 0;
+		goto dread;
+	}
+
+	/* XXXX terrible hack to get at raw data XXXX */
+	if(rawreadok && strncmp(d->name, "--raw--", 7) == 0) {
+		Device *dev;
+		Devsize boff, bsize;
+
+		dev = p->dev;
+		putbuf(p);
+		p = 0;
+
+		boff = number(d->name + 7, 0, 10) * 100000;
+		if(boff < 0)
+			boff = 0;
+		if(boff > devsize(dev))
+			boff = devsize(dev);
+		bsize = devsize(dev) - boff;
+
+		if(offset+count >= 100000*RBUFSIZE)
+			count = 100000*RBUFSIZE - offset;
+
+		if((offset+count)/RBUFSIZE >= bsize)
+			/* will not overflow */
+			count = bsize*RBUFSIZE - offset;
+
+		while(count > 0) {
+			addr = offset / RBUFSIZE;
+			addr += boff;
+			o = offset % RBUFSIZE;
+			n = RBUFSIZE - o;
+			if(n > count)
+				n = count;
+
+			p1 = getbuf(dev, addr, Brd);
+			if(p1) {
+				memmove(ou->data+nread, p1->iobuf+o, n);
+				putbuf(p1);
+			} else
+				memset(ou->data+nread, 0, n);
+			count -= n;
+			nread += n;
+			offset += n;
+		}
+		goto out;
+	}
+
+	if(offset+count > d->size)
+		count = d->size - offset;
+	while(count > 0) {
+		if(p == 0) {
+			p = getbuf(f->fs->dev, f->addr, Brd);
+			d = getdir(p, f->slot);
+			if(!d || !(d->mode & DALLOC)) {
+				ou->err = Ealloc;
+				goto out;
+			}
+		}
+		addr = offset / BUFSIZE;
+		f->lastra = dbufread(p, d, addr, f->lastra, f->uid);
+		o = offset % BUFSIZE;
+		n = BUFSIZE - o;
+		if(n > count)
+			n = count;
+		p1 = dnodebuf1(p, d, addr, 0, f->uid);
+		p = 0;
+		if(p1) {
+			if(checktag(p1, Tfile, QPNONE)) {
+				ou->err = Ephase;
+				putbuf(p1);
+				goto out;
+			}
+			memmove(ou->data+nread, p1->iobuf+o, n);
+			putbuf(p1);
+		} else
+			memset(ou->data+nread, 0, n);
+		count -= n;
+		nread += n;
+		offset += n;
+	}
+	goto out;
+
+dread:
+	for (;;) {
+		if(p == 0) {
+			p = getbuf(f->fs->dev, f->addr, Brd);
+			d = getdir(p, f->slot);
+			if(!d || !(d->mode & DALLOC)) {
+				ou->err = Ealloc;
+				goto out;
+			}
+		}
+		p1 = dnodebuf1(p, d, addr, 0, f->uid);
+		p = 0;
+		if(!p1)
+			goto out;
+		if(checktag(p1, Tdir, QPNONE)) {
+			ou->err = Ephase;
+			putbuf(p1);
+			goto out;
+		}
+		n = DIRREC;
+		for(slot=0; slot<DIRPERBUF; slot++) {
+			d1 = getdir(p1, slot);
+			if(!(d1->mode & DALLOC))
+				continue;
+			if(offset >= n) {
+				offset -= n;
+				continue;
+			}
+			if(count < n) {
+				putbuf(p1);
+				goto out;
+			}
+			if(convD2M9p1(d1, ou->data+nread) != n)
+				print("9p1: dirread convD2M1990\n");
+			nread += n;
+			count -= n;
+		}
+		putbuf(p1);
+		addr++;
+	}
+out:
+	count = in->count - nread;
+	if(count > 0)
+		memset(ou->data+nread, 0, count);
+	if(p)
+		putbuf(p);
+	if(f)
+		qunlock(f);
+	ou->fid = in->fid;
+	ou->count = nread;
+	if(CHAT(cp))
+		print("\tnread = %d\n", nread);
+}
+
+static void
+f_write(Chan *cp, Fcall *in, Fcall *ou)
+{
+	Iobuf *p, *p1;
+	Dentry *d;
+	File *f;
+	Tlock *t;
+	Off offset, addr, qpath;
+	Timet tim;
+	int count, nwrite, o, n;
+
+	if(CHAT(cp)) {
+		print("c_write %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+		print("\toffset = %lld\n", (Wideoff)in->offset);
+		print("\tcount = %ld\n", in->count);
+	}
+
+	offset = in->offset;
+	count = in->count;
+	nwrite = 0;
+	p = 0;
+	f = filep(cp, in->fid, 0);
+	if(!f) {
+		ou->err = Efid;
+		goto out;
+	}
+	if(!(f->open & FWRITE)) {
+		ou->err = Eopen;
+		goto out;
+	}
+	if(f->fs->dev->type == Devro) {
+		ou->err = Eronly;
+		goto out;
+	}
+	if(count < 0 || count > MAXDAT) {
+		ou->err = Ecount;
+		goto out;
+	}
+	if(offset < 0) {
+		ou->err = Eoffset;
+		goto out;
+	}
+	p = getbuf(f->fs->dev, f->addr, Brd|Bmod);
+	d = getdir(p, f->slot);
+	if(!d || !(d->mode & DALLOC)) {
+		ou->err = Ealloc;
+		goto out;
+	}
+	if(ou->err = mkqidcmp(&f->qid, d))
+		goto out;
+	if(t = f->tlock) {
+		tim = toytime();
+		if(t->time < tim || t->file != f) {
+			ou->err = Ebroken;
+			goto out;
+		}
+		/* renew the lock */
+		t->time = tim + TLOCK;
+	}
+	accessdir(p, d, FWRITE, f->uid);
+	if(d->mode & DAPND)
+		offset = d->size;
+	if(offset+count > d->size)
+		d->size = offset+count;
+	while(count > 0) {
+		if(p == 0) {
+			p = getbuf(f->fs->dev, f->addr, Brd|Bmod);
+			d = getdir(p, f->slot);
+			if(!d || !(d->mode & DALLOC)) {
+				ou->err = Ealloc;
+				goto out;
+			}
+		}
+		addr = offset / BUFSIZE;
+		o = offset % BUFSIZE;
+		n = BUFSIZE - o;
+		if(n > count)
+			n = count;
+		qpath = d->qid.path;
+		p1 = dnodebuf1(p, d, addr, Tfile, f->uid);
+		p = 0;
+		if(p1 == 0) {
+			ou->err = Efull;
+			goto out;
+		}
+		if(checktag(p1, Tfile, qpath)) {
+			putbuf(p1);
+			ou->err = Ephase;
+			goto out;
+		}
+		memmove(p1->iobuf+o, in->data+nwrite, n);
+		p1->flags |= Bmod;
+		putbuf(p1);
+		count -= n;
+		nwrite += n;
+		offset += n;
+	}
+	if(CHAT(cp))
+		print("\tnwrite = %d\n", nwrite);
+
+out:
+	if(p)
+		putbuf(p);
+	if(f)
+		qunlock(f);
+	ou->fid = in->fid;
+	ou->count = nwrite;
+}
+
+int
+doremove(File *f, int wok)
+{
+	Iobuf *p, *p1;
+	Dentry *d, *d1;
+	Off addr;
+	int slot, err;
+
+	p = 0;
+	p1 = 0;
+	if(f->fs->dev->type == Devro) {
+		err = Eronly;
+		goto out;
+	}
+	/*
+	 * check on parent directory of file to be deleted
+	 */
+	if(f->wpath == 0 || f->wpath->addr == f->addr) {
+		err = Ephase;
+		goto out;
+	}
+	p1 = getbuf(f->fs->dev, f->wpath->addr, Brd);
+	d1 = getdir(p1, f->wpath->slot);
+	if(!d1 || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)) {
+		err = Ephase;
+		goto out;
+	}
+	if(iaccess(f, d1, DWRITE) && !wok) {
+		err = Eaccess;
+		goto out;
+	}
+	accessdir(p1, d1, FWRITE, f->uid);
+	putbuf(p1);
+	p1 = 0;
+
+	/*
+	 * check on file to be deleted
+	 */
+	p = getbuf(f->fs->dev, f->addr, Brd);
+	d = getdir(p, f->slot);
+	if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+		err = Ealloc;
+		goto out;
+	}
+	if(err = mkqidcmp(&f->qid, d))
+		goto out;
+
+	/*
+	 * if deleting a directory, make sure it is empty
+	 */
+	if((d->mode & DDIR))
+	for(addr=0;; addr++) {
+		p1 = dnodebuf(p, d, addr, 0, f->uid);
+		if(!p1)
+			break;
+		if(checktag(p1, Tdir, d->qid.path)) {
+			err = Ephase;
+			goto out;
+		}
+		for(slot=0; slot<DIRPERBUF; slot++) {
+			d1 = getdir(p1, slot);
+			if(!(d1->mode & DALLOC))
+				continue;
+			err = Eempty;
+			goto out;
+		}
+		putbuf(p1);
+	}
+
+	/*
+	 * do it
+	 */
+	dtrunc(p, d, f->uid);
+	memset(d, 0, sizeof(Dentry));
+	settag(p, Tdir, QPNONE);
+
+out:
+	if(p1)
+		putbuf(p1);
+	if(p)
+		putbuf(p);
+	return err;
+}
+
+static int
+doclunk(File* f, int remove, int wok)
+{
+	Tlock *t;
+	int err;
+
+	err = 0;
+	if(t = f->tlock) {
+		if(t->file == f)
+			t->time = 0;	/* free the lock */
+		f->tlock = 0;
+	}
+	if(remove)
+		err = doremove(f, wok);
+	f->open = 0;
+	freewp(f->wpath);
+	freefp(f);
+
+	return err;
+}
+
+static void
+f_clunk(Chan *cp, Fcall *in, Fcall *ou)
+{
+	File *f;
+
+	if(CHAT(cp)) {
+		print("c_clunk %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+	}
+
+	f = filep(cp, in->fid, 0);
+	if(!f)
+		ou->err = Efid;
+	else {
+		doclunk(f, f->open & FREMOV, 0);
+		qunlock(f);
+	}
+	ou->fid = in->fid;
+}
+
+static void
+f_remove(Chan *cp, Fcall *in, Fcall *ou)
+{
+	File *f;
+
+	if(CHAT(cp)) {
+		print("c_remove %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+	}
+
+	f = filep(cp, in->fid, 0);
+	if(!f)
+		ou->err = Efid;
+	else {
+		ou->err = doclunk(f, 1, cp==cons.chan);
+		qunlock(f);
+	}
+	ou->fid = in->fid;
+}
+
+static void
+f_stat(Chan *cp, Fcall *in, Fcall *ou)
+{
+	Iobuf *p;
+	Dentry *d;
+	File *f;
+
+	if(CHAT(cp)) {
+		print("c_stat %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+	}
+
+	p = 0;
+	memset(ou->stat, 0, sizeof(ou->stat));
+	f = filep(cp, in->fid, 0);
+	if(!f) {
+		ou->err = Efid;
+		goto out;
+	}
+	p = getbuf(f->fs->dev, f->addr, Brd);
+	d = getdir(p, f->slot);
+	if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+		ou->err = Ealloc;
+		goto out;
+	}
+	if(ou->err = mkqidcmp(&f->qid, d))
+		goto out;
+	if(d->qid.path == QPROOT)	/* stat of root gives time */
+		d->atime = time(nil);
+	if(convD2M9p1(d, ou->stat) != DIRREC)
+		print("9p1: stat convD2M\n");
+
+out:
+	if(p)
+		putbuf(p);
+	if(f)
+		qunlock(f);
+	ou->fid = in->fid;
+}
+
+static void
+f_wstat(Chan *cp, Fcall *in, Fcall *ou)
+{
+	Iobuf *p, *p1;
+	Dentry *d, *d1, xd;
+	File *f;
+	int slot;
+	Off addr;
+
+	if(CHAT(cp)) {
+		print("c_wstat %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+	}
+
+	p = 0;
+	p1 = 0;
+	d1 = 0;
+	f = filep(cp, in->fid, 0);
+	if(!f) {
+		ou->err = Efid;
+		goto out;
+	}
+	if(f->fs->dev->type == Devro) {
+		ou->err = Eronly;
+		goto out;
+	}
+
+	/*
+	 * first get parent
+	 */
+	if(f->wpath) {
+		p1 = getbuf(f->fs->dev, f->wpath->addr, Brd);
+		d1 = getdir(p1, f->wpath->slot);
+		if(!d1 || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)) {
+			ou->err = Ephase;
+			goto out;
+		}
+	}
+
+	p = getbuf(f->fs->dev, f->addr, Brd);
+	d = getdir(p, f->slot);
+	if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+		ou->err = Ealloc;
+		goto out;
+	}
+	if(ou->err = mkqidcmp(&f->qid, d))
+		goto out;
+
+	convM2D9p1(in->stat, &xd);
+	if(CHAT(cp)) {
+		print("\td.name = %s\n", xd.name);
+		print("\td.uid  = %d\n", xd.uid);
+		print("\td.gid  = %d\n", xd.gid);
+		print("\td.mode = %o\n", xd.mode);
+	}
+
+	/*
+	 * if user none,
+	 * cant do anything
+	 */
+	if(f->uid == 0) {
+		ou->err = Eaccess;
+		goto out;
+	}
+
+	/*
+	 * if chown,
+	 * must be god
+	 */
+	if(xd.uid != d->uid && !wstatallow) { /* set to allow chown during boot */
+		ou->err = Ewstatu;
+		goto out;
+	}
+
+	/*
+	 * if chgroup,
+	 * must be either
+	 *	a) owner and in new group
+	 *	b) leader of both groups
+	 */
+	if (xd.gid != d->gid &&
+	    (!wstatallow && !writeallow &&  /* set to allow chgrp during boot */
+	     (d->uid != f->uid || !ingroup(f->uid, xd.gid)) &&
+	     (!leadgroup(f->uid, xd.gid) || !leadgroup(f->uid, d->gid)))) {
+		ou->err = Ewstatg;
+		goto out;
+	}
+
+	/*
+	 * if rename,
+	 * must have write permission in parent
+	 */
+	if (strncmp(d->name, xd.name, sizeof(d->name)) != 0) {
+		if (checkname(xd.name) || !d1 ||
+		    strcmp(xd.name, ".") == 0 || strcmp(xd.name, "..") == 0) {
+			ou->err = Ename;
+			goto out;
+		}
+
+		/*
+		 * drop entry to prevent lock, then
+		 * check that destination name is unique,
+		 */
+		putbuf(p);
+		for(addr=0;; addr++) {
+			p = dnodebuf(p1, d1, addr, 0, f->uid);
+			if(!p)
+				break;
+			if(checktag(p, Tdir, d1->qid.path)) {
+				putbuf(p);
+				continue;
+			}
+			for(slot=0; slot<DIRPERBUF; slot++) {
+				d = getdir(p, slot);
+				if(!(d->mode & DALLOC))
+					continue;
+				if(!strncmp(xd.name, d->name, sizeof(xd.name))) {
+					ou->err = Eexist;
+					goto out;
+				}
+			}
+			putbuf(p);
+		}
+
+		/*
+		 * reacquire entry
+		 */
+		p = getbuf(f->fs->dev, f->addr, Brd);
+		d = getdir(p, f->slot);
+		if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
+			ou->err = Ephase;
+			goto out;
+		}
+
+		if (!wstatallow && !writeallow && /* set to allow rename during boot */
+		    (!d1 || iaccess(f, d1, DWRITE))) {
+			ou->err = Eaccess;
+			goto out;
+		}
+	}
+
+	/*
+	 * if mode/time, either
+	 *	a) owner
+	 *	b) leader of either group
+	 */
+	if (d->mtime != xd.mtime ||
+	    ((d->mode^xd.mode) & (DAPND|DLOCK|0777)))
+		if (!wstatallow &&	/* set to allow chmod during boot */
+		    d->uid != f->uid &&
+		    !leadgroup(f->uid, xd.gid) &&
+		    !leadgroup(f->uid, d->gid)) {
+			ou->err = Ewstatu;
+			goto out;
+		}
+	d->mtime = xd.mtime;
+	d->uid = xd.uid;
+	d->gid = xd.gid;
+	d->mode = (xd.mode & (DAPND|DLOCK|0777)) | (d->mode & (DALLOC|DDIR));
+
+	strncpy(d->name, xd.name, sizeof(d->name));
+	accessdir(p, d, FREAD, f->uid);
+
+out:
+	if(p)
+		putbuf(p);
+	if(p1)
+		putbuf(p1);
+	if(f)
+		qunlock(f);
+	ou->fid = in->fid;
+}
+
+static void
+f_clwalk(Chan *cp, Fcall *in, Fcall *ou)
+{
+	int er, fid;
+
+	if(CHAT(cp))
+		print("c_clwalk macro\n");
+
+	f_clone(cp, in, ou);		/* sets tag, fid */
+	if(ou->err)
+		return;
+	fid = in->fid;
+	in->fid = in->newfid;
+	f_walk(cp, in, ou);		/* sets tag, fid, qid */
+	er = ou->err;
+	if(er == Eentry) {
+		/*
+		 * if error is "no entry"
+		 * return non error and fid
+		 */
+		ou->err = 0;
+		f_clunk(cp, in, ou);	/* sets tag, fid */
+		ou->err = 0;
+		ou->fid = fid;
+		if(CHAT(cp))
+			print("\terror: %s\n", errstr9p[er]);
+	} else if(er) {
+		/*
+		 * if any other error
+		 * return an error
+		 */
+		ou->err = 0;
+		f_clunk(cp, in, ou);	/* sets tag, fid */
+		ou->err = er;
+	}
+	/*
+	 * non error
+	 * return newfid
+	 */
+}
+
+void (*call9p1[MAXSYSCALL])(Chan*, Fcall*, Fcall*) =
+{
+	[Tnop]		f_nop,
+	[Tosession]	f_session,
+	[Tsession]	f_session,
+	[Tflush]	f_flush,
+	[Toattach]	f_attach,
+	[Tattach]	f_attach,
+	[Tclone]	f_clone,
+	[Twalk]		f_walk,
+	[Topen]		f_open,
+	[Tcreate]	f_create,
+	[Tread]		f_read,
+	[Twrite]	f_write,
+	[Tclunk]	f_clunk,
+	[Tremove]	f_remove,
+	[Tstat]		f_stat,
+	[Twstat]	f_wstat,
+	[Tclwalk]	f_clwalk,
+};
+
+int
+error9p1(Chan* cp, Msgbuf* mb)
+{
+	Msgbuf *mb1;
+
+	print("type=%d count=%d\n", mb->data[0], mb->count);
+	print(" %.2x %.2x %.2x %.2x\n",
+		mb->data[1]&0xff, mb->data[2]&0xff,
+		mb->data[3]&0xff, mb->data[4]&0xff);
+	print(" %.2x %.2x %.2x %.2x\n",
+		mb->data[5]&0xff, mb->data[6]&0xff,
+		mb->data[7]&0xff, mb->data[8]&0xff);
+	print(" %.2x %.2x %.2x %.2x\n",
+		mb->data[9]&0xff, mb->data[10]&0xff,
+		mb->data[11]&0xff, mb->data[12]&0xff);
+
+	mb1 = mballoc(3, cp, Mbreply4);
+	mb1->data[0] = Rnop;	/* your nop was ok */
+	mb1->data[1] = ~0;
+	mb1->data[2] = ~0;
+	mb1->count = 3;
+	mb1->param = mb->param;
+	fs_send(cp->reply, mb1);
+
+	return 1;
+}
+
+int
+serve9p1(Msgbuf* mb)
+{
+	int t, n;
+	Chan *cp;
+	Msgbuf *mb1;
+	Fcall fi, fo;
+
+	assert(mb != nil);
+	cp = mb->chan;
+	assert(mb->data != nil);
+	if(convM2S9p1(mb->data, &fi, mb->count) == 0){
+		assert(cp != nil);
+		if(cp->protocol == nil)
+			return 0;
+		print("9p1: bad M2S conversion\n");
+		return error9p1(cp, mb);
+	}
+
+	t = fi.type;
+	if(t < 0 || t >= MAXSYSCALL || (t&1) || !call9p1[t]) {
+		print("9p1: bad message type\n");
+		return error9p1(cp, mb);
+	}
+
+	/*
+	 * allocate reply message
+	 */
+	if(t == Tread) {
+		mb1 = mballoc(MAXMSG+MAXDAT, cp, Mbreply2);
+		fo.data = (char*)(mb1->data + 8);
+	} else
+		mb1 = mballoc(MAXMSG, cp, Mbreply3);
+
+	/*
+	 * call the file system
+	 */
+	assert(cp != nil);
+	fo.err = 0;
+
+	(*call9p1[t])(cp, &fi, &fo);
+
+	fo.type = t+1;
+	fo.tag = fi.tag;
+
+	if(fo.err) {
+		if(cons.flags&errorflag)
+			print("\ttype %d: error: %s\n", t, errstr9p[fo.err]);
+		if(CHAT(cp))
+			print("\terror: %s\n", errstr9p[fo.err]);
+		fo.type = Rerror;
+		strncpy(fo.ename, errstr9p[fo.err], sizeof(fo.ename));
+	}
+
+	n = convS2M9p1(&fo, mb1->data);
+	if(n == 0) {
+		print("9p1: bad S2M conversion\n");
+		mbfree(mb1);
+		return error9p1(cp, mb);
+	}
+	mb1->count = n;
+	mb1->param = mb->param;
+	fs_send(cp->reply, mb1);
+
+	return 1;
+}

+ 116 - 0
sys/src/cmd/cwfs/9p1.h

@@ -0,0 +1,116 @@
+#include <authsrv.h>
+
+enum {
+	DIRREC	= 116,		/* size of a directory ascii record */
+	ERRREC	= 64,		/* size of a error record */
+};
+
+typedef	struct	Fcall	Fcall;
+
+struct	Fcall
+{
+	char	type;
+	ushort	fid;
+	short	err;
+	short	tag;
+	union
+	{
+		struct
+		{
+			short	uid;		/* T-Userstr [obs.] */
+			short	oldtag;		/* T-nFlush */
+			Qid9p1	qid;		/* R-Attach, R-Clwalk, R-Walk,
+						 * R-Open, R-Create */
+			char	rauth[AUTHENTLEN];	/* R-attach */
+		};
+		struct
+		{
+			char	uname[NAMELEN];	/* T-nAttach */
+			char	aname[NAMELEN];	/* T-nAttach */
+			char	ticket[TICKETLEN];	/* T-attach */
+			char	auth[AUTHENTLEN];	/* T-attach */
+		};
+		struct
+		{
+			char	ename[ERRREC];	/* R-nError */
+			char	chal[CHALLEN];	/* T-session, R-session */
+			char	authid[NAMELEN];	/* R-session */
+			char	authdom[DOMLEN];	/* R-session */
+		};
+		struct
+		{
+			char	name[NAMELEN];	/* T-Walk, T-Clwalk, T-Create, T-Remove */
+			long	perm;		/* T-Create */
+			ushort	newfid;		/* T-Clone, T-Clwalk */
+			char	mode;		/* T-Create, T-Open */
+		};
+		struct
+		{
+			Off	offset;		/* T-Read, T-Write */
+			long	count;		/* T-Read, T-Write, R-Read */
+			char*	data;		/* T-Write, R-Read */
+		};
+		struct
+		{
+			char	stat[DIRREC];	/* T-Wstat, R-Stat */
+		};
+	};
+};
+
+/*
+ * P9 protocol message types
+ */
+enum
+{
+	Tnop =		50,
+	Rnop,
+	Tosession =	52,
+	Rosession,
+	Terror =	54,	/* illegal */
+	Rerror,
+	Tflush =	56,
+	Rflush,
+	Toattach =	58,
+	Roattach,
+	Tclone =	60,
+	Rclone,
+	Twalk =		62,
+	Rwalk,
+	Topen =		64,
+	Ropen,
+	Tcreate =	66,
+	Rcreate,
+	Tread =		68,
+	Rread,
+	Twrite =	70,
+	Rwrite,
+	Tclunk =	72,
+	Rclunk,
+	Tremove =	74,
+	Rremove,
+	Tstat =		76,
+	Rstat,
+	Twstat =	78,
+	Rwstat,
+	Tclwalk =	80,
+	Rclwalk,
+	Tauth =		82,	/* illegal */
+	Rauth,			/* illegal */
+	Tsession =	84,
+	Rsession,
+	Tattach =	86,
+	Rattach,
+
+	MAXSYSCALL
+};
+
+int	convA2M9p1(Authenticator*, char*, char*);
+void	convM2A9p1(char*, Authenticator*, char*);
+void	convM2T9p1(char*, Ticket*, char*);
+int	convD2M9p1(Dentry*, char*);
+int	convM2D9p1(char*, Dentry*);
+int	convM2S9p1(uchar*, Fcall*, int);
+int	convS2M9p1(Fcall*, uchar*);
+void	fcall9p1(Chan*, Fcall*, Fcall*);
+
+void	(*call9p1[MAXSYSCALL])(Chan*, Fcall*, Fcall*);

+ 523 - 0
sys/src/cmd/cwfs/9p1lib.c

@@ -0,0 +1,523 @@
+#include "all.h"
+
+/* BUG transition */
+// int client9p = 2;
+// int kernel9p = 2;
+
+#include "9p1.h"
+
+#define	CHAR(x)		*p++ = f->x
+#define	SHORT(x)	{ ulong vvv = f->x; *p++ = vvv; *p++ = vvv>>8; }
+#define	LONGINT(q) {*p++ = (q); *p++ = (q)>>8; *p++ = (q)>>16; *p++ = (q)>>24;}
+#define	LONG(x)		{ ulong vvv = f->x; LONGINT(vvv); }
+#define	VLONG(x) { \
+	uvlong q = f->x; \
+	*p++ = (q)>> 0; *p++ = (q)>> 8; *p++ = (q)>>16; *p++ = (q)>>24; \
+	*p++ = (q)>>32; *p++ = (q)>>40; *p++ = (q)>>48; *p++ = (q)>>56; \
+	}
+
+#define	BYTES(x,n)	memmove(p, f->x, n); p += n
+#define	STRING(x,n)	strncpy((char*)p, f->x, n); p += n
+
+int
+convS2M9p1(Fcall *f, uchar *ap)
+{
+	uchar *p;
+	int t;
+
+	p = ap;
+	CHAR(type);
+	t = f->type;
+	SHORT(tag);
+	switch(t) {
+	default:
+		print("convS2M9p1: bad type: %d\n", t);
+		return 0;
+
+	case Tnop:
+	case Tosession:
+		break;
+
+	case Tsession:
+		BYTES(chal, sizeof(f->chal));
+		break;
+
+	case Tflush:
+		SHORT(oldtag);
+		break;
+
+	case Tattach:
+		SHORT(fid);
+		STRING(uname, sizeof(f->uname));
+		STRING(aname, sizeof(f->aname));
+		BYTES(ticket, sizeof(f->ticket));
+		BYTES(auth, sizeof(f->auth));
+		break;
+
+	case Toattach:
+		SHORT(fid);
+		STRING(uname, sizeof(f->uname));
+		STRING(aname, sizeof(f->aname));
+		BYTES(ticket, NAMELEN);
+		break;
+
+	case Tclone:
+		SHORT(fid);
+		SHORT(newfid);
+		break;
+
+	case Twalk:
+		SHORT(fid);
+		STRING(name, sizeof(f->name));
+		break;
+
+	case Tclwalk:
+		SHORT(fid);
+		SHORT(newfid);
+		STRING(name, sizeof(f->name));
+		break;
+
+	case Topen:
+		SHORT(fid);
+		CHAR(mode);
+		break;
+
+	case Tcreate:
+		SHORT(fid);
+		STRING(name, sizeof(f->name));
+		LONG(perm);
+		CHAR(mode);
+		break;
+
+	case Tread:
+		SHORT(fid);
+		VLONG(offset);
+		SHORT(count);
+		break;
+
+	case Twrite:
+		SHORT(fid);
+		VLONG(offset);
+		SHORT(count);
+		p++;
+		if((uchar*)p == (uchar*)f->data) {
+			p += f->count;
+			break;
+		}
+		BYTES(data, f->count);
+		break;
+
+	case Tclunk:
+	case Tremove:
+	case Tstat:
+		SHORT(fid);
+		break;
+
+	case Twstat:
+		SHORT(fid);
+		BYTES(stat, sizeof(f->stat));
+		break;
+/*
+ */
+	case Rnop:
+	case Rosession:
+	case Rflush:
+		break;
+
+	case Rsession:
+		BYTES(chal, sizeof(f->chal));
+		BYTES(authid, sizeof(f->authid));
+		BYTES(authdom, sizeof(f->authdom));
+		break;
+
+	case Rerror:
+		STRING(ename, sizeof(f->ename));
+		break;
+
+	case Rclone:
+	case Rclunk:
+	case Rremove:
+	case Rwstat:
+		SHORT(fid);
+		break;
+
+	case Rwalk:
+	case Ropen:
+	case Rcreate:
+	case Rclwalk:
+		SHORT(fid);
+		LONG(qid.path);
+		LONG(qid.version);
+		break;
+
+	case Rattach:
+		SHORT(fid);
+		LONG(qid.path);
+		LONG(qid.version);
+		BYTES(rauth, sizeof(f->rauth));
+		break;
+
+	case Roattach:
+		SHORT(fid);
+		LONG(qid.path);
+		LONG(qid.version);
+		break;
+
+	case Rread:
+		SHORT(fid);
+		SHORT(count);
+		p++;
+		if((uchar*)p == (uchar*)f->data) {
+			p += f->count;
+			break;
+		}
+		BYTES(data, f->count);
+		break;
+
+	case Rwrite:
+		SHORT(fid);
+		SHORT(count);
+		break;
+
+	case Rstat:
+		SHORT(fid);
+		BYTES(stat, sizeof(f->stat));
+		break;
+	}
+	return p - (uchar*)ap;
+}
+
+/*
+ * buggery to give false qid for
+ * the top 2 levels of the dump fs
+ */
+static ulong
+fakeqid9p1(Dentry *f)
+{
+	ulong q;
+	int c;
+
+	q = f->qid.path;
+	if(q == (QPROOT|QPDIR)) {
+		c = f->name[0];
+		if(isascii(c) && isdigit(c)) {
+			q = 3|QPDIR;
+			c = (c-'0')*10 + (f->name[1]-'0');
+			if(c >= 1 && c <= 12)
+				q = 4|QPDIR;
+		}
+	}
+	return q;
+}
+
+int
+convD2M9p1(Dentry *f, char *ap)
+{
+	uchar *p;
+	ulong q;
+
+	p = (uchar*)ap;
+	STRING(name, sizeof(f->name));
+
+	memset(p, 0, 2*NAMELEN);
+	uidtostr((char*)p, f->uid, 1);
+	p += NAMELEN;
+
+	uidtostr((char*)p, f->gid, 1);
+	p += NAMELEN;
+
+	q = fakeqid9p1(f);
+	LONGINT(q);
+	LONG(qid.version);
+
+	q = f->mode & 0x0fff;
+	if(f->mode & DDIR)
+		q |= PDIR;
+	if(f->mode & DAPND)
+		q |= PAPND;
+	if(f->mode & DLOCK)
+		q |= PLOCK;
+	LONGINT(q);
+
+	LONG(atime);
+	LONG(mtime);
+	VLONG(size);
+	LONGINT(0);
+	return p - (uchar*)ap;
+}
+
+int
+convA2M9p1(Authenticator *f, char *ap, char *key)
+{
+	int n;
+	uchar *p;
+
+	p = (uchar*)ap;
+	CHAR(num);
+	BYTES(chal, CHALLEN);
+	LONG(id);
+	n = p - (uchar*)ap;
+	if(key)
+		encrypt(key, ap, n);
+	return n;
+}
+
+#undef	CHAR
+#undef	SHORT
+#undef	LONG
+#undef	LONGINT
+#undef	VLONG
+#undef	BYTES
+#undef	STRING
+
+#define	CHAR(x)		f->x = *p++
+#define	SHORT(x)	f->x = (p[0] | (p[1]<<8)); p += 2
+#define	LONG(x)	f->x = p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24); p += 4
+#define	VLONG(x) { \
+	f->x =	    (p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24)) | \
+	    (uvlong)(p[4] | (p[5]<<8) | (p[6]<<16) | (p[7]<<24)) << 32; \
+	p += 8; \
+}
+
+#define	BYTES(x,n)	memmove(f->x, p, n); p += n
+#define	STRING(x,n)	memmove(f->x, p, n); p += n
+
+int
+convM2S9p1(uchar *ap, Fcall *f, int n)
+{
+	uchar *p;
+	int t;
+
+	p = ap;
+	CHAR(type);
+	t = f->type;
+	SHORT(tag);
+	switch(t) {
+	default:
+		/*
+		 * only whine if it couldn't be a 9P2000 Tversion.
+		 */
+		if(t != 19 || ap[4] != 100)
+			print("convM2S9p1: bad type: %d\n", f->type);
+		return 0;
+
+	case Tnop:
+	case Tosession:
+		break;
+
+	case Tsession:
+		BYTES(chal, sizeof(f->chal));
+		break;
+
+	case Tflush:
+		SHORT(oldtag);
+		break;
+
+	case Tattach:
+		SHORT(fid);
+		BYTES(uname, sizeof(f->uname));
+		BYTES(aname, sizeof(f->aname));
+		BYTES(ticket, sizeof(f->ticket));
+		BYTES(auth, sizeof(f->auth));
+		break;
+
+	case Toattach:
+		SHORT(fid);
+		BYTES(uname, sizeof(f->uname));
+		BYTES(aname, sizeof(f->aname));
+		BYTES(ticket, NAMELEN);
+		break;
+
+	case Tclone:
+		SHORT(fid);
+		SHORT(newfid);
+		break;
+
+	case Twalk:
+		SHORT(fid);
+		BYTES(name, sizeof(f->name));
+		break;
+
+	case Tclwalk:
+		SHORT(fid);
+		SHORT(newfid);
+		BYTES(name, sizeof(f->name));
+		break;
+
+	case Tremove:
+		SHORT(fid);
+		break;
+
+	case Topen:
+		SHORT(fid);
+		CHAR(mode);
+		break;
+
+	case Tcreate:
+		SHORT(fid);
+		BYTES(name, sizeof(f->name));
+		LONG(perm);
+		CHAR(mode);
+		break;
+
+	case Tread:
+		SHORT(fid);
+		VLONG(offset);
+		SHORT(count);
+		break;
+
+	case Twrite:
+		SHORT(fid);
+		VLONG(offset);
+		SHORT(count);
+		p++;
+		f->data = (char*)p; p += f->count;
+		break;
+
+	case Tclunk:
+	case Tstat:
+		SHORT(fid);
+		break;
+
+	case Twstat:
+		SHORT(fid);
+		BYTES(stat, sizeof(f->stat));
+		break;
+
+/*
+ */
+	case Rnop:
+	case Rosession:
+		break;
+
+	case Rsession:
+		BYTES(chal, sizeof(f->chal));
+		BYTES(authid, sizeof(f->authid));
+		BYTES(authdom, sizeof(f->authdom));
+		break;
+
+	case Rerror:
+		BYTES(ename, sizeof(f->ename));
+		break;
+
+	case Rflush:
+		break;
+
+	case Rclone:
+	case Rclunk:
+	case Rremove:
+	case Rwstat:
+		SHORT(fid);
+		break;
+
+	case Rwalk:
+	case Rclwalk:
+	case Ropen:
+	case Rcreate:
+		SHORT(fid);
+		LONG(qid.path);
+		LONG(qid.version);
+		break;
+
+	case Rattach:
+		SHORT(fid);
+		LONG(qid.path);
+		LONG(qid.version);
+		BYTES(rauth, sizeof(f->rauth));
+		break;
+
+	case Roattach:
+		SHORT(fid);
+		LONG(qid.path);
+		LONG(qid.version);
+		break;
+
+	case Rread:
+		SHORT(fid);
+		SHORT(count);
+		p++;
+		f->data = (char*)p; p += f->count;
+		break;
+
+	case Rwrite:
+		SHORT(fid);
+		SHORT(count);
+		break;
+
+	case Rstat:
+		SHORT(fid);
+		BYTES(stat, sizeof(f->stat));
+		break;
+	}
+	if((uchar*)ap+n == p)
+		return n;
+	return 0;
+}
+
+int
+convM2D9p1(char *ap, Dentry *f)
+{
+	uchar *p;
+	char str[NAMELEN];
+
+	p = (uchar*)ap;
+	BYTES(name, sizeof(f->name));
+
+	memmove(str, p, NAMELEN);
+	p += NAMELEN;
+	f->uid = strtouid(str);
+
+	memmove(str, p, NAMELEN);
+	p += NAMELEN;
+	f->gid = strtouid(str);
+
+	LONG(qid.path);
+	LONG(qid.version);
+
+	LONG(atime);
+	f->mode = (f->atime & 0x0fff) | DALLOC;
+	if(f->atime & PDIR)
+		f->mode |= DDIR;
+	if(f->atime & PAPND)
+		f->mode |= DAPND;
+	if(f->atime & PLOCK)
+		f->mode |= DLOCK;
+
+	LONG(atime);
+	LONG(mtime);
+	VLONG(size);
+	p += 4;
+	return p - (uchar*)ap;
+}
+
+void
+convM2A9p1(char *ap, Authenticator *f, char *key)
+{
+	uchar *p;
+
+	if(key)
+		decrypt(key, ap, AUTHENTLEN);
+	p = (uchar*)ap;
+	CHAR(num);
+	BYTES(chal, CHALLEN);
+	LONG(id);
+	USED(p);
+}
+
+void
+convM2T9p1(char *ap, Ticket *f, char *key)
+{
+	uchar *p;
+
+	if(key)
+		decrypt(key, ap, TICKETLEN);
+	p = (uchar*)ap;
+	CHAR(num);
+	BYTES(chal, CHALLEN);
+	STRING(cuid, NAMELEN);
+	f->cuid[NAMELEN-1] = 0;
+	STRING(suid, NAMELEN);
+	f->suid[NAMELEN-1] = 0;
+	BYTES(key, DESKEYLEN);
+	USED(p);
+}

+ 1856 - 0
sys/src/cmd/cwfs/9p2.c

@@ -0,0 +1,1856 @@
+#include "all.h"
+#include <fcall.h>
+
+enum { MSIZE = MAXDAT+MAXMSG };
+
+static int
+mkmode9p1(ulong mode9p2)
+{
+	int mode;
+
+	/*
+	 * Assume this is for an allocated entry.
+	 */
+	mode = DALLOC|(mode9p2 & 0777);
+	if(mode9p2 & DMEXCL)
+		mode |= DLOCK;
+	if(mode9p2 & DMAPPEND)
+		mode |= DAPND;
+	if(mode9p2 & DMDIR)
+		mode |= DDIR;
+
+	return mode;
+}
+
+void
+mkqid9p1(Qid9p1* qid9p1, Qid* qid)
+{
+	if(qid->path & 0xFFFFFFFF00000000LL)
+		panic("mkqid9p1: path %lluX", (Wideoff)qid->path);
+	qid9p1->path = qid->path & 0xFFFFFFFF;
+	if(qid->type & QTDIR)
+		qid9p1->path |= QPDIR;
+	qid9p1->version = qid->vers;
+}
+
+static int
+mktype9p2(int mode9p1)
+{
+	int type;
+
+	type = 0;
+	if(mode9p1 & DLOCK)
+		type |= QTEXCL;
+	if(mode9p1 & DAPND)
+		type |= QTAPPEND;
+	if(mode9p1 & DDIR)
+		type |= QTDIR;
+
+	return type;
+}
+
+static ulong
+mkmode9p2(int mode9p1)
+{
+	ulong mode;
+
+	mode = mode9p1 & 0777;
+	if(mode9p1 & DLOCK)
+		mode |= DMEXCL;
+	if(mode9p1 & DAPND)
+		mode |= DMAPPEND;
+	if(mode9p1 & DDIR)
+		mode |= DMDIR;
+
+	return mode;
+}
+
+void
+mkqid9p2(Qid* qid, Qid9p1* qid9p1, int mode9p1)
+{
+	qid->path = (ulong)(qid9p1->path & ~QPDIR);
+	qid->vers = qid9p1->version;
+	qid->type = mktype9p2(mode9p1);
+}
+
+static int
+mkdir9p2(Dir* dir, Dentry* dentry, void* strs)
+{
+	char *op, *p;
+
+	memset(dir, 0, sizeof(Dir));
+	mkqid(&dir->qid, dentry, 1);
+	dir->mode = mkmode9p2(dentry->mode);
+	dir->atime = dentry->atime;
+	dir->mtime = dentry->mtime;
+	dir->length = dentry->size;
+
+	op = p = strs;
+	dir->name = p;
+	p += sprint(p, "%s", dentry->name)+1;
+
+	dir->uid = p;
+	uidtostr(p, dentry->uid, 1);
+	p += strlen(p)+1;
+
+	dir->gid = p;
+	uidtostr(p, dentry->gid, 1);
+	p += strlen(p)+1;
+
+	dir->muid = p;
+	uidtostr(p, dentry->muid, 1);
+	p += strlen(p)+1;
+
+	return p-op;
+}
+
+static int
+checkname9p2(char* name)
+{
+	char *p;
+
+	/*
+	 * Return error or 0 if OK.
+	 */
+	if(name == nil || *name == 0)
+		return Ename;
+
+	for(p = name; *p != 0; p++){
+		if(p-name >= NAMELEN-1)
+			return Etoolong;
+		if((*p & 0xFF) <= 040)
+			return Ename;
+	}
+	if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
+		return Edot;
+
+	return 0;
+}
+
+static int
+version(Chan* chan, Fcall* f, Fcall* r)
+{
+	if(chan->protocol != nil)
+		return Eversion;
+
+	if(f->msize < MSIZE)
+		r->msize = f->msize;
+	else
+		r->msize = MSIZE;
+
+	/*
+	 * Should check the '.' stuff here.
+	 */
+	if(strcmp(f->version, VERSION9P) == 0){
+		r->version = VERSION9P;
+		chan->protocol = serve9p2;
+		chan->msize = r->msize;
+	} else
+		r->version = "unknown";
+
+	fileinit(chan);
+	return 0;
+}
+
+struct {
+	Lock;
+	ulong	hi;
+} authpath;
+
+static int
+auth(Chan* chan, Fcall* f, Fcall* r)
+{
+	char *aname;
+	File *file;
+	Filsys *fs;
+	int error;
+
+	if(cons.flags & authdisableflag)
+		return Eauthdisabled;
+
+	error = 0;
+	aname = f->aname;
+
+	if(strcmp(f->uname, "none") == 0)
+		return Eauthnone;
+
+	if(!aname[0])	/* default */
+		aname = "main";
+	file = filep(chan, f->afid, 1);
+	if(file == nil){
+		error = Efidinuse;
+		goto out;
+	}
+	fs = fsstr(aname);
+	if(fs == nil){
+		error = Ebadspc;
+		goto out;
+	}
+	lock(&authpath);
+	file->qid.path = authpath.hi++;
+	unlock(&authpath);
+	file->qid.type = QTAUTH;
+	file->qid.vers = 0;
+	file->fs = fs;
+	file->open = FREAD+FWRITE;
+	freewp(file->wpath);
+	file->wpath = 0;
+	file->auth = authnew(f->uname, f->aname);
+	if(file->auth == nil){
+		error = Eauthfile;
+		goto out;
+	}
+	r->aqid = file->qid;
+
+out:
+	if((cons.flags & attachflag) && error)
+		print("9p2: auth %s %T SUCK EGGS --- %s\n",
+			f->uname, time(nil), errstr9p[error]);
+	if(file != nil){
+		qunlock(file);
+		if(error)
+			freefp(file);
+	}
+	return error;
+}
+
+int
+authorize(Chan* chan, Fcall* f)
+{
+	File* af;
+	int db, uid = -1;
+
+	db = cons.flags & authdebugflag;
+
+	if(strcmp(f->uname, "none") == 0){
+		uid = strtouid(f->uname);
+		if(db)
+			print("permission granted to none: uid %s = %d\n",
+				f->uname, uid);
+		return uid;
+	}
+
+	if(cons.flags & authdisableflag){
+		uid = strtouid(f->uname);
+		if(db)
+			print("permission granted by authdisable uid %s = %d\n",
+				f->uname, uid);
+		return uid;
+	}
+
+	af = filep(chan, f->afid, 0);
+	if(af == nil){
+		if(db)
+			print("authorize: af == nil\n");
+		return -1;
+	}
+	if(af->auth == nil){
+		if(db)
+			print("authorize: af->auth == nil\n");
+		goto out;
+	}
+	if(strcmp(f->uname, authuname(af->auth)) != 0){
+		if(db)
+			print("authorize: strcmp(f->uname, authuname(af->auth)) != 0\n");
+		goto out;
+	}
+	if(strcmp(f->aname, authaname(af->auth)) != 0){
+		if(db)
+			print("authorize: strcmp(f->aname, authaname(af->auth)) != 0\n");
+		goto out;
+	}
+	uid = authuid(af->auth);
+	if(db)
+		print("authorize: uid is %d\n", uid);
+out:
+	qunlock(af);
+	return uid;
+}
+
+static int
+attach(Chan* chan, Fcall* f, Fcall* r)
+{
+	char *aname;
+	Iobuf *p;
+	Dentry *d;
+	File *file;
+	Filsys *fs;
+	Off raddr;
+	int error, u;
+
+	aname = f->aname;
+	if(!aname[0])	/* default */
+		aname = "main";
+	p = nil;
+	error = 0;
+	file = filep(chan, f->fid, 1);
+	if(file == nil){
+		error = Efidinuse;
+		goto out;
+	}
+
+	u = -1;
+	if(chan != cons.chan){
+		if(noattach && strcmp(f->uname, "none")) {
+			error = Enoattach;
+			goto out;
+		}
+		u = authorize(chan, f);
+		if(u < 0){
+			error = Ebadu;
+			goto out;
+		}
+	}
+	file->uid = u;
+
+	fs = fsstr(aname);
+	if(fs == nil){
+		error = Ebadspc;
+		goto out;
+	}
+	raddr = getraddr(fs->dev);
+	p = getbuf(fs->dev, raddr, Brd);
+	if(p == nil || checktag(p, Tdir, QPROOT)){
+		error = Ealloc;
+		goto out;
+	}
+	d = getdir(p, 0);
+	if(d == nil || !(d->mode & DALLOC)){
+		error = Ealloc;
+		goto out;
+	}
+	if (iaccess(file, d, DEXEC) ||
+	    file->uid == 0 && fs->dev->type == Devro) {
+		/*
+		 * 'none' not allowed on dump
+		 */
+		error = Eaccess;
+		goto out;
+	}
+	accessdir(p, d, FREAD, file->uid);
+	mkqid(&file->qid, d, 1);
+	file->fs = fs;
+	file->addr = raddr;
+	file->slot = 0;
+	file->open = 0;
+	freewp(file->wpath);
+	file->wpath = 0;
+
+	r->qid = file->qid;
+
+	strncpy(chan->whoname, f->uname, sizeof(chan->whoname));
+	chan->whotime = time(nil);
+	if(cons.flags & attachflag)
+		print("9p2: attach %s %T to \"%s\" C%d\n",
+			chan->whoname, chan->whotime, fs->name, chan->chan);
+
+out:
+	if((cons.flags & attachflag) && error)
+		print("9p2: attach %s %T SUCK EGGS --- %s\n",
+			f->uname, time(nil), errstr9p[error]);
+	if(p != nil)
+		putbuf(p);
+	if(file != nil){
+		qunlock(file);
+		if(error)
+			freefp(file);
+	}
+	return error;
+}
+
+static int
+flush(Chan* chan, Fcall*, Fcall*)
+{
+	runlock(&chan->reflock);
+	wlock(&chan->reflock);
+	wunlock(&chan->reflock);
+	rlock(&chan->reflock);
+
+	return 0;
+}
+
+static void
+clone(File* nfile, File* file)
+{
+	Wpath *wpath;
+
+	nfile->qid = file->qid;
+
+	lock(&wpathlock);
+	nfile->wpath = file->wpath;
+	for(wpath = nfile->wpath; wpath != nil; wpath = wpath->up)
+		wpath->refs++;
+	unlock(&wpathlock);
+
+	nfile->fs = file->fs;
+	nfile->addr = file->addr;
+	nfile->slot = file->slot;
+	nfile->uid = file->uid;
+	nfile->open = file->open & ~FREMOV;
+}
+
+static int
+walkname(File* file, char* wname, Qid* wqid)
+{
+	Wpath *w;
+	Iobuf *p, *p1;
+	Dentry *d, *d1;
+	int error, slot;
+	Off addr, qpath;
+
+	p = p1 = nil;
+
+	/*
+	 * File must not have been opened for I/O by an open
+	 * or create message and must represent a directory.
+	 */
+	if(file->open != 0){
+		error = Emode;
+		goto out;
+	}
+
+	p = getbuf(file->fs->dev, file->addr, Brd);
+	if(p == nil || checktag(p, Tdir, QPNONE)){
+		error = Edir1;
+		goto out;
+	}
+	d = getdir(p, file->slot);
+	if(d == nil || !(d->mode & DALLOC)){
+		error = Ealloc;
+		goto out;
+	}
+	if(!(d->mode & DDIR)){
+		error = Edir1;
+		goto out;
+	}
+	if(error = mkqidcmp(&file->qid, d))
+		goto out;
+
+	/*
+	 * For walked elements the implied user must
+	 * have permission to search the directory.
+	 */
+	if(file->cp != cons.chan && iaccess(file, d, DEXEC)){
+		error = Eaccess;
+		goto out;
+	}
+	accessdir(p, d, FREAD, file->uid);
+
+	if(strcmp(wname, ".") == 0){
+setdot:
+		if(wqid != nil)
+			*wqid = file->qid;
+		goto out;
+	}
+	if(strcmp(wname, "..") == 0){
+		if(file->wpath == 0)
+			goto setdot;
+		putbuf(p);
+		p = nil;
+		addr = file->wpath->addr;
+		slot = file->wpath->slot;
+		p1 = getbuf(file->fs->dev, addr, Brd);
+		if(p1 == nil || checktag(p1, Tdir, QPNONE)){
+			error = Edir1;
+			goto out;
+		}
+		d1 = getdir(p1, slot);
+		if(d == nil || !(d1->mode & DALLOC)){
+			error = Ephase;
+			goto out;
+		}
+		lock(&wpathlock);
+		file->wpath->refs--;
+		file->wpath = file->wpath->up;
+		unlock(&wpathlock);
+		goto found;
+	}
+
+	for(addr = 0; ; addr++){
+		if(p == nil){
+			p = getbuf(file->fs->dev, file->addr, Brd);
+			if(p == nil || checktag(p, Tdir, QPNONE)){
+				error = Ealloc;
+				goto out;
+			}
+			d = getdir(p, file->slot);
+			if(d == nil || !(d->mode & DALLOC)){
+				error = Ealloc;
+				goto out;
+			}
+		}
+		qpath = d->qid.path;
+		p1 = dnodebuf1(p, d, addr, 0, file->uid);
+		p = nil;
+		if(p1 == nil || checktag(p1, Tdir, qpath)){
+			error = Eentry;
+			goto out;
+		}
+		for(slot = 0; slot < DIRPERBUF; slot++){
+			d1 = getdir(p1, slot);
+			if (!(d1->mode & DALLOC) ||
+			    strncmp(wname, d1->name, NAMELEN) != 0)
+				continue;
+			/*
+			 * update walk path
+			 */
+			if((w = newwp()) == nil){
+				error = Ewalk;
+				goto out;
+			}
+			w->addr = file->addr;
+			w->slot = file->slot;
+			w->up = file->wpath;
+			file->wpath = w;
+			slot += DIRPERBUF*addr;
+			goto found;
+		}
+		putbuf(p1);
+		p1 = nil;
+	}
+
+found:
+	file->addr = p1->addr;
+	mkqid(&file->qid, d1, 1);
+	putbuf(p1);
+	p1 = nil;
+	file->slot = slot;
+	if(wqid != nil)
+		*wqid = file->qid;
+
+out:
+	if(p1 != nil)
+		putbuf(p1);
+	if(p != nil)
+		putbuf(p);
+
+	return error;
+}
+
+static int
+walk(Chan* chan, Fcall* f, Fcall* r)
+{
+	int error, nwname;
+	File *file, *nfile, tfile;
+
+	/*
+	 * The file identified by f->fid must be valid in the
+	 * current session and must not have been opened for I/O
+	 * by an open or create message.
+	 */
+	if((file = filep(chan, f->fid, 0)) == nil)
+		return Efid;
+	if(file->open != 0){
+		qunlock(file);
+		return Emode;
+	}
+
+	/*
+	 * If newfid is not the same as fid, allocate a new file;
+	 * a side effect is checking newfid is not already in use (error);
+	 * if there are no names to walk this will be equivalent to a
+	 * simple 'clone' operation.
+	 * Otherwise, fid and newfid are the same and if there are names
+	 * to walk make a copy of 'file' to be used during the walk as
+	 * 'file' must only be updated on success.
+	 * Finally, it's a no-op if newfid is the same as fid and f->nwname
+	 * is 0.
+	 */
+	r->nwqid = 0;
+	if(f->newfid != f->fid){
+		if((nfile = filep(chan, f->newfid, 1)) == nil){
+			qunlock(file);
+			return Efidinuse;
+		}
+	} else if(f->nwname != 0){
+		nfile = &tfile;
+		memset(nfile, 0, sizeof(File));
+		nfile->cp = chan;
+		nfile->fid = ~0;
+	} else {
+		qunlock(file);
+		return 0;
+	}
+	clone(nfile, file);
+
+	/*
+	 * Should check name is not too long.
+	 */
+	error = 0;
+	for(nwname = 0; nwname < f->nwname; nwname++){
+		error = walkname(nfile, f->wname[nwname], &r->wqid[r->nwqid]);
+		if(error != 0 || ++r->nwqid >= MAXDAT/sizeof(Qid))
+			break;
+	}
+
+	if(f->nwname == 0){
+		/*
+		 * Newfid must be different to fid (see above)
+		 * so this is a simple 'clone' operation - there's
+		 * nothing to do except unlock unless there's
+		 * an error.
+		 */
+		if(error){
+			freewp(nfile->wpath);
+			qunlock(nfile);
+			freefp(nfile);
+		} else
+			qunlock(nfile);
+	} else if(r->nwqid < f->nwname){
+		/*
+		 * Didn't walk all elements, 'clunk' nfile
+		 * and leave 'file' alone.
+		 * Clear error if some of the elements were
+		 * walked OK.
+		 */
+		freewp(nfile->wpath);
+		if(nfile != &tfile){
+			qunlock(nfile);
+			freefp(nfile);
+		}
+		if(r->nwqid != 0)
+			error = 0;
+	} else {
+		/*
+		 * Walked all elements. If newfid is the same
+		 * as fid must update 'file' from the temporary
+		 * copy used during the walk.
+		 * Otherwise just unlock (when using tfile there's
+		 * no need to unlock as it's a local).
+		 */
+		if(nfile == &tfile){
+			file->qid = nfile->qid;
+			freewp(file->wpath);
+			file->wpath = nfile->wpath;
+			file->addr = nfile->addr;
+			file->slot = nfile->slot;
+		} else
+			qunlock(nfile);
+	}
+	qunlock(file);
+
+	return error;
+}
+
+static int
+fs_open(Chan* chan, Fcall* f, Fcall* r)
+{
+	Iobuf *p;
+	Dentry *d;
+	File *file;
+	Tlock *t;
+	Qid qid;
+	int error, ro, fmod, wok;
+
+	wok = 0;
+	p = nil;
+
+	if(chan == cons.chan || writeallow)
+		wok = 1;
+
+	if((file = filep(chan, f->fid, 0)) == nil){
+		error = Efid;
+		goto out;
+	}
+	if(file->open != 0){
+		error = Emode;
+		goto out;
+	}
+
+	/*
+	 * if remove on close, check access here
+	 */
+	ro = file->fs->dev->type == Devro;
+	if(f->mode & ORCLOSE){
+		if(ro){
+			error = Eronly;
+			goto out;
+		}
+		/*
+		 * check on parent directory of file to be deleted
+		 */
+		if(file->wpath == 0 || file->wpath->addr == file->addr){
+			error = Ephase;
+			goto out;
+		}
+		p = getbuf(file->fs->dev, file->wpath->addr, Brd);
+		if(p == nil || checktag(p, Tdir, QPNONE)){
+			error = Ephase;
+			goto out;
+		}
+		d = getdir(p, file->wpath->slot);
+		if(d == nil || !(d->mode & DALLOC)){
+			error = Ephase;
+			goto out;
+		}
+		if(iaccess(file, d, DWRITE)){
+			error = Eaccess;
+			goto out;
+		}
+		putbuf(p);
+	}
+	p = getbuf(file->fs->dev, file->addr, Brd);
+	if(p == nil || checktag(p, Tdir, QPNONE)){
+		error = Ealloc;
+		goto out;
+	}
+	d = getdir(p, file->slot);
+	if(d == nil || !(d->mode & DALLOC)){
+		error = Ealloc;
+		goto out;
+	}
+	if(error = mkqidcmp(&file->qid, d))
+		goto out;
+	mkqid(&qid, d, 1);
+	switch(f->mode & 7){
+
+	case OREAD:
+		if(iaccess(file, d, DREAD) && !wok)
+			goto badaccess;
+		fmod = FREAD;
+		break;
+
+	case OWRITE:
+		if((d->mode & DDIR) || (iaccess(file, d, DWRITE) && !wok))
+			goto badaccess;
+		if(ro){
+			error = Eronly;
+			goto out;
+		}
+		fmod = FWRITE;
+		break;
+
+	case ORDWR:
+		if((d->mode & DDIR)
+		|| (iaccess(file, d, DREAD) && !wok)
+		|| (iaccess(file, d, DWRITE) && !wok))
+			goto badaccess;
+		if(ro){
+			error = Eronly;
+			goto out;
+		}
+		fmod = FREAD+FWRITE;
+		break;
+
+	case OEXEC:
+		if((d->mode & DDIR) || (iaccess(file, d, DEXEC) && !wok))
+			goto badaccess;
+		fmod = FREAD;
+		break;
+
+	default:
+		error = Emode;
+		goto out;
+	}
+	if(f->mode & OTRUNC){
+		if((d->mode & DDIR) || (iaccess(file, d, DWRITE) && !wok))
+			goto badaccess;
+		if(ro){
+			error = Eronly;
+			goto out;
+		}
+	}
+	t = 0;
+	if(d->mode & DLOCK){
+		if((t = tlocked(p, d)) == nil){
+			error = Elocked;
+			goto out;
+		}
+	}
+	if(f->mode & ORCLOSE)
+		fmod |= FREMOV;
+	file->open = fmod;
+	if((f->mode & OTRUNC) && !(d->mode & DAPND)){
+		dtrunc(p, d, file->uid);
+		qid.vers = d->qid.version;
+	}
+	r->qid = qid;
+	file->tlock = t;
+	if(t != nil)
+		t->file = file;
+	file->lastra = 1;
+	goto out;
+
+badaccess:
+	error = Eaccess;
+	file->open = 0;
+
+out:
+	if(p != nil)
+		putbuf(p);
+	if(file != nil)
+		qunlock(file);
+
+	r->iounit = chan->msize-IOHDRSZ;
+
+	return error;
+}
+
+static int
+fs_create(Chan* chan, Fcall* f, Fcall* r)
+{
+	Iobuf *p, *p1;
+	Dentry *d, *d1;
+	File *file;
+	int error, slot, slot1, fmod, wok;
+	Off addr, addr1, path;
+	Tlock *t;
+	Wpath *w;
+
+	wok = 0;
+	p = nil;
+
+	if(chan == cons.chan || writeallow)
+		wok = 1;
+
+	if((file = filep(chan, f->fid, 0)) == nil){
+		error = Efid;
+		goto out;
+	}
+	if(file->fs->dev->type == Devro){
+		error = Eronly;
+		goto out;
+	}
+	if(file->qid.type & QTAUTH){
+		error = Emode;
+		goto out;
+	}
+
+	p = getbuf(file->fs->dev, file->addr, Brd);
+	if(p == nil || checktag(p, Tdir, QPNONE)){
+		error = Ealloc;
+		goto out;
+	}
+	d = getdir(p, file->slot);
+	if(d == nil || !(d->mode & DALLOC)){
+		error = Ealloc;
+		goto out;
+	}
+	if(error = mkqidcmp(&file->qid, d))
+		goto out;
+	if(!(d->mode & DDIR)){
+		error = Edir2;
+		goto out;
+	}
+	if(iaccess(file, d, DWRITE) && !wok) {
+		error = Eaccess;
+		goto out;
+	}
+	accessdir(p, d, FREAD, file->uid);
+
+	/*
+	 * Check the name is valid (and will fit in an old
+	 * directory entry for the moment).
+	 */
+	if(error = checkname9p2(f->name))
+		goto out;
+
+	addr1 = 0;
+	slot1 = 0;	/* set */
+	for(addr = 0; ; addr++){
+		if((p1 = dnodebuf(p, d, addr, 0, file->uid)) == nil){
+			if(addr1 != 0)
+				break;
+			p1 = dnodebuf(p, d, addr, Tdir, file->uid);
+		}
+		if(p1 == nil){
+			error = Efull;
+			goto out;
+		}
+		if(checktag(p1, Tdir, d->qid.path)){
+			putbuf(p1);
+			goto phase;
+		}
+		for(slot = 0; slot < DIRPERBUF; slot++){
+			d1 = getdir(p1, slot);
+			if(!(d1->mode & DALLOC)){
+				if(addr1 == 0){
+					addr1 = p1->addr;
+					slot1 = slot + addr*DIRPERBUF;
+				}
+				continue;
+			}
+			if(strncmp(f->name, d1->name, sizeof(d1->name)) == 0){
+				putbuf(p1);
+				error = Eexist;
+				goto out;
+			}
+		}
+		putbuf(p1);
+	}
+
+	switch(f->mode & 7){
+	case OEXEC:
+	case OREAD:		/* seems only useful to make directories */
+		fmod = FREAD;
+		break;
+
+	case OWRITE:
+		fmod = FWRITE;
+		break;
+
+	case ORDWR:
+		fmod = FREAD+FWRITE;
+		break;
+
+	default:
+		error = Emode;
+		goto out;
+	}
+	if(f->perm & PDIR)
+		if((f->mode & OTRUNC) || (f->perm & PAPND) || (fmod & FWRITE))
+			goto badaccess;
+	/*
+	 * do it
+	 */
+	path = qidpathgen(file->fs->dev);
+	if((p1 = getbuf(file->fs->dev, addr1, Brd|Bimm|Bmod)) == nil)
+		goto phase;
+	d1 = getdir(p1, slot1);
+	if(d1 == nil || checktag(p1, Tdir, d->qid.path)) {
+		putbuf(p1);
+		goto phase;
+	}
+	if(d1->mode & DALLOC){
+		putbuf(p1);
+		goto phase;
+	}
+
+	strncpy(d1->name, f->name, sizeof(d1->name));
+	if(chan == cons.chan){
+		d1->uid = cons.uid;
+		d1->gid = cons.gid;
+	} else {
+		d1->uid = file->uid;
+		d1->gid = d->gid;
+		f->perm &= d->mode | ~0666;
+		if(f->perm & PDIR)
+			f->perm &= d->mode | ~0777;
+	}
+	d1->qid.path = path;
+	d1->qid.version = 0;
+	d1->mode = DALLOC | (f->perm & 0777);
+	if(f->perm & PDIR) {
+		d1->mode |= DDIR;
+		d1->qid.path |= QPDIR;
+	}
+	if(f->perm & PAPND)
+		d1->mode |= DAPND;
+	t = nil;
+	if(f->perm & PLOCK){
+		d1->mode |= DLOCK;
+		t = tlocked(p1, d1);
+		/* if nil, out of tlock structures */
+	}
+	accessdir(p1, d1, FWRITE, file->uid);
+	mkqid(&r->qid, d1, 0);
+	putbuf(p1);
+	accessdir(p, d, FWRITE, file->uid);
+
+	/*
+	 * do a walk to new directory entry
+	 */
+	if((w = newwp()) == nil){
+		error = Ewalk;
+		goto out;
+	}
+	w->addr = file->addr;
+	w->slot = file->slot;
+	w->up = file->wpath;
+	file->wpath = w;
+	file->qid = r->qid;
+	file->tlock = t;
+	if(t != nil)
+		t->file = file;
+	file->lastra = 1;
+	if(f->mode & ORCLOSE)
+		fmod |= FREMOV;
+	file->open = fmod;
+	file->addr = addr1;
+	file->slot = slot1;
+	goto out;
+
+badaccess:
+	error = Eaccess;
+	goto out;
+
+phase:
+	error = Ephase;
+
+out:
+	if(p != nil)
+		putbuf(p);
+	if(file != nil)
+		qunlock(file);
+
+	r->iounit = chan->msize-IOHDRSZ;
+
+	return error;
+}
+
+static int
+fs_read(Chan* chan, Fcall* f, Fcall* r, uchar* data)
+{
+	Iobuf *p, *p1;
+	File *file;
+	Dentry *d, *d1;
+	Tlock *t;
+	Off addr, offset, start;
+	Timet tim;
+	int error, iounit, nread, count, n, o, slot;
+	Msgbuf *dmb;
+	Dir dir;
+
+	p = nil;
+
+	error = 0;
+	count = f->count;
+	offset = f->offset;
+	nread = 0;
+	if((file = filep(chan, f->fid, 0)) == nil){
+		error = Efid;
+		goto out;
+	}
+	if(!(file->open & FREAD)){
+		error = Eopen;
+		goto out;
+	}
+	iounit = chan->msize-IOHDRSZ;
+	if(count < 0 || count > iounit){
+		error = Ecount;
+		goto out;
+	}
+	if(offset < 0){
+		error = Eoffset;
+		goto out;
+	}
+	if(file->qid.type & QTAUTH){
+		nread = authread(file, (uchar*)data, count);
+		if(nread < 0)
+			error = Eauth2;
+		goto out;
+	}
+	p = getbuf(file->fs->dev, file->addr, Brd);
+	if(p == nil || checktag(p, Tdir, QPNONE)){
+		error = Ealloc;
+		goto out;
+	}
+	d = getdir(p, file->slot);
+	if(d == nil || !(d->mode & DALLOC)){
+		error = Ealloc;
+		goto out;
+	}
+	if(error = mkqidcmp(&file->qid, d))
+		goto out;
+	if(t = file->tlock){
+		tim = toytime();
+		if(t->time < tim || t->file != file){
+			error = Ebroken;
+			goto out;
+		}
+		/* renew the lock */
+		t->time = tim + TLOCK;
+	}
+	accessdir(p, d, FREAD, file->uid);
+	if(d->mode & DDIR)
+		goto dread;
+	if(offset+count > d->size)
+		count = d->size - offset;
+	while(count > 0){
+		if(p == nil){
+			p = getbuf(file->fs->dev, file->addr, Brd);
+			if(p == nil || checktag(p, Tdir, QPNONE)){
+				error = Ealloc;
+				goto out;
+			}
+			d = getdir(p, file->slot);
+			if(d == nil || !(d->mode & DALLOC)){
+				error = Ealloc;
+				goto out;
+			}
+		}
+		addr = offset / BUFSIZE;
+		file->lastra = dbufread(p, d, addr, file->lastra, file->uid);
+		o = offset % BUFSIZE;
+		n = BUFSIZE - o;
+		if(n > count)
+			n = count;
+		p1 = dnodebuf1(p, d, addr, 0, file->uid);
+		p = nil;
+		if(p1 != nil){
+			if(checktag(p1, Tfile, QPNONE)){
+				error = Ephase;
+				putbuf(p1);
+				goto out;
+			}
+			memmove(data+nread, p1->iobuf+o, n);
+			putbuf(p1);
+		} else
+			memset(data+nread, 0, n);
+		count -= n;
+		nread += n;
+		offset += n;
+	}
+	goto out;
+
+dread:
+	/*
+	 * Pick up where we left off last time if nothing has changed,
+	 * otherwise must scan from the beginning.
+	 */
+	if(offset == file->doffset /*&& file->qid.vers == file->dvers*/){
+		addr = file->dslot/DIRPERBUF;
+		slot = file->dslot%DIRPERBUF;
+		start = offset;
+	} else {
+		addr = 0;
+		slot = 0;
+		start = 0;
+	}
+
+	dmb = mballoc(iounit, chan, Mbreply1);
+	for (;;) {
+		if(p == nil){
+			/*
+			 * This is just a check to ensure the entry hasn't
+			 * gone away during the read of each directory block.
+			 */
+			p = getbuf(file->fs->dev, file->addr, Brd);
+			if(p == nil || checktag(p, Tdir, QPNONE)){
+				error = Ealloc;
+				goto out1;
+			}
+			d = getdir(p, file->slot);
+			if(d == nil || !(d->mode & DALLOC)){
+				error = Ealloc;
+				goto out1;
+			}
+		}
+		p1 = dnodebuf1(p, d, addr, 0, file->uid);
+		p = nil;
+		if(p1 == nil)
+			goto out1;
+		if(checktag(p1, Tdir, QPNONE)){
+			error = Ephase;
+			putbuf(p1);
+			goto out1;
+		}
+
+		for(; slot < DIRPERBUF; slot++){
+			d1 = getdir(p1, slot);
+			if(!(d1->mode & DALLOC))
+				continue;
+			mkdir9p2(&dir, d1, dmb->data);
+			n = convD2M(&dir, data+nread, iounit - nread);
+			if(n <= BIT16SZ){
+				putbuf(p1);
+				goto out1;
+			}
+			start += n;
+			if(start < offset)
+				continue;
+			if(count < n){
+				putbuf(p1);
+				goto out1;
+			}
+			count -= n;
+			nread += n;
+			offset += n;
+		}
+		putbuf(p1);
+		slot = 0;
+		addr++;
+	}
+out1:
+	mbfree(dmb);
+	if(error == 0){
+		file->doffset = offset;
+		file->dvers = file->qid.vers;
+		file->dslot = slot+DIRPERBUF*addr;
+	}
+
+out:
+	/*
+	 * Do we need this any more?
+	count = f->count - nread;
+	if(count > 0)
+		memset(data+nread, 0, count);
+	 */
+	if(p != nil)
+		putbuf(p);
+	if(file != nil)
+		qunlock(file);
+	r->count = nread;
+	r->data = (char*)data;
+
+	return error;
+}
+
+static int
+fs_write(Chan* chan, Fcall* f, Fcall* r)
+{
+	Iobuf *p, *p1;
+	Dentry *d;
+	File *file;
+	Tlock *t;
+	Off offset, addr, qpath;
+	Timet tim;
+	int count, error, nwrite, o, n;
+
+	error = 0;
+	offset = f->offset;
+	count = f->count;
+
+	nwrite = 0;
+	p = nil;
+
+	if((file = filep(chan, f->fid, 0)) == nil){
+		error = Efid;
+		goto out;
+	}
+	if(!(file->open & FWRITE)){
+		error = Eopen;
+		goto out;
+	}
+	if(count < 0 || count > chan->msize-IOHDRSZ){
+		error = Ecount;
+		goto out;
+	}
+	if(offset < 0) {
+		error = Eoffset;
+		goto out;
+	}
+
+	if(file->qid.type & QTAUTH){
+		nwrite = authwrite(file, (uchar*)f->data, count);
+		if(nwrite < 0)
+			error = Eauth2;
+		goto out;
+	} else if(file->fs->dev->type == Devro){
+		error = Eronly;
+		goto out;
+	}
+
+	if ((p = getbuf(file->fs->dev, file->addr, Brd|Bmod)) == nil ||
+	    (d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)) {
+		error = Ealloc;
+		goto out;
+	}
+	if(error = mkqidcmp(&file->qid, d))
+		goto out;
+	if(t = file->tlock) {
+		tim = toytime();
+		if(t->time < tim || t->file != file){
+			error = Ebroken;
+			goto out;
+		}
+		/* renew the lock */
+		t->time = tim + TLOCK;
+	}
+	accessdir(p, d, FWRITE, file->uid);
+	if(d->mode & DAPND)
+		offset = d->size;
+	if(offset+count > d->size)
+		d->size = offset+count;
+	while(count > 0){
+		if(p == nil){
+			p = getbuf(file->fs->dev, file->addr, Brd|Bmod);
+			if(p == nil){
+				error = Ealloc;
+				goto out;
+			}
+			d = getdir(p, file->slot);
+			if(d == nil || !(d->mode & DALLOC)){
+				error = Ealloc;
+				goto out;
+			}
+		}
+		addr = offset / BUFSIZE;
+		o = offset % BUFSIZE;
+		n = BUFSIZE - o;
+		if(n > count)
+			n = count;
+		qpath = d->qid.path;
+		p1 = dnodebuf1(p, d, addr, Tfile, file->uid);
+		p = nil;
+		if(p1 == nil) {
+			error = Efull;
+			goto out;
+		}
+		if(checktag(p1, Tfile, qpath)){
+			putbuf(p1);
+			error = Ephase;
+			goto out;
+		}
+		memmove(p1->iobuf+o, f->data+nwrite, n);
+		p1->flags |= Bmod;
+		putbuf(p1);
+		count -= n;
+		nwrite += n;
+		offset += n;
+	}
+
+out:
+	if(p != nil)
+		putbuf(p);
+	if(file != nil)
+		qunlock(file);
+	r->count = nwrite;
+
+	return error;
+}
+
+static int
+_clunk(File* file, int remove, int wok)
+{
+	Tlock *t;
+	int error;
+
+	error = 0;
+	if(t = file->tlock){
+		if(t->file == file)
+			t->time = 0;		/* free the lock */
+		file->tlock = 0;
+	}
+	if(remove && (file->qid.type & QTAUTH) == 0)
+		error = doremove(file, wok);
+	file->open = 0;
+	freewp(file->wpath);
+	authfree(file->auth);
+	freefp(file);
+	qunlock(file);
+
+	return error;
+}
+
+static int
+clunk(Chan* chan, Fcall* f, Fcall*)
+{
+	File *file;
+
+	if((file = filep(chan, f->fid, 0)) == nil)
+		return Efid;
+
+	_clunk(file, file->open & FREMOV, 0);
+	return 0;
+}
+
+static int
+fs_remove(Chan* chan, Fcall* f, Fcall*)
+{
+	File *file;
+
+	if((file = filep(chan, f->fid, 0)) == nil)
+		return Efid;
+
+	return _clunk(file, 1, chan == cons.chan);
+}
+
+static int
+fs_stat(Chan* chan, Fcall* f, Fcall* r, uchar* data)
+{
+	Dir dir;
+	Iobuf *p;
+	Dentry *d, dentry;
+	File *file;
+	int error, len;
+
+	error = 0;
+	p = nil;
+	if((file = filep(chan, f->fid, 0)) == nil)
+		return Efid;
+	if(file->qid.type & QTAUTH){
+		memset(&dentry, 0, sizeof dentry);
+		d = &dentry;
+		mkqid9p1(&d->qid, &file->qid);
+		strcpy(d->name, "#¿");
+		d->uid = authuid(file->auth);
+		d->gid = d->uid;
+		d->muid = d->uid;
+		d->atime = time(nil);
+		d->mtime = d->atime;
+		d->size = 0;
+	} else {
+		p = getbuf(file->fs->dev, file->addr, Brd);
+		if(p == nil || checktag(p, Tdir, QPNONE)){
+			error = Edir1;
+			goto out;
+		}
+		d = getdir(p, file->slot);
+		if(d == nil || !(d->mode & DALLOC)){
+			error = Ealloc;
+			goto out;
+		}
+		if(error = mkqidcmp(&file->qid, d))
+			goto out;
+
+		if(d->qid.path == QPROOT)	/* stat of root gives time */
+			d->atime = time(nil);
+	}
+	len = mkdir9p2(&dir, d, data);
+	data += len;
+
+	if((r->nstat = convD2M(&dir, data, chan->msize - len)) == 0)
+		error = Eedge;
+	r->stat = data;
+
+out:
+	if(p != nil)
+		putbuf(p);
+	if(file != nil)
+		qunlock(file);
+
+	return error;
+}
+
+static int
+fs_wstat(Chan* chan, Fcall* f, Fcall*, char* strs)
+{
+	Iobuf *p, *p1;
+	Dentry *d, *d1;
+	File *file;
+	int error, err, gid, gl, muid, op, slot, tsync, uid;
+	long addr;
+	Dir dir;
+
+	if(convM2D(f->stat, f->nstat, &dir, strs) == 0)
+		return Econvert;
+
+	/*
+	 * Get the file.
+	 * If user 'none' (uid == 0), can't do anything;
+	 * if filesystem is read-only, can't change anything.
+	 */
+	if((file = filep(chan, f->fid, 0)) == nil)
+		return Efid;
+	p = p1 = nil;
+	if(file->uid == 0){
+		error = Eaccess;
+		goto out;
+	}
+	if(file->fs->dev->type == Devro){
+		error = Eronly;
+		goto out;
+	}
+	if(file->qid.type & QTAUTH){
+		error = Emode;
+		goto out;
+	}
+
+	/*
+	 * Get the current entry and check it is still valid.
+	 */
+	p = getbuf(file->fs->dev, file->addr, Brd);
+	if(p == nil || checktag(p, Tdir, QPNONE)){
+		error = Ealloc;
+		goto out;
+	}
+	d = getdir(p, file->slot);
+	if(d == nil || !(d->mode & DALLOC)){
+		error = Ealloc;
+		goto out;
+	}
+	if(error = mkqidcmp(&file->qid, d))
+		goto out;
+
+	/*
+	 * Run through each of the (sub-)fields in the provided Dir
+	 * checking for validity and whether it's a default:
+	 * .type, .dev and .atime are completely ignored and not checked;
+	 * .qid.path, .qid.vers and .muid are checked for validity but
+	 * any attempt to change them is an error.
+	 * .qid.type/.mode, .mtime, .name, .length, .uid and .gid can
+	 * possibly be changed.
+	 *
+	 * 'Op' flags there are changed fields, i.e. it's not a no-op.
+	 * 'Tsync' flags all fields are defaulted.
+	 *
+	 * Wstatallow and writeallow are set to allow changes during the
+	 * fileserver bootstrap phase.
+	 */
+	tsync = 1;
+	if(dir.qid.path != ~0){
+		if(dir.qid.path != file->qid.path){
+			error = Ewstatp;
+			goto out;
+		}
+		tsync = 0;
+	}
+	if(dir.qid.vers != ~0){
+		if(dir.qid.vers != file->qid.vers){
+			error = Ewstatv;
+			goto out;
+		}
+		tsync = 0;
+	}
+	if(dir.muid != nil && *dir.muid != '\0'){
+		muid = strtouid(dir.muid);
+		if(muid != d->muid && !wstatallow){
+			error = Ewstatm;
+			goto out;
+		}
+		tsync = 0;
+	}
+
+	/*
+	 * .qid.type and .mode have some bits in common. Only .mode
+	 * is currently needed for comparisons with the old mode but
+	 * if there are changes to the bits also encoded in .qid.type
+	 * then file->qid must be updated appropriately later.
+	 */
+	if(dir.qid.type == (uchar)~0){
+		if(dir.mode == ~0)
+			dir.qid.type = mktype9p2(d->mode);
+		else
+			dir.qid.type = dir.mode>>24;
+	} else
+		tsync = 0;
+	if(dir.mode == ~0)
+		dir.mode = mkmode9p2(d->mode);
+	else
+		tsync = 0;
+
+	/*
+	 * Check dir.qid.type and dir.mode agree, check for any unknown
+	 * type/mode bits, check for an attempt to change the directory bit.
+	 */
+	if(dir.qid.type != ((dir.mode>>24) & 0xFF)){
+		error = Ewstatq;
+		goto out;
+	}
+	if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|0777)){
+		error = Ewstatb;
+		goto out;
+	}
+
+	op = dir.mode^mkmode9p2(d->mode);
+	if(op & DMDIR){
+		error = Ewstatd;
+		goto out;
+	}
+
+	if(dir.mtime != ~0){
+		if(dir.mtime != d->mtime)
+			op = 1;
+		tsync = 0;
+	} else
+		dir.mtime = d->mtime;
+
+	if(dir.length == ~(Off)0)
+		dir.length = d->size;
+	else {
+		if (dir.length < 0) {
+			error = Ewstatl;
+			goto out;
+		} else if(dir.length != d->size)
+			op = 1;
+		tsync = 0;
+	}
+
+	/*
+	 * Check for permission to change .mode, .mtime or .length,
+	 * must be owner or leader of either group, for which test gid
+	 * is needed; permission checks on gid will be done later.
+	 * 'Gl' counts whether neither, one or both groups are led.
+	 */
+	if(dir.gid != nil && *dir.gid != '\0'){
+		gid = strtouid(dir.gid);
+		tsync = 0;
+	} else
+		gid = d->gid;
+	gl = leadgroup(file->uid, gid) != 0;
+	gl += leadgroup(file->uid, d->gid) != 0;
+
+	if(op && !wstatallow && d->uid != file->uid && !gl){
+		error = Ewstato;
+		goto out;
+	}
+
+	/*
+	 * Rename.
+	 * Check .name is valid and different to the current.
+	 */
+	if(dir.name != nil && *dir.name != '\0'){
+		if(error = checkname9p2(dir.name))
+			goto out;
+		if(strncmp(dir.name, d->name, NAMELEN))
+			op = 1;
+		else
+			dir.name = d->name;
+		tsync = 0;
+	} else
+		dir.name = d->name;
+
+	/*
+	 * If the name is really to be changed check it's unique
+	 * and there is write permission in the parent.
+	 */
+	if(dir.name != d->name){
+		/*
+		 * First get parent.
+		 * Must drop current entry to prevent
+		 * deadlock when searching that new name
+		 * already exists below.
+		 */
+		putbuf(p);
+		p = nil;
+
+		if(file->wpath == nil){
+			error = Ephase;
+			goto out;
+		}
+		p1 = getbuf(file->fs->dev, file->wpath->addr, Brd);
+		if(p1 == nil || checktag(p1, Tdir, QPNONE)){
+			error = Ephase;
+			goto out;
+		}
+		d1 = getdir(p1, file->wpath->slot);
+		if(d1 == nil || !(d1->mode & DALLOC)){
+			error = Ephase;
+			goto out;
+		}
+
+		/*
+		 * Check entries in parent for new name.
+		 */
+		for(addr = 0; ; addr++){
+			if((p = dnodebuf(p1, d1, addr, 0, file->uid)) == nil)
+				break;
+			if(checktag(p, Tdir, d1->qid.path)){
+				putbuf(p);
+				continue;
+			}
+			for(slot = 0; slot < DIRPERBUF; slot++){
+				d = getdir(p, slot);
+				if(!(d->mode & DALLOC) ||
+				   strncmp(dir.name, d->name, sizeof d->name))
+					continue;
+				error = Eexist;
+				goto out;
+			}
+			putbuf(p);
+		}
+
+		/*
+		 * Reacquire entry and check it's still OK.
+		 */
+		p = getbuf(file->fs->dev, file->addr, Brd);
+		if(p == nil || checktag(p, Tdir, QPNONE)){
+			error = Ephase;
+			goto out;
+		}
+		d = getdir(p, file->slot);
+		if(d == nil || !(d->mode & DALLOC)){
+			error = Ephase;
+			goto out;
+		}
+
+		/*
+		 * Check write permission in the parent.
+		 */
+		if(!wstatallow && !writeallow && iaccess(file, d1, DWRITE)){
+			error = Eaccess;
+			goto out;
+		}
+	}
+
+	/*
+	 * Check for permission to change owner - must be god.
+	 */
+	if(dir.uid != nil && *dir.uid != '\0'){
+		uid = strtouid(dir.uid);
+		if(uid != d->uid){
+			if(!wstatallow){
+				error = Ewstatu;
+				goto out;
+			}
+			op = 1;
+		}
+		tsync = 0;
+	} else
+		uid = d->uid;
+
+	/*
+	 * Check for permission to change group, must be
+	 * either owner and in new group or leader of both groups.
+	 */
+	if(gid != d->gid){
+		if(!(wstatallow || writeallow)
+		&& !(d->uid == file->uid && ingroup(file->uid, gid))
+		&& !(gl == 2)){
+			error = Ewstatg;
+			goto out;
+		}
+		op = 1;
+	}
+
+	/*
+	 * Checks all done, update if necessary.
+	 */
+	if(op){
+		d->mode = mkmode9p1(dir.mode);
+		file->qid.type = mktype9p2(d->mode);
+		d->mtime = dir.mtime;
+		if (dir.length < d->size) {
+			err = dtrunclen(p, d, dir.length, uid);
+			if (error == 0)
+				error = err;
+		}
+		d->size = dir.length;
+		if(dir.name != d->name)
+			strncpy(d->name, dir.name, sizeof(d->name));
+		d->uid = uid;
+		d->gid = gid;
+	}
+	if(!tsync)
+		accessdir(p, d, FREAD, file->uid);
+
+out:
+	if(p != nil)
+		putbuf(p);
+	if(p1 != nil)
+		putbuf(p1);
+	qunlock(file);
+
+	return error;
+}
+
+int
+serve9p2(Msgbuf* mb)
+{
+	Chan *chan;
+	Fcall f, r;
+	Msgbuf *data, *rmb;
+	char ename[64];
+	int error, n, type;
+	static int once;
+
+	if(once == 0){
+		fmtinstall('F', fcallfmt);
+		once = 1;
+	}
+
+	/*
+	 * 0 return means i don't understand this message,
+	 * 1 return means i dealt with it, including error
+	 * replies.
+	 */
+	if(convM2S(mb->data, mb->count, &f) != mb->count)
+{
+print("didn't like %d byte message\n", mb->count);
+		return 0;
+}
+	type = f.type;
+	if(type < Tversion || type >= Tmax || (type & 1) || type == Terror)
+		return 0;
+
+	chan = mb->chan;
+	if(CHAT(chan))
+		print("9p2: f %F\n", &f);
+	r.type = type+1;
+	r.tag = f.tag;
+	error = 0;
+	data = nil;
+
+	switch(type){
+	default:
+		r.type = Rerror;
+		snprint(ename, sizeof(ename), "unknown message: %F", &f);
+		r.ename = ename;
+		break;
+	case Tversion:
+		error = version(chan, &f, &r);
+		break;
+	case Tauth:
+		error = auth(chan, &f, &r);
+		break;
+	case Tattach:
+		error = attach(chan, &f, &r);
+		break;
+	case Tflush:
+		error = flush(chan, &f, &r);
+		break;
+	case Twalk:
+		error = walk(chan, &f, &r);
+		break;
+	case Topen:
+		error = fs_open(chan, &f, &r);
+		break;
+	case Tcreate:
+		error = fs_create(chan, &f, &r);
+		break;
+	case Tread:
+		data = mballoc(chan->msize, chan, Mbreply1);
+		error = fs_read(chan, &f, &r, data->data);
+		break;
+	case Twrite:
+		error = fs_write(chan, &f, &r);
+		break;
+	case Tclunk:
+		error = clunk(chan, &f, &r);
+		break;
+	case Tremove:
+		error = fs_remove(chan, &f, &r);
+		break;
+	case Tstat:
+		data = mballoc(chan->msize, chan, Mbreply1);
+		error = fs_stat(chan, &f, &r, data->data);
+		break;
+	case Twstat:
+		data = mballoc(chan->msize, chan, Mbreply1);
+		error = fs_wstat(chan, &f, &r, (char*)data->data);
+		break;
+	}
+
+	if(error != 0){
+		r.type = Rerror;
+		if(error >= MAXERR){
+			snprint(ename, sizeof(ename), "error %d", error);
+			r.ename = ename;
+		} else
+			r.ename = errstr9p[error];
+	}
+	if(CHAT(chan))
+		print("9p2: r %F\n", &r);
+
+	rmb = mballoc(chan->msize, chan, Mbreply2);
+	n = convS2M(&r, rmb->data, chan->msize);
+	if(data != nil)
+		mbfree(data);
+	if(n == 0){
+		type = r.type;
+		r.type = Rerror;
+
+		/*
+		 * If a Tversion has not been seen on the chan then
+		 * chan->msize will be 0. In that case craft a special
+		 * Rerror message. It's fortunate that the mballoc above
+		 * for rmb will have returned a Msgbuf of MAXMSG size
+		 * when given a request with count of 0...
+		 */
+		if(chan->msize == 0){
+			r.ename = "Tversion not seen";
+			n = convS2M(&r, rmb->data, MAXMSG);
+		} else {
+			snprint(ename, sizeof(ename), "9p2: convS2M: type %d",
+				type);
+			r.ename = ename;
+			n = convS2M(&r, rmb->data, chan->msize);
+		}
+		print("%s\n", r.ename);
+		if(n == 0){
+			/*
+			 * What to do here, the failure notification failed?
+			 */
+			mbfree(rmb);
+			return 1;
+		}
+	}
+	rmb->count = n;
+	rmb->param = mb->param;
+
+	/* done 9P processing, write reply to network */
+	fs_send(chan->reply, rmb);
+
+	return 1;
+}

+ 8 - 0
sys/src/cmd/cwfs/README

@@ -0,0 +1,8 @@
+This is Ken's optical-jukebox fileserver, expanded to 64-bit disk
+addresses and quadruple indirect blocks, then ported to user mode.
+
+The APC UPS shut-down code has been moved into a separate command,
+watchapc.
+
+To customise, set RBUFSIZE and choose 32bit.h or 64bit.h in dat.h, and
+tweak localconfinit(), fsprotocol[] and startsb[] in conf.c.

+ 112 - 0
sys/src/cmd/cwfs/all.h

@@ -0,0 +1,112 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#define Tfile Tfilescsi		/* avoid name conflict */
+#include <disk.h>
+#undef	Tfile
+#include <bio.h>
+#include <ip.h>
+
+#include "dat.h"
+#include "portfns.h"
+
+#define malloc(n)	ialloc(n, 0)
+
+#define	CHAT(cp) ((cons.flags&chatflag) || \
+			((cp) && (((Chan*)(cp))->flags&chatflag)))
+#define	QID9P1(a,b)	(Qid9p1){a,b}
+
+#define SECOND(n)	(n)
+#define MINUTE(n)	((n)*SECOND(60))
+#define HOUR(n)		((n)*MINUTE(60))
+#define DAY(n)		((n)*HOUR(24))
+
+enum {
+	QPDIR		= 0x80000000L,
+	QPNONE		= 0,
+	QPROOT		= 1,
+	QPSUPER		= 2,
+
+	/*
+	 * perm argument in 9P create
+	 */
+	PDIR		= 1L<<31,	/* is a directory */
+	PAPND		= 1L<<30,	/* is append only */
+	PLOCK		= 1L<<29,	/* is locked on open */
+
+	FID1		= 1,
+	FID2		= 2,
+
+	MAXBIAS		= SECOND(20),
+	TLOCK		= MINUTE(5),
+};
+
+Uid*	uid;
+short*	gidspace;
+Lock	printing;
+Time	tim;
+File*	files;
+Wpath*	wpaths;
+Lock	wpathlock;
+char*	errstr9p[MAXERR];
+Chan*	chans;
+RWLock	mainlock;
+Timet	fs_mktime;
+Timet	boottime;
+Queue*	serveq;
+Queue*	raheadq;
+Rabuf*	rabuffree;
+QLock	reflock;
+Lock	rabuflock;
+Tlock	tlocks[NTLOCK];
+Lock	tlocklock;
+Device*	devnone;
+Startsb	startsb[5];
+int	mballocs[MAXCAT];
+
+/* from config block */
+char	service[50];		/* my name */
+Filsys	filsys[30];		/* named file systems */
+/*
+ * these are only documentation, but putting them in the config block makes
+ * them visible.  the real values are compiled into cwfs.
+ */
+typedef struct Fspar Fspar;
+struct Fspar {
+	char*	name;
+	long	actual;		/* compiled-in value */
+	long	declared;
+} fspar[];
+
+ulong	roflag;
+ulong	errorflag;
+ulong	chatflag;
+ulong	attachflag;
+ulong	authdebugflag;
+ulong	authdisableflag;
+int	noattach;
+int	wstatallow;		/* set to circumvent wstat permissions */
+int	writeallow;		/* set to circumvent write permissions */
+int	duallow;		/* single user to allow du */
+int	readonly;		/* disable writes if true */
+
+int	noauth;			/* Debug */
+
+int	rawreadok;		/* allow reading raw data */
+
+File*	flist[5003];		/* base of file structures */
+Lock	flock;			/* manipulate flist */
+
+long	growacct[1000];
+
+struct
+{
+	RWLock	uidlock;
+	Iobuf*	uidbuf;
+	int	flen;
+	int	find;
+} uidgc;
+
+extern	char	statecall[];
+extern	char*	wormscode[];
+extern	char*	tagnames[];

+ 326 - 0
sys/src/cmd/cwfs/auth.c

@@ -0,0 +1,326 @@
+#include "all.h"
+#include "io.h"
+#include <authsrv.h>
+
+Nvrsafe	nvr;
+
+static int gotnvr;	/* flag: nvr contains nvram; it could be bad */
+
+char*
+nvrgetconfig(void)
+{
+	return conf.confdev;
+}
+
+/*
+ * we shouldn't be writing nvram any more.
+ * the secstore/config field is now just secstore key.
+ * we still use authid, authdom and machkey for authentication.
+ */
+
+int
+nvrcheck(void)
+{
+	uchar csum;
+
+	if (readnvram(&nvr, NVread) < 0) {
+		print("nvrcheck: can't read nvram\n");
+		return 1;
+	} else
+		gotnvr = 1;
+	print("nvr read\n");
+
+	csum = nvcsum(nvr.machkey, sizeof nvr.machkey);
+	if(csum != nvr.machsum) {
+		print("\n\n ** NVR key checksum is incorrect  **\n");
+		print(" ** set password to allow attaches **\n\n");
+		memset(nvr.machkey, 0, sizeof nvr.machkey);
+		return 1;
+	}
+
+	return 0;
+}
+
+int
+nvrsetconfig(char* word)
+{
+	/* config block is on device `word' */
+	USED(word);
+	return 0;
+}
+
+int
+conslock(void)
+{
+	char *ln;
+	char nkey1[DESKEYLEN];
+	static char zeroes[DESKEYLEN];
+
+	if(memcmp(nvr.machkey, zeroes, DESKEYLEN) == 0) {
+		print("no password set\n");
+		return 0;
+	}
+
+	for(;;) {
+		print("%s password:", service);
+		/* could turn off echo here */
+
+		if ((ln = Brdline(&bin, '\n')) == nil)
+			return 0;
+		ln[Blinelen(&bin)-1] = '\0';
+
+		/* could turn on echo here */
+		memset(nkey1, 0, DESKEYLEN);
+		passtokey(nkey1, ln);
+		if(memcmp(nkey1, nvr.machkey, DESKEYLEN) == 0) {
+			prdate();
+			break;
+		}
+
+		print("Bad password\n");
+		delay(1000);
+	}
+	return 1;
+}
+
+/*
+ *  authentication specific to 9P2000
+ */
+
+/* authentication states */
+enum
+{
+	HaveProtos=1,
+	NeedProto,
+	HaveOK,
+	NeedCchal,
+	HaveSinfo,
+	NeedTicket,
+	HaveSauthenticator,
+	SSuccess,
+};
+
+char *phasename[] =
+{
+[HaveProtos]	"HaveProtos",
+[NeedProto]	"NeedProto",
+[HaveOK]	"HaveOK",
+[NeedCchal]	"NeedCchal",
+[HaveSinfo]	"HaveSinfo",
+[NeedTicket]	"NeedTicket",
+[HaveSauthenticator]	"HaveSauthenticator",
+[SSuccess]	"SSuccess",
+};
+
+/* authentication structure */
+struct	Auth
+{
+	int	inuse;
+	char	uname[NAMELEN];	/* requestor's remote user name */
+	char	aname[NAMELEN];	/* requested aname */
+	Userid	uid;		/* uid decided on */
+	int	phase;
+	char	cchal[CHALLEN];
+	char	tbuf[TICKETLEN+AUTHENTLEN];	/* server ticket */
+	Ticket	t;
+	Ticketreq tr;
+};
+
+Auth*	auths;
+Lock	authlock;
+
+void
+authinit(void)
+{
+	auths = malloc(conf.nauth * sizeof(*auths));
+}
+
+static int
+failure(Auth *s, char *why)
+{
+	int i;
+
+if(*why)print("authentication failed: %s: %s\n", phasename[s->phase], why);
+	srand((ulong)s + time(nil));
+	for(i = 0; i < CHALLEN; i++)
+		s->tr.chal[i] = nrand(256);
+	s->uid = -1;
+	strncpy(s->tr.authid, nvr.authid, NAMELEN);
+	strncpy(s->tr.authdom, nvr.authdom, DOMLEN);
+	memmove(s->cchal, s->tr.chal, sizeof(s->cchal));
+	s->phase = HaveProtos;
+	return -1;
+}
+
+Auth*
+authnew(char *uname, char *aname)
+{
+	static int si = 0;
+	int i, nwrap;
+	Auth *s;
+
+	i = si;
+	nwrap = 0;
+	for(;;){
+		if(i < 0 || i >= conf.nauth){
+			if(++nwrap > 1)
+				return nil;
+			i = 0;
+		}
+		s = &auths[i++];
+		if(s->inuse)
+			continue;
+		lock(&authlock);
+		if(s->inuse == 0){
+			s->inuse = 1;
+			strncpy(s->uname, uname, NAMELEN-1);
+			strncpy(s->aname, aname, NAMELEN-1);
+			failure(s, "");
+			si = i;
+			unlock(&authlock);
+			break;
+		}
+		unlock(&authlock);
+	}
+	return s;
+}
+
+void
+authfree(Auth *s)
+{
+	if(s != nil)
+		s->inuse = 0;
+}
+
+int
+authread(File* file, uchar* data, int n)
+{
+	Auth *s;
+	int m;
+
+	s = file->auth;
+	if(s == nil)
+		return -1;
+
+	switch(s->phase){
+	default:
+		return failure(s, "unexpected phase");
+	case HaveProtos:
+		m = snprint((char*)data, n, "v.2 p9sk1@%s", nvr.authdom) + 1;
+		s->phase = NeedProto;
+		break;
+	case HaveOK:
+		m = 3;
+		if(n < m)
+			return failure(s, "read too short");
+		strcpy((char*)data, "OK");
+		s->phase = NeedCchal;
+		break;
+	case HaveSinfo:
+		m = TICKREQLEN;
+		if(n < m)
+			return failure(s, "read too short");
+		convTR2M(&s->tr, (char*)data);
+		s->phase = NeedTicket;
+		break;
+	case HaveSauthenticator:
+		m = AUTHENTLEN;
+		if(n < m)
+			return failure(s, "read too short");
+		memmove(data, s->tbuf+TICKETLEN, m);
+		s->phase = SSuccess;
+		break;
+	}
+	return m;
+}
+
+int
+authwrite(File* file, uchar *data, int n)
+{
+	Auth *s;
+	int m;
+	char *p, *d;
+	Authenticator a;
+
+	s = file->auth;
+	if(s == nil)
+		return -1;
+
+	switch(s->phase){
+	default:
+		return failure(s, "unknown phase");
+	case NeedProto:
+		p = (char*)data;
+		if(p[n-1] != 0)
+			return failure(s, "proto missing terminator");
+		d = strchr(p, ' ');
+		if(d == nil)
+			return failure(s, "proto missing separator");
+		*d++ = 0;
+		if(strcmp(p, "p9sk1") != 0)
+			return failure(s, "unknown proto");
+		if(strcmp(d, nvr.authdom) != 0)
+			return failure(s, "unknown domain");
+		s->phase = HaveOK;
+		m = n;
+		break;
+	case NeedCchal:
+		m = CHALLEN;
+		if(n < m)
+			return failure(s, "client challenge too short");
+		memmove(s->cchal, data, sizeof(s->cchal));
+		s->phase = HaveSinfo;
+		break;
+	case NeedTicket:
+		m = TICKETLEN+AUTHENTLEN;
+		if(n < m)
+			return failure(s, "ticket+auth too short");
+
+		convM2T((char*)data, &s->t, nvr.machkey);
+		if(s->t.num != AuthTs
+		|| memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0)
+			return failure(s, "bad ticket");
+
+		convM2A((char*)data+TICKETLEN, &a, s->t.key);
+		if(a.num != AuthAc
+		|| memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0
+		|| a.id != 0)
+			return failure(s, "bad authenticator");
+
+		/* at this point, we're convinced */
+		s->uid = strtouid(s->t.suid);
+		if(s->uid < 0)
+			return failure(s, "unknown user");
+		if(cons.flags & authdebugflag)
+			print("user %s = %d authenticated\n",
+				s->t.suid, s->uid);
+
+		/* create an authenticator to send back */
+		a.num = AuthAs;
+		memmove(a.chal, s->cchal, sizeof(a.chal));
+		a.id = 0;
+		convA2M(&a, s->tbuf+TICKETLEN, s->t.key);
+
+		s->phase = HaveSauthenticator;
+		break;
+	}
+	return m;
+}
+
+int
+authuid(Auth* s)
+{
+	return s->uid;
+}
+
+char*
+authaname(Auth* s)
+{
+	return s->aname;
+}
+
+char*
+authuname(Auth* s)
+{
+	return s->uname;
+}

+ 838 - 0
sys/src/cmd/cwfs/chk.c

@@ -0,0 +1,838 @@
+#include	"all.h"
+
+/* augmented Dentry */
+typedef struct {
+	Dentry	*d;
+	Off	qpath;
+	int	ns;
+} Extdentry;
+
+static	char*	abits;
+static	long	sizabits;
+static	char*	qbits;
+static	long	sizqbits;
+
+static	char*	name;
+static	long	sizname;
+
+static	Off	fstart;
+static	Off	fsize;
+static	Off	nfiles;
+static	Off	maxq;
+static	Device*	dev;
+static	Off	ndup;
+static	Off	nused;
+static	Off	nfdup;
+static	Off	nqbad;
+static	Off	nfree;
+static	Off	nbad;
+static	int	mod;
+static	int	flags;
+static	int	ronly;
+static	int	cwflag;
+static	Devsize	sbaddr;
+static	Devsize	oldblock;
+
+static	int	depth;
+static	int	maxdepth;
+static	uchar	*lowstack, *startstack;
+
+/* local prototypes */
+static	int	amark(Off);
+static	void*	chkalloc(ulong);
+static	void	ckfreelist(Superb*);
+static	int	fmark(Off);
+static	int	fsck(Dentry*);
+static	int	ftest(Off);
+static	Dentry*	maked(Off, int, Off);
+static	void	missing(void);
+static	void	mkfreelist(Superb*);
+static	void	modd(Off, int, Dentry*);
+static	void	qmark(Off);
+static	void	trfreelist(Superb*);
+static	void	xaddfree(Device*, Off, Superb*, Iobuf*);
+static	void	xflush(Device*, Superb*, Iobuf*);
+static	void	xread(Off, Off);
+static	Iobuf*	xtag(Off, int, Off);
+
+static void *
+chkalloc(ulong n)
+{
+	char *p = mallocz(n, 1);
+
+	if (p == nil)
+		panic("chkalloc: out of memory");
+	return p;
+}
+
+void
+chkfree(void *p)
+{
+	free(p);
+}
+
+/*
+ * check flags
+ */
+enum
+{
+	Crdall	= (1<<0),	/* read all files */
+	Ctag	= (1<<1),	/* rebuild tags */
+	Cpfile	= (1<<2),	/* print files */
+	Cpdir	= (1<<3),	/* print directories */
+	Cfree	= (1<<4),	/* rebuild free list */
+//	Csetqid	= (1<<5),	/* resequence qids */
+	Cream	= (1<<6),	/* clear all bad tags */
+	Cbad	= (1<<7),	/* clear all bad blocks */
+	Ctouch	= (1<<8),	/* touch old dir and indir */
+	Ctrim	= (1<<9),   /* trim fsize back to fit when checking free list */
+};
+
+static struct {
+	char*	option;
+	long	flag;
+} ckoption[] = {
+	"rdall",	Crdall,
+	"tag",		Ctag,
+	"pfile",	Cpfile,
+	"pdir",		Cpdir,
+	"free",		Cfree,
+//	"setqid",	Csetqid,
+	"ream",		Cream,
+	"bad",		Cbad,
+	"touch",	Ctouch,
+	"trim",		Ctrim,
+	0,
+};
+
+void
+cmd_check(int argc, char *argv[])
+{
+	long f, i, flag;
+	Off raddr;
+	Filsys *fs;
+	Iobuf *p;
+	Superb *sb;
+	Dentry *d;
+
+	flag = 0;
+	for(i=1; i<argc; i++) {
+		for(f=0; ckoption[f].option; f++)
+			if(strcmp(argv[i], ckoption[f].option) == 0)
+				goto found;
+		print("unknown check option %s\n", argv[i]);
+		for(f=0; ckoption[f].option; f++)
+			print("\t%s\n", ckoption[f].option);
+		return;
+	found:
+		flag |= ckoption[f].flag;
+	}
+	fs = cons.curfs;
+
+	dev = fs->dev;
+	ronly = (dev->type == Devro);
+	cwflag = (dev->type == Devcw) | (dev->type == Devro);
+	if(!ronly)
+		wlock(&mainlock);		/* check */
+	flags = flag;
+
+	name = abits = qbits = nil;		/* in case of goto */
+	sbaddr = superaddr(dev);
+	raddr = getraddr(dev);
+	p = xtag(sbaddr, Tsuper, QPSUPER);
+	if(!p)
+		goto out;
+	sb = (Superb*)p->iobuf;
+	fstart = 2;
+	cons.noage = 1;
+
+	/* 200 is slop since qidgen is likely to be a little bit low */
+	sizqbits = (sb->qidgen+200 + 7) / 8;
+	qbits = chkalloc(sizqbits);
+
+	fsize = sb->fsize;
+	sizabits = (fsize-fstart + 7)/8;
+	abits = chkalloc(sizabits);
+
+	sizname = 4000;
+	name = chkalloc(sizname);
+	sizname -= NAMELEN+10;	/* for safety */
+
+	mod = 0;
+	nfree = 0;
+	nfdup = 0;
+	nused = 0;
+	nbad = 0;
+	ndup = 0;
+	nqbad = 0;
+	depth = 0;
+	maxdepth = 0;
+
+	if(flags & Ctouch) {
+		/* round fsize down to start of current side */
+		int s;
+		Devsize dsize;
+
+		oldblock = 0;
+		for (s = 0; dsize = wormsizeside(dev, s),
+		     dsize > 0 && oldblock + dsize < fsize; s++)
+			oldblock += dsize;
+		print("oldblock = %lld\n", (Wideoff)oldblock);
+	}
+	amark(sbaddr);
+	if(cwflag) {
+		amark(sb->roraddr);
+		amark(sb->next);
+	}
+
+	print("checking filsys: %s\n", fs->name);
+	nfiles = 0;
+	maxq = 0;
+
+	d = maked(raddr, 0, QPROOT);
+	if(d) {
+		amark(raddr);
+		if(fsck(d))
+			modd(raddr, 0, d);
+		chkfree(d);
+		depth--;
+		if(depth)
+			print("depth not zero on return\n");
+	}
+
+	if(flags & Cfree)
+		if(cwflag)
+			trfreelist(sb);
+		else
+			mkfreelist(sb);
+
+	if(sb->qidgen < maxq)
+		print("qid generator low path=%lld maxq=%lld\n",
+			(Wideoff)sb->qidgen, (Wideoff)maxq);
+	if(!(flags & Cfree))
+		ckfreelist(sb);
+	if(mod) {
+		sb->qidgen = maxq;
+		print("file system was modified\n");
+		settag(p, Tsuper, QPNONE);
+	}
+
+	print("nfiles = %lld\n", (Wideoff)nfiles);
+	print("fsize  = %lld\n", (Wideoff)fsize);
+	print("nused  = %lld\n", (Wideoff)nused);
+	print("ndup   = %lld\n", (Wideoff)ndup);
+	print("nfree  = %lld\n", (Wideoff)nfree);
+	print("tfree  = %lld\n", (Wideoff)sb->tfree);
+	print("nfdup  = %lld\n", (Wideoff)nfdup);
+	print("nmiss  = %lld\n", (Wideoff)fsize-fstart-nused-nfree);
+	print("nbad   = %lld\n", (Wideoff)nbad);
+	print("nqbad  = %lld\n", (Wideoff)nqbad);
+	print("maxq   = %lld\n", (Wideoff)maxq);
+	print("base stack=%llud\n", (vlong)startstack);
+	/* high-water mark of stack usage */
+	print("high stack=%llud\n", (vlong)lowstack);
+	print("deepest recursion=%d\n", maxdepth-1);	/* one-origin */
+	if(!cwflag)
+		missing();
+
+out:
+	cons.noage = 0;
+	putbuf(p);
+	chkfree(name);
+	chkfree(abits);
+	chkfree(qbits);
+	name = abits = qbits = nil;
+	if(!ronly)
+		wunlock(&mainlock);
+}
+
+/*
+ * if *blkp is already allocated and Cbad is set, zero it.
+ * returns *blkp if it's free, else 0.
+ */
+static Off
+blkck(Off *blkp, int *flgp)
+{
+	Off a = *blkp;
+
+	if(amark(a)) {
+		if(flags & Cbad) {
+			*blkp = 0;
+			*flgp |= Bmod;
+		}
+		a = 0;
+	}
+	return a;
+}
+
+/*
+ * if a block address within a Dentry, *blkp, is already allocated
+ * and Cbad is set, zero it.
+ * stores 0 into *resp if already allocated, else stores *blkp.
+ * returns dmod count.
+ */
+static int
+daddrck(Off *blkp, Off *resp)
+{
+	int dmod = 0;
+
+	if(amark(*blkp)) {
+		if(flags & Cbad) {
+			*blkp = 0;
+			dmod++;
+		}
+		*resp = 0;
+	} else
+		*resp = *blkp;
+	return dmod;
+}
+
+/*
+ * under Ctouch, read block `a' if it's in range.
+ * returns dmod count.
+ */
+static int
+touch(Off a)
+{
+	if((flags&Ctouch) && a < oldblock) {
+		Iobuf *pd = getbuf(dev, a, Brd|Bmod);
+
+		if(pd)
+			putbuf(pd);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * if d is a directory, touch it and check all its entries in block a.
+ * if not, under Crdall, read a.
+ * returns dmod count.
+ */
+static int
+dirck(Extdentry *ed, Off a)
+{
+	int k, dmod = 0;
+
+	if(ed->d->mode & DDIR) {
+		dmod += touch(a);
+		for(k=0; k<DIRPERBUF; k++) {
+			Dentry *nd = maked(a, k, ed->qpath);
+
+			if(nd == nil)
+				break;
+			if(fsck(nd)) {
+				modd(a, k, nd);
+				dmod++;
+			}
+			chkfree(nd);
+			depth--;
+			name[ed->ns] = 0;
+		}
+	} else if(flags & Crdall)
+		xread(a, ed->qpath);
+	return dmod;
+}
+
+/*
+ * touch a, check a's tag for Tind1, Tind2, etc.
+ * if the tag is right, validate each block number in the indirect block,
+ * and check each block (mostly in case we are reading a huge directory).
+ */
+static int
+indirck(Extdentry *ed, Off a, int tag)
+{
+	int i, dmod = 0;
+	Iobuf *p1;
+
+	if (a == 0)
+		return dmod;
+	dmod = touch(a);
+	if (p1 = xtag(a, tag, ed->qpath)) {
+		for(i=0; i<INDPERBUF; i++) {
+			a = blkck(&((Off *)p1->iobuf)[i], &p1->flags);
+			if (a)
+				/*
+				 * check each block named in this
+				 * indirect(^n) block (a).
+				 */
+				if (tag == Tind1)
+					dmod +=   dirck(ed, a);
+				else
+					dmod += indirck(ed, a, tag-1);
+		}
+		putbuf(p1);
+	}
+	return dmod;
+}
+
+static int
+indiraddrck(Extdentry *ed, Off *indirp, int tag)
+{
+	int dmod;
+	Off a;
+
+	dmod = daddrck(indirp, &a);
+	return dmod + indirck(ed, a, tag);
+}
+
+/* if result is true, *d was modified */
+static int
+fsck(Dentry *d)
+{
+	int i, dmod;
+	Extdentry edent;
+
+	depth++;
+	if(depth >= maxdepth)
+		maxdepth = depth;
+	if (lowstack == nil)
+		startstack = lowstack = (uchar *)&edent;
+	/* more precise check, assumes downward-growing stack */
+	if ((uchar *)&edent < lowstack)
+		lowstack = (uchar *)&edent;
+
+	/* check that entry is allocated */
+	if(!(d->mode & DALLOC))
+		return 0;
+	nfiles++;
+
+	/* we stash qpath & ns in an Extdentry for eventual use by dirck() */
+	memset(&edent, 0, sizeof edent);
+	edent.d = d;
+
+	/* check name */
+	edent.ns = strlen(name);
+	i = strlen(d->name);
+	if(i >= NAMELEN) {
+		d->name[NAMELEN-1] = 0;
+		print("%s->name (%s) not terminated\n", name, d->name);
+		return 0;
+	}
+	edent.ns += i;
+	if(edent.ns >= sizname) {
+		print("%s->name (%s) name too large\n", name, d->name);
+		return 0;
+	}
+	strcat(name, d->name);
+
+	if(d->mode & DDIR) {
+		if(edent.ns > 1) {
+			strcat(name, "/");
+			edent.ns++;
+		}
+		if(flags & Cpdir) {
+			print("%s\n", name);
+			prflush();
+		}
+	} else if(flags & Cpfile) {
+		print("%s\n", name);
+		prflush();
+	}
+
+	/* check qid */
+	edent.qpath = d->qid.path & ~QPDIR;
+	qmark(edent.qpath);
+	if(edent.qpath > maxq)
+		maxq = edent.qpath;
+
+	/* check direct blocks (the common case) */
+	dmod = 0;
+	{
+		Off a;
+
+		for(i=0; i<NDBLOCK; i++) {
+			dmod += daddrck(&d->dblock[i], &a);
+			if (a)
+				dmod += dirck(&edent, a);
+		}
+	}
+	/* check indirect^n blocks, if any */
+	for (i = 0; i < NIBLOCK; i++)
+		dmod += indiraddrck(&edent, &d->iblocks[i], Tind1+i);
+	return dmod;
+}
+
+enum { XFEN = FEPERBUF + 6 };
+
+typedef struct {
+	int	flag;
+	int	count;
+	int	next;
+	Off	addr[XFEN];
+} Xfree;
+
+static void
+xaddfree(Device *dev, Off a, Superb *sb, Iobuf *p)
+{
+	Xfree *x;
+
+	x = (Xfree*)p->iobuf;
+	if(x->count < XFEN) {
+		x->addr[x->count] = a;
+		x->count++;
+		return;
+	}
+	if(!x->flag) {
+		memset(&sb->fbuf, 0, sizeof(sb->fbuf));
+		sb->fbuf.free[0] = 0L;
+		sb->fbuf.nfree = 1;
+		sb->tfree = 0;
+		x->flag = 1;
+	}
+	addfree(dev, a, sb);
+}
+
+static void
+xflush(Device *dev, Superb *sb, Iobuf *p)
+{
+	int i;
+	Xfree *x;
+
+	x = (Xfree*)p->iobuf;
+	if(!x->flag) {
+		memset(&sb->fbuf, 0, sizeof(sb->fbuf));
+		sb->fbuf.free[0] = 0L;
+		sb->fbuf.nfree = 1;
+		sb->tfree = 0;
+	}
+	for(i=0; i<x->count; i++)
+		addfree(dev, x->addr[i], sb);
+}
+
+/*
+ * make freelist
+ * from existing freelist
+ * (cw devices)
+ */
+static void
+trfreelist(Superb *sb)
+{
+	Off a, n;
+	int i;
+	Iobuf *p, *xp;
+	Fbuf *fb;
+
+
+	xp = getbuf(devnone, Cckbuf, 0);
+	memset(xp->iobuf, 0, BUFSIZE);
+	fb = &sb->fbuf;
+	p = 0;
+	for(;;) {
+		n = fb->nfree;
+		if(n < 0 || n > FEPERBUF)
+			break;
+		for(i=1; i<n; i++) {
+			a = fb->free[i];
+			if(a && !ftest(a))
+				xaddfree(dev, a, sb, xp);
+		}
+		a = fb->free[0];
+		if(!a)
+			break;
+		if(ftest(a))
+			break;
+		xaddfree(dev, a, sb, xp);
+		if(p)
+			putbuf(p);
+		p = xtag(a, Tfree, QPNONE);
+		if(!p)
+			break;
+		fb = (Fbuf*)p->iobuf;
+	}
+	if(p)
+		putbuf(p);
+	xflush(dev, sb, xp);
+	putbuf(xp);
+	mod++;
+	print("%lld blocks free\n", (Wideoff)sb->tfree);
+}
+
+static void
+ckfreelist(Superb *sb)
+{
+	Off a, lo, hi;
+	int n, i;
+	Iobuf *p;
+	Fbuf *fb;
+
+	strcpy(name, "free list");
+	print("check %s\n", name);
+	fb = &sb->fbuf;
+	a = sbaddr;
+	p = 0;
+	lo = 0;
+	hi = 0;
+	for(;;) {
+		n = fb->nfree;
+		if(n < 0 || n > FEPERBUF) {
+			print("check: nfree bad %lld\n", (Wideoff)a);
+			break;
+		}
+		for(i=1; i<n; i++) {
+			a = fb->free[i];
+			if(a && !fmark(a)) {
+				if(!lo || lo > a)
+					lo = a;
+				if(!hi || hi < a)
+					hi = a;
+			}
+		}
+		a = fb->free[0];
+		if(!a)
+			break;
+		if(fmark(a))
+			break;
+		if(!lo || lo > a)
+			lo = a;
+		if(!hi || hi < a)
+			hi = a;
+		if(p)
+			putbuf(p);
+		p = xtag(a, Tfree, QPNONE);
+		if(!p)
+			break;
+		fb = (Fbuf*)p->iobuf;
+	}
+	if(p)
+		putbuf(p);
+	if (flags & Ctrim) {
+		fsize = hi--;		/* fsize = hi + 1 */
+		sb->fsize = fsize;
+		mod++;
+		print("set fsize to %lld\n", (Wideoff)fsize);
+	}
+	print("lo = %lld; hi = %lld\n", (Wideoff)lo, (Wideoff)hi);
+}
+
+/*
+ * make freelist from scratch
+ */
+static void
+mkfreelist(Superb *sb)
+{
+	Off a;
+	int i, b;
+
+	if(ronly) {
+		print("cant make freelist on ronly device\n");
+		return;
+	}
+	strcpy(name, "free list");
+	memset(&sb->fbuf, 0, sizeof(sb->fbuf));
+	sb->fbuf.free[0] = 0L;
+	sb->fbuf.nfree = 1;
+	sb->tfree = 0;
+	for(a=fsize-fstart-1; a >= 0; a--) {
+		i = a/8;
+		if(i < 0 || i >= sizabits)
+			continue;
+		b = 1 << (a&7);
+		if(abits[i] & b)
+			continue;
+		addfree(dev, fstart+a, sb);
+	}
+	print("%lld blocks free\n", (Wideoff)sb->tfree);
+	mod++;
+}
+
+static Dentry*
+maked(Off a, int s, Off qpath)
+{
+	Iobuf *p;
+	Dentry *d, *d1;
+
+	p = xtag(a, Tdir, qpath);
+	if(!p)
+		return 0;
+	d = getdir(p, s);
+	d1 = chkalloc(sizeof(Dentry));
+	memmove(d1, d, sizeof(Dentry));
+	putbuf(p);
+	return d1;
+}
+
+static void
+modd(Off a, int s, Dentry *d1)
+{
+	Iobuf *p;
+	Dentry *d;
+
+	if(!(flags & Cbad))
+		return;
+	p = getbuf(dev, a, Brd);
+	d = getdir(p, s);
+	if(!d) {
+		if(p)
+			putbuf(p);
+		return;
+	}
+	memmove(d, d1, sizeof(Dentry));
+	p->flags |= Bmod;
+	putbuf(p);
+}
+
+static void
+xread(Off a, Off qpath)
+{
+	Iobuf *p;
+
+	p = xtag(a, Tfile, qpath);
+	if(p)
+		putbuf(p);
+}
+
+static Iobuf*
+xtag(Off a, int tag, Off qpath)
+{
+	Iobuf *p;
+
+	if(a == 0)
+		return 0;
+	p = getbuf(dev, a, Brd);
+	if(!p) {
+		print("check: \"%s\": xtag: p null\n", name);
+		if(flags & (Cream|Ctag)) {
+			p = getbuf(dev, a, Bmod);
+			if(p) {
+				memset(p->iobuf, 0, RBUFSIZE);
+				settag(p, tag, qpath);
+				mod++;
+				return p;
+			}
+		}
+		return 0;
+	}
+	if(checktag(p, tag, qpath)) {
+		print("check: \"%s\": xtag: checktag\n", name);
+		if(flags & (Cream|Ctag)) {
+			if(flags & Cream)
+				memset(p->iobuf, 0, RBUFSIZE);
+			settag(p, tag, qpath);
+			mod++;
+			return p;
+		}
+		return p;
+	}
+	return p;
+}
+
+static int
+amark(Off a)
+{
+	Off i;
+	int b;
+
+	if(a < fstart || a >= fsize) {
+		if(a == 0)
+			return 0;
+		print("check: \"%s\": range %lld\n",
+			name, (Wideoff)a);
+		nbad++;
+		return 1;
+	}
+	a -= fstart;
+	i = a/8;
+	b = 1 << (a&7);
+	if(abits[i] & b) {
+		if(!ronly)
+			if(ndup < 10)
+				print("check: \"%s\": address dup %lld\n",
+					name, (Wideoff)fstart+a);
+			else if(ndup == 10)
+				print("...");
+		ndup++;
+		return 1;
+	}
+	abits[i] |= b;
+	nused++;
+	return 0;
+}
+
+static int
+fmark(Off a)
+{
+	Off i;
+	int b;
+
+	if(a < fstart || a >= fsize) {
+		print("check: \"%s\": range %lld\n",
+			name, (Wideoff)a);
+		nbad++;
+		return 1;
+	}
+	a -= fstart;
+	i = a/8;
+	b = 1 << (a&7);
+	if(abits[i] & b) {
+		print("check: \"%s\": address dup %lld\n",
+			name, (Wideoff)fstart+a);
+		nfdup++;
+		return 1;
+	}
+	abits[i] |= b;
+	nfree++;
+	return 0;
+}
+
+static int
+ftest(Off a)
+{
+	Off i;
+	int b;
+
+	if(a < fstart || a >= fsize)
+		return 1;
+	a -= fstart;
+	i = a/8;
+	b = 1 << (a&7);
+	if(abits[i] & b)
+		return 1;
+	abits[i] |= b;
+	return 0;
+}
+
+static void
+missing(void)
+{
+	Off a, i;
+	int b, n;
+
+	n = 0;
+	for(a=fsize-fstart-1; a>=0; a--) {
+		i = a/8;
+		b = 1 << (a&7);
+		if(!(abits[i] & b)) {
+			print("missing: %lld\n", (Wideoff)fstart+a);
+			n++;
+		}
+		if(n > 10) {
+			print(" ...\n");
+			break;
+		}
+	}
+}
+
+static void
+qmark(Off qpath)
+{
+	int b;
+	Off i;
+
+	i = qpath/8;
+	b = 1 << (qpath&7);
+	if(i < 0 || i >= sizqbits) {
+		nqbad++;
+		if(nqbad < 20)
+			print("check: \"%s\": qid out of range %llux\n",
+				name, (Wideoff)qpath);
+		return;
+	}
+	if((qbits[i] & b) && !ronly) {
+		nqbad++;
+		if(nqbad < 20)
+			print("check: \"%s\": qid dup %llux\n", name,
+				(Wideoff)qpath);
+	}
+	qbits[i] |= b;
+}

+ 31 - 0
sys/src/cmd/cwfs/choline/conf.c

@@ -0,0 +1,31 @@
+/* choline-specific configuration */
+
+#include "all.h"
+
+#ifndef	DATE
+#define	DATE 1170808167L
+#endif
+
+Timet	fs_mktime = DATE;			/* set by mkfile */
+
+Startsb	startsb[] = {
+	"main",		2,
+	nil,
+};
+
+void
+localconfinit(void)
+{
+	conf.nfile = 60000;
+	conf.nodump = 0;
+	conf.firstsb = 12565379;
+	conf.recovsb = 0;
+	conf.nlgmsg = 100;
+	conf.nsmmsg = 500;
+}
+
+int (*fsprotocol[])(Msgbuf*) = {
+	serve9p1,
+	serve9p2,
+	nil,
+};

+ 36 - 0
sys/src/cmd/cwfs/choline/dat.h

@@ -0,0 +1,36 @@
+/* choline's configuration: 16K blocks, 32-bit sizes */
+
+/*
+ * The most fundamental constant.
+ * The code will not compile with RBUFSIZE made a variable;
+ * for one thing, RBUFSIZE determines FEPERBUF, which determines
+ * the number of elements in a free-list-block array.
+ */
+#ifndef RBUFSIZE
+#define RBUFSIZE	(16*1024)	/* raw buffer size */
+#endif
+#include "32bit.h"
+/*
+ * setting this to zero permits the use of discs of different sizes, but
+ * can make jukeinit() quite slow while the robotics work through each disc
+ * twice (once per side).
+ */
+enum { FIXEDSIZE = 1 };
+
+
+#include "portdat.h"
+
+enum { MAXBANK = 2 };
+
+typedef struct Mbank {
+	ulong	base;
+	ulong	limit;
+} Mbank;
+
+typedef struct Mconf {
+	Lock;
+	Mbank	bank[MAXBANK];
+	int	nbank;
+	ulong	memsize;
+} Mconf;
+extern Mconf mconf;

+ 2 - 0
sys/src/cmd/cwfs/choline/mkfile

@@ -0,0 +1,2 @@
+FS=choline
+<../portmkfile

+ 844 - 0
sys/src/cmd/cwfs/con.c

@@ -0,0 +1,844 @@
+#include "all.h"
+
+static	Command	command[100];
+static	Flag	flag[35];
+static	char	statsdef[20];	/* default stats list */
+static	int	whoflag;
+
+static	void	consserve1(void *);
+static	void	installcmds(void);
+
+void
+consserve(void)
+{
+	int i;
+
+	strncpy(cons.chan->whochan, "console", sizeof(cons.chan->whochan));
+	installcmds();
+	con_session();
+	cmd_exec("cfs");
+	cmd_exec("users");
+	cmd_exec("version");
+
+	for(i = 0; command[i].arg0; i++)
+		if(strcmp("cwcmd", command[i].arg0) == 0){
+			cmd_exec("cwcmd touchsb");
+			break;
+		}
+
+	newproc(consserve1, 0, "con");
+}
+
+/* console commands process */
+static void
+consserve1(void *)
+{
+	char *conline;
+
+	for (;;) {
+		/* conslock(); */
+		do {
+			print("%s: ", service);
+			if ((conline = Brdline(&bin, '\n')) == nil)
+				print("\n");
+			else {
+				conline[Blinelen(&bin)-1] = '\0';
+				cmd_exec(conline);
+			}
+		} while (conline != nil);
+	}
+}
+
+static int
+cmdcmp(void *va, void *vb)
+{
+	Command *a, *b;
+
+	a = va;
+	b = vb;
+	return strcmp(a->arg0, b->arg0);
+}
+
+void
+cmd_install(char *arg0, char *help, void (*func)(int, char*[]))
+{
+	int i;
+
+	qlock(&cons);
+	for(i=0; command[i].arg0; i++)
+		;
+	if(i >= nelem(command)-2) {
+		qunlock(&cons);
+		print("cmd_install: too many commands\n");
+		return;
+	}
+	command[i+1].arg0 = 0;
+	command[i].help = help;
+	command[i].func = func;
+	command[i].arg0 = arg0;
+	qsort(command, i+1, sizeof(Command), cmdcmp);
+	qunlock(&cons);
+}
+
+void
+cmd_exec(char *arg)
+{
+	char line[2*Maxword], *s;
+	char *argv[10];
+	int argc, i, c;
+
+	if(strlen(arg) >= nelem(line)-2) {
+		print("cmd_exec: line too long\n");
+		return;
+	}
+	strcpy(line, arg);
+
+	argc = 0;
+	s = line;
+	c = *s++;
+	for(;;) {
+		while(isascii(c) && isspace(c))
+			c = *s++;
+		if(c == 0)
+			break;
+		if(argc >= nelem(argv)-2) {
+			print("cmd_exec: too many args\n");
+			return;
+		}
+		argv[argc++] = s-1;
+		while((!isascii(c) || !isspace(c)) && c != '\0')
+			c = *s++;
+		s[-1] = 0;
+	}
+	if(argc <= 0)
+		return;
+	for(i=0; s=command[i].arg0; i++)
+		if(strcmp(argv[0], s) == 0) {
+			(*command[i].func)(argc, argv);
+			prflush();
+			return;
+		}
+	print("cmd_exec: unknown command: %s\n", argv[0]);
+}
+
+static void
+cmd_halt(int, char *[])
+{
+	wlock(&mainlock);	/* halt */
+	sync("halt");
+	exit();
+}
+
+static void
+cmd_duallow(int argc, char *argv[])
+{
+	int uid;
+
+	if(argc <= 1) {
+		duallow = 0;
+		return;
+	}
+
+	uid = strtouid(argv[1]);
+	if(uid < 0)
+		uid = number(argv[1], -2, 10);
+	if(uid < 0) {
+		print("bad uid %s\n", argv[1]);
+		return;
+	}
+	duallow = uid;
+}
+
+static void
+cmd_stats(int argc, char *argv[])
+{
+	int i, c;
+	char buf[30], *s, *p, *q;
+
+	if(argc <= 1) {
+		if(statsdef[0] == 0)
+			strcpy(statsdef, "a");
+		sprint(buf, "stats s%s", statsdef);
+		cmd_exec(buf);
+		return;
+	}
+
+	strcpy(buf, "stat");
+	p = strchr(buf, 0);
+	p[1] = 0;
+
+	q = 0;
+	for(i = 1; i < argc; i++)
+		for(s = argv[i]; c = *s; s++) {
+			if(c == 's')
+				continue;
+			if(c == '-') {
+				q = statsdef;
+				continue;
+			}
+			if(q) {
+				*q++ = c;
+				*q = 0;
+			}
+			*p = c;
+			cmd_exec(buf);
+		}
+}
+
+static void
+cmd_stata(int, char *[])
+{
+	int i;
+
+	print("cons stats\n");
+//	print("\twork =%7W%7W%7W rps\n", cons.work+0, cons.work+1, cons.work+2);
+//	print("\trate =%7W%7W%7W tBps\n", cons.rate+0, cons.rate+1, cons.rate+2);
+//	print("\thits =%7W%7W%7W iops\n", cons.bhit+0, cons.bhit+1, cons.bhit+2);
+//	print("\tread =%7W%7W%7W iops\n", cons.bread+0, cons.bread+1, cons.bread+2);
+//	print("\trah  =%7W%7W%7W iops\n", cons.brahead+0, cons.brahead+1, cons.brahead+2);
+//	print("\tinit =%7W%7W%7W iops\n", cons.binit+0, cons.binit+1, cons.binit+2);
+	print("\tbufs =    %3ld sm %3ld lg %ld res\n",
+		cons.nsmall, cons.nlarge, cons.nreseq);
+
+	for(i=0; i<nelem(mballocs); i++)
+		if(mballocs[i])
+			print("\t[%d]=%d\n", i, mballocs[i]);
+
+	print("\tioerr=    %3ld wr %3ld ww %3ld dr %3ld dw\n",
+		cons.nwormre, cons.nwormwe, cons.nwrenre, cons.nwrenwe);
+	print("\tcache=     %9ld hit %9ld miss\n",
+		cons.nwormhit, cons.nwormmiss);
+}
+
+static int
+flagcmp(void *va, void *vb)
+{
+	Flag *a, *b;
+
+	a = va;
+	b = vb;
+	return strcmp(a->arg0, b->arg0);
+}
+
+ulong
+flag_install(char *arg, char *help)
+{
+	int i;
+
+	qlock(&cons);
+	for(i=0; flag[i].arg0; i++)
+		;
+	if(i >= 32) {
+		qunlock(&cons);
+		print("flag_install: too many flags\n");
+		return 0;
+	}
+	flag[i+1].arg0 = 0;
+	flag[i].arg0 = arg;
+	flag[i].help = help;
+	flag[i].flag = 1<<i;
+	qsort(flag, i+1, sizeof(Flag), flagcmp);
+	qunlock(&cons);
+	return 1<<i;
+}
+
+void
+cmd_flag(int argc, char *argv[])
+{
+	int f, n, i, j;
+	char *s;
+	Chan *cp;
+
+	if(argc <= 1) {
+		for(i=0; flag[i].arg0; i++)
+			print("%.4lux %s %s\n",
+				flag[i].flag, flag[i].arg0, flag[i].help);
+		if(cons.flags)
+			print("flag[*]   = %.4lux\n", cons.flags);
+		for(cp = chans; cp; cp = cp->next)
+			if(cp->flags)
+				print("flag[%3d] = %.4lux\n", cp->chan, cp->flags);
+		return;
+	}
+
+	f = 0;
+	n = -1;
+	for(i=1; i<argc; i++) {
+		for(j=0; s=flag[j].arg0; j++)
+			if(strcmp(s, argv[i]) == 0)
+				goto found;
+		j = number(argv[i], -1, 10);
+		if(j < 0) {
+			print("bad flag argument: %s\n", argv[i]);
+			continue;
+		}
+		n = j;
+		continue;
+	found:
+		f |= flag[j].flag;
+	}
+
+	if(n < 0) {
+		cons.flags ^= f;
+		if(f == 0)
+			cons.flags = 0;
+		print("flag      = %.8lux\n", cons.flags);
+		return;
+	}
+	for(cp = chans; cp; cp = cp->next)
+		if(cp->chan == n) {
+			cp->flags ^= f;
+			if(f == 0)
+				cp->flags = 0;
+			print("flag[%3d] = %.8lux\n", cp->chan, cp->flags);
+			return;
+		}
+	print("no such channel\n");
+}
+
+static void
+cmd_who(int argc, char *argv[])
+{
+	Chan *cp;
+	int i, c;
+
+	c = 0;
+	for(cp = chans; cp; cp = cp->next) {
+		if(cp->whotime == 0 && !(cons.flags & whoflag)) {
+			c++;
+			continue;
+		}
+		if(argc > 1) {
+			for(i=1; i<argc; i++)
+				if(strcmp(argv[i], cp->whoname) == 0)
+					break;
+			if(i >= argc) {
+				c++;
+				continue;
+			}
+		}
+		print("%3d: %10s %24s", cp->chan,
+			cp->whoname? cp->whoname: "<nowhoname>", cp->whochan);
+		if(cp->whoprint)
+			cp->whoprint(cp);
+		print("\n");
+		prflush();
+	}
+	if(c > 0)
+		print("%d chans not listed\n", c);
+}
+
+static void
+cmd_hangup(int argc, char *argv[])
+{
+	Chan *cp;
+	int n;
+
+	if(argc < 2) {
+		print("usage: hangup chan-number\n");
+		return;
+	}
+	n = number(argv[1], -1, 10);
+	for(cp = chans; cp; cp = cp->next) {
+		if(cp->whotime == 0) {
+			if(cp->chan == n)
+				print("that chan is hung up\n");
+			continue;
+		}
+		if(cp->chan == n) {
+			/* need more than just fileinit with tcp */
+			chanhangup(cp, "console command", 1);
+			fileinit(cp);
+		}
+	}
+}
+
+static void
+cmd_sync(int, char *[])
+{
+	wlock(&mainlock);	/* sync */
+	sync("command");
+	wunlock(&mainlock);
+	print("\n");
+}
+
+static void
+cmd_help(int argc, char *argv[])
+{
+	char *arg;
+	int i, j;
+
+	for(i=0; arg=command[i].arg0; i++) {
+		if(argc > 1) {
+			for(j=1; j<argc; j++)
+				if(strcmp(argv[j], arg) == 0)
+					goto found;
+			continue;
+		}
+	found:
+		print("\t%s %s\n", arg, command[i].help);
+		prflush();
+	}
+}
+
+void
+cmd_fstat(int argc, char *argv[])
+{
+	int i;
+
+	for(i=1; i<argc; i++) {
+		if(walkto(argv[i])) {
+			print("cant stat %s\n", argv[i]);
+			continue;
+		}
+		con_fstat(FID2);
+	}
+}
+
+void
+cmd_create(int argc, char *argv[])
+{
+	int uid, gid;
+	long perm;
+	char elem[NAMELEN], *p;
+
+	if(argc < 5) {
+		print("usage: create path uid gid mode [lad]\n");
+		return;
+	}
+
+	p = utfrrune(argv[1], '/');
+	if(p) {
+		*p++ = 0;
+		if(walkto(argv[1])) {
+			print("create failed in walkto: %s\n", p);
+			return;
+		}
+	} else {
+		if(walkto("/"))
+			return;
+		p = argv[1];
+	}
+	if(strlen(p) >= NAMELEN) {
+		print("name too long %s\n", p);
+		return;
+	}
+
+	memset(elem, 0, sizeof(elem));
+	strcpy(elem, p);
+
+	uid = strtouid(argv[2]);
+	if(uid < -1)
+		uid = number(argv[2], -2, 10);
+	if(uid < -1) {
+		print("bad uid %s\n", argv[2]);
+		return;
+	}
+
+	gid = strtouid(argv[3]);
+	if(gid < -1)
+		gid = number(argv[3], -2, 10);
+	if(gid < -1) {
+		print("bad gid %s\n", argv[3]);
+		return;
+	}
+
+	perm = number(argv[4], 0777, 8) & 0777;
+
+	if(argc > 5) {
+		if(strchr(argv[5], 'l'))
+			perm |= PLOCK;
+		if(strchr(argv[5], 'a'))
+			perm |= PAPND;
+		if(strchr(argv[5], 'd'))
+			perm |= PDIR;
+	}
+
+	if(con_create(FID2, elem, uid, gid, perm, 0))
+		print("create failed: %s/%s\n", argv[1], p);
+}
+
+static void
+cmd_clri(int argc, char *argv[])
+{
+	int i;
+
+	for(i=1; i<argc; i++) {
+		if(walkto(argv[i])) {
+			print("cant remove %s\n", argv[i]);
+			continue;
+		}
+		con_clri(FID2);
+	}
+}
+
+static void
+cmd_allow(int, char**)
+{
+	wstatallow = writeallow = 1;
+}
+
+static void
+cmd_disallow(int, char**)
+{
+	wstatallow = writeallow = 0;
+}
+
+void
+ckblock(Device *d, Off a, int typ, Off qpath)
+{
+	Iobuf *p;
+
+	if(a) {
+		p = getbuf(d, a, Brd);
+		if(p) {
+			checktag(p, typ, qpath);
+			putbuf(p);
+		}
+	}
+}
+
+void
+doclean(Iobuf *p, Dentry *d, int n, Off a)
+{
+	int i, mod, typ;
+	Off qpath;
+
+	mod = 0;
+	qpath = d->qid.path;
+	typ = Tfile;
+	if(d->mode & DDIR)
+		typ = Tdir;
+	for(i=0; i<NDBLOCK; i++) {
+		print("dblock[%d] = %lld\n", i, (Wideoff)d->dblock[i]);
+		ckblock(p->dev, d->dblock[i], typ, qpath);
+		if(i == n) {
+			d->dblock[i] = a;
+			mod = 1;
+			print("dblock[%d] modified %lld\n", i, (Wideoff)a);
+		}
+	}
+
+	/* add NDBLOCK so user can cite block address by index */
+	for (i = 0; i < NIBLOCK; i++) {
+		print("iblocks[%d] = %lld\n", NDBLOCK+i, (Wideoff)d->iblocks[i]);
+		ckblock(p->dev, d->iblocks[i], Tind1+i, qpath);
+		if(NDBLOCK+i == n) {
+			d->iblocks[i] = a;
+			mod = 1;
+			print("iblocks[%d] modified %lld\n", NDBLOCK+i, (Wideoff)a);
+		}
+	}
+
+	if(mod)
+		p->flags |= Bmod|Bimm;
+}
+
+static void
+cmd_clean(int argc, char *argv[])
+{
+	int n;
+	Off a;
+	Iobuf *p;
+	Dentry *d;
+	File *f;
+
+	p = 0;
+	f = 0;
+	while(argc > 1) {
+		n = -1;
+		if(argc > 2)
+			n = number(argv[2], -1, 10);
+		a = 0;
+		if(argc > 3)
+			a = number(argv[3], 0, 10);
+		if(walkto(argv[1])) {
+			print("cant remove %s\n", argv[1]);
+			break;
+		}
+		f = filep(cons.chan, FID2, 0);
+		if(!f)
+			break;
+		if(n >= 0 && f->fs->dev->type == Devro) {
+			print("readonly %s\n", argv[1]);
+			break;
+		}
+		p = getbuf(f->fs->dev, f->addr, Brd);
+		d = getdir(p, f->slot);
+		if(!d || !(d->mode & DALLOC)) {
+			print("not alloc %s\n", argv[1]);
+			break;
+		}
+		doclean(p, d, n, a);
+		break;
+	}
+	if(f)
+		qunlock(f);
+	if(p)
+		putbuf(p);
+}
+
+static void
+cmd_remove(int argc, char *argv[])
+{
+	int i;
+
+	for(i=1; i<argc; i++) {
+		if(walkto(argv[i])) {
+			print("cant remove %s\n", argv[i]);
+			continue;
+		}
+		con_remove(FID2);
+	}
+}
+
+static void
+cmd_version(int, char *[])
+{
+	print("%d-bit %s as of %T\n", sizeof(Off)*8 - 1, service, fs_mktime);
+	print("\tlast boot %T\n", boottime);
+}
+
+static void
+cmd_cfs(int argc, char *argv[])
+{
+	Filsys *fs;
+	char *name;
+
+	name = "main";
+	if(argc > 1)
+		name = argv[1];
+	fs = fsstr(name);
+	if(fs == 0) {
+		print("%s: unknown file system\n", name);
+		if(cons.curfs)
+			return;
+		fs = &filsys[0];
+	}
+	if(con_attach(FID1, "adm", fs->name))
+		panic("FID1 attach to root");
+	cons.curfs = fs;
+	print("current fs is \"%s\"\n", cons.curfs->name);
+}
+
+static void
+cmd_prof(int argc, char *argv[])
+{
+	int n;
+	long m, o;
+	char *p;
+
+	if(cons.profbuf == 0) {
+		print("no buffer\n");
+		return;
+	}
+	n = !cons.profile;
+	if(argc > 1)
+		n = number(argv[1], n, 10);
+	if(n && !cons.profile) {
+		print("clr and start\n");
+		memset(cons.profbuf, 0, cons.nprofbuf*sizeof(cons.profbuf[0]));
+		cons.profile = 1;
+		return;
+	}
+	if(!n && cons.profile) {
+		cons.profile = 0;
+		print("stop and write\n");
+		if(walkto("/adm/kprofdata"))
+			goto bad;
+		if(con_open(FID2, OWRITE|OTRUNC)) {
+		bad:
+			print("cant open /adm/kprofdata\n");
+			return;
+		}
+		p = (char*)cons.profbuf;
+		for(m=0; m<cons.nprofbuf; m++) {
+			n = cons.profbuf[m];
+			p[0] = n>>24;
+			p[1] = n>>16;
+			p[2] = n>>8;
+			p[3] = n>>0;
+			p += 4;
+		}
+
+		m = cons.nprofbuf*sizeof(cons.profbuf[0]);
+		o = 0;
+		while(m > 0) {
+			n = 8192;
+			if(n > m)
+				n = m;
+			con_write(FID2, (char*)cons.profbuf+o, o, n);
+			m -= n;
+			o += n;
+		}
+		return;
+	}
+}
+
+static void
+cmd_time(int argc, char *argv[])
+{
+	int i, len;
+	char *cmd;
+	Timet t1, t2;
+
+	t1 = time(nil);
+	len = 0;
+	for(i=1; i<argc; i++)
+		len += 1 + strlen(argv[i]);
+	cmd = malloc(len + 1);
+	cmd[0] = 0;
+	for(i=1; i<argc; i++) {
+		strcat(cmd, " ");
+		strcat(cmd, argv[i]);
+	}
+	cmd_exec(cmd);
+	t2 = time(nil);
+	free(cmd);
+	print("time = %ld ms\n", TK2MS(t2-t1));
+}
+
+void
+cmd_noattach(int, char *[])
+{
+	noattach = !noattach;
+	if(noattach)
+		print("attaches are DISABLED\n");
+}
+
+void
+cmd_files(int, char *[])
+{
+	long i, n;
+	Chan *cp;
+
+	for(cp = chans; cp; cp = cp->next)
+		cp->nfile = 0;
+
+	lock(&flock);
+	n = 0;
+	for(i=0; i<conf.nfile; i++)
+		if(files[i].cp) {
+			n++;
+			files[i].cp->nfile++;
+		}
+	print("%ld out of %ld files used\n", n, conf.nfile);
+	unlock(&flock);
+
+	n = 0;
+	for(cp = chans; cp; cp = cp->next)
+		if(cp->nfile) {
+			print("%3d: %5d\n", cp->chan, cp->nfile);
+			prflush();
+			n += cp->nfile;
+		}
+	print("%ld out of %ld files used\n", n, conf.nfile);
+}
+
+static void
+installcmds(void)
+{
+	cmd_install("allow", "-- disable permission checking", cmd_allow);
+	cmd_install("cfs", "[file] -- set current filesystem", cmd_cfs);
+	cmd_install("clean", "file [bno [addr]] -- block print/fix", cmd_clean);
+	cmd_install("check", "[options]", cmd_check);
+	cmd_install("clri", "[file ...] -- purge files/dirs", cmd_clri);
+	cmd_install("create", "path uid gid perm [lad] -- make a file/dir", cmd_create);
+	cmd_install("disallow", "-- enable permission checking", cmd_disallow);
+	cmd_install("duallow", "uid -- duallow", cmd_duallow);
+	cmd_install("flag", "-- print set flags", cmd_flag);
+	cmd_install("fstat", "path -- print info on a file/dir", cmd_fstat);
+	cmd_install("halt", "-- return to boot rom", cmd_halt);
+	cmd_install("help", "", cmd_help);
+	cmd_install("newuser", "username -- add user to /adm/users", cmd_newuser);
+	cmd_install("profile", "[01] -- fs profile", cmd_prof);
+	cmd_install("remove", "[file ...] -- remove files/dirs", cmd_remove);
+	cmd_install("stata", "-- overall stats", cmd_stata);
+	cmd_install("stats", "[[-]flags ...] -- various stats", cmd_stats);
+	cmd_install("sync", "", cmd_sync);
+	cmd_install("time", "command -- time another command", cmd_time);
+	cmd_install("users", "[file] -- read /adm/users", cmd_users);
+	cmd_install("version", "-- print time of mk and boot", cmd_version);
+	cmd_install("who", "[user ...] -- print attaches", cmd_who);
+	cmd_install("hangup", "chan -- clunk files", cmd_hangup);
+	cmd_install("printconf", "-- print configuration", cmd_printconf);
+	cmd_install("noattach", "toggle noattach flag", cmd_noattach);
+	cmd_install("files", "report on files structure", cmd_files);
+
+	attachflag = flag_install("attach", "-- attach calls");
+	chatflag = flag_install("chat", "-- verbose");
+	errorflag = flag_install("error", "-- on errors");
+	whoflag = flag_install("allchans", "-- on who");
+	authdebugflag = flag_install("authdebug", "-- report authentications");
+	authdisableflag = flag_install("authdisable", "-- disable authentication");
+}
+
+int
+walkto(char *name)
+{
+	char elem[NAMELEN], *p;
+	int n;
+
+	if(con_clone(FID1, FID2))
+		return 1;
+
+	for(;;) {
+		p = utfrune(name, '/');
+		if(p == nil)
+			p = strchr(name, '\0');
+		if(p == name) {
+			if(*name == '\0')
+				return 0;
+			name = p+1;
+			continue;
+		}
+		n = p-name;
+		if(n > NAMELEN)
+			return 1;
+		memset(elem, 0, sizeof(elem));
+		memmove(elem, name, n);
+		if(con_walk(FID2, elem))
+			return 1;
+		name = p;
+	}
+}
+
+/* needs to parse and return vlongs to cope with new larger block numbers */
+vlong
+number(char *arg, int def, int base)
+{
+	int c, sign, any;
+	vlong n;
+
+	if(arg == nil)
+		return def;
+
+	sign = 0;
+	any = 0;
+	n = 0;
+
+	for (c = *arg; isascii(c) && isspace(c) && c != '\n'; c = *arg)
+		arg++;
+	if(c == '-') {
+		sign = 1;
+		arg++;
+		c = *arg;
+	}
+	while (isascii(c) && (isdigit(c) || base == 16 && isxdigit(c))) {
+		n *= base;
+		if(c >= 'a' && c <= 'f')
+			n += c - 'a' + 10;
+		else if(c >= 'A' && c <= 'F')
+			n += c - 'A' + 10;
+		else
+			n += c - '0';
+		arg++;
+		c = *arg;
+		any = 1;
+	}
+	if(!any)
+		return def;
+	if(sign)
+		n = -n;
+	return n;
+}

+ 1037 - 0
sys/src/cmd/cwfs/config.c

@@ -0,0 +1,1037 @@
+#include "all.h"
+#include "io.h"
+
+static void	dowormcopy(void);
+static int	dodevcopy(void);
+
+struct {
+	char*	icharp;
+	char*	charp;
+	int	error;
+	int	newconf;	/* clear before start */
+	int	modconf;	/* write back when done */
+	int	nextiter;
+	int	lastiter;
+	int	diriter;
+	Device*	lastcw;
+	Device*	devlist;
+} f;
+
+static Device* confdev;
+static int copyworm = 0, copydev = 0;
+static char *src, *dest;
+
+static int resetparams;
+
+Fspar fspar[] = {
+	{ "blocksize",	RBUFSIZE, },
+	{ "daddrbits",	sizeof(Off)*8, },
+	{ "indirblks",	NIBLOCK, },
+	{ "dirblks",	NDBLOCK, },
+	{ "namelen",	NAMELEN, },
+	{ nil, 0, },
+};
+
+int
+devcmpr(Device *d1, Device *d2)
+{
+	while (d1 != d2) {
+		if(d1 == nil || d2 == nil || d1->type != d2->type)
+			return 1;
+
+		switch(d1->type) {
+		default:
+			print("can't compare dev: %Z\n", d1);
+			panic("devcmp");
+			return 1;
+
+		case Devmcat:
+		case Devmlev:
+		case Devmirr:
+			d1 = d1->cat.first;
+			d2 = d2->cat.first;
+			while(d1 && d2) {
+				if(devcmpr(d1, d2))
+					return 1;
+				d1 = d1->link;
+				d2 = d2->link;
+			}
+			break;
+
+		case Devnone:
+			return 0;
+
+		case Devro:
+			d1 = d1->ro.parent;
+			d2 = d2->ro.parent;
+			break;
+
+		case Devjuke:
+		case Devcw:
+			if(devcmpr(d1->cw.c, d2->cw.c))
+				return 1;
+			d1 = d1->cw.w;
+			d2 = d2->cw.w;
+			break;
+
+		case Devfworm:
+			d1 = d1->fw.fw;
+			d2 = d2->fw.fw;
+			break;
+
+		case Devwren:
+		case Devworm:
+		case Devlworm:
+			if(d1->wren.ctrl == d2->wren.ctrl)
+			if(d1->wren.targ == d2->wren.targ)
+			if(d1->wren.lun == d2->wren.lun)
+				return 0;
+			return 1;
+
+		case Devpart:
+			if(d1->part.base == d2->part.base)
+			if(d1->part.size == d2->part.size) {
+				d1 = d1->part.d;
+				d2 = d2->part.d;
+				break;
+			}
+			return 1;
+		}
+	}
+	return 0;
+}
+
+void
+cdiag(char *s, int c1)
+{
+
+	f.charp--;
+	if(f.error == 0) {
+		print("config diag: %s -- <%c>\n", s, c1);
+		f.error = 1;
+	}
+}
+
+int
+cnumb(void)
+{
+	int c, n;
+
+	c = *f.charp++;
+	if(c == '<') {
+		n = f.nextiter;
+		if(n >= 0) {
+			f.nextiter = n+f.diriter;
+			if(n == f.lastiter) {
+				f.nextiter = -1;
+				f.lastiter = -1;
+			}
+			do {
+				c = *f.charp++;
+			} while (c != '>');
+			return n;
+		}
+		n = cnumb();
+		if(*f.charp++ != '-') {
+			cdiag("- expected", f.charp[-1]);
+			return 0;
+		}
+		c = cnumb();
+		if(*f.charp++ != '>') {
+			cdiag("> expected", f.charp[-1]);
+			return 0;
+		}
+		f.lastiter = c;
+		f.diriter = 1;
+		if(n > c)
+			f.diriter = -1;
+		f.nextiter = n+f.diriter;
+		return n;
+	}
+	if(!isascii(c) || !isdigit(c)) {
+		cdiag("number expected", c);
+		return 0;
+	}
+	n = 0;
+	while(isascii(c) && isdigit(c)) {
+		n = n*10 + (c-'0');
+		c = *f.charp++;
+	}
+	f.charp--;
+	return n;
+}
+
+Device*
+config1(int c)
+{
+	Device *d, *t;
+	int m;
+
+	d = malloc(sizeof(Device));
+	do {
+		t = config();
+		if(d->cat.first == 0)
+			d->cat.first = t;
+		else
+			d->cat.last->link = t;
+		d->cat.last = t;
+		if(f.error)
+			return devnone;
+		m = *f.charp;
+		if(c == '(' && m == ')')
+			d->type = Devmcat;
+		else if(c == '[' && m == ']')
+			d->type = Devmlev;
+		else if(c == '{' && m == '}')
+			d->type = Devmirr;
+	} while (d->type == 0);
+	f.charp++;
+	if(d->cat.first == d->cat.last)
+		d = d->cat.first;
+	return d;
+}
+
+static void
+map(Device *d)
+{
+	Map *map;
+
+	if (d->type != Devwren || d->wren.mapped)
+		return;
+	for (map = devmap; map != nil; map = map->next)
+		if (devcmpr(d, map->fdev) == 0)
+			break;
+	if (map == nil)
+		return;
+	if (access(map->to, AEXIST) >= 0)
+{ print("map: mapped wren %Z to existing file %s\n", d, map->to); // DEBUG
+		d->wren.file = map->to;		/* wren -> file mapping */
+}
+	else if (map->tdev != nil)
+{ print("map: mapped wren %Z to dev %Z\n", d, map->tdev); // DEBUG
+		*d = *map->tdev;		/* wren -> wren mapping */
+}
+	else
+		print("bad mapping %Z to %s; no such file or device",
+			d, map->to);
+	d->wren.mapped = 1;
+}
+
+Device*
+config(void)
+{
+	int c, m;
+	Device *d;
+	char *icp;
+
+	if(f.error)
+		return devnone;
+	d = malloc(sizeof(Device));
+
+	c = *f.charp++;
+	switch(c) {
+	default:
+		cdiag("unknown type", c);
+		return devnone;
+
+	case '(':	/* (d+) one or multiple cat */
+	case '[':	/* [d+] one or multiple interleave */
+	case '{':	/* {d+} a mirrored device and optional mirrors */
+		return config1(c);
+
+	case 'f':	/* fd fake worm */
+		d->type = Devfworm;
+		d->fw.fw = config();
+		break;
+
+	case 'n':
+		d->type = Devnone;
+		break;
+
+	case 'w':	/* w[#.]#[.#] wren	[ctrl] unit [lun] */
+	case 'r':	/* r# worm side */
+	case 'l':	/* l# labelled-worm side */
+		icp = f.charp;
+		d->type = Devwren;
+		d->wren.ctrl = 0;
+		d->wren.targ = cnumb();
+		d->wren.lun = 0;
+		m = *f.charp;
+		if(m == '.') {
+			f.charp++;
+			d->wren.lun = cnumb();
+			m = *f.charp;
+			if(m == '.') {
+				f.charp++;
+				d->wren.ctrl = d->wren.targ;
+				d->wren.targ = d->wren.lun;
+				d->wren.lun = cnumb();
+			}
+		}
+		if(f.nextiter >= 0)
+			f.charp = icp-1;
+		if(c == 'r')		/* worms are virtual and not uniqued */
+			d->type = Devworm;
+		else if(c == 'l')
+			d->type = Devlworm;
+		else
+			map(d);		/* subject wrens to optional mapping */
+		break;
+
+	case 'o':	/* o ro part of last cw */
+		if(f.lastcw == 0) {
+			cdiag("no cw to match", c);
+			return devnone;
+		}
+		return f.lastcw->cw.ro;
+
+	case 'j':	/* DD jukebox */
+		d->type = Devjuke;
+		d->j.j = config();
+		d->j.m = config();
+		break;
+
+	case 'c':	/* cache/worm */
+		d->type = Devcw;
+		d->cw.c = config();
+		d->cw.w = config();
+		d->cw.ro = malloc(sizeof(Device));
+		d->cw.ro->type = Devro;
+		d->cw.ro->ro.parent = d;
+		f.lastcw = d;
+		break;
+
+	case 'p':	/* pd#.# partition base% size% */
+		d->type = Devpart;
+		d->part.d = config();
+		d->part.base = cnumb();
+		c = *f.charp++;
+		if(c != '.')
+			cdiag("dot expected", c);
+		d->part.size = cnumb();
+		break;
+
+	case 'x':	/* xD swab a device's metadata */
+		d->type = Devswab;
+		d->swab.d = config();
+		break;
+	}
+	d->dlink = f.devlist;
+	f.devlist = d;
+	return d;
+}
+
+Device*
+iconfig(char *s)
+{
+	Device *d;
+
+	f.nextiter = -1;
+	f.lastiter = -1;
+	f.error = 0;
+	f.icharp = s;
+	f.charp = f.icharp;
+	d = config();
+	if(*f.charp) {
+		cdiag("junk on end", *f.charp);
+		f.error = 1;
+	}
+	return d;
+}
+
+int
+testconfig(char *s)
+{
+	iconfig(s);
+	return f.error;
+}
+
+/*
+ * if b is a prefix of a, return 0.
+ */
+int
+astrcmp(char *a, char *b)
+{
+	int n, c;
+
+	n = strlen(b);
+	if(memcmp(a, b, n) != 0)
+		return 1;
+	c = a[n];
+	if(c == '\0')
+		return 0;
+	if(a[n+1])
+		return 1;
+	if(isascii(c) && isdigit(c))
+		return 0;
+	return 1;
+}
+
+static Fspar *
+getpar(char *name)
+{
+	Fspar *fsp;
+
+	for (fsp = fspar; fsp->name != nil; fsp++)
+		if (strcmp(name, fsp->name) == 0)
+			return fsp;
+	return nil;
+}
+
+/*
+ * continue to parse obsolete keywords so that old configurations can
+ * still work.
+ */
+void
+mergeconf(Iobuf *p)
+{
+	char word[Maxword+1];
+	char *cp;
+	Filsys *fs;
+	Fspar *fsp;
+
+	for (cp = p->iobuf; *cp != '\0'; cp++) {
+		cp = getwrd(word, cp);
+		if(word[0] == '\0')
+			break;
+		else if (word[0] == '#')
+			while (*cp != '\n' && *cp != '\0')
+				cp++;
+		else if(strcmp(word, "service") == 0) {
+			cp = getwrd(word, cp);
+			if(service[0] == 0)
+				strncpy(service, word, sizeof service);
+		} else if(strcmp(word, "ipauth") == 0)	/* obsolete */
+			cp = getwrd(word, cp);
+		else if(astrcmp(word, "ip") == 0)	/* obsolete */
+			cp = getwrd(word, cp);
+		else if(astrcmp(word, "ipgw") == 0)	/* obsolete */
+			cp = getwrd(word, cp);
+		else if(astrcmp(word, "ipsntp") == 0)	/* obsolete */
+			cp = getwrd(word, cp);
+		else if(astrcmp(word, "ipmask") == 0)	/* obsolete */
+			cp = getwrd(word, cp);
+		else if(strcmp(word, "filsys") == 0) {
+			cp = getwrd(word, cp);
+			for(fs = filsys; fs < filsys + nelem(filsys) - 1 &&
+			    fs->name; fs++)
+				if(strcmp(fs->name, word) == 0)
+					break;
+			if (fs >= filsys + nelem(filsys) - 1)
+				panic("out of filsys structures");
+			if (fs->name && strcmp(fs->name, word) == 0 &&
+			    fs->flags & FEDIT)
+				cp = getwrd(word, cp);	/* swallow conf */
+			else {
+				fs->name = strdup(word);
+				cp = getwrd(word, cp);
+				if (word[0] == '\0')
+					fs->conf = nil;
+				else
+					fs->conf = strdup(word);
+			}
+		} else if ((fsp = getpar(word)) != nil) {
+			cp = getwrd(word, cp);
+			if (!isascii(word[0]) || !isdigit(word[0]))
+				print("bad %s value: %s", fsp->name, word);
+			else
+				fsp->declared = atol(word);
+		} else {
+			putbuf(p);
+			panic("unknown keyword in config block: %s", word);
+		}
+
+		if(*cp != '\n') {
+			putbuf(p);
+			panic("syntax error in config block at `%s'", word);
+		}
+	}
+}
+
+void
+cmd_printconf(int, char *[])
+{
+	char *p, *s;
+	Iobuf *iob;
+
+	iob = getbuf(confdev, 0, Brd);
+	if(iob == nil)
+		return;
+	if(checktag(iob, Tconfig, 0)){
+		putbuf(iob);
+		return;
+	}
+
+	print("config %s\n", nvrgetconfig());
+	for(s = p = iob->iobuf; *p != 0 && p < iob->iobuf+BUFSIZE; ){
+		if(*p++ != '\n')
+			continue;
+		if (strncmp(s, "ip", 2) != 0)	/* don't print obsolete cmds */
+			print("%.*s", (int)(p-s), s);
+		s = p;
+	}
+	if(p != s)
+		print("%.*s", (int)(p-s), s);
+	print("end\n");
+
+	putbuf(iob);
+}
+
+void
+sysinit(void)
+{
+	int error;
+	char *cp, *ep;
+	Device *d;
+	Filsys *fs;
+	Fspar *fsp;
+	Iobuf *p;
+
+	cons.chan = fs_chaninit(Devcon, 1, 0);
+
+start:
+	/*
+	 * part 1 -- read the config file
+	 */
+	devnone = iconfig("n");
+
+	cp = nvrgetconfig();
+	print("config %s\n", cp);
+
+	confdev = d = iconfig(cp);
+	devinit(d);
+	if(f.newconf) {
+		p = getbuf(d, 0, Bmod);
+		memset(p->iobuf, 0, RBUFSIZE);
+		settag(p, Tconfig, 0);
+	} else
+		p = getbuf(d, 0, Brd|Bmod);
+	if(!p || checktag(p, Tconfig, 0))
+		panic("config io");
+
+	mergeconf(p);
+
+	if (resetparams) {
+		for (fsp = fspar; fsp->name != nil; fsp++)
+			fsp->declared = 0;
+		resetparams = 0;
+	}
+
+	for (fsp = fspar; fsp->name != nil; fsp++) {
+		/* supply defaults from this cwfs instance */
+		if (fsp->declared == 0) {
+			fsp->declared = fsp->actual;
+			f.modconf = 1;
+		}
+		/* warn if declared value is not our compiled-in value */
+		if (fsp->declared != fsp->actual)
+			print("warning: config %s %ld != compiled-in %ld\n",
+				fsp->name, fsp->declared, fsp->actual);
+	}
+
+	if(f.modconf) {
+		memset(p->iobuf, 0, BUFSIZE);
+		p->flags |= Bmod|Bimm;
+		cp = p->iobuf;
+		ep = p->iobuf + RBUFSIZE - 1;
+		if(service[0])
+			cp = seprint(cp, ep, "service %s\n", service);
+		for(fs=filsys; fs->name; fs++)
+			if(fs->conf && fs->conf[0] != '\0')
+				cp = seprint(cp, ep, "filsys %s %s\n", fs->name,
+					fs->conf);
+
+		for (fsp = fspar; fsp->name != nil; fsp++)
+			cp = seprint(cp, ep, "%s %ld\n",
+				fsp->name, fsp->declared);
+
+		putbuf(p);
+		f.modconf = f.newconf = 0;
+		print("config block written\n");
+		goto start;
+	}
+	putbuf(p);
+
+	print("service    %s\n", service);
+
+loop:
+	/*
+	 * part 2 -- squeeze out the deleted filesystems
+	 */
+	for(fs=filsys; fs->name; fs++)
+		if(fs->conf == nil || fs->conf[0] == '\0') {
+			for(; fs->name; fs++)
+				*fs = *(fs+1);
+			goto loop;
+		}
+	if(filsys[0].name == nil)
+		panic("no filsys");
+
+	/*
+	 * part 3 -- compile the device expression
+	 */
+	error = 0;
+	for(fs=filsys; fs->name; fs++) {
+		print("filsys %s %s\n", fs->name, fs->conf);
+		fs->dev = iconfig(fs->conf);
+		if(f.error) {
+			error = 1;
+			continue;
+		}
+	}
+	if(error)
+		panic("fs config");
+
+	/*
+	 * part 4 -- initialize the devices
+	 */
+	for(fs=filsys; fs->name; fs++) {
+		delay(3000);
+		print("sysinit: %s\n", fs->name);
+		if(fs->flags & FREAM)
+			devream(fs->dev, 1);
+		if(fs->flags & FRECOVER)
+			devrecover(fs->dev);
+		devinit(fs->dev);
+	}
+
+	/*
+	 * part 5 -- optionally copy devices or worms
+	 */
+	if (copyworm) {
+		dowormcopy();		/* can return if user quits early */
+		panic("copyworm bailed out!");
+	}
+	if (copydev)
+		if (dodevcopy() < 0)
+			panic("copydev failed!");
+		else
+			panic("copydev done.");
+}
+
+/* an unfinished idea.  a non-blocking rawchar() would help. */
+static int
+userabort(char *msg)
+{
+	USED(msg);
+	return 0;
+}
+
+static int
+blockok(Device *d, Off a)
+{
+	Iobuf *p = getbuf(d, a, Brd);
+
+	if (p == 0) {
+		print("i/o error reading %Z block %lld\n", d, (Wideoff)a);
+		return 0;
+	}
+	putbuf(p);
+	return 1;
+}
+
+/*
+ * special case for fake worms only:
+ * we need to size the inner cw's worm device.
+ * in particular, we want to avoid copying the fake-worm bitmap
+ * at the end of the device.
+ *
+ * N.B.: for real worms (e.g. cw jukes), we need to compute devsize(cw(juke)),
+ * *NOT* devsize(juke).
+ */
+static Device *
+wormof(Device *dev)
+{
+	Device *worm = dev, *cw;
+
+	if (dev->type == Devfworm) {
+		cw = dev->fw.fw;
+		if (cw != nil && cw->type == Devcw)
+			worm = cw->cw.w;
+	}
+	// print("wormof(%Z)=%Z\n", dev, worm);
+	return worm;
+}
+
+/*
+ * return the number of the highest-numbered block actually written, plus 1.
+ * 0 indicates an error.
+ */
+static Devsize
+writtensize(Device *worm)
+{
+	Devsize lim = devsize(worm);
+	Iobuf *p;
+
+	print("devsize(%Z) = %lld\n", worm, (Wideoff)lim);
+	if (!blockok(worm, 0) || !blockok(worm, lim-1))
+		return 0;
+	delay(5*1000);
+	if (userabort("sanity checks"))
+		return 0;
+
+	/* find worm's last valid block in case "worm" is an (f)worm */
+	while (lim > 0) {
+		if (userabort("sizing")) {
+			lim = 0;		/* you lose */
+			break;
+		}
+		--lim;
+		p = getbuf(worm, lim, Brd);
+		if (p != 0) {			/* actually read one okay? */
+			putbuf(p);
+			break;
+		}
+	}
+	print("limit(%Z) = %lld\n", worm, (Wideoff)lim);
+	if (lim <= 0)
+		return 0;
+	return lim + 1;
+}
+
+/* copy worm fs from "main"'s inner worm to "output" */
+static void
+dowormcopy(void)
+{
+	Filsys *f1, *f2;
+	Device *fdev, *from, *to = nil;
+	Iobuf *p;
+	Off a;
+	Devsize lim;
+
+	/*
+	 * convert file system names into Filsyss and Devices.
+	 */
+
+	f1 = fsstr("main");
+	if(f1 == nil)
+		panic("main file system missing");
+	fdev = f1->dev;
+	from = wormof(fdev);			/* fake worm special */
+	if (from->type != Devfworm && from->type != Devcw) {
+		print("main file system is not a worm; copyworm may not do what you want!\n");
+		print("waiting for 20 seconds...\n");
+		delay(20000);
+	}
+
+	f2 = fsstr("output");
+	if(f2 == nil) {
+		print("no output file system - check only\n\n");
+		print("reading worm from %Z (worm %Z)\n", fdev, from);
+	} else {
+		to = f2->dev;
+		print("\ncopying worm from %Z (worm %Z) to %Z, starting in 8 seconds\n",
+			fdev, from, to);
+		delay(8000);
+	}
+	if (userabort("preparing to copy"))
+		return;
+
+	/*
+	 * initialise devices, size them, more sanity checking.
+	 */
+
+	devinit(from);
+	if (0 && fdev != from) {
+		devinit(fdev);
+		print("debugging, sizing %Z first\n", fdev);
+		writtensize(fdev);
+	}
+	lim = writtensize(from);
+	if(lim == 0)
+		panic("no blocks to copy on %Z", from);
+	if (to) {
+		print("reaming %Z in 8 seconds\n", to);
+		delay(8000);
+		if (userabort("preparing to ream & copy"))
+			return;
+		devream(to, 0);
+		devinit(to);
+		print("copying worm: %lld blocks from %Z to %Z\n",
+			(Wideoff)lim, from, to);
+	}
+	/* can't read to's blocks in case to is a real WORM device */
+
+	/*
+	 * Copy written fs blocks, a block at a time (or just read
+	 * if no "output" fs).
+	 */
+
+	for (a = 0; a < lim; a++) {
+		if (userabort("copy"))
+			break;
+		p = getbuf(from, a, Brd);
+		/*
+		 * if from is a real WORM device, we'll get errors trying to
+		 * read unwritten blocks, but the unwritten blocks need not
+		 * be contiguous.
+		 */
+		if (p == 0) {
+			print("%lld not written yet; can't read\n", (Wideoff)a);
+			continue;
+		}
+		if (to != 0 && devwrite(to, p->addr, p->iobuf) != 0) {
+			print("out block %lld: write error; bailing",
+				(Wideoff)a);
+			break;
+		}
+		putbuf(p);
+		if(a % 20000 == 0)
+			print("block %lld %T\n", (Wideoff)a, time(nil));
+	}
+
+	/*
+	 * wrap up: sync target, loop
+	 */
+	print("copied %lld blocks from %Z to %Z\n", (Wideoff)a, from, to);
+	sync("wormcopy");
+	delay(2000);
+	print("looping; reset the machine at any time.\n");
+	for (; ; )
+		continue;		/* await reset */
+}
+
+/* copy device from src to dest */
+static int
+dodevcopy(void)
+{
+	Device *from, *to;
+	Iobuf *p;
+	Off a;
+	Devsize lim, tosize;
+
+	/*
+	 * convert config strings into Devices.
+	 */
+	from = iconfig(src);
+	if(f.error || from == nil) {
+		print("bad src device %s\n", src);
+		return -1;
+	}
+	to = iconfig(dest);
+	if(f.error || to == nil) {
+		print("bad dest device %s\n", dest);
+		return -1;
+	}
+
+	/*
+	 * initialise devices, size them, more sanity checking.
+	 */
+
+	devinit(from);
+	lim = devsize(from);
+	if(lim == 0)
+		panic("no blocks to copy on %Z", from);
+	devinit(to);
+	tosize = devsize(to);
+	if(tosize == 0)
+		panic("no blocks to copy on %Z", to);
+
+	/* use smaller of the device sizes */
+	if (tosize < lim)
+		lim = tosize;
+
+	print("copy %Z to %Z in 8 seconds\n", from, to);
+	delay(8000);
+	if (userabort("preparing to copy"))
+		return -1;
+	print("copying dev: %lld blocks from %Z to %Z\n", (Wideoff)lim,
+		from, to);
+
+	/*
+	 * Copy all blocks, a block at a time.
+	 */
+
+	for (a = 0; a < lim; a++) {
+		if (userabort("copy"))
+			break;
+		p = getbuf(from, a, Brd);
+		/*
+		 * if from is a real WORM device, we'll get errors trying to
+		 * read unwritten blocks, but the unwritten blocks need not
+		 * be contiguous.
+		 */
+		if (p == 0) {
+			print("%lld not written yet; can't read\n", (Wideoff)a);
+			continue;
+		}
+		if (to != 0 && devwrite(to, p->addr, p->iobuf) != 0) {
+			print("out block %lld: write error; bailing",
+				(Wideoff)a);
+			break;
+		}
+		putbuf(p);
+		if(a % 20000 == 0)
+			print("block %lld %T\n", (Wideoff)a, time(nil));
+	}
+
+	/*
+	 * wrap up: sync target
+	 */
+	print("copied %lld blocks from %Z to %Z\n", (Wideoff)a, from, to);
+	sync("devcopy");
+	return 0;
+}
+
+static void
+setconfig(char *dev)
+{
+	if (dev != nil && !testconfig(dev))
+		nvrsetconfig(dev);	/* if it fails, it will complain */
+}
+
+void
+arginit(void)
+{
+	int verb;
+	char *line;
+	char word[Maxword+1], *cp;
+	Filsys *fs;
+
+	if(nvrcheck() == 0) {
+		setconfig(conf.confdev);
+		if (!conf.configfirst)
+			return;
+	}
+
+	/* nvr was bad or invoker requested configuration step */
+	setconfig(conf.confdev);
+	for (;;) {
+		print("config: ");
+		if ((line = Brdline(&bin, '\n')) == nil)
+			return;
+		line[Blinelen(&bin)-1] = '\0';
+
+		cp = getwrd(word, line);
+		if (word[0] == '\0' || word[0] == '#')
+			continue;
+		if(strcmp(word, "end") == 0)
+			return;
+		if(strcmp(word, "halt") == 0)
+			exit();
+		if(strcmp(word, "queryjuke") == 0) {
+			getwrd(word, cp);
+			if(testconfig(word) == 0)
+				querychanger(iconfig(word));
+			continue;
+		}
+
+		if(strcmp(word, "allow") == 0) {
+			wstatallow = 1;
+			writeallow = 1;
+			continue;
+		}
+		if(strcmp(word, "copyworm") == 0) {
+			copyworm = 1;
+			continue;
+		}
+		if(strcmp(word, "copydev") == 0) {
+			cp = getwrd(word, cp);
+			if(testconfig(word))
+				continue;
+			src = strdup(word);
+			getwrd(word, cp);
+			if(testconfig(word))
+				continue;
+			dest = strdup(word);
+			copydev = 1;
+			continue;
+		}
+		if(strcmp(word, "noauth") == 0) {
+			noauth = !noauth;
+			continue;
+		}
+		if(strcmp(word, "noattach") == 0) {
+			noattach = !noattach;
+			continue;
+		}
+		if(strcmp(word, "readonly") == 0) {
+			readonly = 1;
+			continue;
+		}
+
+		if(strcmp(word, "ream") == 0) {
+			verb = FREAM;
+			goto gfsname;
+		}
+		if(strcmp(word, "recover") == 0) {
+			verb = FRECOVER;
+			goto gfsname;
+		}
+		if(strcmp(word, "filsys") == 0) {
+			verb = FEDIT;
+			goto gfsname;
+		}
+
+		if(strcmp(word, "nvram") == 0) {
+			getwrd(word, cp);
+			if(testconfig(word))
+				continue;
+			/* if it fails, it will complain */
+			nvrsetconfig(word);
+			continue;
+		}
+		if(strcmp(word, "config") == 0) {
+			getwrd(word, cp);
+			if(!testconfig(word) && nvrsetconfig(word) == 0)
+				f.newconf = 1;
+			continue;
+		}
+		if(strcmp(word, "service") == 0) {
+			getwrd(word, cp);
+			strncpy(service, word, sizeof service);
+			f.modconf = 1;
+			continue;
+		}
+		if (strcmp(word, "resetparams") == 0) {
+			resetparams++;
+			continue;
+		}
+
+		/*
+		 * continue to parse obsolete keywords so that old
+		 * configurations can still work.
+		 */
+		if (strcmp(word, "ipauth") != 0 &&
+		    astrcmp(word, "ip") != 0 &&
+		    astrcmp(word, "ipgw") != 0 &&
+		    astrcmp(word, "ipmask") != 0 &&
+		    astrcmp(word, "ipsntp") != 0) {
+			print("unknown config command\n");
+			print("\ttype end to get out\n");
+			continue;
+		}
+
+		getwrd(word, cp);
+		f.modconf = 1;
+		continue;
+
+	gfsname:
+		cp = getwrd(word, cp);
+		for(fs=filsys; fs->name; fs++)
+			if(strcmp(word, fs->name) == 0)
+				break;
+		if (fs->name == nil) {
+			memset(fs, 0, sizeof *fs);
+			fs->name = strdup(word);
+		}
+		switch(verb) {
+		case FREAM:
+			if(strcmp(fs->name, "main") == 0)
+				wstatallow = 1;	/* only set, never reset */
+			/* fallthrough */
+		case FRECOVER:
+			fs->flags |= verb;
+			break;
+		case FEDIT:
+			f.modconf = 1;
+			getwrd(word, cp);
+			fs->flags |= verb;
+			if(word[0] == 0)
+				fs->conf = nil;
+			else if(!testconfig(word))
+				fs->conf = strdup(word);
+			break;
+		}
+	}
+}

+ 309 - 0
sys/src/cmd/cwfs/console.c

@@ -0,0 +1,309 @@
+#include	"all.h"
+
+#include	"9p1.h"
+
+void
+fcall9p1(Chan *cp, Fcall *in, Fcall *ou)
+{
+	int t;
+
+	rlock(&mainlock);
+	t = in->type;
+	if(t < 0 || t >= MAXSYSCALL || (t&1) || !call9p1[t]) {
+		print("bad message type %d\n", t);
+		panic("");
+	}
+	ou->type = t+1;
+	ou->err = 0;
+
+	rlock(&cp->reflock);
+	(*call9p1[t])(cp, in, ou);
+	runlock(&cp->reflock);
+
+	if(ou->err && CHAT(cp))
+		print("\terror: %s\n", errstr9p[ou->err]);
+	runlock(&mainlock);
+}
+
+int
+con_session(void)
+{
+	Fcall in, ou;
+
+	in.type = Tsession;
+	fcall9p1(cons.chan, &in, &ou);
+	return ou.err;
+}
+
+int
+con_attach(int fid, char *uid, char *arg)
+{
+	Fcall in, ou;
+
+	in.type = Tattach;
+	in.fid = fid;
+	strncpy(in.uname, uid, NAMELEN);
+	strncpy(in.aname, arg, NAMELEN);
+	fcall9p1(cons.chan, &in, &ou);
+	return ou.err;
+}
+
+int
+con_clone(int fid1, int fid2)
+{
+	Fcall in, ou;
+
+	in.type = Tclone;
+	in.fid = fid1;
+	in.newfid = fid2;
+	fcall9p1(cons.chan, &in, &ou);
+	return ou.err;
+}
+
+int
+con_walk(int fid, char *name)
+{
+	Fcall in, ou;
+
+	in.type = Twalk;
+	in.fid = fid;
+	strncpy(in.name, name, NAMELEN);
+	fcall9p1(cons.chan, &in, &ou);
+	return ou.err;
+}
+
+int
+con_open(int fid, int mode)
+{
+	Fcall in, ou;
+
+	in.type = Topen;
+	in.fid = fid;
+	in.mode = mode;
+	fcall9p1(cons.chan, &in, &ou);
+	return ou.err;
+}
+
+int
+con_read(int fid, char *data, Off offset, int count)
+{
+	Fcall in, ou;
+
+	in.type = Tread;
+	in.fid = fid;
+	in.offset = offset;
+	in.count = count;
+	ou.data = data;
+	fcall9p1(cons.chan, &in, &ou);
+	if(ou.err)
+		return 0;
+	return ou.count;
+}
+
+int
+con_write(int fid, char *data, Off offset, int count)
+{
+	Fcall in, ou;
+
+	in.type = Twrite;
+	in.fid = fid;
+	in.data = data;
+	in.offset = offset;
+	in.count = count;
+	fcall9p1(cons.chan, &in, &ou);
+	if(ou.err)
+		return 0;
+	return ou.count;
+}
+
+int
+con_remove(int fid)
+{
+	Fcall in, ou;
+
+	in.type = Tremove;
+	in.fid = fid;
+	fcall9p1(cons.chan, &in, &ou);
+	return ou.err;
+}
+
+int
+con_create(int fid, char *name, int uid, int gid, long perm, int mode)
+{
+	Fcall in, ou;
+
+	in.type = Tcreate;
+	in.fid = fid;
+	strncpy(in.name, name, NAMELEN);
+	in.perm = perm;
+	in.mode = mode;
+	cons.uid = uid;			/* beyond ugly */
+	cons.gid = gid;
+	fcall9p1(cons.chan, &in, &ou);
+	return ou.err;
+}
+
+int
+doclri(File *f)
+{
+	Iobuf *p, *p1;
+	Dentry *d, *d1;
+	int err;
+
+	err = 0;
+	p = 0;
+	p1 = 0;
+	if(f->fs->dev->type == Devro) {
+		err = Eronly;
+		goto out;
+	}
+	/*
+	 * check on parent directory of file to be deleted
+	 */
+	if(f->wpath == 0 || f->wpath->addr == f->addr) {
+		err = Ephase;
+		goto out;
+	}
+	p1 = getbuf(f->fs->dev, f->wpath->addr, Brd);
+	d1 = getdir(p1, f->wpath->slot);
+	if(!d1 || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)) {
+		err = Ephase;
+		goto out;
+	}
+
+	accessdir(p1, d1, FWRITE, 0);
+	putbuf(p1);
+	p1 = 0;
+
+	/*
+	 * check on file to be deleted
+	 */
+	p = getbuf(f->fs->dev, f->addr, Brd);
+	d = getdir(p, f->slot);
+
+	/*
+	 * do it
+	 */
+	memset(d, 0, sizeof(Dentry));
+	settag(p, Tdir, QPNONE);
+	freewp(f->wpath);
+	freefp(f);
+
+out:
+	if(p1)
+		putbuf(p1);
+	if(p)
+		putbuf(p);
+	return err;
+}
+
+void
+f_fstat(Chan *cp, Fcall *in, Fcall *ou)
+{
+	File *f;
+	Iobuf *p;
+	Dentry *d;
+	int i;
+
+	if(CHAT(cp)) {
+		print("c_fstat %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+	}
+
+	p = 0;
+	f = filep(cp, in->fid, 0);
+	if(!f) {
+		ou->err = Efid;
+		goto out;
+	}
+	p = getbuf(f->fs->dev, f->addr, Brd);
+	d = getdir(p, f->slot);
+	if(d == 0)
+		goto out;
+
+	print("name = %.*s\n", NAMELEN, d->name);
+	print("uid = %d; gid = %d; muid = %d\n", d->uid, d->gid, d->muid);
+	print("size = %lld; qid = %llux/%lux\n", (Wideoff)d->size,
+		(Wideoff)d->qid.path, d->qid.version);
+	print("atime = %ld; mtime = %ld\n", d->atime, d->mtime);
+	print("dblock =");
+	for(i=0; i<NDBLOCK; i++)
+		print(" %lld", (Wideoff)d->dblock[i]);
+	for (i = 0; i < NIBLOCK; i++)
+		print("; iblocks[%d] = %lld", i, (Wideoff)d->iblocks[i]);
+	print("\n\n");
+
+out:
+	if(p)
+		putbuf(p);
+	ou->fid = in->fid;
+	if(f)
+		qunlock(f);
+}
+
+void
+f_clri(Chan *cp, Fcall *in, Fcall *ou)
+{
+	File *f;
+
+	if(CHAT(cp)) {
+		print("c_clri %d\n", cp->chan);
+		print("\tfid = %d\n", in->fid);
+	}
+
+	f = filep(cp, in->fid, 0);
+	if(!f) {
+		ou->err = Efid;
+		goto out;
+	}
+	ou->err = doclri(f);
+
+out:
+	ou->fid = in->fid;
+	if(f)
+		qunlock(f);
+}
+
+int
+con_clri(int fid)
+{
+	Fcall in, ou;
+	Chan *cp;
+
+	in.type = Tremove;
+	in.fid = fid;
+	cp = cons.chan;
+
+	rlock(&mainlock);
+	ou.type = Tremove+1;
+	ou.err = 0;
+
+	rlock(&cp->reflock);
+	f_clri(cp, &in, &ou);
+	runlock(&cp->reflock);
+
+	runlock(&mainlock);
+	return ou.err;
+}
+
+int
+con_fstat(int fid)
+{
+	Fcall in, ou;
+	Chan *cp;
+
+	in.type = Tstat;
+	in.fid = fid;
+	cp = cons.chan;
+
+	rlock(&mainlock);
+	ou.type = Tstat+1;
+	ou.err = 0;
+
+	rlock(&cp->reflock);
+	f_fstat(cp, &in, &ou);
+	runlock(&cp->reflock);
+
+	runlock(&mainlock);
+	return ou.err;
+}

+ 2259 - 0
sys/src/cmd/cwfs/cw.c

@@ -0,0 +1,2259 @@
+/*
+ * cached-worm device
+ */
+#include "all.h"
+
+#define	CDEV(d)		((d)->cw.c)
+#define	WDEV(d)		((d)->cw.w)
+#define	RDEV(d)		((d)->cw.ro)
+
+enum {
+	DEBUG		= 0,
+	FIRST		= SUPER_ADDR,
+
+	ADDFREE		= 100,
+	CACHE_ADDR	= SUPER_ADDR,
+	MAXAGE		= 10000,
+};
+
+/* cache state */
+enum
+{
+	/* states -- beware these are recorded on the cache */
+				/*    cache    worm	*/
+	Cnone = 0,		/*	0	?	*/
+	Cdirty,			/*	1	0	*/
+	Cdump,			/*	1	0->1	*/
+	Cread,			/*	1	1	*/
+	Cwrite,			/*	2	1	*/
+	Cdump1,			/* inactive form of dump */
+	Cerror,
+
+	/* opcodes -- these are not recorded */
+	Onone,
+	Oread,
+	Owrite,
+	Ogrow,
+	Odump,
+	Orele,
+	Ofree,
+};
+
+typedef	struct	Cw	Cw;
+struct	Cw
+{
+	Device*	dev;
+	Device*	cdev;
+	Device*	wdev;
+	Device*	rodev;
+	Cw*	link;
+
+	int	dbucket;	/* last bucket dumped */
+	Off	daddr;		/* last block dumped */
+	Off	ncopy;
+	int	nodump;
+/*
+ * following are cached variables for dumps
+ */
+	Off	fsize;
+	Off	ndump;
+	int	depth;
+	int	all;		/* local flag to recur on modified dirs */
+	int	allflag;	/* global flag to recur on modified dirs */
+	Off	falsehits;	/* times recur found modified blocks */
+	struct {
+		char	name[500];
+		char	namepad[NAMELEN+10];
+	};
+};
+
+static char* cwnames[] =
+{
+	[Cnone]		"none",
+	[Cdirty]	"dirty",
+	[Cdump]		"dump",
+	[Cread]		"read",
+	[Cwrite]	"write",
+	[Cdump1]	"dump1",
+	[Cerror]	"error",
+
+	[Onone]		"none",
+	[Oread]		"read",
+	[Owrite]	"write",
+	[Ogrow]		"grow",
+	[Odump]		"dump",
+	[Orele]		"rele",
+};
+
+Centry*	getcentry(Bucket*, Off);
+int	cwio(Device*, Off, void*, int);
+void	cmd_cwcmd(int, char*[]);
+
+/*
+ * console command
+ * initiate a dump
+ */
+void
+cmd_dump(int argc, char *argv[])
+{
+	Filsys *fs;
+
+	fs = cons.curfs;
+	if(argc > 1)
+		fs = fsstr(argv[1]);
+	if(fs == 0) {
+		print("%s: unknown file system\n", argv[1]);
+		return;
+	}
+	cfsdump(fs);
+}
+
+/*
+ * console command
+ * worm stats
+ */
+static void
+cmd_statw(int, char*[])
+{
+	Filsys *fs;
+	Iobuf *p;
+	Superb *sb;
+	Cache *h;
+	Bucket *b;
+	Centry *c, *ce;
+	Off m, nw, bw, state[Onone];
+	Off sbfsize, sbcwraddr, sbroraddr, sblast, sbnext;
+	Off hmsize, hmaddr, dsize, dsizepct;
+	Device *dev;
+	Cw *cw;
+	int s;
+
+	fs = cons.curfs;
+	dev = fs->dev;
+	if(dev->type != Devcw) {
+		print("curfs not type cw\n");
+		return;
+	}
+
+	cw = dev->private;
+	if(cw == 0) {
+		print("curfs not inited\n");
+		return;
+	}
+
+	print("cwstats %s\n", fs->name);
+
+	sbfsize = 0;
+	sbcwraddr = 0;
+	sbroraddr = 0;
+	sblast = 0;
+	sbnext = 0;
+
+	print("\tfilesys %s\n", fs->name);
+//	print("\tnio   =%7W%7W%7W\n", cw->ncwio+0, cw->ncwio+1, cw->ncwio+2);
+	p = getbuf(dev, cwsaddr(dev), Brd);
+	if(!p || checktag(p, Tsuper, QPSUPER)) {
+		print("cwstats: checktag super\n");
+		if(p) {
+			putbuf(p);
+			p = 0;
+		}
+	}
+	if(p) {
+		sb = (Superb*)p->iobuf;
+		sbfsize = sb->fsize;
+		sbcwraddr = sb->cwraddr;
+		sbroraddr = sb->roraddr;
+		sblast = sb->last;
+		sbnext = sb->next;
+		putbuf(p);
+	}
+
+	p = getbuf(cw->cdev, CACHE_ADDR, Brd|Bres);
+	if(!p || checktag(p, Tcache, QPSUPER)) {
+		print("cwstats: checktag c bucket\n");
+		if(p)
+			putbuf(p);
+		return;
+	}
+	h = (Cache*)p->iobuf;
+	hmaddr = h->maddr;
+	hmsize = h->msize;
+
+	print("\t\tmaddr  = %8lld\n", (Wideoff)hmaddr);
+	print("\t\tmsize  = %8lld\n", (Wideoff)hmsize);
+	print("\t\tcaddr  = %8lld\n", (Wideoff)h->caddr);
+	print("\t\tcsize  = %8lld\n", (Wideoff)h->csize);
+	print("\t\tsbaddr = %8lld\n", (Wideoff)h->sbaddr);
+	print("\t\tcraddr = %8lld %8lld\n",
+		(Wideoff)h->cwraddr, (Wideoff)sbcwraddr);
+	print("\t\troaddr = %8lld %8lld\n",
+		(Wideoff)h->roraddr, (Wideoff)sbroraddr);
+	/* print stats in terms of (first-)disc sides */
+	dsize = wormsizeside(dev, 0);
+	if (dsize < 1) {
+		if (DEBUG)
+			print("wormsizeside returned size %lld for %Z side 0\n",
+				(Wideoff)dsize, dev);
+		dsize = h->wsize;	/* it's probably a fake worm */
+		if (dsize < 1)
+			dsize = 1000;	/* don't divide by zero */
+	}
+	dsizepct = dsize/100;
+	print("\t\tfsize  = %8lld %8lld %2lld+%2lld%%\n", (Wideoff)h->fsize,
+		(Wideoff)sbfsize, (Wideoff)h->fsize/dsize,
+		(Wideoff)(h->fsize%dsize)/dsizepct);
+	print("\t\tslast  =          %8lld\n", (Wideoff)sblast);
+	print("\t\tsnext  =          %8lld\n", (Wideoff)sbnext);
+	print("\t\twmax   = %8lld          %2lld+%2lld%%\n",
+		(Wideoff)h->wmax, (Wideoff)h->wmax/dsize,
+		(Wideoff)(h->wmax%dsize)/dsizepct);
+	print("\t\twsize  = %8lld          %2lld+%2lld%%\n",
+		(Wideoff)h->wsize, (Wideoff)h->wsize/dsize,
+		(Wideoff)(h->wsize%dsize)/dsizepct);
+	putbuf(p);
+
+	bw = 0;			/* max filled bucket */
+	memset(state, 0, sizeof(state));
+	for(m = 0; m < hmsize; m++) {
+		p = getbuf(cw->cdev, hmaddr + m/BKPERBLK, Brd);
+		if(!p || checktag(p, Tbuck, hmaddr + m/BKPERBLK)) {
+			print("cwstats: checktag c bucket\n");
+			if(p)
+				putbuf(p);
+			return;
+		}
+		b = (Bucket*)p->iobuf + m%BKPERBLK;
+		ce = b->entry + CEPERBK;
+		nw = 0;
+		for(c = b->entry; c < ce; c++) {
+			s = c->state;
+			state[s]++;
+			if(s != Cnone && s != Cread)
+				nw++;
+		}
+		putbuf(p);
+		if(nw > bw)
+			bw = nw;
+	}
+	for(s = Cnone; s < Cerror; s++)
+		print("\t\t%6lld %s\n", (Wideoff)state[s], cwnames[s]);
+	print("\t\tcache %2lld%% full\n", ((Wideoff)bw*100)/CEPERBK);
+}
+
+int
+dumpblock(Device *dev)
+{
+	Iobuf *p, *cb, *p1, *p2;
+	Cache *h;
+	Centry *c, *ce, *bc;
+	Bucket *b;
+	Off m, a, msize, maddr, wmax, caddr;
+	int s1, s2, count;
+	Cw *cw;
+
+	cw = dev->private;
+	if(cw == 0 || cw->nodump)
+		return 0;
+
+	cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bres);
+	h = (Cache*)cb->iobuf;
+	msize = h->msize;
+	maddr = h->maddr;
+	wmax = h->wmax;
+	caddr = h->caddr;
+	putbuf(cb);
+
+	for(m=msize; m>=0; m--) {
+		a = cw->dbucket + 1;
+		if(a < 0 || a >= msize)
+			a = 0;
+		cw->dbucket = a;
+		p = getbuf(cw->cdev, maddr + a/BKPERBLK, Brd);
+		b = (Bucket*)p->iobuf + a%BKPERBLK;
+		ce = b->entry + CEPERBK;
+		bc = 0;
+		for(c = b->entry; c < ce; c++)
+			if(c->state == Cdump) {
+				if(bc == 0) {
+					bc = c;
+					continue;
+				}
+				if(c->waddr < cw->daddr) {
+					if(bc->waddr < cw->daddr &&
+					   bc->waddr > c->waddr)
+						bc = c;
+					continue;
+				}
+				if(bc->waddr < cw->daddr ||
+				   bc->waddr > c->waddr)
+					bc = c;
+			}
+		if(bc) {
+			c = bc;
+			goto found;
+		}
+		putbuf(p);
+	}
+	if(cw->ncopy) {
+		print("%lld blocks copied to worm\n", (Wideoff)cw->ncopy);
+		cw->ncopy = 0;
+	}
+	cw->nodump = 1;
+	return 0;
+
+found:
+	a = a*CEPERBK + (c - b->entry) + caddr;
+	p1 = getbuf(devnone, Cwdump1, 0);
+	count = 0;
+
+retry:
+	count++;
+	if(count > 10)
+		goto stop;
+	if(devread(cw->cdev, a, p1->iobuf))
+		goto stop;
+	m = c->waddr;
+	cw->daddr = m;
+	s1 = devwrite(cw->wdev, m, p1->iobuf);
+	if(s1) {
+		p2 = getbuf(devnone, Cwdump2, 0);
+		s2 = devread(cw->wdev, m, p2->iobuf);
+		if(s2) {
+			if(s1 == 0x61 && s2 == 0x60) {
+				putbuf(p2);
+				goto retry;
+			}
+			goto stop1;
+		}
+		if(memcmp(p1->iobuf, p2->iobuf, RBUFSIZE))
+			goto stop1;
+		putbuf(p2);
+	}
+	/*
+	 * reread and compare
+	 */
+	if(conf.dumpreread) {
+		p2 = getbuf(devnone, Cwdump2, 0);
+		s1 = devread(cw->wdev, m, p2->iobuf);
+		if(s1)
+			goto stop1;
+		if(memcmp(p1->iobuf, p2->iobuf, RBUFSIZE)) {
+			print("reread C%lld W%lld didnt compare\n",
+				(Wideoff)a, (Wideoff)m);
+			goto stop1;
+		}
+		putbuf(p2);
+	}
+
+	putbuf(p1);
+	c->state = Cread;
+	p->flags |= Bmod;
+	putbuf(p);
+
+	if(m > wmax) {
+		cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bmod|Bres);
+		h = (Cache*)cb->iobuf;
+		if(m > h->wmax)
+			h->wmax = m;
+		putbuf(cb);
+	}
+	cw->ncopy++;
+	return 1;
+
+stop1:
+	putbuf(p2);
+	putbuf(p1);
+	c->state = Cdump1;
+	p->flags |= Bmod;
+	putbuf(p);
+	return 1;
+
+stop:
+	putbuf(p1);
+	putbuf(p);
+	print("stopping dump!!\n");
+	cw->nodump = 1;
+	return 0;
+}
+
+void
+cwinit1(Device *dev)
+{
+	Cw *cw;
+	static int first;
+
+	cw = dev->private;
+	if(cw)
+		return;
+
+	if(first == 0) {
+		cmd_install("dump", "-- make dump backup to worm", cmd_dump);
+		cmd_install("statw", "-- cache/worm stats", cmd_statw);
+		cmd_install("cwcmd", "subcommand -- cache/worm errata", cmd_cwcmd);
+		roflag = flag_install("ro", "-- ro reads and writes");
+		first = 1;
+	}
+	cw = malloc(sizeof(Cw));
+	dev->private = cw;
+
+	cw->allflag = 0;
+
+	cw->dev = dev;
+	cw->cdev = CDEV(dev);
+	cw->wdev = WDEV(dev);
+	cw->rodev = RDEV(dev);
+
+	devinit(cw->cdev);
+	devinit(cw->wdev);
+}
+
+void
+cwinit(Device *dev)
+{
+	Cw *cw;
+	Cache *h;
+	Iobuf *cb, *p;
+	Off l, m;
+
+	cwinit1(dev);
+
+	cw = dev->private;
+	l = devsize(cw->wdev);
+	cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bmod|Bres);
+	h = (Cache*)cb->iobuf;
+	h->toytime = toytime() + SECOND(30);
+	h->time = time(nil);
+	m = h->wsize;
+	if(l != m) {
+		print("wdev changed size %lld to %lld\n",
+			(Wideoff)m, (Wideoff)l);
+		h->wsize = l;
+		cb->flags |= Bmod;
+	}
+
+	for(m=0; m<h->msize; m++) {
+		p = getbuf(cw->cdev, h->maddr + m/BKPERBLK, Brd);
+		if(!p || checktag(p, Tbuck, h->maddr + m/BKPERBLK))
+			panic("cwinit: checktag c bucket");
+		putbuf(p);
+	}
+	putbuf(cb);
+}
+
+Off
+cwsaddr(Device *dev)
+{
+	Iobuf *cb;
+	Off sa;
+
+	cb = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bres);
+	sa = ((Cache*)cb->iobuf)->sbaddr;
+	putbuf(cb);
+	return sa;
+}
+
+Off
+cwraddr(Device *dev)
+{
+	Iobuf *cb;
+	Off ra;
+
+	switch(dev->type) {
+	default:
+		print("unknown dev in cwraddr %Z\n", dev);
+		return 1;
+
+	case Devcw:
+		cb = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bres);
+		ra = ((Cache*)cb->iobuf)->cwraddr;
+		break;
+
+	case Devro:
+		cb = getbuf(CDEV(dev->ro.parent), CACHE_ADDR, Brd|Bres);
+		ra = ((Cache*)cb->iobuf)->roraddr;
+		break;
+	}
+	putbuf(cb);
+	return ra;
+}
+
+Devsize
+cwsize(Device *dev)
+{
+	Iobuf *cb;
+	Devsize fs;
+
+	cb = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bres);
+	fs = ((Cache*)cb->iobuf)->fsize;
+	putbuf(cb);
+	return fs;
+}
+
+int
+cwread(Device *dev, Off b, void *c)
+{
+	return cwio(dev, b, c, Oread) == Cerror;
+}
+
+int
+cwwrite(Device *dev, Off b, void *c)
+{
+	return cwio(dev, b, c, Owrite) == Cerror;
+}
+
+int
+roread(Device *dev, Off b, void *c)
+{
+	Device *d;
+	int s;
+
+	/*
+	 * maybe better is to try buffer pool first
+	 */
+	d = dev->ro.parent;
+	if(d == 0 || d->type != Devcw ||
+	   d->private == 0 || RDEV(d) != dev) {
+		print("bad rodev %Z\n", dev);
+		return 1;
+	}
+	s = cwio(d, b, 0, Onone);
+	if(s == Cdump || s == Cdump1 || s == Cread) {
+		s = cwio(d, b, c, Oread);
+		if(s == Cdump || s == Cdump1 || s == Cread) {
+			if(cons.flags & roflag)
+				print("roread: %Z %lld -> %Z(hit)\n",
+					dev, (Wideoff)b, d);
+			return 0;
+		}
+	}
+	if(cons.flags & roflag)
+		print("roread: %Z %lld -> %Z(miss)\n",
+			dev, (Wideoff)b, WDEV(d));
+	return devread(WDEV(d), b, c);
+}
+
+int
+cwio(Device *dev, Off addr, void *buf, int opcode)
+{
+	Iobuf *p, *p1, *p2, *cb;
+	Cache *h;
+	Bucket *b;
+	Centry *c;
+	Off bn, a1, a2, max, newmax;
+	int state;
+	Cw *cw;
+
+	cw = dev->private;
+
+	cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bres);
+	h = (Cache*)cb->iobuf;
+	if(toytime() >= h->toytime) {
+		cb->flags |= Bmod;
+		h->toytime = toytime() + SECOND(30);
+		h->time = time(nil);
+	}
+
+	if(addr < 0) {
+		putbuf(cb);
+		return Cerror;
+	}
+
+	bn = addr % h->msize;
+	a1 = h->maddr + bn/BKPERBLK;
+	a2 = bn*CEPERBK + h->caddr;
+	max = h->wmax;
+
+	putbuf(cb);
+	newmax = 0;
+
+	p = getbuf(cw->cdev, a1, Brd|Bmod);
+	if(!p || checktag(p, Tbuck, a1))
+		panic("cwio: checktag c bucket");
+	b = (Bucket*)p->iobuf + bn%BKPERBLK;
+
+	c = getcentry(b, addr);
+	if(c == 0) {
+		putbuf(p);
+		print("%Z disk cache bucket %lld is full\n",
+			cw->cdev, (Wideoff)a1);
+		return Cerror;
+	}
+	a2 += c - b->entry;
+
+	state = c->state;
+	switch(opcode) {
+	default:
+		goto bad;
+
+	case Onone:
+		break;
+
+	case Oread:
+		switch(state) {
+		default:
+			goto bad;
+
+		case Cread:
+			if(!devread(cw->cdev, a2, buf))
+				break;
+			c->state = Cnone;
+
+		case Cnone:
+			if(devread(cw->wdev, addr, buf)) {
+				state = Cerror;
+				break;
+			}
+			if(addr > max)
+				newmax = addr;
+			if(!devwrite(cw->cdev, a2, buf))
+				c->state = Cread;
+			break;
+
+		case Cdirty:
+		case Cdump:
+		case Cdump1:
+		case Cwrite:
+			if(devread(cw->cdev, a2, buf))
+				state = Cerror;
+			break;
+		}
+		break;
+
+	case Owrite:
+		switch(state) {
+		default:
+			goto bad;
+
+		case Cdump:
+		case Cdump1:
+			/*
+			 * this is hard part -- a dump block must be
+			 * sent to the worm if it is rewritten.
+			 * if this causes an error, there is no
+			 * place to save the dump1 data. the block
+			 * is just reclassified as 'dump1' (botch)
+			 */
+			p1 = getbuf(devnone, Cwio1, 0);
+			if(devread(cw->cdev, a2, p1->iobuf)) {
+				putbuf(p1);
+				print("cwio: write induced dump error - r cache\n");
+
+			casenone:
+				if(devwrite(cw->cdev, a2, buf)) {
+					state = Cerror;
+					break;
+				}
+				c->state = Cdump1;
+				break;
+			}
+			if(devwrite(cw->wdev, addr, p1->iobuf)) {
+				p2 = getbuf(devnone, Cwio2, 0);
+				if(devread(cw->wdev, addr, p2->iobuf)) {
+					putbuf(p1);
+					putbuf(p2);
+					print("cwio: write induced dump error - r+w worm\n");
+					goto casenone;
+				}
+				if(memcmp(p1->iobuf, p2->iobuf, RBUFSIZE)) {
+					putbuf(p1);
+					putbuf(p2);
+					print("cwio: write induced dump error - w worm\n");
+					goto casenone;
+				}
+				putbuf(p2);
+			}
+			putbuf(p1);
+			c->state = Cread;
+			if(addr > max)
+				newmax = addr;
+			cw->ncopy++;
+
+		case Cnone:
+		case Cread:
+			if(devwrite(cw->cdev, a2, buf)) {
+				state = Cerror;
+				break;
+			}
+			c->state = Cwrite;
+			break;
+
+		case Cdirty:
+		case Cwrite:
+			if(devwrite(cw->cdev, a2, buf))
+				state = Cerror;
+			break;
+		}
+		break;
+
+	case Ogrow:
+		if(state != Cnone) {
+			print("%Z for block %lld cwgrow with state = %s\n",
+				cw->cdev, (Wideoff)addr, cwnames[state]);
+			break;
+		}
+		c->state = Cdirty;
+		break;
+
+	case Odump:
+		if(state != Cdirty) {	/* BOTCH */
+			print("%Z for block %lld cwdump with state = %s\n",
+				cw->cdev, (Wideoff)addr, cwnames[state]);
+			break;
+		}
+		c->state = Cdump;
+		cw->ndump++;	/* only called from dump command */
+		break;
+
+	case Orele:
+		if(state != Cwrite) {
+			if(state != Cdump1)
+				print("%Z for block %lld cwrele with state = %s\n",
+					cw->cdev, (Wideoff)addr, cwnames[state]);
+			break;
+		}
+		c->state = Cnone;
+		break;
+
+	case Ofree:
+		if(state == Cwrite || state == Cread)
+			c->state = Cnone;
+		break;
+	}
+	if(DEBUG)
+		print("cwio: %Z %lld s=%s o=%s ns=%s\n",
+			dev, (Wideoff)addr, cwnames[state],
+			cwnames[opcode],
+			cwnames[c->state]);
+	putbuf(p);
+	if(newmax) {
+		cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bmod|Bres);
+		h = (Cache*)cb->iobuf;
+		if(newmax > h->wmax)
+			h->wmax = newmax;
+		putbuf(cb);
+	}
+	return state;
+
+bad:
+	print("%Z block %lld cw state = %s; cw opcode = %s",
+		dev, (Wideoff)addr, cwnames[state], cwnames[opcode]);
+	return Cerror;
+}
+
+
+int
+cwgrow(Device *dev, Superb *sb, int uid)
+{
+	char str[NAMELEN];
+	Iobuf *cb;
+	Cache *h;
+	Filsys *filsys;
+	Off fs, nfs, ws;
+
+	cb = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bmod|Bres);
+	h = (Cache*)cb->iobuf;
+	ws = h->wsize;
+	fs = h->fsize;
+	if(fs >= ws)
+		return 0;
+	nfs = fs + ADDFREE;
+	if(nfs >= ws)
+		nfs = ws;
+	h->fsize = nfs;
+	putbuf(cb);
+
+	sb->fsize = nfs;
+	filsys = dev2fs(dev);
+	if (filsys == nil)
+		print("%Z", dev);
+	else
+		print("%s", filsys->name);
+	uidtostr(str, uid, 1);
+	print(" grow from %lld to %lld limit %lld by %s uid=%d\n",
+		(Wideoff)fs, (Wideoff)nfs, (Wideoff)ws, str, uid);
+	for(nfs--; nfs>=fs; nfs--)
+		switch(cwio(dev, nfs, 0, Ogrow)) {
+		case Cerror:
+			return 0;
+		case Cnone:
+			addfree(dev, nfs, sb);
+		}
+	return 1;
+}
+
+int
+cwfree(Device *dev, Off addr)
+{
+	int state;
+
+	if(dev->type == Devcw) {
+		state = cwio(dev, addr, 0, Ofree);
+		if(state != Cdirty)
+			return 1;	/* do not put in freelist */
+	}
+	return 0;			/* put in freelist */
+}
+
+#ifdef unused
+int
+bktcheck(Bucket *b)
+{
+	Centry *c, *c1, *c2, *ce;
+	int err;
+
+	err = 0;
+	if(b->agegen < CEPERBK || b->agegen > MAXAGE) {
+		print("agegen %ld\n", b->agegen);
+		err = 1;
+	}
+
+	ce = b->entry + CEPERBK;
+	c1 = 0;		/* lowest age last pass */
+	for(;;) {
+		c2 = 0;		/* lowest age this pass */
+		for(c = b->entry; c < ce; c++) {
+			if(c1 != 0 && c != c1) {
+				if(c->age == c1->age) {
+					print("same age %d\n", c->age);
+					err = 1;
+				}
+				if(c1->waddr == c->waddr)
+				if(c1->state != Cnone)
+				if(c->state != Cnone) {
+					print("same waddr %lld\n",
+						(Wideoff)c->waddr);
+					err = 1;
+				}
+			}
+			if(c1 != 0 && c->age <= c1->age)
+				continue;
+			if(c2 == 0 || c->age < c2->age)
+				c2 = c;
+		}
+		if(c2 == 0)
+			break;
+		c1 = c2;
+		if(c1->age >= b->agegen) {
+			print("age >= generator %d %ld\n", c1->age, b->agegen);
+			err = 1;
+		}
+	}
+	return err;
+}
+#endif
+
+void
+resequence(Bucket *b)
+{
+	Centry *c, *ce, *cr;
+	int age, i;
+
+	ce = b->entry + CEPERBK;
+	for(c = b->entry; c < ce; c++) {
+		c->age += CEPERBK;
+		if(c->age < CEPERBK)
+			c->age = MAXAGE;
+	}
+	b->agegen += CEPERBK;
+
+	age = 0;
+	for(i=0;; i++) {
+		cr = 0;
+		for(c = b->entry; c < ce; c++) {
+			if(c->age < i)
+				continue;
+			if(cr == 0 || c->age < age) {
+				cr = c;
+				age = c->age;
+			}
+		}
+		if(cr == 0)
+			break;
+		cr->age = i;
+	}
+	b->agegen = i;
+	cons.nreseq++;
+}
+
+Centry*
+getcentry(Bucket *b, Off addr)
+{
+	Centry *c, *ce, *cr;
+	int s, age;
+
+	/*
+	 * search for cache hit
+	 * find oldest block as byproduct
+	 */
+	ce = b->entry + CEPERBK;
+	age = 0;
+	cr = 0;
+	for(c = b->entry; c < ce; c++) {
+		s = c->state;
+		if(s == Cnone) {
+			cr = c;
+			age = 0;
+			continue;
+		}
+		if(c->waddr == addr)
+			goto found;
+		if(s == Cread)
+			if(cr == 0 || c->age < age) {
+				cr = c;
+				age = c->age;
+			}
+	}
+
+	/*
+	 * remap entry
+	 */
+	c = cr;
+	if(c == 0)
+		return 0;	/* bucket is full */
+
+	c->state = Cnone;
+	c->waddr = addr;
+
+found:
+	/*
+	 * update the age to get filo cache.
+	 * small number in age means old
+	 */
+	if(!cons.noage || c->state == Cnone) {
+		age = b->agegen;
+		c->age = age;
+		age++;
+		b->agegen = age;
+		if(age < 0 || age >= MAXAGE)
+			resequence(b);
+	}
+	return c;
+}
+
+/*
+ * ream the cache
+ * calculate new buckets
+ */
+Iobuf*
+cacheinit(Device *dev)
+{
+	Iobuf *cb, *p;
+	Cache *h;
+	Device *cdev;
+	Off m;
+
+	print("cache init %Z\n", dev);
+	cdev = CDEV(dev);
+	devinit(cdev);
+
+	cb = getbuf(cdev, CACHE_ADDR, Bmod|Bres);
+	memset(cb->iobuf, 0, RBUFSIZE);
+	settag(cb, Tcache, QPSUPER);
+	h = (Cache*)cb->iobuf;
+
+	/*
+	 * calculate csize such that
+	 * tsize = msize/BKPERBLK + csize and
+	 * msize = csize/CEPERBK
+	 */
+	h->maddr = CACHE_ADDR + 1;
+	m = devsize(cdev) - h->maddr;
+	h->csize = ((Devsize)(m-1) * CEPERBK*BKPERBLK) / (CEPERBK*BKPERBLK+1);
+	h->msize = h->csize/CEPERBK - 5;
+	while(!prime(h->msize))
+		h->msize--;
+	h->csize = h->msize*CEPERBK;
+	h->caddr = h->maddr + (h->msize+BKPERBLK-1)/BKPERBLK;
+	h->wsize = devsize(WDEV(dev));
+
+	if(h->msize <= 0)
+		panic("cache too small");
+	if(h->caddr + h->csize > m)
+		panic("cache size error");
+
+	/*
+	 * setup cache map
+	 */
+	for(m=h->maddr; m<h->caddr; m++) {
+		p = getbuf(cdev, m, Bmod);
+		memset(p->iobuf, 0, RBUFSIZE);
+		settag(p, Tbuck, m);
+		putbuf(p);
+	}
+	print("done cacheinit\n");
+	return cb;
+}
+
+Off
+getstartsb(Device *dev)
+{
+	Filsys *f;
+	Startsb *s;
+
+	for(f=filsys; f->name; f++)
+		if(devcmpr(f->dev, dev) == 0) {
+			for(s=startsb; s->name; s++)
+				if(strcmp(f->name, s->name) == 0)
+					return s->startsb;
+			print(
+		"getstartsb: no special starting superblock for %Z %s\n",
+				dev, f->name);
+			return FIRST;
+		}
+	print("getstartsb: no filsys for device %Z\n", dev);
+	return FIRST;
+}
+
+/*
+ * ream the cache
+ * calculate new buckets
+ * get superblock from
+ * last worm dump block.
+ */
+void
+cwrecover(Device *dev)
+{
+	Iobuf *p, *cb;
+	Cache *h;
+	Superb *s;
+	Off m, baddr;
+	Device *wdev;
+
+//	print("cwrecover %Z\n", dev);	// DEBUG
+	cwinit1(dev);
+	wdev = WDEV(dev);
+
+	p = getbuf(devnone, Cwxx1, 0);
+	s = (Superb*)p->iobuf;
+	baddr = 0;
+	m = getstartsb(dev);
+	localconfinit();
+	if(conf.firstsb)
+		m = conf.firstsb;
+	for(;;) {
+		memset(p->iobuf, 0, RBUFSIZE);
+		if(devread(wdev, m, p->iobuf) ||
+		   checktag(p, Tsuper, QPSUPER))
+			break;
+		baddr = m;
+		m = s->next;
+		print("dump %lld is good; %lld next\n", (Wideoff)baddr, (Wideoff)m);
+		if(baddr == conf.recovsb)
+			break;
+	}
+	putbuf(p);
+	if(!baddr)
+		panic("recover: no superblock");
+
+	p = getbuf(wdev, baddr, Brd);
+	s = (Superb*)p->iobuf;
+
+	cb = cacheinit(dev);
+	h = (Cache*)cb->iobuf;
+	h->sbaddr = baddr;
+	h->cwraddr = s->cwraddr;
+	h->roraddr = s->roraddr;
+	h->fsize = s->fsize + 100;		/* this must be conservative */
+	if(conf.recovcw)
+		h->cwraddr = conf.recovcw;
+	if(conf.recovro)
+		h->roraddr = conf.recovro;
+
+	putbuf(cb);
+	putbuf(p);
+
+	p = getbuf(dev, baddr, Brd|Bmod);
+	s = (Superb*)p->iobuf;
+
+	memset(&s->fbuf, 0, sizeof(s->fbuf));
+	s->fbuf.free[0] = 0;
+	s->fbuf.nfree = 1;
+	s->tfree = 0;
+	if(conf.recovcw)
+		s->cwraddr = conf.recovcw;
+	if(conf.recovro)
+		s->roraddr = conf.recovro;
+
+	putbuf(p);
+	print("done recover\n");
+}
+
+/*
+ * ream the cache
+ * calculate new buckets
+ * initialize superblock.
+ */
+void
+cwream(Device *dev)
+{
+	Iobuf *p, *cb;
+	Cache *h;
+	Superb *s;
+	Off m, baddr;
+	Device *cdev;
+
+	print("cwream %Z\n", dev);
+	cwinit1(dev);
+	cdev = CDEV(dev);
+	devinit(cdev);
+
+	baddr = FIRST;	/*	baddr   = super addr
+				baddr+1 = cw root
+				baddr+2 = ro root
+				baddr+3 = reserved next superblock */
+
+	cb = cacheinit(dev);
+	h = (Cache*)cb->iobuf;
+
+	h->sbaddr = baddr;
+	h->cwraddr = baddr+1;
+	h->roraddr = baddr+2;
+	h->fsize = 0;	/* prevents superream from freeing */
+
+	putbuf(cb);
+
+	for(m=0; m<3; m++)
+		cwio(dev, baddr+m, 0, Ogrow);
+	superream(dev, baddr);
+	rootream(dev, baddr+1);			/* cw root */
+	rootream(dev, baddr+2);			/* ro root */
+
+	cb = getbuf(cdev, CACHE_ADDR, Brd|Bmod|Bres);
+	h = (Cache*)cb->iobuf;
+	h->fsize = baddr+4;
+	putbuf(cb);
+
+	p = getbuf(dev, baddr, Brd|Bmod|Bimm);
+	s = (Superb*)p->iobuf;
+	s->last = baddr;
+	s->cwraddr = baddr+1;
+	s->roraddr = baddr+2;
+	s->next = baddr+3;
+	s->fsize = baddr+4;
+	putbuf(p);
+
+	for(m=0; m<3; m++)
+		cwio(dev, baddr+m, 0, Odump);
+}
+
+Off
+rewalk1(Cw *cw, Off addr, int slot, Wpath *up)
+{
+	Iobuf *p, *p1;
+	Dentry *d;
+
+	if(up == 0)
+		return cwraddr(cw->dev);
+	up->addr = rewalk1(cw, up->addr, up->slot, up->up);
+	p = getbuf(cw->dev, up->addr, Brd|Bmod);
+	d = getdir(p, up->slot);
+	if(!d || !(d->mode & DALLOC)) {
+		print("rewalk1 1\n");
+		if(p)
+			putbuf(p);
+		return addr;
+	}
+	p1 = dnodebuf(p, d, slot/DIRPERBUF, 0, 0);
+	if(!p1) {
+		print("rewalk1 2\n");
+		if(p)
+			putbuf(p);
+		return addr;
+	}
+	if(DEBUG)
+		print("rewalk1 %lld to %lld \"%s\"\n",
+			(Wideoff)addr, (Wideoff)p1->addr, d->name);
+	addr = p1->addr;
+	p1->flags |= Bmod;
+	putbuf(p1);
+	putbuf(p);
+	return addr;
+}
+
+Off
+rewalk2(Cw *cw, Off addr, int slot, Wpath *up)
+{
+	Iobuf *p, *p1;
+	Dentry *d;
+
+	if(up == 0)
+		return cwraddr(cw->rodev);
+	up->addr = rewalk2(cw, up->addr, up->slot, up->up);
+	p = getbuf(cw->rodev, up->addr, Brd);
+	d = getdir(p, up->slot);
+	if(!d || !(d->mode & DALLOC)) {
+		print("rewalk2 1\n");
+		if(p)
+			putbuf(p);
+		return addr;
+	}
+	p1 = dnodebuf(p, d, slot/DIRPERBUF, 0, 0);
+	if(!p1) {
+		print("rewalk2 2\n");
+		if(p)
+			putbuf(p);
+		return addr;
+	}
+	if(DEBUG)
+		print("rewalk2 %lld to %lld \"%s\"\n",
+			(Wideoff)addr, (Wideoff)p1->addr, d->name);
+	addr = p1->addr;
+	putbuf(p1);
+	putbuf(p);
+	return addr;
+}
+
+void
+rewalk(Cw *cw)
+{
+	int h;
+	File *f;
+
+	for(h=0; h<nelem(flist); h++)
+		for(f=flist[h]; f; f=f->next) {
+			if(!f->fs)
+				continue;
+			if(cw->dev == f->fs->dev)
+				f->addr = rewalk1(cw, f->addr, f->slot, f->wpath);
+			else
+			if(cw->rodev == f->fs->dev)
+				f->addr = rewalk2(cw, f->addr, f->slot, f->wpath);
+		}
+}
+
+Off
+split(Cw *cw, Iobuf *p, Off addr)
+{
+	Off na;
+	int state;
+
+	na = 0;
+	if(p && (p->flags & Bmod)) {
+		p->flags |= Bimm;
+		putbuf(p);
+		p = 0;
+	}
+	state = cwio(cw->dev, addr, 0, Onone);	/* read the state (twice?) */
+	switch(state) {
+	default:
+		panic("split: unknown state %s", cwnames[state]);
+
+	case Cerror:
+	case Cnone:
+	case Cdump:
+	case Cread:
+		break;
+
+	case Cdump1:
+	case Cwrite:
+		/*
+		 * botch.. could be done by relabeling
+		 */
+		if(!p) {
+			p = getbuf(cw->dev, addr, Brd);
+			if(!p) {
+				print("split: null getbuf\n");
+				break;
+			}
+		}
+		na = cw->fsize;
+		cw->fsize = na+1;
+		cwio(cw->dev, na, 0, Ogrow);
+		cwio(cw->dev, na, p->iobuf, Owrite);
+		cwio(cw->dev, na, 0, Odump);
+		cwio(cw->dev, addr, 0, Orele);
+		break;
+
+	case Cdirty:
+		cwio(cw->dev, addr, 0, Odump);
+		break;
+	}
+	if(p)
+		putbuf(p);
+	return na;
+}
+
+int
+isdirty(Cw *cw, Iobuf *p, Off addr, int tag)
+{
+	int s;
+
+	if(p && (p->flags & Bmod))
+		return 1;
+	s = cwio(cw->dev, addr, 0, Onone);
+	if(s == Cdirty || s == Cwrite)
+		return 1;
+	if(tag >= Tind1 && tag <= Tmaxind)
+		/* botch, get these modified */
+		if(s != Cnone)
+			return 1;
+	return 0;
+}
+
+Off
+cwrecur(Cw *cw, Off addr, int tag, int tag1, long qp)
+{
+	Iobuf *p;
+	Dentry *d;
+	int i, j, shouldstop;
+	Off na;
+	char *np;
+
+	shouldstop = 0;
+	p = getbuf(cw->dev, addr, Bprobe);
+	if(!isdirty(cw, p, addr, tag)) {
+		if(!cw->all) {
+			if(DEBUG)
+				print("cwrecur: %lld t=%s not dirty %s\n",
+					(Wideoff)addr, tagnames[tag], cw->name);
+			if(p)
+				putbuf(p);
+			return 0;
+		}
+		shouldstop = 1;
+	}
+	if(DEBUG)
+		print("cwrecur: %lld t=%s %s\n",
+			(Wideoff)addr, tagnames[tag], cw->name);
+	if(cw->depth >= 100) {
+		print("dump depth too great %s\n", cw->name);
+		if(p)
+			putbuf(p);
+		return 0;
+	}
+	cw->depth++;
+
+	switch(tag) {
+	default:
+		print("cwrecur: unknown tag %d %s\n", tag, cw->name);
+
+	case Tfile:
+		break;
+
+	case Tsuper:
+	case Tdir:
+		if(!p) {
+			p = getbuf(cw->dev, addr, Brd);
+			if(!p) {
+				print("cwrecur: Tdir p null %s\n",
+					cw->name);
+				break;
+			}
+		}
+		if(tag == Tdir) {
+			cw->namepad[0] = 0;	/* force room */
+			np = strchr(cw->name, 0);
+			*np++ = '/';
+		} else {
+			np = 0;	/* set */
+			cw->name[0] = 0;
+		}
+
+		for(i=0; i<DIRPERBUF; i++) {
+			d = getdir(p, i);
+			if(!(d->mode & DALLOC))
+				continue;
+			qp = d->qid.path & ~QPDIR;
+			if(tag == Tdir)
+				strncpy(np, d->name, NAMELEN);
+			else
+			if(i > 0)
+				print("cwrecur: root with >1 directory\n");
+			tag1 = Tfile;
+			if(d->mode & DDIR)
+				tag1 = Tdir;
+			for(j=0; j<NDBLOCK; j++) {
+				na = d->dblock[j];
+				if(na) {
+					na = cwrecur(cw, na, tag1, 0, qp);
+					if(na) {
+						d->dblock[j] = na;
+						p->flags |= Bmod;
+					}
+				}
+			}
+			for (j = 0; j < NIBLOCK; j++) {
+				na = d->iblocks[j];
+				if(na) {
+					na = cwrecur(cw, na, Tind1+j, tag1, qp);
+					if(na) {
+						d->iblocks[j] = na;
+						p->flags |= Bmod;
+					}
+				}
+			}
+		}
+		break;
+
+	case Tind1:
+		j = tag1;
+		tag1 = 0;
+		goto tind;
+
+	case Tind2:
+#ifndef COMPAT32
+	case Tind3:
+	case Tind4:
+	/* add more Tind tags here ... */
+#endif
+		j = tag-1;
+	tind:
+		if(!p) {
+			p = getbuf(cw->dev, addr, Brd);
+			if(!p) {
+				print("cwrecur: Tind p null %s\n", cw->name);
+				break;
+			}
+		}
+		for(i=0; i<INDPERBUF; i++) {
+			na = ((Off *)p->iobuf)[i];
+			if(na) {
+				na = cwrecur(cw, na, j, tag1, qp);
+				if(na) {
+					((Off *)p->iobuf)[i] = na;
+					p->flags |= Bmod;
+				}
+			}
+		}
+		break;
+	}
+	na = split(cw, p, addr);
+	cw->depth--;
+	if(na && shouldstop) {
+		if(cw->falsehits < 10)
+			print("shouldstop %lld %lld t=%s %s\n",
+				(Wideoff)addr, (Wideoff)na,
+				tagnames[tag], cw->name);
+		cw->falsehits++;
+	}
+	return na;
+}
+
+Timet	nextdump(Timet t);
+
+void
+cfsdump(Filsys *fs)
+{
+	long m, n, i;
+	Off orba, rba, oroa, roa, sba, a;
+	Timet tim;
+	char tstr[20];
+	Iobuf *pr, *p1, *p;
+	Dentry *dr, *d1, *d;
+	Cache *h;
+	Superb *s;
+	Cw *cw;
+
+	if(fs->dev->type != Devcw) {
+		print("cant dump; not cw device: %Z\n", fs->dev);
+		return;
+	}
+	cw = fs->dev->private;
+	if(cw == 0) {
+		print("cant dump: has not been inited: %Z\n", fs->dev);
+		return;
+	}
+
+	tim = toytime();
+	wlock(&mainlock);		/* dump */
+
+	/*
+	 * set up static structure
+	 * with frequent variables
+	 */
+	cw->ndump = 0;
+	cw->name[0] = 0;
+	cw->depth = 0;
+
+	/*
+	 * cw root
+	 */
+	sync("before dump");
+	cw->fsize = cwsize(cw->dev);
+	orba = cwraddr(cw->dev);
+	print("cwroot %lld", (Wideoff)orba);
+	cons.noage = 1;
+	cw->all = cw->allflag;
+	rba = cwrecur(cw, orba, Tsuper, 0, QPROOT);
+	if(rba == 0)
+		rba = orba;
+	print("->%lld\n", (Wideoff)rba);
+	sync("after cw");
+
+	/*
+	 * partial super block
+	 */
+	p = getbuf(cw->dev, cwsaddr(cw->dev), Brd|Bmod|Bimm);
+	s = (Superb*)p->iobuf;
+	s->fsize = cw->fsize;
+	s->cwraddr = rba;
+	putbuf(p);
+
+	/*
+	 * partial cache block
+	 */
+	p = getbuf(cw->cdev, CACHE_ADDR, Brd|Bmod|Bimm|Bres);
+	h = (Cache*)p->iobuf;
+	h->fsize = cw->fsize;
+	h->cwraddr = rba;
+	putbuf(p);
+
+	/*
+	 * ro root
+	 */
+	oroa = cwraddr(cw->rodev);
+	pr = getbuf(cw->dev, oroa, Brd|Bmod);
+	dr = getdir(pr, 0);
+
+	datestr(tstr, time(nil));	/* tstr = "yyyymmdd" */
+	n = 0;
+	for(a=0;; a++) {
+		p1 = dnodebuf(pr, dr, a, Tdir, 0);
+		if(!p1)
+			goto bad;
+		n++;
+		for(i=0; i<DIRPERBUF; i++) {
+			d1 = getdir(p1, i);
+			if(!d1)
+				goto bad;
+			if(!(d1->mode & DALLOC))
+				goto found1;
+			if(!memcmp(d1->name, tstr, 4))
+				goto found2;	/* found entry */
+		}
+		putbuf(p1);
+	}
+
+	/*
+	 * no year directory, create one
+	 */
+found1:
+	p = getbuf(cw->dev, rba, Brd);
+	d = getdir(p, 0);
+	d1->qid = d->qid;
+	d1->qid.version += n;
+	memmove(d1->name, tstr, 4);
+	d1->mode = d->mode;
+	d1->uid = d->uid;
+	d1->gid = d->gid;
+	putbuf(p);
+	accessdir(p1, d1, FWRITE, 0);
+
+	/*
+	 * put mmdd[count] in year directory
+	 */
+found2:
+	accessdir(p1, d1, FREAD, 0);
+	putbuf(pr);
+	pr = p1;
+	dr = d1;
+
+	n = 0;
+	m = 0;
+	for(a=0;; a++) {
+		p1 = dnodebuf(pr, dr, a, Tdir, 0);
+		if(!p1)
+			goto bad;
+		n++;
+		for(i=0; i<DIRPERBUF; i++) {
+			d1 = getdir(p1, i);
+			if(!d1)
+				goto bad;
+			if(!(d1->mode & DALLOC))
+				goto found;
+			if(!memcmp(d1->name, tstr+4, 4))
+				m++;
+		}
+		putbuf(p1);
+	}
+
+	/*
+	 * empty slot put in root
+	 */
+found:
+	if(m)	/* how many dumps this date */
+		sprint(tstr+8, "%ld", m);
+
+	p = getbuf(cw->dev, rba, Brd);
+	d = getdir(p, 0);
+	*d1 = *d;				/* qid is QPROOT */
+	putbuf(p);
+	strcpy(d1->name, tstr+4);
+	d1->qid.version += n;
+	accessdir(p1, d1, FWRITE, 0);
+	putbuf(p1);
+	putbuf(pr);
+
+	cw->fsize = cwsize(cw->dev);
+	oroa = cwraddr(cw->rodev);		/* probably redundant */
+	print("roroot %lld", (Wideoff)oroa);
+
+	cons.noage = 0;
+	cw->all = 0;
+	roa = cwrecur(cw, oroa, Tsuper, 0, QPROOT);
+	if(roa == 0) {
+		print("[same]");
+		roa = oroa;
+	}
+	print("->%lld /%.4s/%s\n", (Wideoff)roa, tstr, tstr+4);
+	sync("after ro");
+
+	/*
+	 * final super block
+	 */
+	a = cwsaddr(cw->dev);
+	print("sblock %lld", (Wideoff)a);
+	p = getbuf(cw->dev, a, Brd|Bmod|Bimm);
+	s = (Superb*)p->iobuf;
+	s->last = a;
+	sba = s->next;
+	s->next = cw->fsize;
+	cw->fsize++;
+	s->fsize = cw->fsize;
+	s->roraddr = roa;
+
+	cwio(cw->dev, sba, 0, Ogrow);
+	cwio(cw->dev, sba, p->iobuf, Owrite);
+	cwio(cw->dev, sba, 0, Odump);
+	print("->%lld (->%lld)\n", (Wideoff)sba, (Wideoff)s->next);
+
+	putbuf(p);
+
+	/*
+	 * final cache block
+	 */
+	p = getbuf(cw->cdev, CACHE_ADDR, Brd|Bmod|Bimm|Bres);
+	h = (Cache*)p->iobuf;
+	h->fsize = cw->fsize;
+	h->roraddr = roa;
+	h->sbaddr = sba;
+	putbuf(p);
+
+	rewalk(cw);
+	sync("all done");
+
+	print("%lld blocks queued for worm\n", (Wideoff)cw->ndump);
+	print("%lld falsehits\n", (Wideoff)cw->falsehits);
+	cw->nodump = 0;
+
+	/*
+	 * extend all of the locks
+	 */
+	tim = toytime() - tim;
+	for(i=0; i<NTLOCK; i++)
+		if(tlocks[i].time > 0)
+			tlocks[i].time += tim;
+
+	wunlock(&mainlock);
+	nextdump(time(nil));
+	return;
+
+bad:
+	panic("dump: bad");
+}
+
+void
+mvstates(Device *dev, int s1, int s2, int side)
+{
+	Iobuf *p, *cb;
+	Cache *h;
+	Bucket *b;
+	Centry *c, *ce;
+	Off m, lo, hi, msize, maddr;
+	Cw *cw;
+
+	cw = dev->private;
+	lo = 0;
+	hi = lo + devsize(dev->cw.w);	/* size of all sides totalled */
+	if(side >= 0) {
+		/* operate on only a single disc side */
+		Sidestarts ss;
+
+		wormsidestarts(dev, side, &ss);
+		lo = ss.sstart;
+		hi = ss.s1start;
+	}
+	cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bres);
+	if(!cb || checktag(cb, Tcache, QPSUPER))
+		panic("cwstats: checktag c bucket");
+	h = (Cache*)cb->iobuf;
+	msize = h->msize;
+	maddr = h->maddr;
+	putbuf(cb);
+
+	for(m=0; m<msize; m++) {
+		p = getbuf(cw->cdev, maddr + m/BKPERBLK, Brd|Bmod);
+		if(!p || checktag(p, Tbuck, maddr + m/BKPERBLK))
+			panic("cwtest: checktag c bucket");
+		b = (Bucket*)p->iobuf + m%BKPERBLK;
+		ce = b->entry + CEPERBK;
+		for(c=b->entry; c<ce; c++)
+			if(c->state == s1 && c->waddr >= lo && c->waddr < hi)
+				c->state = s2;
+		putbuf(p);
+	}
+}
+
+void
+prchain(Device *dev, Off m, int flg)
+{
+	Iobuf *p;
+	Superb *s;
+
+	if(m == 0) {
+		if(flg)
+			m = cwsaddr(dev);
+		else
+			m = getstartsb(dev);
+	}
+	p = getbuf(devnone, Cwxx2, 0);
+	s = (Superb*)p->iobuf;
+	for(;;) {
+		memset(p->iobuf, 0, RBUFSIZE);
+		if(devread(WDEV(dev), m, p->iobuf) ||
+		   checktag(p, Tsuper, QPSUPER))
+			break;
+		if(flg) {
+			print("dump %lld is good; %lld prev\n", (Wideoff)m,
+				(Wideoff)s->last);
+			print("\t%lld cwroot; %lld roroot\n",
+				(Wideoff)s->cwraddr, (Wideoff)s->roraddr);
+			if(m <= s->last)
+				break;
+			m = s->last;
+		} else {
+			print("dump %lld is good; %lld next\n", (Wideoff)m,
+				(Wideoff)s->next);
+			print("\t%lld cwroot; %lld roroot\n",
+				(Wideoff)s->cwraddr, (Wideoff)s->roraddr);
+			if(m >= s->next)
+				break;
+			m = s->next;
+		}
+	}
+	putbuf(p);
+}
+
+void
+touchsb(Device *dev)
+{
+	Iobuf *p;
+	Off m;
+
+	m = cwsaddr(dev);
+	p = getbuf(devnone, Cwxx2, 0);
+
+	memset(p->iobuf, 0, RBUFSIZE);
+	if(devread(WDEV(dev), m, p->iobuf) ||
+	   checktag(p, Tsuper, QPSUPER))
+		print("%Z block %lld WORM SUPER BLOCK READ FAILED\n",
+			WDEV(dev), (Wideoff)m);
+	else
+		print("%Z touch superblock %lld\n", WDEV(dev), (Wideoff)m);
+	putbuf(p);
+}
+
+void
+storesb(Device *dev, Off last, int doit)
+{
+	Iobuf *ph, *ps;
+	Cache *h;
+	Superb *s;
+	Off sbaddr, qidgen;
+
+	sbaddr = cwsaddr(dev);
+
+	ps = getbuf(devnone, Cwxx2, 0);
+	if(!ps) {
+		print("sbstore: getbuf\n");
+		return;
+	}
+
+	/*
+	 * try to read last sb
+	 */
+	memset(ps->iobuf, 0, RBUFSIZE);
+	if(devread(WDEV(dev), last, ps->iobuf) ||
+	   checktag(ps, Tsuper, QPSUPER))
+		print("read last failed\n");
+	else
+		print("read last succeeded\n");
+
+	s = (Superb*)ps->iobuf;
+	qidgen = s->qidgen;
+	if(qidgen == 0)
+		qidgen = 0x31415;
+	qidgen += 1000;
+	if(s->next != sbaddr)
+		print("next(last) is not sbaddr %lld %lld\n",
+			(Wideoff)s->next, (Wideoff)sbaddr);
+	else
+		print("next(last) is sbaddr\n");
+
+	/*
+	 * read cached superblock
+	 */
+	ph = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bres);
+	if(!ph || checktag(ph, Tcache, QPSUPER)) {
+		print("cwstats: checktag c bucket\n");
+		if(ph)
+			putbuf(ph);
+		putbuf(ps);
+		return;
+	} else
+		print("read cached sb succeeded\n");
+
+	h = (Cache*)ph->iobuf;
+
+	memset(ps->iobuf, 0, RBUFSIZE);
+	settag(ps, Tsuper, QPSUPER);
+	ps->flags = 0;
+	s = (Superb*)ps->iobuf;
+
+	s->cwraddr = h->cwraddr;
+	s->roraddr = h->roraddr;
+	s->fsize = h->fsize;
+	s->fstart = 2;
+	s->last = last;
+	s->next = h->roraddr+1;
+
+	s->qidgen = qidgen;
+	putbuf(ph);
+
+	if(s->fsize-1 != s->next ||
+	   s->fsize-2 != s->roraddr ||
+	   s->fsize-5 != s->cwraddr) {
+		print("addrs not in relationship %lld %lld %lld %lld\n",
+			(Wideoff)s->cwraddr, (Wideoff)s->roraddr,
+			(Wideoff)s->next, (Wideoff)s->fsize);
+		putbuf(ps);
+		return;
+	} else
+		print("addresses in relation\n");
+
+	if(doit)
+	if(devwrite(WDEV(dev), sbaddr, ps->iobuf))
+		print("%Z block %lld WORM SUPER BLOCK WRITE FAILED\n",
+			WDEV(dev), (Wideoff)sbaddr);
+	ps->flags = 0;
+	putbuf(ps);
+}
+
+void
+savecache(Device *dev)
+{
+	Iobuf *p, *cb;
+	Cache *h;
+	Bucket *b;
+	Centry *c, *ce;
+	long n, left;
+	Off m, maddr, msize, *longp, nbyte;
+	Device *cdev;
+
+	if(walkto("/adm/cache") || con_open(FID2, OWRITE|OTRUNC)) {
+		print("cant open /adm/cache\n");
+		return;
+	}
+	cdev = CDEV(dev);
+	cb = getbuf(cdev, CACHE_ADDR, Brd|Bres);
+	if(!cb || checktag(cb, Tcache, QPSUPER))
+		panic("savecache: checktag c bucket");
+	h = (Cache*)cb->iobuf;
+	msize = h->msize;
+	maddr = h->maddr;
+	putbuf(cb);
+
+	n = BUFSIZE;			/* calculate write size */
+	if(n > MAXDAT)
+		n = MAXDAT;
+
+	cb = getbuf(devnone, Cwxx4, 0);
+	longp = (Off *)cb->iobuf;
+	left = n/sizeof(Off);
+	cons.offset = 0;
+
+	for(m=0; m<msize; m++) {
+		if(left < BKPERBLK) {
+			nbyte = (n/sizeof(Off) - left) * sizeof(Off);
+			con_write(FID2, cb->iobuf, cons.offset, nbyte);
+			cons.offset += nbyte;
+			longp = (Off *)cb->iobuf;
+			left = n/sizeof(Off);
+		}
+		p = getbuf(cdev, maddr + m/BKPERBLK, Brd);
+		if(!p || checktag(p, Tbuck, maddr + m/BKPERBLK))
+			panic("cwtest: checktag c bucket");
+		b = (Bucket*)p->iobuf + m%BKPERBLK;
+		ce = b->entry + CEPERBK;
+		for(c = b->entry; c < ce; c++)
+			if(c->state == Cread) {
+				*longp++ = c->waddr;
+				left--;
+			}
+		putbuf(p);
+	}
+	nbyte = (n/sizeof(Off) - left) * sizeof(Off);
+	con_write(FID2, cb->iobuf, cons.offset, nbyte);
+	putbuf(cb);
+}
+
+void
+loadcache(Device *dev, int dskno)
+{
+	Iobuf *p, *cb;
+	Off m, nbyte, *longp, count;
+	Sidestarts ss;
+
+	if(walkto("/adm/cache") || con_open(FID2, OREAD)) {
+		print("cant open /adm/cache\n");
+		return;
+	}
+
+	cb = getbuf(devnone, Cwxx4, 0);
+	cons.offset = 0;
+	count = 0;
+
+	if (dskno >= 0)
+		wormsidestarts(dev, dskno, &ss);
+	for(;;) {
+		memset(cb->iobuf, 0, BUFSIZE);
+		nbyte = con_read(FID2, cb->iobuf, cons.offset, 100) / sizeof(Off);
+		if(nbyte <= 0)
+			break;
+		cons.offset += nbyte * sizeof(Off);
+		longp = (Off *)cb->iobuf;
+		while(nbyte > 0) {
+			m = *longp++;
+			nbyte--;
+			if(m == 0)
+				continue;
+			/* if given a diskno, restrict to just that disc side */
+			if(dskno < 0 || m >= ss.sstart && m < ss.s1start) {
+				p = getbuf(dev, m, Brd);
+				if(p)
+					putbuf(p);
+				count++;
+			}
+		}
+	}
+	putbuf(cb);
+	print("%lld blocks loaded from worm %d\n", (Wideoff)count, dskno);
+}
+
+void
+morecache(Device *dev, int dskno, Off size)
+{
+	Iobuf *p;
+	Off m, ml, mh, mm, count;
+	Cache *h;
+	Sidestarts ss;
+
+	p = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bres);
+	if(!p || checktag(p, Tcache, QPSUPER))
+		panic("savecache: checktag c bucket");
+	h = (Cache*)p->iobuf;
+	mm = h->wmax;
+	putbuf(p);
+
+	wormsidestarts(dev, dskno, &ss);
+	ml = ss.sstart;		/* start at beginning of disc side #dskno */
+	mh = ml + size;
+	if(mh > mm) {
+		mh = mm;
+		print("limited to %lld\n", (Wideoff)mh-ml);
+	}
+
+	count = 0;
+	for(m=ml; m < mh; m++) {
+		p = getbuf(dev, m, Brd);
+		if(p)
+			putbuf(p);
+		count++;
+	}
+	print("%lld blocks loaded from worm %d\n", (Wideoff)count, dskno);
+}
+
+void
+blockcmp(Device *dev, Off wa, Off ca)
+{
+	Iobuf *p1, *p2;
+	int i, c;
+
+	p1 = getbuf(WDEV(dev), wa, Brd);
+	if(!p1) {
+		print("blockcmp: wdev error\n");
+		return;
+	}
+
+	p2 = getbuf(CDEV(dev), ca, Brd);
+	if(!p2) {
+		print("blockcmp: cdev error\n");
+		putbuf(p1);
+		return;
+	}
+
+	c = 0;
+	for(i=0; i<RBUFSIZE; i++)
+		if(p1->iobuf[i] != p2->iobuf[i]) {
+			print("%4d: %.2x %.2x\n",
+				i,
+				p1->iobuf[i]&0xff,
+				p2->iobuf[i]&0xff);
+			c++;
+			if(c >= 10)
+				break;
+		}
+
+	if(c == 0)
+		print("no error\n");
+	putbuf(p1);
+	putbuf(p2);
+}
+
+void
+wblock(Device *dev, Off addr)
+{
+	Iobuf *p1;
+	int i;
+
+	p1 = getbuf(dev, addr, Brd);
+	if(p1) {
+		i = devwrite(WDEV(dev), addr, p1->iobuf);
+		print("i = %d\n", i);
+		putbuf(p1);
+	}
+}
+
+void
+cwtest(Device*)
+{
+}
+
+#ifdef	XXX
+/* garbage to change sb size
+ * probably will need it someday
+ */
+	fsz = number(0, 0, 10);
+	count = 0;
+	if(fsz == number(0, -1, 10))
+		count = -1;		/* really do it */
+	print("fsize = %ld\n", fsz);
+	cdev = CDEV(dev);
+	cb = getbuf(cdev, CACHE_ADDR, Brd|Bres);
+	if(!cb || checktag(cb, Tcache, QPSUPER))
+		panic("cwstats: checktag c bucket");
+	h = (Cache*)cb->iobuf;
+	for(m=0; m<h->msize; m++) {
+		p = getbuf(cdev, h->maddr + m/BKPERBLK, Brd|Bmod);
+		if(!p || checktag(p, Tbuck, h->maddr + m/BKPERBLK))
+			panic("cwtest: checktag c bucket");
+		b = (Bucket*)p->iobuf + m%BKPERBLK;
+		ce = b->entry + CEPERBK;
+		for(c=b->entry; c<ce; c++) {
+			if(c->waddr < fsz)
+				continue;
+			if(count < 0) {
+				c->state = Cnone;
+				continue;
+			}
+			if(c->state != Cdirty)
+				count++;
+		}
+		putbuf(p);
+	}
+	if(count < 0) {
+		print("old cache hsize = %ld\n", h->fsize);
+		h->fsize = fsz;
+		cb->flags |= Bmod;
+		p = getbuf(dev, h->sbaddr, Brd|Bmod);
+		s = (Superb*)p->iobuf;
+		print("old super hsize = %ld\n", s->fsize);
+		s->fsize = fsz;
+		putbuf(p);
+	}
+	putbuf(cb);
+	print("count = %lld\n", (Wideoff)count);
+#endif
+
+int
+convstate(char *name)
+{
+	int i;
+
+	for(i=0; i<nelem(cwnames); i++)
+		if(cwnames[i])
+			if(strcmp(cwnames[i], name) == 0)
+				return i;
+	return -1;
+}
+
+void
+searchtag(Device *d, Off a, int tag, int n)
+{
+	Iobuf *p;
+	Tag *t;
+	int i;
+
+	if(a == 0)
+		a = getstartsb(d);
+	p = getbuf(devnone, Cwxx2, 0);
+	t = (Tag*)(p->iobuf+BUFSIZE);
+	for(i=0; i<n; i++) {
+		memset(p->iobuf, 0, RBUFSIZE);
+		if(devread(WDEV(d), a+i, p->iobuf)) {
+			if(n == 1000)
+				break;
+			continue;
+		}
+		if(t->tag == tag) {
+			print("tag %d found at %Z %lld\n", tag, d, (Wideoff)a+i);
+			break;
+		}
+	}
+	putbuf(p);
+}
+
+void
+cmd_cwcmd(int argc, char *argv[])
+{
+	Device *dev;
+	char *arg;
+	char str[28];
+	Off s1, s2, a, b, n;
+	Cw *cw;
+
+	if(argc <= 1) {
+		print("\tcwcmd mvstate state1 state2 [platter]\n");
+		print("\tcwcmd prchain [start] [bakflg]\n");
+		print("\tcwcmd searchtag [start] [tag] [blocks]\n");
+		print("\tcwcmd touchsb\n");
+		print("\tcwcmd savecache\n");
+		print("\tcwcmd loadcache [dskno]\n");
+		print("\tcwcmd morecache dskno [count]\n");
+		print("\tcwcmd blockcmp wbno cbno\n");
+		print("\tcwcmd startdump [01]\n");
+		print("\tcwcmd acct\n");
+		print("\tcwcmd clearacct\n");
+		return;
+	}
+	arg = argv[1];
+
+	/*
+	 * items not depend on a cw filesystem
+	 */
+	if(strcmp(arg, "acct") == 0) {
+		for(a=0; a<nelem(growacct); a++) {
+			b = growacct[a];
+			if(b) {
+				uidtostr(str, a, 1);
+				print("%10lld %s\n",
+					((Wideoff)b*ADDFREE*RBUFSIZE+500000)/1000000,
+					str);
+			}
+		}
+		return;
+	}
+	if(strcmp(arg, "clearacct") == 0) {
+		memset(growacct, 0, sizeof(growacct));
+		return;
+	}
+
+	/*
+	 * items depend on cw filesystem
+	 */
+	dev = cons.curfs->dev;
+	if(dev == 0 || dev->type != Devcw || dev->private == 0) {
+		print("cfs not a cw filesystem: %Z\n", dev);
+		return;
+	}
+	cw = dev->private;
+	if(strcmp(arg, "searchtag") == 0) {
+		a = 0;
+		if(argc > 2)
+			a = number(argv[2], 0, 10);
+		b = Tsuper;
+		if(argc > 3)
+			b = number(argv[3], 0, 10);
+		n = 1000;
+		if(argc > 4)
+			n = number(argv[4], 0, 10);
+		searchtag(dev, a, b, n);
+	} else if(strcmp(arg, "mvstate") == 0) {
+		if(argc < 4)
+			goto bad;
+		s1 = convstate(argv[2]);
+		s2 = convstate(argv[3]);
+		if(s1 < 0 || s2 < 0)
+			goto bad;
+		a = -1;
+		if(argc > 4)
+			a = number(argv[4], 0, 10);
+		mvstates(dev, s1, s2, a);
+		return;
+	bad:
+		print("cwcmd mvstate: bad args\n");
+	} else if(strcmp(arg, "prchain") == 0) {
+		a = 0;
+		if(argc > 2)
+			a = number(argv[2], 0, 10);
+		s1 = 0;
+		if(argc > 3)
+			s1 = number(argv[3], 0, 10);
+		prchain(dev, a, s1);
+	} else if(strcmp(arg, "touchsb") == 0)
+		touchsb(dev);
+	else if(strcmp(arg, "savecache") == 0)
+		savecache(dev);
+	else if(strcmp(arg, "loadcache") == 0) {
+		s1 = -1;
+		if(argc > 2)
+			s1 = number(argv[2], 0, 10);
+		loadcache(dev, s1);
+	} else if(strcmp(arg, "morecache") == 0) {
+		if(argc <= 2) {
+			print("arg count\n");
+			return;
+		}
+		s1 = number(argv[2], 0, 10);
+		if(argc > 3)
+			s2 = number(argv[3], 0, 10);
+		else
+			s2 = wormsizeside(dev, s1); /* default to 1 disc side */
+		morecache(dev, s1, s2);
+	} else if(strcmp(arg, "blockcmp") == 0) {
+		if(argc < 4) {
+			print("cannot arg count\n");
+			return;
+		}
+		s1 = number(argv[2], 0, 10);
+		s2 = number(argv[3], 0, 10);
+		blockcmp(dev, s1, s2);
+	} else if(strcmp(arg, "startdump") == 0) {
+		if(argc > 2)
+			cw->nodump = number(argv[2], 0, 10);
+		cw->nodump = !cw->nodump;
+		if(cw->nodump)
+			print("dump stopped\n");
+		else
+			print("dump allowed\n");
+	} else if(strcmp(arg, "allflag") == 0) {
+		if(argc > 2)
+			cw->allflag = number(argv[2], 0, 10);
+		else
+			cw->allflag = !cw->allflag;
+		print("allflag = %d; falsehits = %lld\n",
+			cw->allflag, (Wideoff)cw->falsehits);
+	} else if(strcmp(arg, "storesb") == 0) {
+		a = 4168344;
+		b = 0;
+		if(argc > 2)
+			a = number(argv[2], 4168344, 10);
+		if(argc > 3)
+			b = number(argv[3], 0, 10);
+		storesb(dev, a, b);
+	} else if(strcmp(arg, "test") == 0)
+		cwtest(dev);
+	else
+		print("unknown cwcmd %s\n", arg);
+}

+ 32 - 0
sys/src/cmd/cwfs/cwfs/conf.c

@@ -0,0 +1,32 @@
+/* generic old-cw configuration */
+
+#include "all.h"
+
+#ifndef	DATE
+#define	DATE 1170808167L
+#endif
+
+Timet	fs_mktime = DATE;			/* set by mkfile */
+
+Startsb	startsb[] = {
+	"main",		2,
+	nil,
+};
+
+void
+localconfinit(void)
+{
+	conf.nfile = 40000;
+	conf.nodump = 0;
+//	conf.nodump = 1;		/* jukebox is r/o */
+	conf.firstsb = 13219302;
+	conf.recovsb = 0;
+	conf.nlgmsg = 100;
+	conf.nsmmsg = 500;
+}
+
+int (*fsprotocol[])(Msgbuf*) = {
+	serve9p1,
+	serve9p2,
+	nil,
+};

+ 36 - 0
sys/src/cmd/cwfs/cwfs/dat.h

@@ -0,0 +1,36 @@
+/* generic old-cw configuration: 16K blocks, 32-bit sizes */
+
+/*
+ * The most fundamental constant.
+ * The code will not compile with RBUFSIZE made a variable;
+ * for one thing, RBUFSIZE determines FEPERBUF, which determines
+ * the number of elements in a free-list-block array.
+ */
+#ifndef RBUFSIZE
+#define RBUFSIZE	(16*1024)	/* raw buffer size */
+#endif
+#include "32bit.h"
+/*
+ * setting this to zero permits the use of discs of different sizes, but
+ * can make jukeinit() quite slow while the robotics work through each disc
+ * twice (once per side).
+ */
+enum { FIXEDSIZE = 1 };
+
+
+#include "portdat.h"
+
+enum { MAXBANK = 2 };
+
+typedef struct Mbank {
+	ulong	base;
+	ulong	limit;
+} Mbank;
+
+typedef struct Mconf {
+	Lock;
+	Mbank	bank[MAXBANK];
+	int	nbank;
+	ulong	memsize;
+} Mconf;
+extern Mconf mconf;

+ 2 - 0
sys/src/cmd/cwfs/cwfs/mkfile

@@ -0,0 +1,2 @@
+FS=''
+<../portmkfile

+ 150 - 0
sys/src/cmd/cwfs/data.c

@@ -0,0 +1,150 @@
+#include	"all.h"
+
+char	*errstr9p[MAXERR] =
+{
+	[Ebadspc]	"attach -- bad specifier",
+	[Efid]		"unknown fid",
+	[Echar]		"bad character in directory name",
+	[Eopen]		"read/write -- on non open fid",
+	[Ecount]	"read/write -- count too big",
+	[Ealloc]	"phase error -- directory entry not allocated",
+	[Eqid]		"phase error -- qid does not match",
+	[Eaccess]	"access permission denied",
+	[Eentry]	"directory entry not found",
+	[Emode]		"open/create -- unknown mode",
+	[Edir1]		"walk -- in a non-directory",
+	[Edir2]		"create -- in a non-directory",
+	[Ephase]	"phase error -- cannot happen",
+	[Eexist]	"create/wstat -- file exists",
+	[Edot]		"create/wstat -- . and .. illegal names",
+	[Eempty]	"remove -- directory not empty",
+	[Ebadu]		"attach -- unknown user or failed authentication",
+	[Enoattach]	"attach -- system maintenance",
+	[Ewstatb]	"wstat -- unknown bits in qid.type/mode",
+	[Ewstatd]	"wstat -- attempt to change directory",
+	[Ewstatg]	"wstat -- not in group",
+	[Ewstatl]	"wstat -- attempt to make length negative",
+	[Ewstatm]	"wstat -- attempt to change muid",
+	[Ewstato]	"wstat -- not owner or group leader",
+	[Ewstatp]	"wstat -- attempt to change qid.path",
+	[Ewstatq]	"wstat -- qid.type/dir.mode mismatch",
+	[Ewstatu]	"wstat -- not owner",
+	[Ewstatv]	"wstat -- attempt to change qid.vers",
+	[Ename]		"create/wstat -- bad character in file name",
+	[Ewalk]		"walk -- too many (system wide)",
+	[Eronly]	"file system read only",
+	[Efull]		"file system full",
+	[Eoffset]	"read/write -- offset negative",
+	[Elocked]	"open/create -- file is locked",
+	[Ebroken]	"read/write -- lock is broken",
+	[Eauth]		"attach -- authentication failed",
+	[Eauth2]	"read/write -- authentication unimplemented",
+	[Etoolong]	"name too long",
+	[Efidinuse]	"fid in use",
+	[Econvert]	"protocol botch",
+	[Eversion]	"version conversion",
+	[Eauthnone]	"auth -- user 'none' requires no authentication",
+	[Eauthdisabled]	"auth -- authentication disabled",	/* development */
+	[Eauthfile]	"auth -- out of auth files",
+	[Eedge]		"at the bleeding edge",		/* development */
+};
+
+char*	wormscode[0x80] =
+{
+	[0x00]	"no sense",
+	[0x01]	"invalid command",
+	[0x02]	"recovered error",
+	[0x03]	"illegal request",
+	[0x06]	"unit attention",
+	[0x07]	"parity error",
+	[0x08]	"message reject error",
+	[0x0a]	"copy aborted",
+	[0x0b]	"initiator detected error",
+	[0x0c]	"select re-select failed",
+	[0x0e]	"miscompare",
+
+	[0x10]	"ecc trouble occurred",
+	[0x11]	"time out error",
+	[0x12]	"controller error",
+	[0x13]	"sony i/f II hardware/firmware error",
+	[0x14]	"scsi hardware/firmware error",
+	[0x15]	"rom version unmatched error",
+	[0x16]	"logical block address out of range",
+
+	[0x20]	"command not terminated",
+	[0x21]	"drive interface parity error",
+	[0x22]	"loading trouble",
+	[0x23]	"focus trouble",
+	[0x24]	"tracking trouble",
+	[0x25]	"spindle trouble",
+	[0x26]	"slide trouble",
+	[0x27]	"skew trouble",
+	[0x28]	"head lead out",
+	[0x29]	"write modulation trouble",
+	[0x2a]	"under laser power",
+	[0x2b]	"over laser power",
+	[0x2f]	"drive error",
+
+	[0x30]	"drive power off",
+	[0x31]	"no disk in drive",
+	[0x32]	"drive not ready",
+	[0x38]	"disk already exists in drive",
+	[0x39]	"no disk in shelf",
+	[0x3a]	"disk already exists in shelf",
+
+	[0x40]	"write warning",
+	[0x41]	"write error",
+	[0x42]	"disk error",
+	[0x43]	"cannot read disk ID",
+	[0x44]	"write protect error 1",
+	[0x45]	"write protect error 2",
+	[0x46]	"disk warning",
+	[0x47]	"alternation trouble",
+
+	[0x50]	"specified address not found",
+	[0x51]	"address block not found",
+	[0x52]	"all address could not be read",
+	[0x53]	"data could not be read",
+	[0x54]	"uncorrectable read error",
+	[0x55]	"tracking error",
+	[0x56]	"write servo error",
+	[0x57]	"write monitor error",
+	[0x58]	"write verify error",
+
+	[0x60]	"no data in specified address",
+	[0x61]	"blank check failed",
+	[0x62]	"controller diagnostics failed",
+	[0x63]	"drive diagnostice failed",
+	[0x64]	"diagnostice aborted",
+	[0x67]	"juke diagnostice failed",
+	[0x68]	"z-axis servo failed",
+	[0x69]	"roter servo error",
+	[0x6a]	"hook servo error",
+	[0x6b]	"I/O self error",
+	[0x6c]	"drive 0 error",
+	[0x6d]	"drive 1 error",
+	[0x6e]	"shelf error",
+	[0x6f]	"carrier error",
+
+	[0x70]	"rob made me do it",
+	[0x71]	"out of range",
+};
+
+char*	tagnames[] =
+{
+	[Tbuck]		"Tbuck",
+	[Tdir]		"Tdir",
+	[Tfile]		"Tfile",
+	[Tfree]		"Tfree",
+	[Tind1]		"Tind1",
+	[Tind2]		"Tind2",
+#ifndef COMPAT32
+	[Tind3]		"Tind3",
+	[Tind4]		"Tind4",
+	/* add more Tind tags here ... */
+#endif
+	[Tnone]		"Tnone",
+	[Tsuper]	"Tsuper",
+	[Tvirgo]	"Tvirgo",
+	[Tcache]	"Tcache",
+};

+ 337 - 0
sys/src/cmd/cwfs/dentry.c

@@ -0,0 +1,337 @@
+#include "all.h"
+
+Dentry*
+getdir(Iobuf *p, int slot)
+{
+	if(!p)
+		return 0;
+	return (Dentry*)p->iobuf + slot%DIRPERBUF;
+}
+
+void
+accessdir(Iobuf *p, Dentry *d, int f, int uid)
+{
+	Timet t;
+
+	if(p && p->dev->type != Devro) {
+		p->flags |= Bmod;
+		t = time(nil);
+		if(f & (FREAD|FWRITE))
+			d->atime = t;
+		if(f & FWRITE) {
+			d->mtime = t;
+			d->muid = uid;
+			d->qid.version++;
+		}
+	}
+}
+
+void
+preread(Device *d, Off addr)
+{
+	Rabuf *rb;
+
+	if(addr == 0)
+		return;
+	if(raheadq->count+10 >= raheadq->size)	/* ugly knowing layout */
+		return;
+	lock(&rabuflock);
+	rb = rabuffree;
+	if(rb == 0) {
+		unlock(&rabuflock);
+		return;
+	}
+	rabuffree = rb->link;
+	unlock(&rabuflock);
+	rb->dev = d;
+	rb->addr = addr;
+	fs_send(raheadq, rb);
+}
+
+Off
+rel2abs(Iobuf *p, Dentry *d, Off a, int tag, int putb, int uid)
+{
+	int i;
+	Off addr, qpath, indaddrs = 1, div;
+	Device *dev;
+
+	if(a < 0) {
+		print("rel2abs: neg offset\n");
+		if(putb)
+			putbuf(p);
+		return 0;
+	}
+	dev = p->dev;
+	qpath = d->qid.path;
+
+	/* is `a' a direct block? */
+	if(a < NDBLOCK) {
+		addr = d->dblock[a];
+		if(!addr && tag) {
+			addr = bufalloc(dev, tag, qpath, uid);
+			d->dblock[a] = addr;
+			p->flags |= Bmod|Bimm;
+		}
+		if(putb)
+			putbuf(p);
+		return addr;
+	}
+	a -= NDBLOCK;
+
+	/*
+	 * loop through indirect block depths.
+	 */
+	for (i = 0; i < NIBLOCK; i++) {
+		indaddrs *= INDPERBUF;
+		/* is a's disk addr in this indir block or one of its kids? */
+		if (a < indaddrs) {
+			addr = d->iblocks[i];
+			if(!addr && tag) {
+				addr = bufalloc(dev, Tind1+i, qpath, uid);
+				d->iblocks[i] = addr;
+				p->flags |= Bmod|Bimm;
+			}
+			if(putb)
+				putbuf(p);
+
+			div = indaddrs;
+			for (; i >= 0; i--) {
+				div /= INDPERBUF;
+				if (div <= 0)
+					panic("rel2abs: non-positive divisor");
+				addr = indfetch(dev, qpath, addr,
+					(a/div)%INDPERBUF, Tind1+i,
+					(i == 0? tag: Tind1+i-1), uid);
+			}
+			return addr;
+		}
+		a -= indaddrs;
+	}
+	if(putb)
+		putbuf(p);
+
+	/* quintuple-indirect blocks not implemented. */
+	print("rel2abs: no %d-deep indirect\n", NIBLOCK+1);
+	return 0;
+}
+
+/*
+ * read-ahead strategy
+ * on second block, read RAGAP blocks,
+ * thereafter, read RAGAP ahead of current pos
+ */
+Off
+dbufread(Iobuf *p, Dentry *d, Off a, Off ra, int uid)
+{
+	Off addr;
+
+	if(a == 0)
+		return 1;
+	if(a == 1 && ra == 1) {
+		while(ra < a+RAGAP) {
+			ra++;
+			addr = rel2abs(p, d, ra, 0, 0, uid);
+			if(!addr)
+				return 0;
+			preread(p->dev, addr);
+		}
+		return ra+1;
+	}
+	if(ra == a+RAGAP) {
+		addr = rel2abs(p, d, ra, 0, 0, uid);
+		if(!addr)
+			return 0;
+		preread(p->dev, addr);
+		return ra+1;
+	}
+	return ra;
+}
+
+Iobuf*
+dnodebuf(Iobuf *p, Dentry *d, Off a, int tag, int uid)
+{
+	Off addr;
+
+	addr = rel2abs(p, d, a, tag, 0, uid);
+	if(addr)
+		return getbuf(p->dev, addr, Brd);
+	return 0;
+}
+
+/*
+ * same as dnodebuf but it calls putbuf(p)
+ * to reduce interference.
+ */
+Iobuf*
+dnodebuf1(Iobuf *p, Dentry *d, Off a, int tag, int uid)
+{
+	Off addr;
+	Device *dev;
+
+	dev = p->dev;
+	addr = rel2abs(p, d, a, tag, 1, uid);
+	if(addr)
+		return getbuf(dev, addr, Brd);
+	return 0;
+
+}
+
+Off
+indfetch(Device* d, Off qpath, Off addr, Off a, int itag, int tag, int uid)
+{
+	Iobuf *bp;
+
+	if(!addr)
+		return 0;
+	bp = getbuf(d, addr, Brd);
+	if(!bp || checktag(bp, itag, qpath)) {
+		if(!bp) {
+			print("ind fetch bp = 0\n");
+			return 0;
+		}
+		print("ind fetch tag\n");
+		putbuf(bp);
+		return 0;
+	}
+	addr = ((Off *)bp->iobuf)[a];
+	if(!addr && tag) {
+		addr = bufalloc(d, tag, qpath, uid);
+		if(addr) {
+			((Off *)bp->iobuf)[a] = addr;
+			bp->flags |= Bmod;
+			if(tag == Tdir)
+				bp->flags |= Bimm;
+			settag(bp, itag, qpath);
+		}
+	}
+	putbuf(bp);
+	return addr;
+}
+
+/* return INDPERBUF^exp */
+Off
+ibbpow(int exp)
+{
+	static Off pows[] = {
+		1,
+		INDPERBUF,
+		(Off)INDPERBUF*INDPERBUF,
+		(Off)INDPERBUF*(Off)INDPERBUF*INDPERBUF,
+		(Off)INDPERBUF*(Off)INDPERBUF*(Off)INDPERBUF*INDPERBUF,
+	};
+
+	if (exp < 0)
+		return 0;
+	else if (exp >= nelem(pows)) {	/* not in table? do it long-hand */
+		Off indpow = 1;
+
+		while (exp-- > 0 && indpow > 0)
+			indpow *= INDPERBUF;
+		return indpow;
+	} else
+		return pows[exp];
+}
+
+/* return sum of INDPERBUF^n for 1 ≤ n ≤ exp */
+Off
+ibbpowsum(int exp)
+{
+	Off indsum = 0;
+
+	for (; exp > 0; exp--)
+		indsum += ibbpow(exp);
+	return indsum;
+}
+
+/* zero bytes past new file length; return an error code */
+int
+trunczero(Truncstate *ts)
+{
+	int blkoff = ts->newsize % BUFSIZE;
+	Iobuf *pd;
+
+	pd = dnodebuf(ts->p, ts->d, ts->lastblk, Tfile, ts->uid);
+	if (pd == nil || checktag(pd, Tfile, QPNONE)) {
+		if (pd != nil)
+			putbuf(pd);
+		ts->err = Ephase;
+		return Ephase;
+	}
+	memset(pd->iobuf+blkoff, 0, BUFSIZE - blkoff);
+	putbuf(pd);
+	return 0;
+}
+
+/*
+ * truncate d (in p) to length `newsize'.
+ * if larger, just increase size.
+ * if smaller, deallocate blocks after last one
+ * still in file at new size.  last byte to keep
+ * is newsize-1, due to zero origin.
+ * we free in forward order because it's simpler to get right.
+ * if the final block at the new size is partially-filled,
+ * zero the remainder.
+ */
+int
+dtrunclen(Iobuf *p, Dentry *d, Off newsize, int uid)
+{
+	int i, pastlast;
+	Truncstate trunc;
+
+	if (newsize <= 0) {
+		dtrunc(p, d, uid);
+		return 0;
+	}
+	memset(&trunc, 0, sizeof trunc);
+	trunc.d = d;
+	trunc.p = p;
+	trunc.uid = uid;
+	trunc.newsize = newsize;
+	trunc.lastblk = newsize/BUFSIZE;
+	if (newsize % BUFSIZE == 0)
+		trunc.lastblk--;
+	else
+		trunczero(&trunc);
+	for (i = 0; i < NDBLOCK; i++)
+		if (trunc.pastlast) {
+			trunc.relblk = i;
+			buffree(p->dev, d->dblock[i], 0, &trunc);
+			d->dblock[i] = 0;
+		} else if (i == trunc.lastblk)
+			trunc.pastlast = 1;
+	trunc.relblk = NDBLOCK;
+	for (i = 0; i < NIBLOCK; i++) {
+		pastlast = trunc.pastlast;
+		buffree(p->dev, d->iblocks[i], i+1, &trunc);
+		if (pastlast)
+			d->iblocks[i] = 0;
+	}
+
+	d->size = newsize;
+	p->flags |= Bmod|Bimm;
+	accessdir(p, d, FWRITE, uid);
+	return trunc.err;
+}
+
+/*
+ * truncate d (in p) to zero length.
+ * freeing blocks in reverse order is traditional, from Unix,
+ * in an attempt to keep the free list contiguous.
+ */
+void
+dtrunc(Iobuf *p, Dentry *d, int uid)
+{
+	int i;
+
+	for (i = NIBLOCK-1; i >= 0; i--) {
+		buffree(p->dev, d->iblocks[i], i+1, nil);
+		d->iblocks[i] = 0;
+	}
+	for (i = NDBLOCK-1; i >= 0; i--) {
+		buffree(p->dev, d->dblock[i], 0, nil);
+		d->dblock[i] = 0;
+	}
+	d->size = 0;
+	p->flags |= Bmod|Bimm;
+	accessdir(p, d, FWRITE, uid);
+}

+ 68 - 0
sys/src/cmd/cwfs/doc/changes

@@ -0,0 +1,68 @@
+	changes to Ken's file server to make this 63-bit file server
+
+		Geoff Collyer
+		July—October 2004
+
+note: 2⁶⁳=9,223,372,036,854,775,808 or 8EB (9.2×10ⁱ⁸)
+
+• identified longs that refer to offsets, sizes and block numbers, and
+changed them to type Off (vlong); fixed all print formats to match.
+fixed byte-swapping for the 'x' config to match.
+
+• fixed VLONG 9p1 message packing and unpacking macros to actually
+handle 64-bit offsets and sizes.
+
+• implemented triple-indirect blocks.  affected code in
+	dev/cw.c	port/con.c	port/dentry.c	port/sub.c
+	port/chk.c	port/console.c	port/portdat.h
+
+• Fri Aug  6 16:50:59 PDT 2004
+	; ./sizes
+	Plan 9 v4 63-bit file server
+		sizeof(Dentry) = 124
+		sizeof(Cache)  =  88
+
+• added long(er) file name components (56 bytes), long enough for all but one
+  name in my /.longnames file (68-byte .xml name).
+
+• Fri Aug  6 21:43:41 PDT 2004
+	; ./sizes
+	Plan 9 v4 63-bit file server sizes
+		sizeof(Dentry) = 160
+		sizeof(Cache)  =  88
+
+• touched up lib.h (from libc.h) to bring it up to date with formatting
+  functions, verbs & flags.
+• check now reports stack usage: 320 bytes upon entry to fsck first time,
+  92 bytes of stack per recursion.  given 16000 bytes of stack,
+  that's 170 recursions maximum.
+• booted xtc (terminal) from fs64 (used fs64 as main file system)
+
+note: current file server with triple-indirect blocks at 4k block size
+	has a maximum file size of ~505GB (5.42×10ⁱⁱ).
+	with quadruple-indirect blocks, max would be ~275TB @ 4k block size.
+
+• got igbe fs driver working (a couple small changes)
+• eliminated some gotos (started with 580, down to 454)
+• added quadruple indirect blocks: lets us reach 2⁶⁳ with a 32kB block size
+• got igbe boot driver & pxe booting working
+• on-disk qid paths are now Offs, but 9p1 qids on the wire are still ulongs
+• generalised & parameterised indirect block implementation
+• tested with plain w0 fs, cached fake worm on w0, cw jukebox (hp 160fx)
+• ip directories in fs & fs64 are identical except for whitespace and
+  goto-elimination
+• replaced most of nemo's ide code with newer ide code lifted from 9load,
+  then from cpu kernel (sdata.c & support).  this brings us dma, rwm & lba48,
+  finds ide controllers by itself, even pci ones, & copes with dead drives
+  (i.e., doesn't panic).
+• fixed long-standing bug that caused a 5-second delay before each console
+  prompt on systems without a serial console.
+• further type parameterisation: Userid (short), Timet (long), Devsize (vlong).
+    Comment on v7 kernel portability work, quoting scj & dmr from BSTJ v57
+    #6 part 2., p. 2038: ``The important data types used within the
+    system were identified and specified using typedef: disk offsets,
+    absolute times, internal device names, and the like.  This effort was
+    carried out by K. Thompson.''
+• corrected compat.h dependencies in mkfiles
+• eliminated all warnings
+• implemented truncation via wstat

+ 181 - 0
sys/src/cmd/cwfs/doc/emelie.boot

@@ -0,0 +1,181 @@
+Mon Feb 12 13:06:31: ether#0: i82557: port 0xEC00 irq 10: 00A0C90AC853
+Mon Feb 12 13:06:31: Boot devices: fd0 ether0
+Mon Feb 12 13:06:33: boot from: ether0
+Mon Feb 12 13:06:33: lookout (135.104.9.7!67): /386/9pcfs
+Mon Feb 12 13:06:33: 299219+119508+190052=608779
+Mon Feb 12 13:06:33: entry: 0x80100020
+Mon Feb 12 13:06:35: cpu0: 267MHz GenuineIntel PentiumII (cpuid: AX 0x0634 DX 0x80f9ff)
+Mon Feb 12 13:06:36: ether0: i82557: 100Mbps port 0xec00 irq 10: 00a0c90ac853
+Mon Feb 12 13:06:37: scsi#0: buslogic: port 0x334 irq 11
+Mon Feb 12 13:06:37: scsi#1: buslogic: port 0x330 irq 15
+Mon Feb 12 13:06:39: 
+Mon Feb 12 13:06:39: iobufinit
+Mon Feb 12 13:06:39: bank[0]: base 0x0009fc00, limit 0x0009fc00
+Mon Feb 12 13:06:39: bank[1]: base 0x01298130, limit 0x10000000
+Mon Feb 12 13:06:40: 	13779 buffers; 1723 hashes
+Mon Feb 12 13:06:41: 	mem left = 22380543
+Mon Feb 12 13:06:41: 		out of = 268435456
+Mon Feb 12 13:06:41: nvr read
+Mon Feb 12 13:06:42: found plan9.nvr attr 0x0 start 0x25a len 64064
+Mon Feb 12 13:06:42: for config mode hit a key within 5 seconds
+Mon Feb 12 13:06:48: 	no config
+Mon Feb 12 13:06:48: sysinit
+Mon Feb 12 13:06:48: config w1.0.0
+Mon Feb 12 13:06:48: 	devinit w1.0.0
+Mon Feb 12 13:06:48: scsi#1.0: SEAGATE ST39173WC       4218LM387268
+Mon Feb 12 13:06:48: 	drive w1.0.0:
+Mon Feb 12 13:06:48: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:06:48: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:06:48: 		32 multiplier
+Mon Feb 12 13:06:48: service    emelie
+Mon Feb 12 13:06:48: ipauth  135.104.9.7
+Mon Feb 12 13:06:48: ipsntp  135.104.9.52
+Mon Feb 12 13:06:48: ip0     135.104.9.42
+Mon Feb 12 13:06:48: ipgw0   135.104.9.1
+Mon Feb 12 13:06:48: ipmask0 255.255.255.0
+Mon Feb 12 13:06:48: filsys main c[w1.<0-5>.0]j(w6w5w4w3w2)(l<0-236>l<238-474>)
+Mon Feb 12 13:06:48: filsys dump o
+Mon Feb 12 13:06:48: filsys other [w1.<9-14>.0]
+Mon Feb 12 13:06:48: filsys temp [w1.8.0]
+Mon Feb 12 13:06:51: sysinit: main
+Mon Feb 12 13:06:51: 	devinit c[w1.0.0-w1.5.0]j(w6-w2)(l0-l474)
+Mon Feb 12 13:06:51: 	devinit [w1.0.0-w1.5.0]
+Mon Feb 12 13:06:51: 	devinit w1.0.0
+Mon Feb 12 13:06:51: 	drive w1.0.0:
+Mon Feb 12 13:06:51: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:06:51: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:06:51: 		32 multiplier
+Mon Feb 12 13:06:51: 	devinit w1.1.0
+Mon Feb 12 13:06:51: scsi#1.1: SEAGATE ST39173WC       6244LM399207
+Mon Feb 12 13:06:51: 	drive w1.1.0:
+Mon Feb 12 13:06:51: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:06:51: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:06:51: 		32 multiplier
+Mon Feb 12 13:06:51: 	devinit w1.2.0
+Mon Feb 12 13:06:51: scsi#1.2: SEAGATE ST39173WC       4218LMJ04565
+Mon Feb 12 13:06:51: 	drive w1.2.0:
+Mon Feb 12 13:06:51: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:06:51: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:06:51: 		32 multiplier
+Mon Feb 12 13:06:51: 	devinit w1.3.0
+Mon Feb 12 13:06:51: scsi#1.3: SEAGATE ST39173WC       4218LM412029
+Mon Feb 12 13:06:51: 	drive w1.3.0:
+Mon Feb 12 13:06:51: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:06:51: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:06:52: 		32 multiplier
+Mon Feb 12 13:06:52: 	devinit w1.4.0
+Mon Feb 12 13:06:52: scsi#1.4: SEAGATE ST39173WC       4218LM412834
+Mon Feb 12 13:06:52: 	drive w1.4.0:
+Mon Feb 12 13:06:52: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:06:52: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:06:52: 		32 multiplier
+Mon Feb 12 13:06:52: 	devinit w1.5.0
+Mon Feb 12 13:06:52: scsi#1.5: SEAGATE ST39173WC       4218LM364585
+Mon Feb 12 13:06:52: 	drive w1.5.0:
+Mon Feb 12 13:06:52: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:06:52: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:06:52: 		32 multiplier
+Mon Feb 12 13:06:52: 	devinit j(w6-w2)(l0-l474)
+Mon Feb 12 13:06:52: alloc juke w6
+Mon Feb 12 13:06:52: scsi#0.6: HP      C1107J          1.50
+Mon Feb 12 13:06:52: 	mt 16 2
+Mon Feb 12 13:06:52: 	se 31 238
+Mon Feb 12 13:06:52: 	ie 20 1
+Mon Feb 12 13:06:52: 	dt 1 4
+Mon Feb 12 13:06:52: 	rot 1
+Mon Feb 12 13:07:02: import/export 0 #38 0.0
+Mon Feb 12 13:07:02: data transfer 0 #08 0.0
+Mon Feb 12 13:07:02: data transfer 1 #08 0.0
+Mon Feb 12 13:07:02: data transfer 2 #08 0.0
+Mon Feb 12 13:07:02: data transfer 3 #08 0.0
+Mon Feb 12 13:07:02: 	shelves r0-r237
+Mon Feb 12 13:07:02: 	load   r0 drive w2
+Mon Feb 12 13:07:10: scsi#0.2: HP      C1113F          1.129807307965    8XMO  
+Mon Feb 12 13:07:10: 	worm l0: drive w2
+Mon Feb 12 13:07:10: 		1263471 blocks at 2048 bytes each
+Mon Feb 12 13:07:10: 		157934 logical blocks at 16384 bytes each
+Mon Feb 12 13:07:10: 		8 multiplier
+Mon Feb 12 13:07:10: label l0 ordinal 0
+Mon Feb 12 13:07:18: sysinit: dump
+Mon Feb 12 13:07:18: 	devinit o[w1.0.0-w1.5.0]j(w6-w2)(l0-l474)
+Mon Feb 12 13:07:21: sysinit: other
+Mon Feb 12 13:07:21: 	devinit [w1.9.0-w1.14.0]
+Mon Feb 12 13:07:21: 	devinit w1.9.0
+Mon Feb 12 13:07:21: scsi#1.9: SEAGATE ST39173WC       4218LM367449
+Mon Feb 12 13:07:21: 	drive w1.9.0:
+Mon Feb 12 13:07:21: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:07:21: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:07:21: 		32 multiplier
+Mon Feb 12 13:07:21: 	devinit w1.10.0
+Mon Feb 12 13:07:21: scsi#1.10: SEAGATE ST39173WC       4218LM392062
+Mon Feb 12 13:07:21: 	drive w1.10.0:
+Mon Feb 12 13:07:21: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:07:21: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:07:21: 		32 multiplier
+Mon Feb 12 13:07:21: 	devinit w1.11.0
+Mon Feb 12 13:07:21: scsi#1.11: SEAGATE ST39173WC       4218LMB43130
+Mon Feb 12 13:07:21: 	drive w1.11.0:
+Mon Feb 12 13:07:22: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:07:22: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:07:22: 		32 multiplier
+Mon Feb 12 13:07:22: 	devinit w1.12.0
+Mon Feb 12 13:07:22: scsi#1.12: SEAGATE ST39173WC       4218LMB38636
+Mon Feb 12 13:07:22: 	drive w1.12.0:
+Mon Feb 12 13:07:22: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:07:22: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:07:22: 		32 multiplier
+Mon Feb 12 13:07:22: 	devinit w1.13.0
+Mon Feb 12 13:07:22: scsi#1.13: SEAGATE ST39173WC       4218LM391739
+Mon Feb 12 13:07:22: 	drive w1.13.0:
+Mon Feb 12 13:07:22: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:07:22: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:07:22: 		32 multiplier
+Mon Feb 12 13:07:22: 	devinit w1.14.0
+Mon Feb 12 13:07:22: scsi#1.14: SEAGATE ST39173WC       4218LM408443
+Mon Feb 12 13:07:22: 	drive w1.14.0:
+Mon Feb 12 13:07:22: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:07:22: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:07:22: 		32 multiplier
+Mon Feb 12 13:07:24: sysinit: temp
+Mon Feb 12 13:07:24: 	devinit w1.8.0
+Mon Feb 12 13:07:24: scsi#1.8: SEAGATE ST39173WC       4218LMC15632
+Mon Feb 12 13:07:24: 	drive w1.8.0:
+Mon Feb 12 13:07:24: 		17783239 blocks at 512 bytes each
+Mon Feb 12 13:07:24: 		555726 logical blocks at 16384 bytes each
+Mon Feb 12 13:07:24: 		32 multiplier
+Mon Feb 12 13:07:24: ether0o: 00a0c90ac853 135.104.9.42
+Mon Feb 12 13:07:24: ether0i: 00a0c90ac853 135.104.9.42
+Mon Feb 12 13:07:25: next dump at Tue Feb 13 05:00:00 2007
+Mon Feb 12 13:07:25: current fs is "main"
+Mon Feb 12 13:07:25: 582 uids read, 189 groups used
+Mon Feb 12 13:07:25: emelie as of Wed May  3 12:58:03 2006
+Mon Feb 12 13:07:25: 	last boot Mon Feb 12 13:06:03 2007
+Mon Feb 12 13:07:25: 	load   r226 drive w3
+Mon Feb 12 13:07:33: scsi#0.3: HP      C1113J          1.129809312612    8XMO  
+Mon Feb 12 13:07:33: 	worm l226: drive w3
+Mon Feb 12 13:07:33: 		1263471 blocks at 2048 bytes each
+Mon Feb 12 13:07:33: 		157934 logical blocks at 16384 bytes each
+Mon Feb 12 13:07:33: 		8 multiplier
+Mon Feb 12 13:07:33: label l226 ordinal 226
+Mon Feb 12 13:07:33: j(w6-w2)(l0-l474) touch superblock 35709856
+Mon Feb 12 13:07:35: emelie: version
+Mon Feb 12 13:07:36: emelie as of Wed May  3 12:58:03 2006
+Mon Feb 12 13:07:36: 	last boot Mon Feb 12 13:06:03 2007
+Mon Feb 12 13:07:44: emelie: printconf
+Mon Feb 12 13:07:44: config w1.0.0
+Mon Feb 12 13:07:44: service emelie
+Mon Feb 12 13:07:44: filsys main c[w1.<0-5>.0]j(w6w5w4w3w2)(l<0-236>l<238-474>)
+Mon Feb 12 13:07:44: filsys dump o
+Mon Feb 12 13:07:44: filsys other [w1.<9-14>.0]
+Mon Feb 12 13:07:44: filsys temp [w1.8.0]
+Mon Feb 12 13:07:44: ipauth 135.104.9.7
+Mon Feb 12 13:07:44: ipsntp 135.104.9.52
+Mon Feb 12 13:07:44: ip0 135.104.9.42
+Mon Feb 12 13:07:44: ipgw0 135.104.9.1
+Mon Feb 12 13:07:44: ipmask0 255.255.255.0
+Mon Feb 12 13:07:44: end
+Mon Feb 12 13:07:48: emelie: date
+Mon Feb 12 13:07:48: Mon Feb 12 13:07:03 2007 + 0
+Mon Feb 12 13:08:48: emelie: sntp: 1171303728
+Mon Feb 12 13:17:14: 	time   r0 drive w2
+Mon Feb 12 13:17:39: 	time   r226 drive w3

+ 29 - 0
sys/src/cmd/cwfs/doc/user.mode.boot

@@ -0,0 +1,29 @@
+j(w1.6.0-w1.2.0)(l0-l474) touch superblock 35709856
+emelie: version
+31-bit emelie as of Mon Feb 12 19:28:40 2007
+	last boot Mon Feb 12 19:29:31 2007
+emelie: stats
+cons stats
+	work =      0      0      0 rps
+	rate =      0      0      0 tBps
+	hits =      0      0      0 iops
+	read =      0      0      0 iops
+	rah  =      0      0      0 iops
+	init =      0      0      0 iops
+	bufs =    500 sm 100 lg 0 res
+	ioerr=      0 wr   0 ww   0 dr   0 dw
+	cache=             0 hit         0 miss
+emelie: printconf
+config w0
+service emelie
+filsys main c[w<0-5>]j(w1.<6-2>.0)(l<0-236>l<238-474>)
+filsys dump o
+filsys other [w<9-14>]
+filsys temp w8
+ipauth 135.104.9.7
+ipsntp 135.104.9.52
+ip0 135.104.9.42
+ipgw0 135.104.9.1
+ipmask0 255.255.255.0
+end
+emelie: 

+ 25 - 0
sys/src/cmd/cwfs/doc/words

@@ -0,0 +1,25 @@
+'emelie' is for any Plan 9 machine with supported hardware (excluding
+the SONY jukebox), and will make an object '9emeliefs' and use a 16KB
+block size and 32-bit file sizes.  choline is similar, but with
+conf.nfile cranked up.
+
+fs uses a 4KB block size and 32-bit file sizes, rereads all blocks
+written to the WORM, and is configured with more `large message'
+buffers than is usual (for gigabit Ethernet).  fs64 is similar but
+uses an 8KB block size and 64-bit file sizes, offsets and block
+numbers, and consequently can only serve 9P2000, not 9P1.
+
+9netics32.16k is like fs, but uses a 16KB block size and does not
+reread blocks written to the WORM.  9netics64.8k is like fs64, but
+uses an 8KB block size and does not reread blocks written to the WORM.
+
+To spin-off a new version to play with, say 'test':
+
+	cd /sys/src/cmd/cwfs
+	mkdir test
+	for (f in mkfile dat.h conf.c)
+		sed '1s/emelie/test/' emelie/$f >test/$f
+
+and hack as appropriate.  The primary choices are block size and 32-
+or 64-bit sizes (in dat.h), and various configuration tweaks and
+choice of 9P variants to serve (in conf.c).

+ 128 - 0
sys/src/cmd/cwfs/doc/worm.fs

@@ -0,0 +1,128 @@
+# fs: new main file server on 633MHz machine with 4 IDE disks & 4K blocks
+# was called rosewood at first
+config h0
+service fs
+ip0     66.120.90.177
+ipgw0   66.120.90.186
+ipmask0 255.255.255.0
+ipsntp  66.120.90.185
+filsys main c{p(h0)0.25p(h2)0.25}f{p(h0)25.75p(h2)25.75}
+filsys dump o
+filsys other h3
+# later added: filsys spare h1
+# ream other
+# ream main
+# recover main
+allow
+end
+
+h0 20GB "main":  25% cache and 75% fake-worm;
+h1 40GB "spare": holds test venti used for backup
+h2 20GB "main":  (h1.0.0) a mirror of h0.
+h3 40GB "other": (h1.1.0) ``scratch'' space
+
+---
+halted. press a key to reboot.
+
+ether#0: i82557: port 0x9400 irq 10: 00A0C9E02756
+dev A0 port 1F0 config 427A capabilities 2F00 mwdma 0007 udma 103F
+dev A0 port 170 config 427A capabilities 2F00 mwdma 0007 udma 043F
+dev B0 port 170 config 045A capabilities 0F00 mwdma 0007 udma 043F
+found 9PCFSIMR.    attr 0x0 start 0x423 len 391193
+.239705.............................+41712.....+181524=462941
+entry: 0x80100020
+cpu0: 635MHz GenuineIntel PentiumIII/Xeon (cpuid: AX 0x0686 DX 0x383f9ff)
+ether0: i82557: 100Mbps port 0x9400 irq 10: 00a0c9e02756
+
+iobufinit
+        114253 buffers; 14281 hashes
+        mem left = 44040191
+                out of = 528482304
+nvr read
+found plan9.nvr attr 0x0 start 0x18c len 677
+for config mode hit a key within 5 seconds
+
+config: filsys main c{p(h0)0.25p(h2)0.25}f{p(h0)25.75p(h2)25.75}
+config: filsys dump o
+config: filsys other h3
+config: recover main 
+config: ream other
+config: allow
+config: end
+ysinits
+config h0
+        devinit h0
+i hd0: DW CDW02E0-B00HB0F    lba/atapi/drqintr: 1/0/0  C/H/S: 0/0/0  CAP: 39102336
+hd0: LBA 39102336 sectors
+ideinit(device 9ce00948 ctrl 0 targ 0) driveno 0 dp 802eff24
+config block written
+config h0
+        devinit h0
+ideinit(device 9ce00988 ctrl 0 targ 0) driveno 0 dp 802eff24
+service    rosewood
+ipauth  0.0.0.0
+ipsntp  66.120.90.185
+ip0     66.120.90.194
+ipgw0   66.120.90.186
+ipmask0 255.255.255.0
+filsys main c{p(h0)0.25p(h2)0.25}f{p(h0)25.75p(h2)25.75}
+filsys dump o
+filsys other h3
+sysinit: main
+recover: c{p(h0)0.25p(h2)0.25}f{p(h0)25.75p(h2)25.75}
+        devinit {p(h0)0.25p(h2)0.25}
+        devinit p(h0)0.25
+        devinit h0
+ideinit(device 9ce00a68 ctrl 0 targ 0) driveno 0 dp 802eff24
+atasize(9ce00a68):  39102336 -> 4887552
+        devinit p(h2)0.25
+        devinit h2
+i hd2: DW CDW02E0-B00HB0F    lba/atapi/drqintr: 1/0/0  C/H/S: 0/0/0  CAP: 39102336
+hd2: LBA 39102336 sectors
+ideinit(device 9ce00ae8 ctrl 0 targ 2) driveno 2 dp 802f4324
+atasize(9ce00ae8):  39102336 -> 4887552
+        devinit f{p(h0)25.75p(h2)25.75}
+fworm init
+        devinit {p(h0)25.75p(h2)25.75}
+        devinit p(h0)25.75
+        devinit h0
+ideinit(device 9ce00bc8 ctrl 0 targ 0) driveno 0 dp 802eff24
+atasize(9ce00bc8):  39102336 -> 4887552
+        devinit p(h2)25.75
+        devinit h2
+ideinit(device 9ce00c48 ctrl 0 targ 2) driveno 2 dp 802f4324
+atasize(9ce00c48):  39102336 -> 4887552
+dump 2 is good; 5 next
+dump 5 is good; 494408 next
+dump 494408 is good; 495193 next
+dump 495193 is good; 495224 next
+dump 495224 is good; 496007 next
+dump 496007 is good; 496062 next
+dump 496062 is good; 496089 next
+dump 496089 is good; 496096 next
+dump 496096 is good; 496118 next
+dump 496118 is good; 496882 next
+fworm: read 496882
+cache init c{p(h0)0.25p(h2)0.25}f{p(h0)25.75p(h2)25.75}
+done cacheinit
+done recover
+        devinit c{p(h0)0.25p(h2)0.25}f{p(h0)25.75p(h2)25.75}
+sysinit: dump
+        devinit o{p(h0)0.25p(h2)0.25}f{p(h0)25.75p(h2)25.75}
+sysinit: other
+        devream: h3 1
+        devinit h3
+i hd3: AMTXRO4 0K042H        lba/atapi/drqintr: 1/0/0  C/H/S: 0/0/0  CAP: 78198750
+hd3: LBA 78198750 sectors
+ideinit(device 9ce00ca8 ctrl 0 targ 3) driveno 3 dp 802f44f0
+atasize(9ce00ca8):  78198750 -> 9774592
+ether0o: 00a0c9e02756 66.120.90.194
+ether0i: 00a0c9e02756 66.120.90.194
+next dump at Mon Sep 10 05:00:00 2001
+current fs is "main"
+il: allocating il!66.120.90.189!23230
+41 uids read, 21 groups used
+rosewood as of Sun Sep  9 16:27:27 2001
+        last boot Mon Sep 10 00:56:10 2001
+touch superblock 496118
+rosewood: 

+ 81 - 0
sys/src/cmd/cwfs/doc/worm.fs64

@@ -0,0 +1,81 @@
+# 4k blocks
+fs64: printconf
+config w0
+service fs64
+filsys main w0
+ipauth 0.0.0.0
+ipsntp 216.240.55.164
+ip0 216.240.55.167
+ipmask0 255.255.255.224
+end
+fs64: 
+
+# 4k blocks
+Wed Sep  1 17:46:10 PDT 2004
+fs64: printconf
+config w0
+service fs64
+filsys main cp(w0)0.20fp(w0)20.80
+filsys dump o
+ipauth 0.0.0.0
+ipsntp 216.240.55.164
+ip0 216.240.55.167
+ipmask0 255.255.255.224
+end
+fs64: 
+
+# 8k blocks, preparing for worm jukebox
+Thu Sep  2 00:17:19 PDT 2004
+fs64: printconf
+config w0
+service fs64
+filsys main cp(w0)0.20fp(w0)20.80
+filsys dump o
+ipauth 0.0.0.0
+ipsntp 216.240.55.164
+ip0 216.240.55.167
+ipmask0 255.255.255.224
+end
+fs64: 
+
+# 8k blocks with hp 160fx worm jukebox
+# only 1 MO disc inside currently
+Fri Sep  3 23:06:30 PDT 2004
+fs64: printconf
+config w0
+service fs64
+filsys main cp(w0)1.99j(w1.<1-5>.0)(l<0-15>l<16-31>)
+filsys dump o
+ipauth 0.0.0.0
+ipsntp 216.240.55.164
+ip0 216.240.55.167
+ipmask0 255.255.255.224
+end
+
+# add two ide disks: one is the juke's mirror, the other is new other
+Thu Sep 30 00:38:38 PDT 2004
+fs64: printconf
+config w0
+service fs64
+filsys main cp(w0)1.99{h0j(w1.<1-5>.0)(l<0-15>l<16-31>)}
+filsys dump o
+filsys other h2
+ipauth 0.0.0.0
+ipsntp 216.240.55.164
+ip0 216.240.55.167
+ipmask0 255.255.255.224
+end
+
+# now has 8 WO disks and 1 RW disk currently (25 nov 2004),
+# treat 8 WO disks as one set of disks.
+fs64: printconf
+config w0
+service fs64
+filsys main cp(w0)1.99{h0j(w1.<1-5>.0)(l<0-7>l<64-71>l<8-62>l<72-126>)}
+filsys dump o
+filsys other h2
+ipauth 0.0.0.0
+ipsntp 216.240.55.164
+ip0 216.240.55.167
+ipmask0 255.255.255.224
+end

+ 150 - 0
sys/src/cmd/cwfs/doc/worms.32-bit

@@ -0,0 +1,150 @@
+/* configuration commands */
+
+/* configuration commands */
+filsys main j(w6)(r5.<0-18>r5.<20-38>)
+e!0!helix:/386/9pcfs
+
+/* AUDIO */
+ip/dhcp
+ip/tftpd
+con /dev/eia1
+bootp()phaeton:/sys/src/fs/audio/audio
+
+/* AUDIO */
+touch superblock 2835710
+
+config w2
+service audio
+filsys main cw2j(w5w4)(l<0-39>)
+filsys dump o
+ip 135.104.3.106
+ipgw 135.104.3.1
+ipmask 255.255.255.0
+ipauth 135.104.9.7
+end
+
+/* RORO running 9pcfs (16KB blocks)  */
+config w2
+service roro
+filsys main [w2w3]
+ip 135.104.9.29
+ipgw 135.104.9.1
+ipmask 255.255.255.0
+ipauth 135.104.9.7
+end
+
+/* RORO running plan9pc (4KB blocks) */
+config w1
+service roro
+filsys main w1
+ip 135.104.9.29
+ipgw 135.104.9.1
+ipmask 255.255.255.0
+ipauth 135.104.9.7
+end
+
+/* EMELIE */
+e!0!helix:/usr/ken/fs/pc/9pcfs
+130 disks loaded
+
+/* BOOTES 00006b8244f8 */
+superblocks:
+bootes 44930632
+	dump 44945151 is good; 44945224 next
+        	44945220 cwroot; 44945223 roroot
+fornax 8917649
+	dump 8919157 is good; 8919167 next
+        	8919163 cwroot; 8919166 roroot
+	sbaddr =  8919379
+	craddr =  8919383  8919383
+
+/* BOOTES little endian */
+config xw5
+service bootes
+filsys main c[xw5]j(w1.1.8xw1.1.1)(r<0-44>r<50-94>)
+filsys dump o
+filsys fornax c[xw3]j(w1.2.8xw1.2.0)(r<0-18>)
+filsys fdump o
+ip 135.104.9.30
+ipgw 135.104.9.1
+ipmask 255.255.255.0
+ipauth 135.104.9.7
+end
+
+/* BOOTES little endian with only fornax filesystem */
+config xw5
+service bootes
+//filsys main c[xw5]j(w1.1.8xw1.1.1)(r<0-44>r<50-94>)
+//filsys dump o
+filsys main c[xw3]j(w1.2.8xw1.2.0)(r<0-18>)
+filsys dump o
+ip 135.104.9.30
+ipgw 135.104.9.1
+ipmask 255.255.255.0
+ipauth 135.104.9.7
+end
+
+/* BOOTES little endian with only bootes filesystem */
+config xw5
+service bootes
+filsys main c[xw5]j(w1.2.8xw1.2.1)(r<0-44>r<50-94>)
+filsys dump o
+ip 135.104.9.30
+ipgw 135.104.9.1
+ipmask 255.255.255.0
+ipauth 135.104.9.7
+end
+
+/* BOOTES 00006b8244f8 big endian */
+config w5
+service bootes
+filsys main c[w5]j(w0.1.8w0.1.1)(r<0-44>r<50-94>)
+filsys dump o
+//filsys fornax c[w3]j(w0.2.8w0.2.0)(r<0-18>)
+//filsys fdump o
+ip 135.104.9.30
+ipgw 135.104.9.1
+ipmask 255.255.255.0
+ipauth 135.104.9.7
+end
+
+// jukefs
+// DSIZE = 39815
+config w6
+service jukefs
+filsys main cw6j(w5w<1-4>)(r<0-140>r<144-284>)
+filsys dump o
+ip 135.104.9.10
+ipgw 135.104.9.1
+ipmask 255.255.255.0
+ipauth 135.104.9.7
+end
+
+// emelie
+// DSIZE = 157933
+config w1.0.0
+service emelie
+filsys main c[w1.<0-5>.0]j(w6w5w4w3w2)(l<0-236>l<238-474>)
+filsys dump o
+filsys other [w1.<9-14>.0]
+filsys temp [w1.8.0]
+ipauth  135.104.9.7
+ip     135.104.9.42
+ipgw   135.104.9.1
+ipmask 255.255.255.0
+ipsntp 135.104.9.52
+end
+
+// choline
+// DSIZE = 79563-1
+config w1.0.0
+service    choline
+filsys main c[w<1-3>]j(w1.<6-0>.0)(l<0-124>l<128-252>)
+filsys dump o
+filsys other [w<4-6>]
+ipauth  135.104.9.7
+ip	135.104.72.2
+ipgw	135.104.72.1
+ipmask	255.255.255.0
+ipsntp 135.104.9.52
+end

+ 33 - 0
sys/src/cmd/cwfs/emelie/conf.c

@@ -0,0 +1,33 @@
+/* emelie-specific configuration */
+
+#include "all.h"
+
+#ifndef	DATE
+#define	DATE 1170808167L
+#endif
+
+Timet	fs_mktime = DATE;			/* set by mkfile */
+
+Startsb	startsb[] = {
+	"main",		SUPER_ADDR,
+	"old",		SUPER_ADDR,
+	nil,
+};
+
+void
+localconfinit(void)
+{
+	conf.nfile = 40000;
+//	conf.nodump = 0;
+	conf.nodump = 1;		/* jukebox is r/o */
+//	conf.firstsb = 13219302;	/* for main */
+	conf.recovsb = 0;
+	conf.nlgmsg = 100;
+	conf.nsmmsg = 500;
+}
+
+int (*fsprotocol[])(Msgbuf*) = {
+	serve9p1,
+	serve9p2,
+	nil,
+};

+ 36 - 0
sys/src/cmd/cwfs/emelie/dat.h

@@ -0,0 +1,36 @@
+/* emelie's configuration: 16K blocks, 32-bit sizes */
+
+/*
+ * The most fundamental constant.
+ * The code will not compile with RBUFSIZE made a variable;
+ * for one thing, RBUFSIZE determines FEPERBUF, which determines
+ * the number of elements in a free-list-block array.
+ */
+#ifndef RBUFSIZE
+#define RBUFSIZE	(16*1024)	/* raw buffer size */
+#endif
+#include "32bit.h"
+/*
+ * setting this to zero permits the use of discs of different sizes, but
+ * can make jukeinit() quite slow while the robotics work through each disc
+ * twice (once per side).
+ */
+enum { FIXEDSIZE = 1 };
+
+
+#include "portdat.h"
+
+enum { MAXBANK = 2 };
+
+typedef struct Mbank {
+	ulong	base;
+	ulong	limit;
+} Mbank;
+
+typedef struct Mconf {
+	Lock;
+	Mbank	bank[MAXBANK];
+	int	nbank;
+	ulong	memsize;
+} Mconf;
+extern Mconf mconf;

+ 1 - 0
sys/src/cmd/cwfs/emelie/map.w2-w0

@@ -0,0 +1 @@
+w2 w0

+ 2 - 0
sys/src/cmd/cwfs/emelie/mkfile

@@ -0,0 +1,2 @@
+FS=.emelie
+<../portmkfile

+ 32 - 0
sys/src/cmd/cwfs/fs/conf.c

@@ -0,0 +1,32 @@
+/* fs-specific configuration */
+
+#include "all.h"
+
+#ifndef	DATE
+#define	DATE 1170808167L
+#endif
+
+Timet	fs_mktime = DATE;			/* set by mkfile */
+
+Startsb	startsb[] = {
+/*	"main",		2,	*/
+	"main",		810988,	/* discontinuity before sb @ 696262 */
+	0
+};
+
+void
+localconfinit(void)
+{
+	conf.nodump = 0;
+	conf.dumpreread = 1;
+	conf.firstsb = 0;	/* time- & jukebox-dependent optimisation */
+	conf.recovsb = 0;
+	conf.nlgmsg = 1100;	/* @8576 bytes, for packets */
+	conf.nsmmsg = 500;	/* @128 bytes */
+}
+
+int (*fsprotocol[])(Msgbuf*) = {
+	serve9p1,
+	serve9p2,
+	nil,
+};

+ 36 - 0
sys/src/cmd/cwfs/fs/dat.h

@@ -0,0 +1,36 @@
+/* fs's configuration: 4K blocks, 32-bit sizes */
+
+/*
+ * The most fundamental constant.
+ * The code will not compile with RBUFSIZE made a variable;
+ * for one thing, RBUFSIZE determines FEPERBUF, which determines
+ * the number of elements in a free-list-block array.
+ */
+#ifndef RBUFSIZE
+#define RBUFSIZE	(4*1024)	/* raw buffer size */
+#endif
+#include "32bit.h"
+/*
+ * setting this to zero permits the use of discs of different sizes, but
+ * can make jukeinit() quite slow while the robotics work through each disc
+ * twice (once per side).
+ */
+enum { FIXEDSIZE = 1 };
+
+
+#include "portdat.h"
+
+enum { MAXBANK = 2 };
+
+typedef struct Mbank {
+	ulong	base;
+	ulong	limit;
+} Mbank;
+
+typedef struct Mconf {
+	Lock;
+	Mbank	bank[MAXBANK];
+	int	nbank;
+	ulong	memsize;
+} Mconf;
+extern Mconf mconf;

+ 2 - 0
sys/src/cmd/cwfs/fs/mkfile

@@ -0,0 +1,2 @@
+FS=fs
+<../portmkfile

+ 31 - 0
sys/src/cmd/cwfs/fs64/conf.c

@@ -0,0 +1,31 @@
+/* fs64-specific configuration */
+
+#include "all.h"
+
+#ifndef	DATE
+#define	DATE 1170808167L
+#endif
+
+Timet	fs_mktime = DATE;			/* set by mkfile */
+
+Startsb	startsb[] = {
+	"main",		2,
+	nil,
+};
+
+void
+localconfinit(void)
+{
+	conf.nodump = 0;
+	conf.dumpreread = 1;
+	conf.firstsb = 0;	/* time- & jukebox-dependent optimisation */
+	conf.recovsb = 0;
+	conf.nlgmsg = 1100;	/* @8576 bytes, for packets */
+	conf.nsmmsg = 500;	/* @128 bytes */
+}
+
+int (*fsprotocol[])(Msgbuf*) = {
+	/* 64-bit file servers can't serve 9P1 correctly: NAMELEN is too big */
+	serve9p2,
+	nil,
+};

+ 36 - 0
sys/src/cmd/cwfs/fs64/dat.h

@@ -0,0 +1,36 @@
+/* fs64's configuration: 8K blocks, 64-bit sizes */
+
+/*
+ * The most fundamental constant.
+ * The code will not compile with RBUFSIZE made a variable;
+ * for one thing, RBUFSIZE determines FEPERBUF, which determines
+ * the number of elements in a free-list-block array.
+ */
+#ifndef RBUFSIZE
+#define RBUFSIZE	(8*1024)	/* raw buffer size */
+#endif
+#include "64bit.h"
+/*
+ * setting this to zero permits the use of discs of different sizes, but
+ * can make jukeinit() quite slow while the robotics work through each disc
+ * twice (once per side).
+ */
+enum { FIXEDSIZE = 1 };
+
+
+#include "portdat.h"
+
+enum { MAXBANK = 2 };
+
+typedef struct Mbank {
+	ulong	base;
+	ulong	limit;
+} Mbank;
+
+typedef struct Mconf {
+	Lock;
+	Mbank	bank[MAXBANK];
+	int	nbank;
+	ulong	memsize;
+} Mconf;
+extern Mconf mconf;

+ 2 - 0
sys/src/cmd/cwfs/fs64/mkfile

@@ -0,0 +1,2 @@
+FS=.fs64
+<../portmkfile

+ 105 - 0
sys/src/cmd/cwfs/fworm.c

@@ -0,0 +1,105 @@
+#include "all.h"
+
+#define	FDEV(d)		((d)->fw.fw)
+
+enum { DEBUG = 0 };
+
+Devsize
+fwormsize(Device *d)
+{
+	Devsize l;
+
+	l = devsize(FDEV(d));
+	l -= l/(BUFSIZE*8) + 1;
+	return l;
+}
+
+void
+fwormream(Device *d)
+{
+	Iobuf *p;
+	Device *fdev;
+	Off a, b;
+
+	print("fworm ream\n");
+	devinit(d);
+	fdev = FDEV(d);
+	a = fwormsize(d);
+	b = devsize(fdev);
+	print("\tfwsize = %lld\n", (Wideoff)a);
+	print("\tbwsize = %lld\n", (Wideoff)b-a);
+	for(; a < b; a++) {
+		p = getbuf(fdev, a, Bmod|Bres);
+		if(!p)
+			panic("fworm: init");
+		memset(p->iobuf, 0, RBUFSIZE);
+		settag(p, Tvirgo, a);
+		putbuf(p);
+	}
+}
+
+void
+fworminit(Device *d)
+{
+	print("fworm init\n");
+	devinit(FDEV(d));
+}
+
+int
+fwormread(Device *d, Off b, void *c)
+{
+	Iobuf *p;
+	Device *fdev;
+	Devsize l;
+
+	if(DEBUG)
+		print("fworm read  %lld\n", (Wideoff)b);
+	fdev = FDEV(d);
+	l = devsize(fdev);
+	l -= l/(BUFSIZE*8) + 1;
+	if(b >= l)
+		panic("fworm: rbounds %lld", (Wideoff)b);
+	l += b/(BUFSIZE*8);
+
+	p = getbuf(fdev, l, Brd|Bres);
+	if(!p || checktag(p, Tvirgo, l))
+		panic("fworm: checktag %lld", (Wideoff)l);
+	l = b % (BUFSIZE*8);
+	if(!(p->iobuf[l/8] & (1<<(l%8)))) {
+		putbuf(p);
+		print("fworm: read %lld\n", (Wideoff)b);
+		return 1;
+	}
+	putbuf(p);
+	return devread(fdev, b, c);
+}
+
+int
+fwormwrite(Device *d, Off b, void *c)
+{
+	Iobuf *p;
+	Device *fdev;
+	Devsize l;
+
+	if(DEBUG)
+		print("fworm write %lld\n", (Wideoff)b);
+	fdev = FDEV(d);
+	l = devsize(fdev);
+	l -= l/(BUFSIZE*8) + 1;
+	if(b >= l)
+		panic("fworm: wbounds %lld", (Wideoff)b);
+	l += b/(BUFSIZE*8);
+
+	p = getbuf(fdev, l, Brd|Bmod|Bres);
+	if(!p || checktag(p, Tvirgo, l))
+		panic("fworm: checktag %lld", (Wideoff)l);
+	l = b % (BUFSIZE*8);
+	if((p->iobuf[l/8] & (1<<(l%8)))) {
+		putbuf(p);
+		print("fworm: write %lld\n", (Wideoff)b);
+		return 1;
+	}
+	p->iobuf[l/8] |= 1<<(l%8);
+	putbuf(p);
+	return devwrite(fdev, b, c);
+}

+ 38 - 0
sys/src/cmd/cwfs/io.h

@@ -0,0 +1,38 @@
+enum {
+	MaxScsi		= 4,
+	NTarget		= 16,
+	Maxnets		= 8,
+};
+
+/*
+ * SCSI support code.
+ */
+enum {
+	STblank		=-6,		/* blank block */
+	STnomem		=-5,		/* buffer allocation failed */
+	STtimeout	=-4,		/* bus timeout */
+	STownid		=-3,		/* playing with myself */
+	STharderr	=-2,		/* controller error of some kind */
+	STinit		=-1,		/* */
+	STok		= 0,		/* good */
+	STcheck		= 0x02,		/* check condition */
+	STcondmet	= 0x04,		/* condition met/good */
+	STbusy		= 0x08,		/* busy */
+	STintok		= 0x10,		/* intermediate/good */
+	STintcondmet	= 0x14,		/* intermediate/condition met/good */
+	STresconf	= 0x18,		/* reservation conflict */
+	STterminated	= 0x22,		/* command terminated */
+	STqfull		= 0x28,		/* queue full */
+};
+
+typedef struct Target {
+	Scsi	*sc;		/* from openscsi */
+	int	ctlrno;
+	int	targetno;
+	uchar*	inquiry;
+	uchar*	sense;
+
+	QLock;
+	char	id[NAMELEN];
+	int	ok;
+} Target;

+ 289 - 0
sys/src/cmd/cwfs/iobuf.c

@@ -0,0 +1,289 @@
+#include	"all.h"
+#include	"io.h"
+
+enum { DEBUG = 0 };
+
+extern	long nhiob;
+extern	Hiob *hiob;
+
+Iobuf*
+getbuf(Device *d, Off addr, int flag)
+{
+	Iobuf *p, *s;
+	Hiob *hp;
+	Off h;
+
+	if(DEBUG)
+		print("getbuf %Z(%lld) f=%x\n", d, (Wideoff)addr, flag);
+	h = addr + (Off)d*1009;
+	if(h < 0)
+		h = ~h;
+	h %= nhiob;
+	hp = &hiob[h];
+
+loop:
+	lock(hp);
+
+/*
+ * look for it in the active list
+ */
+	s = hp->link;
+	for(p=s;;) {
+		if(p->addr == addr && p->dev == d) {
+			if(p != s) {
+				p->back->fore = p->fore;
+				p->fore->back = p->back;
+				p->fore = s;
+				p->back = s->back;
+				s->back = p;
+				p->back->fore = p;
+				hp->link = p;
+			}
+			unlock(hp);
+			qlock(p);
+			if(p->addr != addr || p->dev != d || iobufmap(p) == 0) {
+				qunlock(p);
+				goto loop;
+			}
+			p->flags |= flag;
+			return p;
+		}
+		p = p->fore;
+		if(p == s)
+			break;
+	}
+	if(flag & Bprobe) {
+		unlock(hp);
+		return 0;
+	}
+
+/*
+ * not found
+ * take oldest unlocked entry in this queue
+ */
+xloop:
+	p = s->back;
+	if(!canqlock(p)) {
+		if(p == hp->link) {
+			unlock(hp);
+			print("iobuf all locked\n");
+			goto loop;
+		}
+		s = p;
+		goto xloop;
+	}
+
+	/*
+	 * its dangerous to flush the pseudo
+	 * devices since they recursively call
+	 * getbuf/putbuf. deadlock!
+	 */
+	if(p->flags & Bres) {
+		qunlock(p);
+		if(p == hp->link) {
+			unlock(hp);
+			print("iobuf all reserved\n");
+			goto loop;
+		}
+		s = p;
+		goto xloop;
+	}
+	if(p->flags & Bmod) {
+		unlock(hp);
+		if(iobufmap(p)) {
+			if(!devwrite(p->dev, p->addr, p->iobuf))
+				p->flags &= ~(Bimm|Bmod);
+			iobufunmap(p);
+		}
+		qunlock(p);
+		goto loop;
+	}
+	hp->link = p;
+	p->addr = addr;
+	p->dev = d;
+	p->flags = flag;
+//	p->pc = getcallerpc(&d);
+	unlock(hp);
+	if(iobufmap(p))
+		if(flag & Brd) {
+			if(!devread(p->dev, p->addr, p->iobuf))
+				return p;
+			iobufunmap(p);
+		} else
+			return p;
+	else
+		print("iobuf cant map buffer\n");
+	p->flags = 0;
+	p->dev = devnone;
+	p->addr = -1;
+	qunlock(p);
+	return 0;
+}
+
+/*
+ * syncblock tries to put out a block per hashline
+ * returns 0 all done,
+ * returns 1 if it missed something
+ */
+int
+syncblock(void)
+{
+	Iobuf *p, *s, *q;
+	Hiob *hp;
+	long h;
+	int flag;
+
+	flag = 0;
+	for(h=0; h<nhiob; h++) {
+		q = 0;
+		hp = &hiob[h];
+		lock(hp);
+		s = hp->link;
+		for(p=s;;) {
+			if(p->flags & Bmod) {
+				if(q)
+					flag = 1;	/* more than 1 mod/line */
+				q = p;
+			}
+			p = p->fore;
+			if(p == s)
+				break;
+		}
+		unlock(hp);
+		if(q) {
+			if(!canqlock(q)) {
+				flag = 1;		/* missed -- was locked */
+				continue;
+			}
+			if(!(q->flags & Bmod)) {
+				qunlock(q);
+				continue;
+			}
+			if(iobufmap(q)) {
+				if(!devwrite(q->dev, q->addr, q->iobuf))
+					q->flags &= ~(Bmod|Bimm);
+				iobufunmap(q);
+			} else
+				flag = 1;
+			qunlock(q);
+		}
+	}
+	return flag;
+}
+
+void
+sync(char *reason)
+{
+	long i;
+
+	print("sync: %s\n", reason);
+	for(i=10*nhiob; i>0; i--)
+		if(!syncblock())
+			return;
+	print("sync shorted\n");
+}
+
+void
+putbuf(Iobuf *p)
+{
+
+	if(canqlock(p))
+		print("buffer not locked %Z(%lld)\n", p->dev, (Wideoff)p->addr);
+	if(p->flags & Bimm) {
+		if(!(p->flags & Bmod))
+			print("imm and no mod %Z(%lld)\n",
+				p->dev, (Wideoff)p->addr);
+		if(!devwrite(p->dev, p->addr, p->iobuf))
+			p->flags &= ~(Bmod|Bimm);
+	}
+	iobufunmap(p);
+	qunlock(p);
+}
+
+int
+checktag(Iobuf *p, int tag, Off qpath)
+{
+	Tag *t;
+	static Off lastaddr;
+
+	t = (Tag*)(p->iobuf+BUFSIZE);
+	if(t->tag != tag) {
+		if(p->flags & Bmod) {
+			print("\ttag = %d/%llud; expected %lld/%d -- not flushed\n",
+				t->tag, (Wideoff)t->path, (Wideoff)qpath, tag);
+			return 2;
+		}
+		if(p->dev != nil && p->dev->type == Devcw)
+			cwfree(p->dev, p->addr);
+		if(p->addr != lastaddr)
+			print("\ttag = %G/%llud; expected %G/%lld -- flushed (%lld)\n",
+				t->tag, (Wideoff)t->path, tag, (Wideoff)qpath,
+				(Wideoff)p->addr);
+		lastaddr = p->addr;
+		p->dev = devnone;
+		p->addr = -1;
+		p->flags = 0;
+		return 2;
+	}
+	if(qpath != QPNONE) {
+		if((qpath ^ t->path) & ~QPDIR) {
+			if(1 || CHAT(0))
+				print("\ttag/path = %llud; expected %d/%llux\n",
+					(Wideoff)t->path, tag, (Wideoff)qpath);
+			return 0;
+		}
+	}
+	return 0;
+}
+
+void
+settag(Iobuf *p, int tag, long qpath)
+{
+	Tag *t;
+
+	t = (Tag*)(p->iobuf+BUFSIZE);
+	t->tag = tag;
+	if(qpath != QPNONE)
+		t->path = qpath & ~QPDIR;
+	p->flags |= Bmod;
+}
+
+int
+qlmatch(QLock *q1, QLock *q2)
+{
+
+	return q1 == q2;
+}
+
+int
+iobufql(QLock *q)
+{
+	Iobuf *p, *s;
+	Hiob *hp;
+	Tag *t;
+	long h;
+	int tag;
+
+	for(h=0; h<nhiob; h++) {
+		hp = &hiob[h];
+		lock(hp);
+		s = hp->link;
+		for(p=s;;) {
+			if(qlmatch(q, p)) {
+				t = (Tag*)(p->iobuf+BUFSIZE);
+				tag = t->tag;
+				if(tag < 0 || tag >= MAXTAG)
+					tag = Tnone;
+				print("\tIobuf %Z(%lld) t=%s\n",
+					p->dev, (Wideoff)p->addr, tagnames[tag]);
+				unlock(hp);
+				return 1;
+			}
+			p = p->fore;
+			if(p == s)
+				break;
+		}
+		unlock(hp);
+	}
+	return 0;
+}

+ 1329 - 0
sys/src/cmd/cwfs/juke.c

@@ -0,0 +1,1329 @@
+/*
+ * drive HP optical-disc jukeboxes (e.g. HP 1200EX).
+ * used to issue SCSI commands directly to the host adapter;
+ * now (via scsi.c) it issues them via scsi(2) using
+ * /dev/sdXX/raw to run the robotics, and uses normal i/o
+ * on /dev/sdXX/data to run the drives.
+ */
+#include "all.h"
+#include "io.h"
+
+enum {
+	SCSInone	= SCSIread,
+	MAXDRIVE	= 10,
+	MAXSIDE		= 500,		/* max. disc sides */
+
+	TWORM		= MINUTE(10),
+	THYSTER		= SECOND(10),
+
+	Sectorsz	= 512,		/* usual disk sector size */
+
+	Jukemagic	= 0xbabfece2,
+};
+
+typedef	struct	Side	Side;
+struct	Side
+{
+	QLock;			/* protects loading/unloading */
+	int	elem;		/* element number */
+	int	drive;		/* if loaded, where */
+	uchar	status;		/* Sunload, etc */
+	uchar	rot;		/* if backside */
+	int	ord;		/* ordinal number for labeling */
+
+	Timet	time;		/* time since last access, to unspin */
+	Timet	stime;		/* time since last spinup, for hysteresis */
+	long	nblock;		/* number of native blocks */
+	long	block;		/* bytes per native block */
+	long	mult;		/* multiplier to get plan9 blocks */
+	long	max;		/* max size in plan9 blocks */
+};
+
+typedef	struct	Juke	Juke;
+struct	Juke
+{
+	QLock;				/* protects drive mechanism */
+	Side	side[MAXSIDE];
+	int	nside;			/* # of storage elements (*2 if rev) */
+	int	ndrive;			/* # of transfer elements */
+	Device*	juke;			/* devworm of changer */
+	Device*	drive[MAXDRIVE];	/* devworm for i/o */
+	uchar	offline[MAXDRIVE];	/* drives removed from service */
+	int	isfixedsize;		/* flag: one size fits all? */
+	long	fixedsize;		/* the one size that fits all */
+	int	probeok;		/* wait for init to probe */
+
+	Scsi*	robot;			/* scsi(2) interface to robotics */
+	char*	robotdir;		/* /dev/sdXX name */
+
+	/*
+	 * geometry returned by mode sense.
+	 * a *0 number (such as mt0) is the `element number' of the
+	 * first element of that type (e.g., mt, or motor transport).
+	 * an n* number is the quantity of them.
+	 */
+	int	mt0,	nmt;	/* motor transports (robot pickers) */
+	int	se0,	nse;	/* storage elements (discs, slots) */
+	int	ie0,	nie;	/* interchange elements (mailbox slots) */
+	int	dt0,	ndt;	/* drives (data transfer?) */
+	int	rot;		/* if true, discs are double-sided */
+
+	ulong	magic;
+	Juke*	link;
+};
+static	Juke*	jukelist;
+
+enum
+{
+	Sempty = 0,	/* does not exist */
+	Sunload,	/* on the shelf */
+	Sstart,		/* loaded and spinning */
+};
+
+static	int	bestdrive(Juke*, int);
+static	void	element(Juke*, int);
+static	int	mmove(Juke*, int, int, int, int);
+static	void	shelves(void);
+static	int	waitready(Juke *, Device*);
+static	int	wormsense(Device*);
+static	Side*	wormunit(Device*);
+
+/* create a new label and try to write it */
+static void
+newlabel(Device *d, Off labelblk, char *labelbuf, unsigned vord)
+{
+	Label *label = (Label *)labelbuf;
+
+	memset(labelbuf, 0, RBUFSIZE);
+	label->magic = Labmagic;
+	label->ord = vord;
+	strncpy(label->service, service, sizeof label->service);
+
+//	if (!okay("write new label"))
+//		print("NOT writing new label\n");
+//	else
+	if (wormwrite(d, labelblk, labelbuf))
+		/* wormwrite will have complained in detail */
+		print("can't write new label on side %d\n", vord);
+	else
+		print("wrote new label on side %d\n", vord);
+}
+
+/* check for label in last block.  call with v qlocked. */
+Side*
+wormlabel(Device *d, Side *v)
+{
+	int vord;
+	Off labelblk = v->max - 1;	/* last block */
+	char labelbuf[RBUFSIZE];
+	Label *label = (Label *)labelbuf;
+	Juke *w = d->private;
+
+	/* wormread calls wormunit, which locks v */
+	vord = v->ord;
+	qunlock(v);
+
+	memset(label, 0, sizeof *label);
+	if (wormread(d, labelblk, labelbuf)) {
+		/*
+		 * wormread will have complained in detail about the error;
+		 * no need to repeat most of that detail.
+		 * probably an unwritten WORM-disc label; write a new one.
+		 */
+		print("error reading label block of side %d\n", vord);
+		newlabel(d, labelblk, labelbuf, vord);
+	} else if (label->magic != Labmagic) {
+		swab8(&label->magic);
+		if (label->magic == Labmagic) {
+			print(
+"side %d's label magic byte-swapped; filsys should be configured with xD",
+				vord);
+			swab2(&label->ord);
+			/* could look for Devswab in Juke's filsys */
+		} else {
+			/*
+			 * magic # is wrong in both byte orders, thus
+			 * probably the label is empty on RW media,
+			 * so create a new one and try to write it.
+			 */
+			print("bad magic number in label of side %d\n", vord);
+			newlabel(d, labelblk, labelbuf, vord);
+		}
+	}
+
+	qlock(v);
+	if (v->ord != vord)
+		panic("wormlabel: side %d switched ordinal to %d underfoot",
+			vord, v->ord);
+	if (label->ord != vord) {
+		print(
+	"labelled worm side %Z has wrong ordinal in label (%d, want %d)",
+			d, label->ord, vord);
+		qunlock(v);
+		cmd_wormreset(0, nil);			/* put discs away */
+		panic("wrong ordinal in label");
+	}
+
+	print("label %Z ordinal %d\n", d, v->ord);
+	qunlock(v);
+	/*
+	 * wormunit should return without calling us again,
+	 * since v is now known.
+	 */
+	if (w != d->private)
+		panic("wormlabel: w != %Z->private", d);
+	return wormunit(d);
+}
+
+/*
+ * mounts and spins up the device
+ *	locks the structure
+ */
+static Side*
+wormunit(Device *d)			/* d is l0 or r2 (e.g.) */
+{
+	int p, drive;
+	Device *dr;			/* w0 or w1.2.0 (e.g.) */
+	Side *v;
+	Juke *w;
+	Dir *dir;
+
+	w = d->private;
+	if (w == nil)
+		panic("wormunit %Z nil juke", d);
+	if (w->magic != Jukemagic)
+		panic("bad magic in Juke for %Z", d);
+	p = d->wren.targ;
+	if(p < 0 || w && p >= w->nside) {
+		panic("wormunit: target %d out of range for %Z", p, d);
+		return 0;
+	}
+
+	/*
+	 * if disk is unloaded, must load it
+	 * into next (circular) logical unit
+	 */
+	v = &w->side[p];
+	qlock(v);
+	if(v->status == Sunload) {
+		for(;;) {
+			qlock(w);
+			drive = bestdrive(w, p);
+			if(drive >= 0)
+				break;
+			qunlock(w);
+			delay(100);
+		}
+		print("\tload   r%ld drive %Z\n", v-w->side, w->drive[drive]);
+		if(mmove(w, w->mt0, v->elem, w->dt0+drive, v->rot)) {
+			qunlock(w);
+			goto sbad;
+		}
+		v->drive = drive;
+		v->status = Sstart;
+		v->stime = toytime();
+		qunlock(w);
+		dr = w->drive[drive];
+		if (!waitready(w, dr))
+			goto sbad;
+		v->stime = toytime();
+	} else
+		dr = w->drive[v->drive];
+	if(v->status != Sstart) {
+		if(v->status == Sempty)
+			print("worm: unit empty %Z\n", d);
+		else
+			print("worm: not started %Z\n", d);
+		goto sbad;
+	}
+
+	v->time = toytime();
+	if(v->block)		/* side is known already */
+		return v;
+
+	/*
+	 * load and record information about side
+	 */
+
+	if (dr->wren.file)
+		dr->wren.sddata = dataof(dr->wren.file);
+	else {
+		if (dr->wren.sddir == nil) {
+			if (dr->type == Devwren)
+				dr->wren.sddir = sdof(dr);
+			if (dr->wren.sddir == nil)
+				panic("wormunit: %Z for %Z not a wren", dr, d);
+		}
+		dr->wren.sddata = smprint("%s/data", dr->wren.sddir);
+	}
+
+	if (dr->wren.fd == 0)
+		dr->wren.fd = open(dr->wren.sddata, ORDWR);
+	if (dr->wren.fd < 0) {
+		print("wormunit: can't open %s for %Z: %r\n", dr->wren.sddata, d);
+		goto sbad;
+	}
+
+	v->block = inqsize(dr->wren.sddata);
+	if(v->block <= 0) {
+		print("\twormunit %Z block size %ld, setting to %d\n",
+			d, v->block, Sectorsz);
+		v->block = Sectorsz;
+	}
+
+	dir = dirfstat(dr->wren.fd);
+	v->nblock = dir->length / v->block;
+	free(dir);
+
+	v->mult = (RBUFSIZE + v->block - 1) / v->block;
+	v->max = (v->nblock + 1) / v->mult;
+
+	print("\tworm %Z: drive %Z (juke drive %d)\n",
+		d, w->drive[v->drive], v->drive);
+	print("\t\t%,ld %ld-byte sectors, ", v->nblock, v->block);
+	print("%,ld %d-byte blocks\n", v->max, RBUFSIZE);
+	print("\t\t%ld multiplier\n", v->mult);
+	if(d->type == Devlworm)
+		return wormlabel(d, v);
+	else
+		return v;
+
+sbad:
+	qunlock(v);
+	return 0;
+}
+
+/* wait 10s for optical drive to spin up */
+static int
+waitready(Juke *w, Device *d)
+{
+	int p, e, rv;
+	char *datanm;
+
+	if (w->magic != Jukemagic)
+		panic("waitready: bad magic in Juke (d->private) for %Z", d);
+	p = d->wren.targ;
+	if(p < 0 || p >= w->nside) {
+		print("waitready: target %d out of range for %Z\n", p, d);
+		return 0;
+	}
+
+	if (d->type == Devwren && d->wren.file)
+		datanm = strdup(d->wren.file);
+	else {
+		if (d->wren.sddir)
+			free(d->wren.sddir);
+		if (d->type == Devwren)
+			d->wren.sddir = sdof(d);
+		if (d->wren.sddir == nil)
+			panic("waitready: d->wren.sddir not set for %Z", d);
+
+		datanm = smprint("%s/data", d->wren.sddir);
+	}
+
+	rv = 0;
+	for(e=0; e < 100; e++) {
+		if (e == 10)
+			print("waitready: waiting for %s to exist\n", datanm); // DEBUG
+		if (access(datanm, AEXIST) >= 0) {
+			rv = 1;
+			break;
+		}
+		delay(200);
+	}
+	if (rv == 0)
+		print("waitready: %s for %Z didn't come ready\n", datanm, d);
+	free(datanm);
+	return rv;
+}
+
+static int
+bestdrive(Juke *w, int side)
+{
+	Side *v, *bv[MAXDRIVE];
+	int i, e, drive;
+	Timet t, t0;
+
+loop:
+	/* build table of what platters on what drives */
+	for(i=0; i<w->ndt; i++)
+		bv[i] = 0;
+
+	v = &w->side[0];
+	for(i=0; i < w->nside; i++, v++)
+		if(v->status == Sstart) {
+			drive = v->drive;
+			if(drive >= 0 && drive < w->ndt)
+				bv[drive] = v;
+		}
+
+	/*
+	 * find oldest drive, but must be
+	 * at least THYSTER old.
+	 */
+	e = w->side[side].elem;
+	t0 = toytime() - THYSTER;
+	t = t0;
+	drive = -1;
+	for(i=0; i<w->ndt; i++) {
+		v = bv[i];
+		if(v == 0) {		/* 2nd priority: empty drive */
+			if(w->offline[i])
+				continue;
+			if(w->drive[i] != devnone) {
+				drive = i;
+				t = 0;
+			}
+			continue;
+		}
+		if(v->elem == e) {	/* 1st priority: other side */
+			drive = -1;
+			if(v->stime < t0)
+				drive = i;
+			break;
+		}
+		if(v->stime < t) {	/* 3rd priority: by time */
+			drive = i;
+			t = v->stime;
+		}
+	}
+
+	if(drive >= 0) {
+		v = bv[drive];
+		if(v) {
+			qlock(v);
+			if(v->status != Sstart) {
+				qunlock(v);
+				goto loop;
+			}
+			print("\tunload r%ld drive %Z\n",
+				v-w->side, w->drive[drive]);
+			if(mmove(w, w->mt0, w->dt0+drive, v->elem, v->rot)) {
+				qunlock(v);
+				goto loop;
+			}
+			v->status = Sunload;
+			qunlock(v);
+		}
+	}
+	return drive;
+}
+
+Devsize
+wormsize(Device *d)
+{
+	Side *v;
+	Juke *w;
+	Devsize size;
+
+	w = d->private;
+	if (w->magic != Jukemagic)
+		print("wormsize: bad magic in Juke (d->private) for %Z\n", d);
+	if(w->isfixedsize && w->fixedsize != 0)
+		size = w->fixedsize;	/* fixed size is now known */
+	else {
+		if (w != d->private)
+			panic("wormsize: w != %Z->private", d);
+		v = wormunit(d);
+		if(v == nil)
+			return 0;
+		size = v->max;
+		qunlock(v);
+		/*
+		 * set fixed size for whole Juke from
+		 * size of first disc examined.
+		 */
+		if(w->isfixedsize)
+			w->fixedsize = size;
+	}
+	if(d->type == Devlworm)
+		return size-1;		/* lie: last block is for label */
+	return size;
+}
+
+/*
+ * return a Devjuke or an mcat (normally of sides) from within d (or nil).
+ * if it's an mcat, the caller must walk it.
+ */
+static Device *
+devtojuke(Device *d, Device *top)
+{
+	while (d != nil)
+		switch(d->type) {
+		default:
+			print("devtojuke: type of device %Z of %Z unknown\n",
+				d, top);
+			return nil;
+
+		case Devjuke:
+			/* jackpot!  d->private is a (Juke *) with nside, &c. */
+			/* FALL THROUGH */
+		case Devmcat:
+		case Devmlev:
+		case Devmirr:
+			/* squint hard & call an mlev or a mirr an mcat */
+			return d;
+
+		case Devworm:
+		case Devlworm:
+			/*
+			 * d->private is a (Juke *) with nside, etc.,
+			 * but we're not supposed to get here.
+			 */
+			print("devtojuke: (l)worm %Z of %Z encountered\n",
+				d, top);
+			/* FALL THROUGH */
+		case Devwren:
+			return nil;
+
+		case Devcw:
+			d = d->cw.w;			/* usually juke */
+			break;
+		case Devro:
+			d = d->ro.parent;		/* cw */
+			break;
+		case Devfworm:
+			d = d->fw.fw;
+			break;
+		case Devpart:
+			d = d->part.d;
+			break;
+		case Devswab:
+			d = d->swab.d;
+			break;
+		}
+	return d;
+}
+
+static int
+devisside(Device *d)
+{
+	return d->type == Devworm || d->type == Devlworm;
+}
+
+static Device *
+findside(Device *juke, int side, Device *top)
+{
+	int i = 0;
+	Device *mcat = juke->j.m, *x;
+	Juke *w = juke->private;
+
+	for (x = mcat->cat.first; x != nil; x = x->link) {
+		if (!devisside(x)) {
+			print("wormsizeside: %Z of %Z of %Z type not (l)worm\n",
+				x, mcat, top);
+			return nil;
+		}
+		i = x->wren.targ;
+		if (i < 0 || i >= w->nside)
+			panic("wormsizeside: side %d in %Z out of range",
+				i, mcat);
+		if (i == side)
+			break;
+	}
+	if (x == nil)
+		return nil;
+	if (w->side[i].time == 0) {
+		print("wormsizeside: side %d not in jukebox %Z\n", i, juke);
+		return nil;
+	}
+	return x;
+}
+
+typedef struct {
+	int	sleft;		/* sides still to visit to reach desired side */
+	int	starget;	/* side of topdev we want */
+	Device	*topdev;
+	int	sawjuke;	/* passed by a jukebox */
+	int	sized;		/* flag: asked wormsize for size of starget */
+} Visit;
+
+/*
+ * walk the Device tree from d looking for Devjukes, counting sides.
+ * the main complication is mcats and the like with Devjukes in them.
+ * use Devjuke's d->private as Juke* and see sides.
+ */
+static Off
+visitsides(Device *d, Device *parentj, Visit *vp)
+{
+	Off size = 0;
+	Device *x;
+	Juke *w;
+
+	/*
+	 * find the first juke or mcat.
+	 * d==nil means we couldn't find one; typically harmless, due to a
+	 * mirror of dissimilar devices.
+	 */
+	d = devtojuke(d, vp->topdev);
+	if (d == nil || vp->sleft < 0)
+		return 0;
+	if (d->type == Devjuke) {    /* jackpot!  d->private is a (Juke *) */
+		vp->sawjuke = 1;
+		w = d->private;
+		/*
+		 * if there aren't enough sides in this jukebox to reach
+		 * the desired one, subtract these sides and pass.
+		 */
+		if (vp->sleft >= w->nside) {
+			vp->sleft -= w->nside;
+			return 0;
+		}
+		/* else this is the right juke, paw through mcat of sides */
+		return visitsides(d->j.m, d, vp);
+	}
+
+	/*
+	 * d will usually be an mcat of sides, but it could be an mcat of
+	 * jukes, for example.  in that case, we need to walk the mcat,
+	 * recursing as needed, until we find the right juke, then stop at
+	 * the right side within its mcat of sides, by comparing side
+	 * numbers, not just by counting (to allow for unused slots).
+	 */
+	x = d->cat.first;
+	if (x == nil) {
+		print("visitsides: %Z of %Z: empty mcat\n", d, vp->topdev);
+		return 0;
+	}
+	if (!devisside(x)) {
+		for (; x != nil && !vp->sized; x = x->link)
+			size = visitsides(x, parentj, vp);
+		return size;
+	}
+
+	/* the side we want is in this jukebox, thus this mcat (d) */
+	if (parentj == nil) {
+		print("visitsides: no parent juke for sides mcat %Z\n", d);
+		vp->sleft = -1;
+		return 0;
+	}
+	if (d != parentj->j.m)
+		panic("visitsides: mcat mismatch %Z vs %Z", d, parentj->j.m);
+	x = findside(parentj, vp->sleft, vp->topdev);
+	if (x == nil) {
+		vp->sleft = -1;
+		return 0;
+	}
+
+	/* we've turned vp->starget into the right Device* */
+	vp->sleft = 0;
+	vp->sized = 1;
+	return wormsize(x);
+}
+
+/*
+ * d must be, or be within, a filesystem config that also contains
+ * the jukebox that `side' resides on.
+ * d is normally a Devcw, but could be Devwren, Devide, Devpart, Devfworm,
+ * etc. if called from chk.c Ctouch code.  Note too that the worm part of
+ * the Devcw might be other than a Devjuke.
+ */
+Devsize
+wormsizeside(Device *d, int side)
+{
+	Devsize size;
+	Visit visit;
+
+	memset(&visit, 0, sizeof visit);
+	visit.starget = visit.sleft = side;
+	visit.topdev = d;
+	size = visitsides(d, nil, &visit);
+	if (visit.sawjuke && (visit.sleft != 0 || !visit.sized)) {
+		print("wormsizeside: fewer than %d sides in %Z\n", side, d);
+		return 0;
+	}
+	return size;
+}
+
+/*
+ * returns starts (in blocks) of side #side and #(side+1) of dev in *stp.
+ * dev should be a Devcw.
+ */
+void
+wormsidestarts(Device *dev, int side, Sidestarts *stp)
+{
+	int s;
+	Devsize dstart;
+
+	for (dstart = s = 0; s < side; s++)
+		dstart += wormsizeside(dev, s);
+	stp->sstart = dstart;
+	stp->s1start = dstart + wormsizeside(dev, side);
+}
+
+int
+wormread(Device *d, Off b, void *c)
+{
+	int r = 0;
+	long max;
+	char name[128];
+	Side *v = wormunit(d);
+	Juke *w = d->private;
+	Device *dr;
+
+	if (v == nil)
+		panic("wormread: nil wormunit(%Z)", d);
+	dr = w->drive[v->drive];
+	if (dr->wren.fd < 0)
+		panic("wormread: unopened fd for %Z", d);
+	max = (d->type == Devlworm? v->max + 1: v->max);
+	if(b >= max) {
+		print("wormread: block out of range %Z(%lld)\n", d, (Wideoff)b);
+		r = 0x071;
+	} else if (seek(dr->wren.fd, (vlong)b*RBUFSIZE, 0) < 0 ||
+	    read(dr->wren.fd, c, RBUFSIZE) != RBUFSIZE) {
+		fd2path(dr->wren.fd, name, sizeof name);
+		print("wormread: error on %Z(%lld) on %s in %s: %r\n",
+			d, (Wideoff)b, name, dr->wren.sddir);
+		cons.nwormre++;
+		r = 1;
+	}
+	qunlock(v);
+	return r;
+}
+
+int
+wormwrite(Device *d, Off b, void *c)
+{
+	int r = 0;
+	long max;
+	char name[128];
+	Side *v = wormunit(d);
+	Juke *w = d->private;
+	Device *dr;
+
+	if (v == nil)
+		panic("wormwrite: nil wormunit(%Z)", d);
+	dr = w->drive[v->drive];
+	if (dr->wren.fd < 0)
+		panic("wormwrite: unopened fd for %Z", d);
+	max = (d->type == Devlworm? v->max + 1: v->max);
+	if(b >= max) {
+		print("wormwrite: block out of range %Z(%lld)\n",
+			d, (Wideoff)b);
+		r = 0x071;
+	} else if (seek(dr->wren.fd, (vlong)b*RBUFSIZE, 0) < 0 ||
+	    write(dr->wren.fd, c, RBUFSIZE) != RBUFSIZE) {
+		fd2path(dr->wren.fd, name, sizeof name);
+		print("wormwrwite: error on %Z(%lld) on %s in %s: %r\n",
+			d, (Wideoff)b, name, dr->wren.sddir);
+		cons.nwormwe++;
+		r = 1;
+	}
+	qunlock(v);
+	return r;
+}
+
+static int
+mmove(Juke *w, int trans, int from, int to, int rot)
+{
+	int s;
+	uchar cmd[12], buf[4];
+	static int recur = 0;
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = 0xa5;		/* move medium */
+	cmd[2] = trans>>8;
+	cmd[3] = trans;
+	cmd[4] = from>>8;
+	cmd[5] = from;
+	cmd[6] = to>>8;
+	cmd[7] = to;
+	if(rot)
+		cmd[10] = 1;
+	s = scsiio(w->juke, SCSInone, cmd, sizeof cmd, buf, 0);	/* mmove */
+	if(s) {
+		print("scsio status #%x\n", s);
+		print("move medium t=%d fr=%d to=%d rot=%d\n",
+			trans, from, to, rot);
+//		panic("mmove");
+		if(recur == 0) {
+			recur = 1;
+			print("element from=%d\n", from);
+			element(w, from);
+			print("element to=%d\n", to);
+			element(w, to);
+			print("element trans=%d\n", trans);
+			element(w, trans);
+			recur = 0;
+		}
+		return 1;
+	}
+	return 0;
+}
+
+static void
+geometry(Juke *w)
+{
+	int s;
+	uchar cmd[6], buf[4+20];
+
+	memset(cmd, 0, sizeof cmd);
+	memset(buf, 0, sizeof buf);
+	cmd[0] = 0x1a;		/* mode sense */
+	cmd[2] = 0x1d;		/* element address assignment */
+	cmd[4] = sizeof buf;	/* allocation length */
+
+	s = scsiio(w->juke, SCSIread, cmd, sizeof cmd, buf, sizeof buf); /* mode sense elem addrs */
+	if(s)
+		panic("geometry #%x", s);
+
+	w->mt0 = (buf[4+2]<<8) | buf[4+3];
+	w->nmt = (buf[4+4]<<8) | buf[4+5];
+	w->se0 = (buf[4+6]<<8) | buf[4+7];
+	w->nse = (buf[4+8]<<8) | buf[4+9];
+	w->ie0 = (buf[4+10]<<8) | buf[4+11];
+	w->nie = (buf[4+12]<<8) | buf[4+13];
+	w->dt0 = (buf[4+14]<<8) | buf[4+15];
+	w->ndt = (buf[4+16]<<8) | buf[4+17];
+
+	memset(cmd, 0, 6);
+	memset(buf, 0, sizeof buf);
+	cmd[0] = 0x1a;		/* mode sense */
+	cmd[2] = 0x1e;		/* transport geometry */
+	cmd[4] = sizeof buf;	/* allocation length */
+
+	s = scsiio(w->juke, SCSIread, cmd, sizeof cmd, buf, sizeof buf); /* mode sense geometry */
+	if(s)
+		panic("geometry #%x", s);
+
+	w->rot = buf[4+2] & 1;
+
+	print("\tmt %d %d\n", w->mt0, w->nmt);
+	print("\tse %d %d\n", w->se0, w->nse);
+	print("\tie %d %d\n", w->ie0, w->nie);
+	print("\tdt %d %d\n", w->dt0, w->ndt);
+	print("\trot %d\n", w->rot);
+	prflush();
+}
+
+/*
+ * read element e's status from jukebox w, move any disc in drive back to its
+ * slot, and update and print software status.
+ */
+static void
+element(Juke *w, int e)
+{
+	uchar cmd[12], buf[8+8+88];
+	int s, t;
+
+	memset(cmd, 0, sizeof cmd);
+	memset(buf, 0, sizeof buf);
+	cmd[0] = 0xb8;		/* read element status */
+	cmd[2] = e>>8;		/* starting element */
+	cmd[3] = e;
+	cmd[5] = 1;		/* number of elements */
+	cmd[9] = sizeof buf;	/* allocation length */
+
+	s = scsiio(w->juke, SCSIread, cmd, sizeof cmd, buf, sizeof buf); /* read elem sts */
+	if(s) {
+		print("scsiio #%x\n", s);
+		goto bad;
+	}
+
+	s = (buf[0]<<8) | buf[1];
+	if(s != e) {
+		print("element = %d\n", s);
+		goto bad;
+	}
+	if(buf[3] != 1) {
+		print("number reported = %d\n", buf[3]);
+		goto bad;
+	}
+	s = (buf[8+8+0]<<8) | buf[8+8+1];
+	if(s != e) {
+		print("element1 = %d\n", s);
+		goto bad;
+	}
+
+	switch(buf[8+0]) {	/* element type */
+	default:
+		print("unknown element %d: %d\n", e, buf[8+0]);
+		goto bad;
+	case 1:			/* transport */
+		s = e - w->mt0;
+		if(s < 0 || s >= w->nmt)
+			goto bad;
+		if(buf[8+8+2] & 1)
+			print("transport %d full %d.%d\n", s,
+				(buf[8+8+10]<<8) | buf[8+8+11],
+				(buf[8+8+9]>>6) & 1);
+		break;
+	case 2:			/* storage */
+		s = e - w->se0;
+		if(s < 0 || s >= w->nse)
+			goto bad;
+		w->side[s].status = Sempty;
+		if(buf[8+8+2] & 1)
+			w->side[s].status = Sunload;
+		if(w->rot)
+			w->side[w->nse+s].status = w->side[s].status;
+		break;
+	case 3:			/* import/export */
+		s = e - w->ie0;
+		if(s < 0 || s >= w->nie)
+			goto bad;
+		print("import/export %d #%.2x %d.%d\n", s,
+			buf[8+8+2],
+			(buf[8+8+10]<<8) | buf[8+8+11],
+			(buf[8+8+9]>>6) & 1);
+		break;
+	case 4:			/* data transfer */
+		s = e - w->dt0;
+		if(s < 0 || s >= w->ndt)
+			goto bad;
+		print("data transfer %d #%.2x %d.%d\n", s,
+			buf[8+8+2],
+			(buf[8+8+10]<<8) | buf[8+8+11],
+			(buf[8+8+9]>>6) & 1);
+		if(buf[8+8+2] & 1) {
+			t = ((buf[8+8+10]<<8) | buf[8+8+11]) - w->se0;
+			if (t < 0 || t >= w->nse || t >= MAXSIDE ||
+			    s >= MAXDRIVE) {
+				print(
+		"element: juke %Z lies; claims side %d is in drive %d\n",
+					w->juke, t, s);	/* lying sack of ... */
+				/*
+				 * at minimum, we've avoided corrupting our
+				 * data structures.  if we know that numbers
+				 * like w->nside are valid here, we could use
+				 * them in more stringent tests.
+				 * perhaps should whack the jukebox upside the
+				 * head here to knock some sense into it.
+				 */
+				goto bad;
+			}
+			print("r%d in drive %d\n", t, s);
+			if(mmove(w, w->mt0, w->dt0+s, w->se0+t,
+			    (buf[8+8+9]>>6) & 1)) {
+				print("mmove initial unload\n");
+				goto bad;
+			}
+			w->side[t].status = Sunload;
+			if(w->rot)
+				w->side[w->nse+t].status = Sunload;
+		}
+		if(buf[8+8+2] & 4) {
+			print("drive w%d has exception #%.2x #%.2x\n", s,
+				buf[8+8+4], buf[8+8+5]);
+			goto bad;
+		}
+		break;
+	}
+	return;
+bad:
+	/* panic("element") */ ;
+}
+
+/*
+ * read all elements' status from jukebox w, move any discs in drives back
+ * to their slots, and update and print software status.
+ */
+static void
+positions(Juke *w)
+{
+	int i, f;
+
+	/* mark empty shelves */
+	for(i=0; i<w->nse; i++)
+		element(w, w->se0+i);
+	for(i=0; i<w->nmt; i++)
+		element(w, w->mt0+i);
+	for(i=0; i<w->nie; i++)
+		element(w, w->ie0+i);
+	for(i=0; i<w->ndt; i++)
+		element(w, w->dt0+i);
+
+	f = 0;
+	for(i=0; i<w->nse; i++)
+		if(w->side[i].status == Sempty) {
+			if(f) {
+				print("r%d\n", i-1);
+				f = 0;
+			}
+		} else {
+			if(!f) {
+				print("\tshelves r%d-", i);
+				f = 1;
+			}
+		}
+	if(f)
+		print("r%d\n", i-1);
+}
+
+static void
+jinit(Juke *w, Device *d, int o)
+{
+	int p;
+	Device *dev = d;
+
+	switch(d->type) {
+	default:
+		print("juke platter not (devmcat of) dev(l)worm: %Z\n", d);
+		panic("jinit: type");
+
+	case Devmcat:
+		/*
+		 * we don't call mcatinit(d) here, so we have to set d->cat.ndev
+		 * ourselves.
+		 */
+		for(d=d->cat.first; d; d=d->link)
+			jinit(w, d, o++);
+		dev->cat.ndev = o;
+		break;
+
+	case Devlworm:
+		p = d->wren.targ;
+		if(p < 0 || p >= w->nside)
+			panic("jinit partition %Z", d);
+		w->side[p].ord = o;
+		/* FALL THROUGH */
+	case Devworm:
+		if(d->private) {
+			print("juke platter private pointer set %p\n",
+				d->private);
+			panic("jinit: private");
+		}
+		d->private = w;
+		break;
+	}
+}
+
+Side*
+wormi(char *arg)
+{
+	int i, j;
+	Juke *w;
+	Side *v;
+
+	i = number(arg, -1, 10) - 1;
+	w = jukelist;
+	if(i < 0 || i >= w->nside) {
+		print("bad unit number %s (%d)\n", arg, i+1);
+		return 0;
+	}
+	j = i;
+	if(j >= w->nse)
+		j -= w->nse;
+	if(j < w->nside) {
+		v = &w->side[j];
+		qlock(v);
+		if(v->status == Sstart) {
+			if(mmove(w, w->mt0, w->dt0+v->drive, v->elem, v->rot)) {
+				qunlock(v);
+				return 0;
+			}
+			v->status = Sunload;
+		}
+		qunlock(v);
+	}
+	j += w->nse;
+	if(j < w->nside) {
+		v = &w->side[j];
+		qlock(v);
+		if(v->status == Sstart) {
+			if(mmove(w, w->mt0, w->dt0+v->drive, v->elem, v->rot)) {
+				qunlock(v);
+				return 0;
+			}
+			v->status = Sunload;
+		}
+		qunlock(v);
+	}
+	v = &w->side[i];
+	qlock(v);
+	return v;
+}
+
+static void
+cmd_wormoffline(int argc, char *argv[])
+{
+	int u, i;
+	Juke *w;
+
+	if(argc <= 1) {
+		print("usage: wormoffline drive\n");
+		return;
+	}
+	u = number(argv[1], -1, 10);
+	w = jukelist;
+	if(u < 0 || u >= w->ndrive) {
+		print("bad drive %s (0<=%d<%d)\n", argv[1], u, w->ndrive);
+		return;
+	}
+	if(w->offline[u])
+		print("drive %d already offline\n", u);
+	w->offline[u] = 1;
+	for(i=0; i<w->ndrive; i++)
+		if(w->offline[i] == 0)
+			return;
+	print("that would take all drives offline\n");
+	w->offline[u] = 0;
+}
+
+static void
+cmd_wormonline(int argc, char *argv[])
+{
+	int u;
+	Juke *w;
+
+	if(argc <= 1) {
+		print("usage: wormonline drive\n");
+		return;
+	}
+	u = number(argv[1], -1, 10);
+	w = jukelist;
+	if(u < 0 || u >= w->ndrive) {
+		print("bad drive %s (0<=%d<%d)\n", argv[1], u, w->ndrive);
+		return;
+	}
+	if(w->offline[u] == 0)
+		print("drive %d already online\n", u);
+	w->offline[u] = 0;
+}
+
+void
+cmd_wormreset(int, char *[])
+{
+	Juke *w;
+
+	for(w=jukelist; w; w=w->link) {
+		qlock(w);
+		positions(w);
+		qunlock(w);
+	}
+}
+
+static void
+cmd_wormeject(int argc, char *argv[])
+{
+	Juke *w;
+	Side *v;
+
+	if(argc <= 1) {
+		print("usage: wormeject unit\n");
+		return;
+	}
+	v = wormi(argv[1]);
+	if(v == 0)
+		return;
+	w = jukelist;
+	mmove(w, w->mt0, v->elem, w->ie0, 0);
+	qunlock(v);
+}
+
+static void
+cmd_wormingest(int argc, char *argv[])
+{
+	Juke *w;
+	Side *v;
+
+	if(argc <= 1) {
+		print("usage: wormingest unit\n");
+		return;
+	}
+	v = wormi(argv[1]);
+	if(v == 0)
+		return;
+	w = jukelist;
+	mmove(w, w->mt0, w->ie0, v->elem, 0);
+	qunlock(v);
+}
+
+static void
+newside(Side *v, int rot, int elem)
+{
+	qlock(v);
+	qunlock(v);
+//	v->name = "shelf";
+	v->elem = elem;
+	v->rot = rot;
+	v->status = Sempty;
+	v->time = toytime();
+}
+
+/*
+ * query jukebox robotics for geometry;
+ * argument is the wren dev of the changer.
+ * result is actually Juke*, but that type is only known in this file.
+ */
+void *
+querychanger(Device *xdev)
+{
+	Juke *w;
+	Side *v;
+	int i;
+
+	if (xdev == nil)
+		panic("querychanger: nil Device");
+	if(xdev->type != Devwren) {
+		print("juke changer not wren %Z\n", xdev);
+		goto bad;
+	}
+	for(w=jukelist; w; w=w->link)
+		if(xdev == w->juke)
+			return w;
+
+	/*
+	 * allocate a juke structure
+	 * no locking problems.
+	 */
+	w = malloc(sizeof(Juke));
+	w->magic = Jukemagic;
+	w->isfixedsize = FIXEDSIZE;
+	w->link = jukelist;
+	jukelist = w;
+
+	print("alloc juke %Z\n", xdev);
+	qlock(w);
+	qunlock(w);
+//	w->name = "juke";
+	w->juke = xdev;
+	w->robotdir = sdof(xdev);
+	w->robot = openscsi(w->robotdir);
+	if (w->robot == nil)
+		panic("can't openscsi(%s): %r", w->robotdir);
+	newscsi(xdev, w->robot);
+	geometry(w);
+
+	/*
+	 * pick up each side
+	 */
+	w->nside = w->nse;
+	if(w->rot)
+		w->nside += w->nside;
+	if(w->nside > MAXSIDE) {
+		print("too many sides: %d max %d\n", w->nside, MAXSIDE);
+		goto bad;
+	}
+	for(i=0; i < w->nse; i++) {
+		v = &w->side[i];
+		newside(v, 0, w->se0 + i);
+		if(w->rot)
+			newside(v + w->nse, 1, w->se0 + i);
+	}
+	positions(w);
+
+	w->ndrive = w->ndt;
+	if(w->ndrive > MAXDRIVE) {
+		print("ndrives truncated to %d\n", MAXDRIVE);
+		w->ndrive = MAXDRIVE;
+	}
+
+	/*
+	 * pick up each drive
+	 */
+	for(i=0; i<w->ndrive; i++)
+		w->drive[i] = devnone;
+	return w;
+bad:
+	panic("querychanger: %Z", xdev);
+	return nil;
+}
+
+void
+jukeinit(Device *d)
+{
+	Juke *w;
+	Device *xdev;
+	int i;
+	static int beenhere = 0;
+
+	/* j(w<changer>w<station0>...)(r<platters>) */
+	if (d == nil)
+		panic("jukeinit: nil Device");
+	xdev = d->j.j;
+	if(xdev == nil || xdev->type != Devmcat) {
+		print("juke union not mcat\n");
+		goto bad;
+	}
+
+	/*
+	 * pick up the changer device
+	 */
+	xdev = xdev->cat.first;
+	w = querychanger(xdev);
+
+	if (!beenhere) {
+		beenhere = 1;
+		cmd_install("wormreset",
+			"-- put drives back where jukebox thinks they belong",
+			cmd_wormreset);
+		cmd_install("wormeject", "unit -- shelf to outside",
+			cmd_wormeject);
+		cmd_install("wormingest", "unit -- outside to shelf",
+			cmd_wormingest);
+		cmd_install("wormoffline", "unit -- disable drive",
+			cmd_wormoffline);
+		cmd_install("wormonline", "unit -- enable drive",
+			cmd_wormonline);
+	}
+
+	/* walk through the worm drives */
+	i = 0;
+	while(xdev = xdev->link) {
+		if(xdev->type != Devwren) {
+			print("drive not devwren: %Z\n", xdev);
+			goto bad;
+		}
+		if(w->drive[i]->type != Devnone &&
+		   xdev != w->drive[i]) {
+			print("double init drive %d %Z %Z\n",
+				i, w->drive[i], xdev);
+			goto bad;
+		}
+		if(i >= w->ndrive) {
+			print("too many drives %Z\n", xdev);
+			goto bad;
+		}
+		w->drive[i++] = xdev;
+	}
+
+	if(i <= 0) {
+		print("no drives\n");
+		goto bad;
+	}
+
+	/*
+	 * put w pointer in each platter
+	 */
+	d->private = w;
+	jinit(w, d->j.m, 0);
+	w->probeok = 1;
+	return;
+
+bad:
+	panic("juke init");
+}
+
+/*
+ * called periodically
+ */
+void
+wormprobe(void)
+{
+	int i, drive;
+	Timet t;
+	Side *v;
+	Juke *w;
+
+	t = toytime() - TWORM;
+	for(w=jukelist; w; w=w->link) {
+		if(w->probeok == 0 || !canqlock(w))
+			continue;
+		for(i=0; i<w->nside; i++) {
+			v = &w->side[i];
+			if(!canqlock(v))
+				continue;
+			if(v->status == Sstart && t > v->time) {
+				drive = v->drive;
+				print("\ttime   r%ld drive %Z\n",
+					v-w->side, w->drive[drive]);
+				mmove(w, w->mt0, w->dt0+drive, v->elem, v->rot);
+				v->status = Sunload;
+			}
+			qunlock(v);
+		}
+		qunlock(w);
+	}
+}

+ 83 - 0
sys/src/cmd/cwfs/lrand.c

@@ -0,0 +1,83 @@
+#include "all.h"
+
+/*
+ *	algorithm by
+ *	D. P. Mitchell & J. A. Reeds
+ */
+enum {
+	LEN	= 607,
+	TAP	= 273,
+	MASK	= 0x7fffffffL,
+	A	= 48271,
+	M	= 2147483647,
+	Q	= 44488,
+	R	= 3399,
+};
+
+#define	NORM	(1.0/(1.0+MASK))
+
+static	ulong	rng_vec[LEN];
+static	ulong*	rng_tap = rng_vec;
+static	ulong*	rng_feed = 0;
+static	Lock	lk;
+
+static void
+isrand(long seed)
+{
+	long lo, hi, x;
+	int i;
+
+	rng_tap = rng_vec;
+	rng_feed = rng_vec+LEN-TAP;
+	seed %= M;
+	if(seed < 0)
+		seed += M;
+	if(seed == 0)
+		seed = 89482311;
+	x = seed;
+	/*
+	 *	Initialize by x[n+1] = 48271 * x[n] mod (2**31 - 1)
+	 */
+	for(i = -20; i < LEN; i++) {
+		hi = x / Q;
+		lo = x % Q;
+		x = A*lo - R*hi;
+		if(x < 0)
+			x += M;
+		if(i >= 0)
+			rng_vec[i] = x;
+	}
+}
+
+void
+srand(long seed)
+{
+	lock(&lk);
+	isrand(seed);
+	unlock(&lk);
+}
+
+long
+lrand(void)
+{
+	ulong x;
+
+	lock(&lk);
+
+	rng_tap--;
+	if(rng_tap < rng_vec) {
+		if(rng_feed == 0) {
+			isrand(1);
+			rng_tap--;
+		}
+		rng_tap += LEN;
+	}
+	rng_feed--;
+	if(rng_feed < rng_vec)
+		rng_feed += LEN;
+	x = (*rng_feed + *rng_tap) & MASK;
+	*rng_feed = x;
+
+	unlock(&lk);
+	return x;
+}

+ 573 - 0
sys/src/cmd/cwfs/main.c

@@ -0,0 +1,573 @@
+/* cached-worm file server */
+#include "all.h"
+#include "io.h"
+#include "9p1.h"
+
+Map *devmap;
+
+Biobuf bin;
+
+void
+machinit(void)
+{
+	active.exiting = 0;
+}
+
+/*
+ * Put a string on the console.
+ */
+void
+puts(char *s, int n)
+{
+	print("%.*s", n, s);
+}
+
+void
+prflush(void)
+{
+}
+
+/*
+ * Print a string on the console.
+ */
+void
+putstrn(char *str, int n)
+{
+	puts(str, n);
+}
+
+/*
+ * get a character from the console
+ */
+int
+getc(void)
+{
+	return Bgetrune(&bin);
+}
+
+void
+panic(char *fmt, ...)
+{
+	int n;
+	va_list arg;
+	char buf[PRINTSIZE];
+
+	va_start(arg, fmt);
+	n = vseprint(buf, buf + sizeof buf, fmt, arg) - buf;
+	va_end(arg);
+	buf[n] = '\0';
+	print("panic: %s\n", buf);
+	exit();
+}
+
+int
+okay(char *quest)
+{
+	char *ln;
+
+	print("okay to %s? ", quest);
+	if ((ln = Brdline(&bin, '\n')) == nil)
+		return 0;
+	ln[Blinelen(&bin)-1] = '\0';
+	if (isascii(*ln) && isupper(*ln))
+		*ln = tolower(*ln);
+	return *ln == 'y';
+}
+
+static void
+mapinit(char *mapfile)
+{
+	int nf;
+	char *ln;
+	char *fields[2];
+	Biobuf *bp;
+	Map *map;
+
+	if (mapfile == nil)
+		return;
+	bp = Bopen(mapfile, OREAD);
+	if (bp == nil)
+		sysfatal("can't read %s", mapfile);
+	devmap = nil;
+	while ((ln = Brdline(bp, '\n')) != nil) {
+		ln[Blinelen(bp)-1] = '\0';
+		if (*ln == '\0' || *ln == '#')
+			continue;
+		nf = tokenize(ln, fields, nelem(fields));
+		if (nf != 2)
+			continue;
+		if(testconfig(fields[0]) != 0) {
+			print("bad `from' device %s in %s\n",
+				fields[0], mapfile);
+			continue;
+		}
+		map = malloc(sizeof *map);
+		map->from = strdup(fields[0]);
+		map->to =   strdup(fields[1]);
+		map->fdev = iconfig(fields[0]);
+		if(testconfig(fields[1]) == 0)
+			map->tdev = iconfig(fields[1]);
+		/* else map->to is the replacement file name */
+		map->next = devmap;
+		devmap = map;
+	}
+	Bterm(bp);
+}
+
+static void
+confinit(void)
+{
+	conf.nmach = 1;
+
+	conf.mem = meminit();
+
+	conf.nuid = 1000;
+	conf.nserve = 15;		/* tunable */
+	conf.nfile = 30000;
+	conf.nlgmsg = 100;
+	conf.nsmmsg = 500;
+
+	localconfinit();
+
+	conf.nwpath = conf.nfile*8;
+	conf.nauth =  conf.nfile/10;
+	conf.gidspace = conf.nuid*3;
+
+	cons.flags = 0;
+
+	if (conf.devmap)
+		mapinit(conf.devmap);
+}
+
+/*
+ * compute BUFSIZE*(NDBLOCK+INDPERBUF+INDPERBUF⁲+INDPERBUF⁳+INDPERBUF⁴)
+ * while watching for overflow; in that case, return 0.
+ */
+
+static uvlong
+adduvlongov(uvlong a, uvlong b)
+{
+	uvlong r = a + b;
+
+	if (r < a || r < b)
+		return 0;
+	return r;
+}
+
+static uvlong
+muluvlongov(uvlong a, uvlong b)
+{
+	uvlong r = a * b;
+
+	if (a != 0 && r/a != b || r < a || r < b)
+		return 0;
+	return r;
+}
+
+static uvlong
+maxsize(void)
+{
+	int i;
+	uvlong max = NDBLOCK, ind = 1;
+
+	for (i = 0; i < NIBLOCK; i++) {
+		ind = muluvlongov(ind, INDPERBUF);	/* power of INDPERBUF */
+		if (ind == 0)
+			return 0;
+		max = adduvlongov(max, ind);
+		if (max == 0)
+			return 0;
+	}
+	return muluvlongov(max, BUFSIZE);
+}
+
+enum {
+	INDPERBUF⁲ = ((uvlong)INDPERBUF *INDPERBUF),
+	INDPERBUF⁴ = ((uvlong)INDPERBUF⁲*INDPERBUF⁲),
+};
+
+static void
+printsizes(void)
+{
+	uvlong max = maxsize();
+
+	print("\tblock size = %d; ", RBUFSIZE);
+	if (max == 0)
+		print("max file size exceeds 2⁶⁴ bytes\n");
+	else {
+		uvlong offlim = 1ULL << (sizeof(Off)*8 - 1);
+
+		if (max >= offlim)
+			max = offlim - 1;
+		print("max file size = %,llud\n", (Wideoff)max);
+	}
+	if (INDPERBUF⁲/INDPERBUF != INDPERBUF)
+		print("overflow computing INDPERBUF⁲\n");
+	if (INDPERBUF⁴/INDPERBUF⁲ != INDPERBUF⁲)
+		print("overflow computing INDPERBUF⁴\n");
+	print("\tINDPERBUF = %d, INDPERBUF^4 = %,lld, ", INDPERBUF,
+		(Wideoff)INDPERBUF⁴);
+	print("CEPERBK = %d\n", CEPERBK);
+	print("\tsizeofs: Dentry = %d, Cache = %d\n",
+		sizeof(Dentry), sizeof(Cache));
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-f][-a ann-str][-m dev-map] config-dev\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, nets = 0;
+	char *ann;
+
+	rfork(RFNOTEG);
+	formatinit();
+	machinit();
+	conf.confdev = "n";		/* Devnone */
+
+	ARGBEGIN{
+	case 'a':
+		ann = EARGF(usage());
+		if (nets >= Maxnets) {
+			fprint(2, "%s: too many networks to announce: %s\n",
+				argv0, ann);
+			exits("too many nets");
+		}
+		annstrs[nets++] = ann;
+		break;
+	case 'f':
+		conf.configfirst++;
+		break;
+	case 'm':
+		conf.devmap = EARGF(usage());
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND
+
+	if (argc != 1)
+		usage();
+	conf.confdev = argv[0];	/* config string for dev holding full config */
+
+	Binit(&bin, 0, OREAD);
+	confinit();
+
+	print("\nPlan 9 %d-bit cached-worm file server with %d-deep indir blks\n",
+		sizeof(Off)*8 - 1, NIBLOCK);
+	printsizes();
+
+	qlock(&reflock);
+	qunlock(&reflock);
+	serveq = newqueue(1000, "9P service");	/* tunable */
+	raheadq = newqueue(1000, "readahead");	/* tunable */
+
+	mbinit();
+	netinit();
+	scsiinit();
+
+	files = malloc(conf.nfile * sizeof *files);
+	for(i=0; i < conf.nfile; i++) {
+		qlock(&files[i]);
+		qunlock(&files[i]);
+	}
+
+	wpaths = malloc(conf.nwpath * sizeof(*wpaths));
+	uid = malloc(conf.nuid * sizeof(*uid));
+	gidspace = malloc(conf.gidspace * sizeof(*gidspace));
+	authinit();
+
+	print("iobufinit\n");
+	iobufinit();
+
+	arginit();
+	boottime = time(nil);
+
+	print("sysinit\n");
+	sysinit();
+
+	/*
+	 * Ethernet i/o processes
+	 */
+	netstart();
+
+	/*
+	 * read ahead processes
+	 */
+	newproc(rahead, 0, "rah");
+
+	/*
+	 * server processes
+	 */
+	for(i=0; i < conf.nserve; i++)
+		newproc(serve, 0, "srv");
+
+	/*
+	 * worm "dump" copy process
+	 */
+	newproc(wormcopy, 0, "wcp");
+
+	/*
+	 * processes to read the console
+	 */
+	consserve();
+
+	/*
+	 * "sync" copy process
+	 * this doesn't return.
+	 */
+	procsetname("scp");
+	synccopy();
+}
+
+/*
+ * read ahead processes.
+ * read message from q and then
+ * read the device.
+ */
+int
+rbcmp(void *va, void *vb)
+{
+	Rabuf *ra, *rb;
+
+	ra = *(Rabuf**)va;
+	rb = *(Rabuf**)vb;
+	if(rb == 0)
+		return 1;
+	if(ra == 0)
+		return -1;
+	if(ra->dev > rb->dev)
+		return 1;
+	if(ra->dev < rb->dev)
+		return -1;
+	if(ra->addr > rb->addr)
+		return 1;
+	if(ra->addr < rb->addr)
+		return -1;
+	return 0;
+}
+
+void
+rahead(void *)
+{
+	Rabuf *rb[50];
+	Iobuf *p;
+	int i, n;
+
+	for (;;) {
+		rb[0] = fs_recv(raheadq, 0);
+		for(n = 1; n < nelem(rb); n++) {
+			if(raheadq->count <= 0)
+				break;
+			rb[n] = fs_recv(raheadq, 0);
+		}
+		qsort(rb, n, sizeof rb[0], rbcmp);
+		for(i = 0; i < n; i++) {
+			if(rb[i] == 0)
+				continue;
+			p = getbuf(rb[i]->dev, rb[i]->addr, Brd);
+			if(p)
+				putbuf(p);
+			lock(&rabuflock);
+			rb[i]->link = rabuffree;
+			rabuffree = rb[i];
+			unlock(&rabuflock);
+		}
+	}
+}
+
+/*
+ * main filesystem server loop.
+ * entered by many processes.
+ * they wait for message buffers and
+ * then process them.
+ */
+void
+serve(void *)
+{
+	int i;
+	Chan *cp;
+	Msgbuf *mb;
+
+	for (;;) {
+		qlock(&reflock);
+		/* read 9P request from a network input process */
+		mb = fs_recv(serveq, 0);
+		assert(mb->magic == Mbmagic);
+		/* fs kernel sets chan in /sys/src/fs/ip/il.c:/^getchan */
+		cp = mb->chan;
+		if (cp == nil)
+			panic("serve: nil mb->chan");
+		rlock(&cp->reflock);
+		qunlock(&reflock);
+
+		rlock(&mainlock);
+
+		if (mb->data == nil)
+			panic("serve: nil mb->data");
+		/* better sniffing code in /sys/src/cmd/disk/kfs/9p12.c */
+		if(cp->protocol == nil){
+			/* do we recognise the protocol in this packet? */
+			/* better sniffing code: /sys/src/cmd/disk/kfs/9p12.c */
+			for(i = 0; fsprotocol[i] != nil; i++)
+				if(fsprotocol[i](mb) != 0) {
+					cp->protocol = fsprotocol[i];
+					break;
+				}
+			if(cp->protocol == nil){
+				print("no protocol for message\n");
+				for(i = 0; i < 12; i++)
+					print(" %2.2uX", mb->data[i]);
+				print("\n");
+			}
+		} else
+			/* process the request, generate an answer and reply */
+			cp->protocol(mb);
+
+		mbfree(mb);
+		runlock(&mainlock);
+		runlock(&cp->reflock);
+	}
+}
+
+void
+exit(void)
+{
+	lock(&active);
+	active.exiting = 1;
+	unlock(&active);
+
+	print("halted at %T.\n", time(nil));
+	postnote(PNGROUP, getpid(), "die");
+	exits(nil);
+}
+
+enum {
+	DUMPTIME = 5,	/* 5 am */
+	WEEKMASK = 0,	/* every day (1=sun, 2=mon, 4=tue, etc.) */
+};
+
+/*
+ * calculate the next dump time.
+ * minimum delay is 100 minutes.
+ */
+Timet
+nextdump(Timet t)
+{
+	Timet nddate = nextime(t+MINUTE(100), DUMPTIME, WEEKMASK);
+
+	if(!conf.nodump)
+		print("next dump at %T\n", nddate);
+	return nddate;
+}
+
+/*
+ * process to copy dump blocks from
+ * cache to worm. it runs flat out when
+ * it gets work, but only looks for
+ * work every 10 seconds.
+ */
+void
+wormcopy(void *)
+{
+	int f, dorecalc = 1;
+	Timet dt, t = 0, nddate = 0, ntoytime = 0;
+	Filsys *fs;
+
+	for (;;) {
+		if (dorecalc) {
+			dorecalc = 0;
+			t = time(nil);
+			nddate = nextdump(t);		/* chatters */
+			ntoytime = time(nil);
+		}
+		dt = time(nil) - t;
+		if(dt < 0 || dt > MINUTE(100)) {
+			if(dt < 0)
+				print("time went back\n");
+			else
+				print("time jumped ahead\n");
+			dorecalc = 1;
+			continue;
+		}
+		t += dt;
+		f = 0;
+		if(t > ntoytime)
+			ntoytime = time(nil) + HOUR(1);
+		else if(t > nddate) {
+			if(!conf.nodump) {
+				print("automatic dump %T\n", t);
+				for(fs=filsys; fs->name; fs++)
+					if(fs->dev->type == Devcw)
+						cfsdump(fs);
+			}
+			dorecalc = 1;
+		} else {
+			rlock(&mainlock);
+			for(fs=filsys; fs->name; fs++)
+				if(fs->dev->type == Devcw)
+					f |= dumpblock(fs->dev);
+			runlock(&mainlock);
+			if(!f)
+				delay(10000);
+			wormprobe();
+		}
+	}
+}
+
+/*
+ * process to synch blocks
+ * it puts out a block/cache-line every second
+ * it waits 10 seconds if caught up.
+ * in both cases, it takes about 10 seconds
+ * to get up-to-date.
+ */
+void
+synccopy(void)
+{
+	int f;
+
+	for (;;) {
+		rlock(&mainlock);
+		f = syncblock();
+		runlock(&mainlock);
+		if(!f)
+			delay(10000);
+		else
+			delay(1000);
+	}
+}
+
+Devsize
+inqsize(char *file)
+{
+	int nf;
+	char *ln, *end, *data = malloc(strlen(file) + 5 + 1);
+	char *fields[4];
+	Devsize rv = -1;
+	Biobuf *bp;
+
+	strcpy(data, file);
+	end = strstr(data, "/data");
+	if (end == nil)
+		strcat(end, "/ctl");
+	else
+		strcpy(end, "/ctl");
+	bp = Bopen(data, OREAD);
+	if (bp) {
+		while (rv < 0 && (ln = Brdline(bp, '\n')) != nil) {
+			ln[Blinelen(bp)-1] = '\0';
+			nf = tokenize(ln, fields, nelem(fields));
+			if (nf == 3 && strcmp(fields[0], "geometry") == 0)
+				rv = atoi(fields[2]);
+		}
+		Bterm(bp);
+	}
+	free(data);
+	return rv;
+}

+ 130 - 0
sys/src/cmd/cwfs/malloc.c

@@ -0,0 +1,130 @@
+#include "all.h"
+#include "io.h"
+
+long	niob;
+long	nhiob;
+Hiob	*hiob;
+
+/*
+ * Called to allocate permanent data structures
+ * Alignment is in number of bytes. It pertains both to the start and
+ * end of the allocated memory.
+ */
+void*
+ialloc(ulong n, int align)
+{
+	void *p = mallocalign(n, align, 0, 0);
+
+	if (p == nil)
+		panic("ialloc: out of memory");
+	memset(p, 0, n);
+	return p;
+}
+
+void
+prbanks(void)
+{
+	Mbank *mbp;
+
+	for(mbp = mconf.bank; mbp < &mconf.bank[mconf.nbank]; mbp++)
+		print("bank[%ld]: base 0x%8.8lux, limit 0x%8.8lux (%.0fMB)\n",
+			mbp - mconf.bank, mbp->base, mbp->limit,
+			(mbp->limit - mbp->base)/(double)MB);
+}
+
+static void
+cmd_memory(int, char *[])
+{
+	prbanks();
+}
+
+enum { HWIDTH = 8 };		/* buffers per hash */
+
+/*
+ * allocate rest of mem
+ * for io buffers.
+ */
+void
+iobufinit(void)
+{
+	long m;
+	int i;
+	char *xiop;
+	Iobuf *p, *q;
+	Hiob *hp;
+	Mbank *mbp;
+
+	wlock(&mainlock);	/* init */
+	wunlock(&mainlock);
+
+	prbanks();
+	m = 0;
+	for(mbp = mconf.bank; mbp < &mconf.bank[mconf.nbank]; mbp++)
+		m += mbp->limit - mbp->base;
+
+	niob = m / (sizeof(Iobuf) + RBUFSIZE + sizeof(Hiob)/HWIDTH);
+	nhiob = niob / HWIDTH;
+	while(!prime(nhiob))
+		nhiob++;
+	print("\t%ld buffers; %ld hashes\n", niob, nhiob);
+	hiob = ialloc(nhiob * sizeof(Hiob), 0);
+	hp = hiob;
+	for(i=0; i<nhiob; i++) {
+		lock(hp);
+		unlock(hp);
+		hp++;
+	}
+	p = ialloc(niob * sizeof(Iobuf), 0);
+	xiop = ialloc(niob * RBUFSIZE, 0);
+	hp = hiob;
+	for(i=0; i < niob; i++) {
+//		p->name = "buf";
+		qlock(p);
+		qunlock(p);
+		if(hp == hiob)
+			hp = hiob + nhiob;
+		hp--;
+		q = hp->link;
+		if(q) {
+			p->fore = q;
+			p->back = q->back;
+			q->back = p;
+			p->back->fore = p;
+		} else {
+			hp->link = p;
+			p->fore = p;
+			p->back = p;
+		}
+		p->dev = devnone;
+		p->addr = -1;
+//		p->xiobuf = ialloc(RBUFSIZE, RBUFSIZE);
+		p->xiobuf = xiop;
+		p->iobuf = (char*)-1;
+		p++;
+		xiop += RBUFSIZE;
+	}
+
+	/*
+	 * Make sure that no more of bank[0] can be used.
+	 */
+	mconf.bank[0].base = mconf.bank[0].limit;
+
+	i = 0;
+	for(mbp = mconf.bank; mbp < &mconf.bank[mconf.nbank]; mbp++)
+		i += mbp->limit - mbp->base;
+	print("\tmem left = %,d, out of %,ld\n", i, conf.mem);
+	/* paranoia: add this command as late as is easy */
+	cmd_install("memory", "-- print ranges of memory banks", cmd_memory);
+}
+
+void*
+iobufmap(Iobuf *p)
+{
+	return p->iobuf = p->xiobuf;
+}
+
+void
+iobufunmap(Iobuf *p)
+{
+	p->iobuf = (char*)-1;
+}

+ 8 - 0
sys/src/cmd/cwfs/mkfile

@@ -0,0 +1,8 @@
+first:V: all
+
+all default clean install installall safeinstall safeinstallall:V:
+	cd cwfs && mk $target
+emelie.%:V:
+	cd emelie && mk $stem
+fs64.%:V:
+	cd fs64 && mk $stem

+ 285 - 0
sys/src/cmd/cwfs/mworm.c

@@ -0,0 +1,285 @@
+#include	"all.h"
+
+/*
+ * multiple cat devices
+ */
+void
+mcatinit(Device *d)
+{
+	Device *x, **list;
+
+	d->cat.ndev = 0;
+	for(x=d->cat.first; x; x=x->link) {
+		devinit(x);
+		d->cat.ndev++;
+	}
+
+	list = malloc(d->cat.ndev*sizeof(Device*));
+	d->private = list;
+	for(x=d->cat.first; x; x=x->link) {
+		*list++ = x;
+		x->size = devsize(x);
+	}
+}
+
+Devsize
+mcatsize(Device *d)
+{
+	Device *x;
+	Devsize l, m;
+
+	l = 0;
+	for(x=d->cat.first; x; x=x->link) {
+		m = x->size;
+		if(m == 0) {
+			m = devsize(x);
+			x->size = m;
+		}
+		l += m;
+	}
+	return l;
+}
+
+int
+mcatread(Device *d, Off b, void *c)
+{
+	Device *x;
+	Devsize l, m;
+
+	l = 0;
+	for(x=d->cat.first; x; x=x->link) {
+		m = x->size;
+		if(m == 0) {
+			m = devsize(x);
+			x->size = m;
+		}
+		if(b < l+m)
+			return devread(x, b-l, c);
+		l += m;
+	}
+	print("mcatread %Z block %lld beyond end %lld\n",
+		d, (Wideoff)b, (Wideoff)l);
+	return 1;
+}
+
+int
+mcatwrite(Device *d, Off b, void *c)
+{
+	Device *x;
+	Devsize l, m;
+
+	l = 0;
+	for(x=d->cat.first; x; x=x->link) {
+		m = x->size;
+		if(m == 0) {
+			m = devsize(x);
+			x->size = m;
+		}
+		if(b < l+m)
+			return devwrite(x, b-l, c);
+		l += m;
+	}
+	print("mcatwrite %Z block %lld beyond end %lld\n",
+		d, (Wideoff)b, (Wideoff)l);
+	return 1;
+}
+
+/*
+ * multiple interleave devices
+ */
+void
+mlevinit(Device *d)
+{
+	Device *x;
+
+	mcatinit(d);
+	for(x=d->cat.first; x; x=x->link)
+		x->size = devsize(x);
+}
+
+Devsize
+mlevsize(Device *d)
+{
+	Device *x;
+	int n;
+	Devsize m, min;
+
+	min = 0;
+	n = 0;
+	for(x=d->cat.first; x; x=x->link) {
+		m = x->size;
+		if(m == 0) {
+			m = devsize(x);
+			x->size = m;
+		}
+		if(min == 0 || m < min)
+			min = m;
+		n++;
+	}
+	return n * min;
+}
+
+int
+mlevread(Device *d, Off b, void *c)
+{
+	int n;
+	Device **list;
+
+	n = d->cat.ndev;
+	list = d->private;
+	return devread(list[b%n], b/n, c);
+}
+
+int
+mlevwrite(Device *d, Off b, void *c)
+{
+	int n;
+	Device **list;
+
+	n = d->cat.ndev;
+	list = d->private;
+	return devwrite(list[b%n], b/n, c);
+}
+
+/*
+ * partition device
+ */
+void
+partinit(Device *d)
+{
+
+	devinit(d->part.d);
+	d->part.d->size = devsize(d->part.d);
+}
+
+Devsize
+partsize(Device *d)
+{
+	Devsize size, l;
+
+	l = d->part.d->size / 100;
+	size = d->part.size * l;
+	if(size == 0)
+		size = l*100;
+	return size;
+}
+
+int
+partread(Device *d, Off b, void *c)
+{
+	Devsize base, size, l;
+
+	l = d->part.d->size / 100;
+	base = d->part.base * l;
+	size = d->part.size * l;
+	if(size == 0)
+		size = l*100;
+	if(b < size)
+		return devread(d->part.d, base+b, c);
+	print("partread %lld %lld\n", (Wideoff)b, (Wideoff)size);
+	return 1;
+}
+
+int
+partwrite(Device *d, Off b, void *c)
+{
+	Devsize base, size, l;
+
+	l = d->part.d->size / 100;
+	base = d->part.base * l;
+	size = d->part.size * l;
+	if(size == 0)
+		size = l*100;
+	if(b < size)
+		return devwrite(d->part.d, base+b, c);
+	print("partwrite %lld %lld\n", (Wideoff)b, (Wideoff)size);
+	return 1;
+}
+
+/*
+ * mirror device
+ */
+void
+mirrinit(Device *d)
+{
+	Device *x;
+
+	mcatinit(d);
+	for(x=d->cat.first; x; x=x->link)
+		x->size = devsize(x);
+}
+
+Devsize
+mirrsize(Device *d)
+{
+	Device *x;
+	int n;
+	Devsize m, min;
+
+	min = 0;
+	n = 0;
+	for(x=d->cat.first; x; x=x->link) {
+		m = x->size;
+		if(m == 0) {
+			m = devsize(x);
+			x->size = m;
+		}
+		if(min == 0 || m < min)
+			min = m;
+		n++;
+	}
+	return min;
+}
+
+int
+mirrread(Device *d, Off b, void *c)
+{
+	Device *x;
+
+	for(x=d->cat.first; x; x=x->link) {
+		if(x->size == 0)
+			x->size = devsize(x);
+		if (devread(x, b, c) == 0)	/* okay? */
+			return 0;
+	}
+	// DANGER WILL ROBINSON - all copies of this block were bad
+	print("mirrread %Z error at block %lld\n", d, (Wideoff)b);
+	return 1;
+}
+
+/*
+ * write the mirror(s) first so that a power outage, for example, will
+ * find the main device written only if the mirrors are too, thus
+ * checking the main device will also correctly check the mirror(s).
+ *
+ * devread and devwrite are synchronous; all buffering must be
+ * implemented at higher levels.
+ */
+static int
+ewrite(Device *x, Off b, void *c)
+{
+	if(x->size == 0)
+		x->size = devsize(x);
+	if (devwrite(x, b, c) != 0) {
+		print("mirrwrite %Z error at block %lld\n", x, (Wideoff)b);
+		return 1;
+	}
+	return 0;
+}
+
+static int
+wrmirrs1st(Device *x, Off b, void *c)	// write any mirrors of x, then x
+{
+	int e;
+
+	if (x == nil)
+		return 0;
+	e = wrmirrs1st(x->link, b, c);
+	return e | ewrite(x, b, c);
+}
+
+int
+mirrwrite(Device *d, Off b, void *c)
+{
+	return wrmirrs1st(d->cat.first, b, c);
+}

+ 454 - 0
sys/src/cmd/cwfs/net.c

@@ -0,0 +1,454 @@
+/* network i/o */
+
+#include "all.h"
+#include "io.h"
+#include <fcall.h>		/* 9p2000 */
+#include <thread.h>
+
+enum {
+	Maxfdata	= 8192,
+	Nqueue		= 200,		/* queue size (tunable) */
+
+	Netclosed	= 0,		/* Connection state */
+	Netopen,
+};
+
+/*
+ * the kernel file server read packets directly from
+ * its ethernet(s) and did all the protocol processing.
+ * if the incoming packets were 9p (over il/ip), they
+ * were queued for the server processes to operate upon.
+ *
+ * in user mode, we have one process per incoming connection
+ * instead, and those processes get just the data, minus
+ * tcp and ip headers, so they just see a stream of 9p messages,
+ * which they then queue for the server processes.
+ *
+ * there used to be more queueing (in the kernel), with separate
+ * processes for ethernet input, il input, 9p processing, il output
+ * and ethernet output, and queues connecting them.  we now let
+ * the kernel's network queues, protocol stacks and processes do
+ * much of this work.
+ *
+ * partly as a result of this, we can now process 9p messages
+ * transported via tcp, exploit multiple x86 processors, and
+ * were able to shed 70% of the file server's source, by line count.
+ *
+ * the upshot is that Ether (now Network) is no longer a perfect fit for
+ * the way network i/o is done now.  the notion of `connection'
+ * is being introduced to complement it.
+ */
+
+typedef struct Network Network;
+typedef struct Netconn Netconn;
+typedef struct Conn9p Conn9p;
+
+/* a network, not necessarily an ethernet */
+struct Network {
+	int	ctlrno;
+	char	iname[NAMELEN];
+	char	oname[NAMELEN];
+
+	char	*dialstr;
+	char	anndir[40];
+	char	lisdir[40];
+	int	annfd;			/* fd from announce */
+};
+
+/* an open tcp (or other transport) connection */
+struct Netconn {
+	Queue*	reply;		/* network output */
+	char*	raddr;		/* remote caller's addr */
+	Chan*	chan;		/* list of tcp channels */
+
+	int	alloc;		/* flag: allocated */
+
+	int	state;
+	Conn9p*	conn9p;		/* not reference-counted */
+
+	Lock;
+};
+
+/*
+ * incoming 9P network connection from a given machine.
+ * typically will multiplex 9P sessions for multiple users.
+ */
+struct Conn9p {
+	QLock;
+	Ref;
+	int	fd;
+	char*	dir;
+	Netconn*netconn;		/* cross-connection */
+	char*	raddr;
+};
+
+static Network netif[Maxnets];
+static struct {
+	Lock;
+	Chan*	chan;
+} netchans;
+static Queue *netoq;		/* only one network output queue is needed */
+
+char *annstrs[Maxnets] = {
+	"tcp!*!9fs",
+};
+
+/* never returns nil */
+static Chan*
+getchan(Conn9p *conn9p)
+{
+	Netconn *netconn;
+	Chan *cp, *xcp;
+
+	lock(&netchans);
+
+	/* look for conn9p's Chan */
+	xcp = nil;
+	for(cp = netchans.chan; cp; cp = netconn->chan) {
+		netconn = cp->pdata;
+		if(!netconn->alloc)
+			xcp = cp;		/* remember free Chan */
+		else if(netconn->raddr != nil &&
+		    strcmp(conn9p->raddr, netconn->raddr) == 0) {
+			unlock(&netchans);
+			return cp;		/* found conn9p's Chan */
+		}
+	}
+
+	/* conn9p's Chan not found; if no free Chan, allocate & fill in one */
+	cp = xcp;
+	if(cp == nil) {
+		cp = fs_chaninit(Devnet, 1, sizeof(Netconn));
+		netconn = cp->pdata;
+		netconn->chan = netchans.chan;
+		netconn->state = Netopen;	/* a guess */
+		/* cross-connect netconn and conn9p */
+		netconn->conn9p = conn9p;	/* not reference-counted */
+		conn9p->netconn = netconn;
+		netchans.chan = cp;
+	}
+
+	/* fill in Chan's netconn */
+	netconn = cp->pdata;
+	netconn->raddr = strdup(conn9p->raddr);
+
+	/* fill in Chan */
+	cp->send = serveq;
+	if (cp->reply == nil)
+		cp->reply = netoq;
+	netconn->reply = netoq;
+	cp->protocol = nil;
+	cp->msize = 0;
+	cp->whotime = 0;
+	strncpy(cp->whochan, conn9p->raddr, sizeof cp->whochan);
+//	cp->whoprint = tcpwhoprint;
+	netconn->alloc = 1;
+
+	unlock(&netchans);
+	return cp;
+}
+
+static char *
+fd2name(int fd)
+{
+	char data[128];
+
+	if (fd2path(fd, data, sizeof data) < 0)
+		return strdup("/GOK");
+	return strdup(data);
+}
+
+static void
+hangupdfd(int dfd)
+{
+	int ctlfd;
+	char *end, *data;
+
+	data = fd2name(dfd);
+	close(dfd);
+
+	end = strstr(data, "/data");
+	if (end != nil)
+		strcpy(end, "/ctl");
+	ctlfd = open(data, OWRITE);
+	if (ctlfd >= 0) {
+		hangup(ctlfd);
+		close(ctlfd);
+	}
+	free(data);
+}
+
+void
+closechan(int n)
+{
+	Chan *cp;
+
+	for(cp = chans; cp; cp = cp->next)
+		if(cp->whotime != 0 && cp->chan == n)
+			fileinit(cp);
+}
+
+void
+nethangup(Chan *cp, char *msg, int dolock)
+{
+	Netconn *netconn;
+
+	netconn = cp->pdata;
+	netconn->state = Netclosed;
+
+	if(msg != nil)
+		print("hangup! %s %s\n", msg, netconn->raddr);
+
+	fileinit(cp);
+	cp->whotime = 0;
+	strcpy(cp->whoname, "<none>");
+
+	if(dolock)
+		lock(&netchans);
+	netconn->alloc = 0;
+	free(netconn->raddr);
+	netconn->raddr = nil;
+	if(dolock)
+		unlock(&netchans);
+}
+
+void
+chanhangup(Chan *cp, char *msg, int dolock)
+{
+	Netconn *netconn = cp->pdata;
+	Conn9p *conn9p = netconn->conn9p;
+
+	if (conn9p->fd > 0)
+		hangupdfd(conn9p->fd);	/* drop it */
+	nethangup(cp, msg, dolock);
+}
+
+/*
+ * returns length of next 9p message (including the length) and
+ * leaves it in the first few bytes of abuf.
+ */
+static long
+size9pmsg(int fd, void *abuf, uint n)
+{
+	int m;
+	uchar *buf = abuf;
+
+	if (n < BIT32SZ)
+		return -1;	/* caller screwed up */
+
+	/* read count */
+	m = readn(fd, buf, BIT32SZ);
+	if(m != BIT32SZ){
+		if(m < 0)
+			return -1;
+		return 0;
+	}
+	return GBIT32(buf);
+}
+
+static int
+readalloc9pmsg(int fd, Msgbuf **mbp)
+{
+	int m, len;
+	uchar lenbuf[BIT32SZ];
+	Msgbuf *mb;
+
+	*mbp = nil;
+	len = size9pmsg(fd, lenbuf, BIT32SZ);
+	if (len <= 0)
+		return len;
+	if(len <= BIT32SZ || len > IOHDRSZ+Maxfdata){
+		werrstr("bad length in 9P2000 message header");
+		return -1;
+	}
+	if ((mb = mballoc(len, nil, Mbeth1)) == nil)
+		panic("readalloc9pmsg: mballoc failed");
+	*mbp = mb;
+	memmove(mb->data, lenbuf, BIT32SZ);
+	len -= BIT32SZ;
+	m = readn(fd, mb->data+BIT32SZ, len);
+	if(m < len)
+		return 0;
+	return BIT32SZ+m;
+}
+
+static void
+connection(void *v)
+{
+	int n;
+	char buf[64];
+	Chan *chan9p;
+	Conn9p *conn9p = v;
+	Msgbuf *mb;
+	NetConnInfo *nci;
+
+	incref(conn9p);			/* count connections */
+	nci = getnetconninfo(conn9p->dir, conn9p->fd);
+	if (nci == nil)
+		panic("connection: getnetconninfo(%s, %d) failed",
+			conn9p->dir, conn9p->fd);
+	conn9p->raddr = nci->raddr;
+
+	chan9p = getchan(conn9p);
+	print("new connection on %s pid %d from %s\n",
+		conn9p->dir, getpid(), conn9p->raddr);
+
+	/*
+	 * reading from a pipe or a network device
+	 * will give an error after a few eof reads.
+	 * however, we cannot tell the difference
+	 * between a zero-length read and an interrupt
+	 * on the processes writing to us,
+	 * so we wait for the error.
+	 */
+	while (conn9p->fd > 0 && (n = readalloc9pmsg(conn9p->fd, &mb)) >= 0) {
+		if(n == 0)
+			continue;
+		mb->param = (uintptr)conn9p;	/* has fd for replies */
+		mb->chan = chan9p;
+
+		assert(mb->magic == Mbmagic);
+		incref(conn9p);			/* & count packets in flight */
+		fs_send(serveq, mb);		/* to 9P server processes */
+		/* mb will be freed by receiving process */
+	}
+
+	rerrstr(buf, sizeof buf);
+
+	qlock(conn9p);
+	print("connection hung up from %s\n", conn9p->dir);
+	if (conn9p->fd > 0)		/* not poisoned yet? */
+		hangupdfd(conn9p->fd);	/* poison the fd */
+
+	nethangup(chan9p, "remote hung up", 1);
+	closechan(chan9p->chan);
+
+	conn9p->fd = -1;		/* poison conn9p */
+	if (decref(conn9p) == 0) {	/* last conn.? turn the lights off */
+		free(conn9p->dir);
+		qunlock(conn9p);
+		free(conn9p);
+	} else
+		qunlock(conn9p);
+
+	freenetconninfo(nci);
+
+	if(buf[0] == '\0' || strstr(buf, "hungup") != nil)
+		exits("");
+	sysfatal("mount read, pid %d", getpid());
+}
+
+static void
+neti(void *v)
+{
+	int lisfd, accfd;
+	Network *net;
+	Conn9p *conn9p;
+
+	net = v;
+	print("net%di\n", net->ctlrno);
+	for(;;) {
+		lisfd = listen(net->anndir, net->lisdir);
+		if (lisfd < 0) {
+			print("listen %s failed: %r\n", net->anndir);
+			continue;
+		}
+
+		/* got new call on lisfd */
+		accfd = accept(lisfd, net->lisdir);
+		if (accfd < 0) {
+			print("accept %d (from %s) failed: %r\n",
+				lisfd, net->lisdir);
+			continue;
+		}
+
+		/* accepted that call */
+		conn9p = malloc(sizeof *conn9p);
+		conn9p->dir = strdup(net->lisdir);
+		conn9p->fd = accfd;
+		newproc(connection, conn9p, smprint("9P read %s", conn9p->dir));
+		close(lisfd);
+	}
+}
+
+/* only need one of these for all network connections, thus all interfaces */
+static void
+neto(void *)
+{
+	int len, datafd;
+	Msgbuf *mb;
+	Conn9p *conn9p;
+
+	print("neto\n");
+	for(;;) {
+		/* receive 9P answer from 9P server processes */
+		while((mb = fs_recv(netoq, 0)) == nil)
+			continue;
+
+		if(mb->data == nil) {
+			print("neto: pkt nil cat=%d free=%d\n",
+				mb->category, mb->flags&FREE);
+			if(!(mb->flags & FREE))
+				mbfree(mb);
+			continue;
+		}
+
+		/* send answer back over the network connection in the reply */
+		len = mb->count;
+		conn9p = (Conn9p *)mb->param;
+		assert(conn9p);
+
+		qlock(conn9p);
+		datafd = conn9p->fd;
+		assert(len >= 0);
+		/* datafd < 0 probably indicates poisoning by the read side */
+		if (datafd < 0 || write(datafd, mb->data, len) != len) {
+			print( "network write error (%r);");
+			print(" closing connection for %s\n", conn9p->dir);
+			nethangup(getchan(conn9p), "network write error", 1);
+			if (datafd > 0)
+				hangupdfd(datafd);	/* drop it */
+			conn9p->fd = -1;		/* poison conn9p */
+		}
+		mbfree(mb);
+		if (decref(conn9p) == 0)
+			panic("neto: zero ref count");
+		qunlock(conn9p);
+	}
+}
+
+void
+netstart(void)
+{
+	int netorun = 0;
+	Network *net;
+
+	if(netoq == nil)
+		netoq = newqueue(Nqueue, "network reply");
+	for(net = &netif[0]; net < &netif[Maxnets]; net++){
+		if(net->dialstr == nil)
+			continue;
+		sprint(net->oname, "neto");
+		if (netorun++ == 0)
+			newproc(neto, nil, net->oname);
+		sprint(net->iname, "net%di", net->ctlrno);
+		newproc(neti, net, net->iname);
+	}
+}
+
+void
+netinit(void)
+{
+	Network *net;
+
+	for (net = netif; net < netif + Maxnets; net++) {
+		net->dialstr = annstrs[net - netif];
+		if (net->dialstr == nil)
+			continue;
+		net->annfd = announce(net->dialstr, net->anndir);
+		/* /bin/service/tcp564 may already have grabbed the port */
+		if (net->annfd < 0)
+			sysfatal("can't announce %s: %r", net->dialstr);
+		print("netinit: announced on %s\n", net->dialstr);
+	}
+}

+ 92 - 0
sys/src/cmd/cwfs/pc.c

@@ -0,0 +1,92 @@
+#include "all.h"
+#include "io.h"
+
+Mconf mconf;
+
+static void
+mconfinit(void)
+{
+	int nf, pgsize = 0;
+	ulong size, userpgs = 0, userused = 0;
+	char *ln, *sl;
+	char *fields[2];
+	Biobuf *bp;
+	Mbank *mbp;
+
+	size = 64*MB;
+	bp = Bopen("#c/swap", OREAD);
+	if (bp != nil) {
+		while ((ln = Brdline(bp, '\n')) != nil) {
+			ln[Blinelen(bp)-1] = '\0';
+			nf = tokenize(ln, fields, nelem(fields));
+			if (nf != 2)
+				continue;
+			if (strcmp(fields[1], "pagesize") == 0)
+				pgsize = atoi(fields[0]);
+			else if (strcmp(fields[1], "user") == 0) {
+				sl = strchr(fields[0], '/');
+				if (sl == nil)
+					continue;
+				userpgs = atol(sl+1);
+				userused = atol(fields[0]);
+			}
+		}
+		Bterm(bp);
+		if (pgsize > 0 && userpgs > 0)
+			size = (((userpgs - userused)*3LL)/4)*pgsize;
+	}
+	mconf.memsize = size;
+	mbp = mconf.bank;
+	mbp->base = 0x40000000;			/* fake addresses */
+	mbp->limit = mbp->base + size;
+	mbp++;
+
+	mconf.nbank = mbp - mconf.bank;
+}
+
+ulong
+meminit(void)
+{
+	conf.nmach = 1;
+	mconfinit();
+	return mconf.memsize;
+}
+
+/*
+ * based on libthread's threadsetname, but drags in less library code.
+ * actually just sets the arguments displayed.
+ */
+void
+procsetname(char *fmt, ...)
+{
+	int fd;
+	char *cmdname;
+	char buf[128];
+	va_list arg;
+
+	va_start(arg, fmt);
+	cmdname = vsmprint(fmt, arg);
+	va_end(arg);
+	if (cmdname == nil)
+		return;
+	snprint(buf, sizeof buf, "#p/%d/args", getpid());
+	if((fd = open(buf, OWRITE)) >= 0){
+		write(fd, cmdname, strlen(cmdname)+1);
+		close(fd);
+	}
+	free(cmdname);
+}
+
+void
+newproc(void (*f)(void *), void *arg, char *text)
+{
+	int kid = rfork(RFPROC|RFMEM|RFNOWAIT);
+
+	if (kid < 0)
+		sysfatal("can't fork: %r");
+	if (kid == 0) {
+		procsetname("%s", text);
+		(*f)(arg);
+		exits("child returned");
+	}
+}

+ 773 - 0
sys/src/cmd/cwfs/portdat.h

@@ -0,0 +1,773 @@
+/*
+ * fundamental constants and types of the implementation
+ * changing any of these changes the layout on disk
+ */
+enum {
+	SUPER_ADDR	= 2,		/* block address of superblock */
+	ROOT_ADDR	= 3,		/* block address of root directory */
+};
+
+/* more fundamental types */
+typedef vlong Wideoff; /* type to widen Off to for printing; ≥ as wide as Off */
+typedef short	Userid;		/* signed internal representation of user-id */
+typedef long	Timet;		/* in seconds since epoch */
+typedef vlong	Devsize;	/* in bytes */
+
+
+/* macros */
+#define NEXT(x, l)	(((x)+1) % (l))
+#define PREV(x, l)	((x) == 0? (l)-1: (x)-1)
+#define	HOWMANY(x, y)	(((x)+((y)-1)) / (y))
+#define ROUNDUP(x, y)	(HOWMANY((x), (y)) * (y))
+
+#define	TK2MS(t) (((ulong)(t)*1000)/HZ)	/* ticks to ms - beware rounding */
+#define	MS2TK(t) (((ulong)(t)*HZ)/1000)	/* ms to ticks - beware rounding */
+#define	TK2SEC(t) ((t)/HZ)		/* ticks to seconds */
+
+/* constants that don't affect disk layout */
+enum {
+	MAXDAT		= 8192,		/* max allowable data message */
+	MAXMSG		= 128,		/* max protocol message sans data */
+
+	MB		= 1024*1024,
+
+	HZ		= 1,		/* clock frequency */
+};
+
+/*
+ * tunable parameters
+ */
+enum {
+	Maxword		= 256,		/* max bytes per command-line word */
+	NTLOCK		= 200,		/* number of active file Tlocks */
+};
+
+typedef struct	Auth	Auth;
+typedef	struct	Bp	Bp;
+typedef	struct	Bucket	Bucket;
+typedef	struct	Cache	Cache;
+typedef	struct	Centry	Centry;
+typedef	struct	Chan	Chan;
+typedef	struct	Command	Command;
+typedef	struct	Conf	Conf;
+typedef	struct	Cons	Cons;
+typedef	struct	Dentry	Dentry;
+typedef struct	Device	Device;
+typedef	struct	Fbuf	Fbuf;
+typedef	struct	File	File;
+typedef	struct	Filsys	Filsys;
+typedef	struct	Filter	Filter;
+typedef	struct	Flag	Flag;
+typedef	struct	Hiob	Hiob;
+typedef	struct	Iobuf	Iobuf;
+typedef	struct	Lock	Lock;
+typedef	struct	Msgbuf	Msgbuf;
+typedef	struct	QLock	QLock;
+typedef struct	Qid9p1	Qid9p1;
+typedef	struct	Queue	Queue;
+typedef	union	Rabuf	Rabuf;
+typedef	struct	Rendez	Rendez;
+typedef	struct	Rtc	Rtc;
+typedef	struct	Startsb	Startsb;
+typedef	struct	Super1	Super1;
+typedef	struct	Superb	Superb;
+typedef	struct	Tag	Tag;
+typedef	struct	Time	Time;
+typedef	struct	Tlock	Tlock;
+typedef	struct	Tm	Tm;
+typedef	struct	Uid	Uid;
+typedef	struct	Wpath	Wpath;
+
+#pragma incomplete Auth
+
+struct	Tag
+{
+	short	pad;		/* make tag end at a long boundary */
+	short	tag;
+	Off	path;
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct	Qid9p1
+{
+	Off	path;			/* was long */
+	ulong	version;		/* should be Off */
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct	Super1
+{
+	Off	fstart;
+	Off	fsize;
+	Off	tfree;
+	Off	qidgen;		/* generator for unique ids */
+	/*
+	 * Stuff for WWC device
+	 */
+	Off	cwraddr;	/* cfs root addr */
+	Off	roraddr;	/* dump root addr */
+	Off	last;		/* last super block addr */
+	Off	next;		/* next super block addr */
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct	Centry
+{
+	ushort	age;
+	short	state;
+	Off	waddr;		/* worm addr */
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct	Dentry
+{
+	char	name[NAMELEN];
+	Userid	uid;
+	Userid	gid;
+	ushort	mode;
+		#define	DALLOC	0x8000
+		#define	DDIR	0x4000
+		#define	DAPND	0x2000
+		#define	DLOCK	0x1000
+		#define	DREAD	0x4
+		#define	DWRITE	0x2
+		#define	DEXEC	0x1
+	Userid	muid;
+	Qid9p1	qid;
+	Off	size;
+	Off	dblock[NDBLOCK];
+	Off	iblocks[NIBLOCK];
+	long	atime;
+	long	mtime;
+};
+
+/*
+ * derived constants
+ */
+enum {
+	BUFSIZE		= RBUFSIZE - sizeof(Tag),
+	DIRPERBUF	= BUFSIZE / sizeof(Dentry),
+	INDPERBUF	= BUFSIZE / sizeof(Off),
+	FEPERBUF	= (BUFSIZE-sizeof(Super1)-sizeof(Off)) / sizeof(Off),
+	SMALLBUF	= MAXMSG,
+	LARGEBUF	= MAXMSG+MAXDAT+256,
+	RAGAP		= (300*1024)/BUFSIZE,	/* readahead parameter */
+	BKPERBLK	= 10,
+	CEPERBK		= (BUFSIZE - BKPERBLK*sizeof(Off)) /
+				(sizeof(Centry)*BKPERBLK),
+};
+
+/*
+ * send/recv queue structure
+ */
+struct	Queue
+{
+	QLock;			/* to manipulate values */
+	Rendez	empty;
+	Rendez	full;
+
+	int	waitedfor;	/* flag */
+	char*	name;		/* for debugging */
+
+	int	size;		/* size of queue */
+	int	loc;		/* circular pointer */
+	int	count;		/* how many in queue */
+	void*	args[1];	/* list of saved pointers, [->size] */
+};
+
+struct	Device
+{
+	uchar	type;
+	uchar	init;
+	Device*	link;			/* link for mcat/mlev/mirror */
+	Device*	dlink;			/* link all devices */
+	void*	private;
+	Devsize	size;
+	union {
+		struct {		/* disk, (l)worm in j.j, sides */
+			int	ctrl;	/* disks only */
+			int	targ;
+			int	lun;	/* not implemented in sd(3) */
+
+			int	mapped;
+			char*	file;	/* ordinary file or dir instead */
+
+			int	fd;
+			char*	sddir;	/* /dev/sdXX name, for juke drives */
+			char*	sddata;	/* /dev/sdXX/data or other file */
+		} wren;
+		struct {		/* mcat mlev mirror */
+			Device*	first;
+			Device*	last;
+			int	ndev;
+		} cat;
+		struct {		/* cw */
+			Device*	c;	/* cache device */
+			Device*	w;	/* worm device */
+			Device*	ro;	/* dump - readonly */
+		} cw;
+		struct {		/* juke */
+			Device*	j;	/* (robotics, worm drives) - wrens */
+			Device*	m;	/* (sides) - r or l devices */
+		} j;
+		struct {		/* ro */
+			Device*	parent;
+		} ro;
+		struct {		/* fworm */
+			Device*	fw;
+		} fw;
+		struct {		/* part */
+			Device*	d;
+			long	base;	/* percentages */
+			long	size;
+		} part;
+		struct {		/* byte-swapped */
+			Device*	d;
+		} swab;
+	};
+};
+
+typedef struct Sidestarts {
+	Devsize	sstart;			/* blocks before start of side */
+	Devsize	s1start;		/* blocks before start of next side */
+} Sidestarts;
+
+union Rabuf {
+	struct {
+		Device*	dev;
+		Off	addr;
+	};
+	Rabuf*	link;
+};
+
+struct	Hiob
+{
+	Iobuf*	link;
+	Lock;
+};
+
+/* a 9P connection */
+struct	Chan
+{
+	char	type;			/* major driver type i.e. Dev* */
+	int	(*protocol)(Msgbuf*);	/* version */
+	int	msize;			/* version */
+	char	whochan[50];
+	char	whoname[NAMELEN];
+	void	(*whoprint)(Chan*);
+	ulong	flags;
+	int	chan;			/* overall channel #, mostly for printing */
+	int	nmsgs;			/* outstanding messages, set under flock -- for flush */
+
+	Timet	whotime;
+	int	nfile;			/* used by cmd_files */
+
+	RWLock	reflock;
+	Chan*	next;			/* link list of chans */
+	Queue*	send;
+	Queue*	reply;
+
+	uchar	authinfo[64];
+
+	void*	pdata;			/* sometimes is a Netconn* */
+};
+
+struct	Filsys
+{
+	char*	name;			/* name of filsys */
+	char*	conf;			/* symbolic configuration */
+	Device*	dev;			/* device that filsys is on */
+	int	flags;
+		#define	FREAM		(1<<0)	/* mkfs */
+		#define	FRECOVER	(1<<1)	/* install last dump */
+		#define	FEDIT		(1<<2)	/* modified */
+};
+
+struct	Startsb
+{
+	char*	name;
+	Off	startsb;
+};
+
+struct	Time
+{
+	Timet	lasttoy;
+	Timet	offset;
+};
+
+/*
+ * array of qids that are locked
+ */
+struct	Tlock
+{
+	Device*	dev;
+	Timet	time;
+	Off	qpath;
+	File*	file;
+};
+
+struct	Cons
+{
+	ulong	flags;		/* overall flags for all channels */
+	QLock;			/* generic qlock for mutex */
+	int	uid;		/* botch -- used to get uid on cons_create */
+	int	gid;		/* botch -- used to get gid on cons_create */
+	int	nuid;		/* number of uids */
+	int	ngid;		/* number of gids */
+	Off	offset;		/* used to read files, c.f. fchar */
+	int	chano;		/* generator for channel numbers */
+	Chan*	chan;		/* console channel */
+	Filsys*	curfs;		/* current filesystem */
+
+	int	profile;	/* are we profiling? */
+	long*	profbuf;
+	ulong	minpc;
+	ulong	maxpc;
+	ulong	nprofbuf;
+
+	long	nlarge;		/* number of large message buffers */
+	long	nsmall;		/* ... small ... */
+	long	nwormre;	/* worm read errors */
+	long	nwormwe;	/* worm write errors */
+	long	nwormhit;	/* worm read cache hits */
+	long	nwormmiss;	/* worm read cache non-hits */
+	int	noage;		/* dont update cache age, dump and check */
+	long	nwrenre;	/* disk read errors */
+	long	nwrenwe;	/* disk write errors */
+	long	nreseq;		/* cache bucket resequence */
+
+//	Filter	work[3];	/* thruput in messages */
+//	Filter	rate[3];	/* thruput in bytes */
+//	Filter	bhit[3];	/* getbufs that hit */
+//	Filter	bread[3];	/* getbufs that miss and read */
+//	Filter	brahead[3];	/* messages to readahead */
+//	Filter	binit[3];	/* getbufs that miss and dont read */
+};
+
+struct	File
+{
+	QLock;
+	Qid	qid;
+	Wpath*	wpath;
+	Chan*	cp;		/* null means a free slot */
+	Tlock*	tlock;		/* if file is locked */
+	File*	next;		/* in cp->flist */
+	Filsys*	fs;
+	Off	addr;
+	long	slot;		/* ordinal # of Dentry with a directory block */
+	Off	lastra;		/* read ahead address */
+	ulong	fid;
+	Userid	uid;
+	Auth	*auth;
+	char	open;
+		#define	FREAD	1
+		#define	FWRITE	2
+		#define	FREMOV	4
+
+	Off	doffset;	/* directory reading */
+	ulong	dvers;
+	long	dslot;
+};
+
+struct	Wpath
+{
+	Wpath*	up;		/* pointer upwards in path */
+	Off	addr;		/* directory entry addr */
+	long	slot;		/* directory entry slot */
+	short	refs;		/* number of files using this structure */
+};
+
+struct	Iobuf
+{
+	QLock;
+	Device*	dev;
+	Iobuf*	fore;		/* for lru */
+	Iobuf*	back;		/* for lru */
+	char*	iobuf;		/* only active while locked */
+	char*	xiobuf;		/* "real" buffer pointer */
+	Off	addr;
+	int	flags;
+};
+
+struct	Uid
+{
+	Userid	uid;		/* user id */
+	Userid	lead;		/* leader of group */
+	Userid	*gtab;		/* group table */
+	int	ngrp;		/* number of group entries */
+	char	name[NAMELEN];	/* user name */
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct	Fbuf
+{
+	Off	nfree;
+	Off	free[FEPERBUF];
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct	Superb
+{
+	Fbuf	fbuf;
+	Super1;
+};
+
+struct	Conf
+{
+	ulong	nmach;		/* processors */
+	ulong	mem;		/* total physical bytes of memory */
+	ulong	nuid;		/* distinct uids */
+	ulong	nserve;		/* server processes */
+	ulong	nfile;		/* number of fid -- system wide */
+	ulong	nwpath;		/* number of active paths, derived from nfile */
+	ulong	gidspace;	/* space for gid names -- derived from nuid */
+
+	ulong	nlgmsg;		/* number of large message buffers */
+	ulong	nsmmsg;		/* number of small message buffers */
+
+	Off	recovcw;	/* recover addresses */
+	Off	recovro;
+	Off	firstsb;
+	Off	recovsb;
+
+	ulong	configfirst;	/* configure before starting normal operation */
+	char	*confdev;
+	char	*devmap;	/* name of config->file device mapping file */
+
+	ulong	nauth;		/* number of Auth structs */
+	uchar	nodump;		/* no periodic dumps */
+	uchar	dumpreread;	/* read and compare in dump copy */
+};
+
+enum {
+	Mbmagic = 0xb0ffe3,
+};
+
+/*
+ * message buffers
+ * 2 types, large and small
+ */
+struct	Msgbuf
+{
+	ulong	magic;
+	short	count;
+	short	flags;
+		#define	LARGE	(1<<0)
+		#define	FREE	(1<<1)
+		#define BFREE	(1<<2)
+		#define BTRACE	(1<<7)
+	Chan*	chan;		/* file server conn within a net. conn */
+	Msgbuf*	next;
+	uintptr	param;		/* misc. use; keep Conn* here */
+
+	int	category;
+	uchar*	data;		/* rp or wp: current processing point */
+	uchar*	xdata;		/* base of allocation */
+};
+
+/*
+ * message buffer categories
+ */
+enum
+{
+	Mxxx		= 0,
+	Mbeth1,
+	Mbreply1,
+	Mbreply2,
+	Mbreply3,
+	Mbreply4,
+	MAXCAT,
+};
+
+enum { PRINTSIZE = 256 };
+
+struct
+{
+	Lock;
+	int	machs;
+	int	exiting;
+} active;
+
+struct	Command
+{
+	char*	arg0;
+	char*	help;
+	void	(*func)(int, char*[]);
+};
+
+struct	Flag
+{
+	char*	arg0;
+	char*	help;
+	ulong	flag;
+};
+
+struct	Rtc
+{
+	int	sec;
+	int	min;
+	int	hour;
+	int	mday;
+	int	mon;
+	int	year;
+};
+
+typedef struct
+{
+	/* constants during a given truncation */
+	Dentry	*d;
+	Iobuf	*p;			/* the block containing *d */
+	int	uid;
+	Off	newsize;
+	Off	lastblk;		/* last data block of file to keep */
+
+	/* variables */
+	Off	relblk;			/* # of current data blk within file */
+	int	pastlast;		/* have we walked past lastblk? */
+	int	err;
+} Truncstate;
+
+/*
+ * cw device
+ */
+
+/* DONT TOUCH, this is the disk structure */
+struct	Cache
+{
+	Off	maddr;		/* cache map addr */
+	Off	msize;		/* cache map size in buckets */
+	Off	caddr;		/* cache addr */
+	Off	csize;		/* cache size */
+	Off	fsize;		/* current size of worm */
+	Off	wsize;		/* max size of the worm */
+	Off	wmax;		/* highwater write */
+
+	Off	sbaddr;		/* super block addr */
+	Off	cwraddr;	/* cw root addr */
+	Off	roraddr;	/* dump root addr */
+
+	Timet	toytime;	/* somewhere convienent */
+	Timet	time;
+};
+
+/* DONT TOUCH, this is the disk structure */
+struct	Bucket
+{
+	long	agegen;		/* generator for ages in this bkt */
+	Centry	entry[CEPERBK];
+};
+
+/* DONT TOUCH, this is in disk structures */
+enum { Labmagic = 0xfeedfacedeadbeefULL, };
+
+/* DONT TOUCH, this is the disk structure */
+typedef struct Label Label;
+struct Label			/* label block on Devlworms, in last block */
+{
+	uvlong	magic;
+	ushort	ord;		/* side number within Juke */
+	char	service[64];	/* documentation only */
+};
+
+typedef struct Map Map;
+struct Map {
+	char	*from;
+	Device	*fdev;
+	char	*to;
+	Device	*tdev;
+	Map	*next;
+};
+
+/*
+ * scsi i/o
+ */
+enum
+{
+	SCSIread = 0,
+	SCSIwrite = 1,
+};
+
+/*
+ * Process states
+ */
+enum
+{
+	Dead = 0,
+	Moribund,
+	Zombie,
+	Ready,
+	Scheding,
+	Running,
+	Queueing,
+	Sending,
+	Recving,
+	MMUing,
+	Exiting,
+	Inwait,
+	Wakeme,
+	Broken,
+};
+
+/*
+ * devnone block numbers
+ */
+enum
+{
+	Cwio1	= 1,
+	Cwio2,
+	Cwxx1,
+	Cwxx2,
+	Cwxx3,
+	Cwxx4,
+	Cwdump1,
+	Cwdump2,
+	Cuidbuf,
+	Cckbuf,
+};
+
+/*
+ * error codes generated from the file server
+ */
+enum
+{
+	Ebadspc = 1,
+	Efid,
+	Echar,
+	Eopen,
+	Ecount,
+	Ealloc,
+	Eqid,
+	Eaccess,
+	Eentry,
+	Emode,
+	Edir1,
+	Edir2,
+	Ephase,
+	Eexist,
+	Edot,
+	Eempty,
+	Ebadu,
+	Enoattach,
+	Ewstatb,
+	Ewstatd,
+	Ewstatg,
+	Ewstatl,
+	Ewstatm,
+	Ewstato,
+	Ewstatp,
+	Ewstatq,
+	Ewstatu,
+	Ewstatv,
+	Ename,
+	Ewalk,
+	Eronly,
+	Efull,
+	Eoffset,
+	Elocked,
+	Ebroken,
+	Eauth,
+	Eauth2,
+	Efidinuse,
+	Etoolong,
+	Econvert,
+	Eversion,
+	Eauthdisabled,
+	Eauthnone,
+	Eauthfile,
+	Eedge,
+	MAXERR
+};
+
+/*
+ * device types
+ */
+enum
+{
+	Devnone	= 0,
+	Devcon,			/* console */
+	Devwren,		/* disk drive */
+	Devworm,		/* scsi optical drive */
+	Devlworm,		/* scsi optical drive (labeled) */
+	Devfworm,		/* fake read-only device */
+	Devjuke,		/* scsi jukebox */
+	Devcw,			/* cache with worm */
+	Devro,			/* readonly worm */
+	Devmcat,		/* multiple cat devices */
+	Devmlev,		/* multiple interleave devices */
+	Devnet,			/* network connection */
+	Devpart,		/* partition */
+	Devfloppy,		/* floppy drive */
+	Devswab,		/* swab data between mem and device */
+	Devmirr,		/* mirror devices */
+	MAXDEV
+};
+
+/*
+ * tags on block
+ */
+/* DONT TOUCH, this is in disk structures */
+/* also, the order from Tdir to Tmaxind is exploited in indirck() & isdirty() */
+enum
+{
+	Tnone		= 0,
+	Tsuper,			/* the super block */
+#ifdef COMPAT32
+	Tdir,			/* directory contents */
+	Tind1,			/* points to blocks */
+	Tind2,			/* points to Tind1 */
+#else
+	Tdirold,
+	Tind1old,
+	Tind2old,
+#endif
+	Tfile,			/* file contents; also defined in disk.h */
+	Tfree,			/* in free list */
+	Tbuck,			/* cache fs bucket */
+	Tvirgo,			/* fake worm virgin bits */
+	Tcache,			/* cw cache things */
+	Tconfig,		/* configuration block */
+#ifndef COMPAT32
+	/* Tdir & indirect blocks are last, to allow for greater depth */
+	Tdir,			/* directory contents */
+	Tind1,			/* points to blocks */
+	Tind2,			/* points to Tind1 */
+	Tind3,			/* points to Tind2 */
+	Tind4,			/* points to Tind3 */
+	Maxtind,
+#endif
+	/* gap for more indirect block depth in future */
+	Tlabel = 32,		/* Devlworm label in last block */
+	MAXTAG,
+
+#ifdef COMPAT32
+	Tmaxind = Tind2,
+#else
+	Tmaxind = Maxtind - 1,
+#endif
+};
+
+/*
+ * flags to getbuf
+ */
+enum
+{
+	Brd	= (1<<0),	/* read the block if miss */
+	Bprobe	= (1<<1),	/* return null if miss */
+	Bmod	= (1<<2),	/* buffer is dirty, needs writing */
+	Bimm	= (1<<3),	/* write immediately on putbuf */
+	Bres	= (1<<4),	/* reserved, never renamed */
+};
+
+Conf	conf;
+Cons	cons;
+
+#pragma	varargck	type	"Z"	Device*
+#pragma	varargck	type	"T"	Timet
+#pragma	varargck	type	"I"	uchar*
+#pragma	varargck	type	"E"	uchar*
+#pragma	varargck	type	"G"	int
+
+extern char	*annstrs[];
+extern Biobuf	bin;
+extern Map	*devmap;
+extern int	(*fsprotocol[])(Msgbuf*);

+ 246 - 0
sys/src/cmd/cwfs/portfns.h

@@ -0,0 +1,246 @@
+void	accessdir(Iobuf*, Dentry*, int, int);
+void	addfree(Device*, Off, Superb*);
+void	arpstart(void);
+void	arginit(void);
+char*	authaname(Auth*);
+void	authinit(void);
+void	authfree(Auth*);
+Auth*	authnew(char*, char*);
+int	authread(File*, uchar*, int);
+int	authuid(Auth*);
+char*	authuname(Auth*);
+int	authwrite(File*, uchar*, int);
+void	cdiag(char*, int);
+int	cnumb(void);
+Device*	config(void);
+int	rawchar(int);
+Off	bufalloc(Device*, int, long, int);
+void	buffree(Device*, Off, int, Truncstate *);
+int	byuid(void*, void*);
+int	canlock(Lock*);
+int	canqlock(QLock*);
+void	cfsdump(Filsys*);
+void	chanhangup(Chan *cp, char *msg, int dolock);
+Chan*	fs_chaninit(int, int, int);
+void	cmd_check(int, char*[]);
+void	cmd_users(int, char*[]);
+void	cmd_newuser(int, char*[]);
+void	cmd_netdb(int, char*[]);
+void	cmd_printconf(int, char*[]);
+void	cmd_wormreset(int, char *[]);
+int	checkname(char*);
+int	checktag(Iobuf*, int, Off);
+int	cksum(void*, int, int);
+int	cksum0(int, int);
+void	cyclstart(void);
+void	dotrace(int);
+int	conschar(void);
+void	consinit(void (*)(char*, int));
+void	consstart(int);
+void	consserve(void);
+int	conslock(void);
+int	con_attach(int, char*, char*);
+int	con_clone(int, int);
+int	con_create(int, char*, int, int, long, int);
+int	con_clri(int);
+int	con_fstat(int);
+int	con_open(int, int);
+int	con_read(int, char*, Off, int);
+int	con_remove(int);
+int	con_session(void);
+int	con_walk(int, char*);
+int	con_write(int, char*, Off, int);
+int	cwgrow(Device*, Superb*, int);
+int	cwfree(Device*, Off);
+void	cwinit(Device*);
+Off	cwraddr(Device*);
+int	cwread(Device*, Off, void*);
+void	cwream(Device*);
+void	cwrecover(Device*);
+Off	cwsaddr(Device*);
+Devsize	cwsize(Device*);
+int	cwwrite(Device*, Off, void*);
+char*	dataof(char *file);
+void	datestr(char*, Timet);
+Off	dbufread(Iobuf*, Dentry*, Off, Off, int);
+void	delay(int);
+Filsys* dev2fs(Device *dev);
+int	devcmpr(Device*, Device*);
+void	devream(Device*, int);
+void	devrecover(Device*);
+void	devinit(Device*);
+int	devread(Device*, Off, void*);
+Devsize	devsize(Device*);
+int	devwrite(Device*, Off, void*);
+Iobuf*	dnodebuf(Iobuf*, Dentry*, Off, int, int);
+Iobuf*	dnodebuf1(Iobuf*, Dentry*, Off, int, int);
+int	doremove(File*, int);
+void	dtrunc(Iobuf*, Dentry*, int);
+int	dtrunclen(Iobuf *p, Dentry *, Off newsize, int uid);
+int	dumpblock(Device*);
+void	netinit(void);
+void	netstart(void);
+void	exit(void);
+void	fileinit(Chan*);
+File*	filep(Chan*, ulong, int);
+void	firmware(void);
+int	fname(char*);
+int	fpair(char*, char*);
+void	formatinit(void);
+int	fread(void*, int);
+void	freefp(File*);
+void	freewp(Wpath*);
+Filsys*	fsstr(char*);
+Devsize	fwormsize(Device*);
+void	fwormream(Device*);
+void	fworminit(Device*);
+int	fwormread(Device*, Off, void*);
+int	fwormwrite(Device*, Off, void*);
+char*	getauthlist(void);
+Iobuf*	getbuf(Device*, Off, int);
+char*	getwrd(char*, char*);
+int	getc(void);
+Dentry*	getdir(Iobuf*, int);
+Chan*	getlcp(uchar*, long);
+Off	getraddr(Device*);
+void	hexdump(void*, int);
+int	iaccess(File*, Dentry*, int);
+void*	ialloc(ulong, int);
+Off	ibbpow(int);
+Off	ibbpowsum(int);
+Device*	iconfig(char *);
+Off	indfetch(Device*, Off, Off, Off , int, int, int);
+int	ingroup(int, int);
+int	inh(int, uchar*);
+Devsize	inqsize(char *file);
+void	iobufinit(void);
+void*	iobufmap(Iobuf*);
+void	iobufunmap(Iobuf*);
+int	iobufql(QLock*);
+int	jukeread(Device*, Off, void*);
+int	jukewrite(Device*, Off, void*);
+void	jukeinit(Device*);
+void	jukeream(Device*);
+void	jukerecover(Device*);
+Off	jukesaddr(Device*);
+Devsize	jukesize(Device*);
+void	kbdchar(int);
+void	lights(int, int);
+void	launchinit(void);
+void	localconfinit(void);
+int	leadgroup(int, int);
+void	lock(Lock*);
+void	lockinit(void);
+void	machinit(void);
+Msgbuf*	mballoc(int, Chan*, int);
+void	mbinit(void);
+void	mbfree(Msgbuf*);
+ulong	meminit(void);
+Iobuf*	movebuf(Iobuf*);
+void	mcatinit(Device*);
+int	mcatread(Device*, Off, void*);
+Devsize	mcatsize(Device*);
+int	mcatwrite(Device*, Off, void*);
+void	mirrinit(Device*);
+int	mirrread(Device*, Off, void*);
+Devsize	mirrsize(Device*);
+int	mirrwrite(Device*, Off, void*);
+void	mkqid(Qid*, Dentry*, int);
+int	mkqidcmp(Qid*, Dentry*);
+void	mkqid9p1(Qid9p1*, Qid*);
+void	mkqid9p2(Qid*, Qid9p1*, int);
+void	mlevinit(Device*);
+int	mlevread(Device*, Off, void*);
+Devsize	mlevsize(Device*);
+int	mlevwrite(Device*, Off, void*);
+int	nametokey(char*, char*);
+File*	newfp(void);
+void	newscsi(Device *d, Scsi *sc);
+Queue*	newqueue(int, char*);
+void	newstart(void);
+Wpath*	newwp(void);
+Auth*	newauth(void);
+int	nvrcheck(void);
+char*	nvrgetconfig(void);
+int	nvrsetconfig(char*);
+int	walkto(char*);
+vlong	number(char*, int, int);
+int	okay(char *quest);
+void	online(void);
+void	panic(char*, ...);
+void	partinit(Device*);
+int	partread(Device*, Off, void*);
+Devsize	partsize(Device*);
+int	partwrite(Device*, Off, void*);
+void	prdate(void);
+void	preread(Device*, Off);
+void	prflush(void);
+int	prime(vlong);
+void	printinit(void);
+void	procinit(void);
+void	procsetname(char *fmt, ...);
+void	putbuf(Iobuf*);
+void	putstrn(char *str, int n);
+Off	qidpathgen(Device*);
+void	qlock(QLock*);
+void*	querychanger(Device *);
+void	qunlock(QLock*);
+void	rahead(void *);
+void	ream(Filsys*);
+void*	fs_recv(Queue*, int);
+void	rootream(Device*, Off);
+int	roread(Device*, Off, void*);
+void	rstate(Chan*, int);
+Timet	rtc2sec(Rtc *);
+void	sched(void);
+void	schedinit(void);
+int	scsiio(Device*, int, uchar*, int, void*, int);
+void	scsiinit(void);
+Off	scsiread(int, void*, long);
+Devsize	scsiseek(int, Devsize);
+Off	scsiwrite(int, void*, long);
+char*	sdof(Device*);
+void	sec2rtc(Timet, Rtc *);
+void	fs_send(Queue*, void*);
+void	serve(void *);
+int	serve9p1(Msgbuf*);
+int	serve9p2(Msgbuf*);
+void	settag(Iobuf*, int, long);
+void	settime(Timet);
+void	startprint(void);
+int	strtouid(char*);
+Off	superaddr(Device*);
+void	superream(Device*, Off);
+void	swab(void*, int);
+void	swab2(void *c);
+void	swab8(void *c);
+void	sync(char*);
+int	syncblock(void);
+void	sysinit(void);
+int	testconfig(char *s);
+int	Tfmt(Fmt*);
+Timet	nextime(Timet, int, int);
+Tlock*	tlocked(Iobuf*, Dentry*);
+Timet	toytime(void);
+Timet	rtctime(void);
+void	setrtc(Timet);
+void	uidtostr(char*, int, int);
+Uid*	uidpstr(char*);
+void	unlock(Lock*);
+void	newproc(void(*)(void *), void*, char*);
+void	wormcopy(void *);
+void	wormprobe(void);
+void	synccopy(void);
+long	wormsearch(Device*, int, long, long);
+int	wormread(Device*, Off, void*);
+Devsize	wormsize(Device*);
+Devsize	wormsizeside(Device *, int side);
+void	wormsidestarts(Device *dev, int side, Sidestarts *stp);
+int	wormwrite(Device*, Off, void*);
+void	wreninit(Device*);
+int	wrenread(Device*, Off, void*);
+Devsize	wrensize(Device*);
+int	wrenwrite(Device*, Off, void*);
+void	cmd_exec(char*);
+void	cmd_install(char*, char*, void (*)(int, char*[]));
+ulong	flag_install(char*, char*);

+ 61 - 0
sys/src/cmd/cwfs/portmkfile

@@ -0,0 +1,61 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin
+TARG=cwfs$FS
+OFILES=\
+	9p1.$O\
+	9p1lib.$O\
+	9p2.$O\
+	auth.$O\
+	chk.$O\
+	con.$O\
+	config.$O\
+	console.$O\
+	cw.$O\
+	data.$O\
+	dentry.$O\
+	fworm.$O\
+	iobuf.$O\
+	juke.$O\
+	main.$O\
+	malloc.$O\
+	mworm.$O\
+	net.$O\
+	pc.$O\
+	scsi.$O\
+	sub.$O\
+	time.$O\
+	uidgid.$O\
+	wren.$O\
+
+HFILES=\
+	../32bit.h\
+	../64bit.h\
+	../9p1.h\
+	../all.h\
+	dat.h\
+	../io.h\
+	../portdat.h\
+	../portfns.h\
+
+# SRC=${OBJ:%.$O=../%.c}
+CFLAGS=$CFLAGS -I..
+
+default:V: $O.cwfs$FS
+
+</sys/src/cmd/mkmany
+
+%.$O:	../%.c
+	$CC $CFLAGS ../$stem.c
+%.$O:	../%.s
+	$AS $AFLAGS ../$stem.s
+
+acid: ${OFILES:%.acid=../%.c}
+
+$O.cwfs$FS: conf.c $OFILES
+	$CC $CFLAGS -DDATE'='`{date -n} conf.c
+	$LD -o $target $OFILES conf.$O
+	size $target
+
+test:V: 8.cwfs$FS
+	cputype=debug $prereq -a 'tcp!*!1234' </dev/null

+ 423 - 0
sys/src/cmd/cwfs/scsi.c

@@ -0,0 +1,423 @@
+/*
+ * interface to scsi devices via scsi(2) to sd(3),
+ * which does not implement LUNs.
+ */
+#include "all.h"
+#include "io.h"
+
+enum {
+	Ninquiry	= 255,
+	Nsense		= 255,
+
+	CMDtest		= 0x00,
+	CMDreqsense	= 0x03,
+	CMDread6	= 0x08,
+	CMDwrite6	= 0x0A,
+	CMDinquiry	= 0x12,
+	CMDstart	= 0x1B,
+	CMDread10	= 0x28,
+	CMDwrite10	= 0x2A,
+};
+
+typedef struct {
+	Target	target[NTarget];
+} Ctlr;
+static Ctlr scsictlr[MaxScsi];
+
+extern int scsiverbose;
+
+void
+scsiinit(void)
+{
+	Ctlr *ctlr;
+	int ctlrno, targetno;
+	Target *tp;
+
+	scsiverbose = 1;
+	for(ctlrno = 0; ctlrno < MaxScsi; ctlrno++){
+		ctlr = &scsictlr[ctlrno];
+		memset(ctlr, 0, sizeof(Ctlr));
+		for(targetno = 0; targetno < NTarget; targetno++){
+			tp = &ctlr->target[targetno];
+
+			qlock(tp);
+			qunlock(tp);
+			sprint(tp->id, "scsictlr#%d.%d", ctlrno, targetno);
+
+			tp->ctlrno = ctlrno;
+			tp->targetno = targetno;
+			tp->inquiry = malloc(Ninquiry);
+			tp->sense = malloc(Nsense);
+		}
+	}
+}
+
+static uchar lastcmd[16];
+static int lastcmdsz;
+
+static int
+sense2stcode(uchar *sense)
+{
+	switch(sense[2] & 0x0F){
+	case 6:						/* unit attention */
+		/*
+		 * 0x28 - not ready to ready transition,
+		 *	  medium may have changed.
+		 * 0x29 - power on, RESET or BUS DEVICE RESET occurred.
+		 */
+		if(sense[12] != 0x28 && sense[12] != 0x29)
+			return STcheck;
+		/*FALLTHROUGH*/
+	case 0:						/* no sense */
+	case 1:						/* recovered error */
+		return STok;
+	case 8:						/* blank data */
+		return STblank;
+	case 2:						/* not ready */
+		if(sense[12] == 0x3A)			/* medium not present */
+			return STcheck;
+		/*FALLTHROUGH*/
+	default:
+		/*
+		 * If unit is becoming ready, rather than not ready,
+		 * then wait a little then poke it again; should this
+		 * be here or in the caller?
+		 */
+		if((sense[12] == 0x04 && sense[13] == 0x01)) {
+			// delay(500);
+			// scsitest(tp, lun);
+			fprint(2, "sense2stcode: unit becoming ready\n");
+			return STcheck;			/* not exactly right */
+		}
+		return STcheck;
+	}
+}
+
+/*
+ * issue the SCSI command via scsi(2).  lun must already be in cmd[1].
+ */
+static int
+doscsi(Target* tp, int rw, uchar* cmd, int cbytes, void* data, int* dbytes)
+{
+	int lun, db = 0;
+	uchar reqcmd[6], reqdata[Nsense], dummy[1];
+	Scsi *sc;
+
+	sc = tp->sc;
+	if (sc == nil)
+		panic("doscsi: nil tp->sc");
+	lun = cmd[1] >> 5;	/* save lun in case we need it for reqsense */
+
+	/* cope with zero arguments */
+	if (dbytes != nil)
+		db = *dbytes;
+	if (data == nil)
+		data = dummy;
+
+	if (scsi(sc, cmd, cbytes, data, db, rw) >= 0)
+		return STok;
+
+	/* cmd failed, get whatever sense data we can */
+	memset(reqcmd, 0, sizeof reqcmd);
+	reqcmd[0] = CMDreqsense;
+	reqcmd[1] = lun<<5;
+	reqcmd[4] = Nsense;
+	memset(reqdata, 0, sizeof reqdata);
+	if (scsicmd(sc, reqcmd, sizeof reqcmd, reqdata, sizeof reqdata,
+	    Sread) < 0)
+		return STharderr;
+
+	/* translate sense data to ST* codes */
+	return sense2stcode(reqdata);
+}
+
+static int
+scsiexec(Target* tp, int rw, uchar* cmd, int cbytes, void* data, int* dbytes)
+{
+	int s;
+
+	/*
+	 * issue the SCSI command.  lun must already be in cmd[1].
+	 */
+	s = doscsi(tp, rw, cmd, cbytes, data, dbytes);
+	switch(s){
+
+	case STcheck:
+		memmove(lastcmd, cmd, cbytes);
+		lastcmdsz = cbytes;
+		/*FALLTHROUGH*/
+
+	default:
+		/*
+		 * It's more complicated than this.  There are conditions which
+		 * are 'ok' but for which the returned status code is not 'STok'.
+		 * Also, not all conditions require a reqsense, there may be a
+		 * need to do a reqsense here when necessary and making it
+		 * available to the caller somehow.
+		 *
+		 * Later.
+		 */
+		break;
+	}
+
+	return s;
+}
+
+static int
+scsitest(Target* tp, char lun)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = CMDtest;
+	cmd[1] = lun<<5;
+	return scsiexec(tp, SCSIread, cmd, sizeof cmd, 0, 0);
+
+}
+
+static int
+scsistart(Target* tp, char lun, int start)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = CMDstart;
+	cmd[1] = lun<<5;
+	if(start)
+		cmd[4] = 1;
+	return scsiexec(tp, SCSIread, cmd, sizeof cmd, 0, 0);
+}
+
+static int
+scsiinquiry(Target* tp, char lun, int* nbytes)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = CMDinquiry;
+	cmd[1] = lun<<5;
+	*nbytes = Ninquiry;
+	cmd[4] = *nbytes;
+	return scsiexec(tp, SCSIread, cmd, sizeof cmd, tp->inquiry, nbytes);
+}
+
+static char *key[] =
+{
+	"no sense",
+	"recovered error",
+	"not ready",
+	"medium error",
+	"hardware error",
+	"illegal request",
+	"unit attention",
+	"data protect",
+	"blank check",
+	"vendor specific",
+	"copy aborted",
+	"aborted command",
+	"equal",
+	"volume overflow",
+	"miscompare",
+	"reserved"
+};
+
+static int
+scsireqsense(Target* tp, char lun, int* nbytes, int quiet)
+{
+	char *s;
+	int n, status, try;
+	uchar cmd[6], *sense;
+
+	sense = tp->sense;
+	for(try = 0; try < 20; try++) {
+		memset(cmd, 0, sizeof cmd);
+		cmd[0] = CMDreqsense;
+		cmd[1] = lun<<5;
+		cmd[4] = Ninquiry;
+		memset(sense, 0, Ninquiry);
+
+		*nbytes = Ninquiry;
+		status = scsiexec(tp, SCSIread, cmd, sizeof cmd, sense, nbytes);
+		if(status != STok)
+			return status;
+		*nbytes = sense[0x07]+8;
+
+		switch(sense[2] & 0x0F){
+		case 6:					/* unit attention */
+			/*
+			 * 0x28 - not ready to ready transition,
+			 *	  medium may have changed.
+			 * 0x29 - power on, RESET or BUS DEVICE RESET occurred.
+			 */
+			if(sense[12] != 0x28 && sense[12] != 0x29)
+				goto buggery;
+			/*FALLTHROUGH*/
+		case 0:					/* no sense */
+		case 1:					/* recovered error */
+			return STok;
+		case 8:					/* blank data */
+			return STblank;
+		case 2:					/* not ready */
+			if(sense[12] == 0x3A)		/* medium not present */
+				goto buggery;
+			/*FALLTHROUGH*/
+		default:
+			/*
+			 * If unit is becoming ready, rather than not ready,
+			 * then wait a little then poke it again; should this
+			 * be here or in the caller?
+			 */
+			if((sense[12] == 0x04 && sense[13] == 0x01)){
+				delay(500);
+				scsitest(tp, lun);
+				break;
+			}
+			goto buggery;
+		}
+	}
+
+buggery:
+	if(quiet == 0){
+		s = key[sense[2]&0x0F];
+		print("%s: reqsense: '%s' code #%2.2ux #%2.2ux\n",
+			tp->id, s, sense[12], sense[13]);
+		print("%s: byte 2: #%2.2ux, bytes 15-17: #%2.2ux #%2.2ux #%2.2ux\n",
+			tp->id, sense[2], sense[15], sense[16], sense[17]);
+		print("lastcmd (%d): ", lastcmdsz);
+		for(n = 0; n < lastcmdsz; n++)
+			print(" #%2.2ux", lastcmd[n]);
+		print("\n");
+	}
+
+	return STcheck;
+}
+
+static Target*
+scsitarget(Device* d)
+{
+	int ctlrno, targetno;
+
+	ctlrno = d->wren.ctrl;
+	if(ctlrno < 0 || ctlrno >= MaxScsi /* || scsictlr[ctlrno].io == nil */)
+		return 0;
+	targetno = d->wren.targ;
+	if(targetno < 0 || targetno >= NTarget)
+		return 0;
+	return &scsictlr[ctlrno].target[targetno];
+}
+
+static void
+scsiprobe(Device* d)
+{
+	Target *tp;
+	int nbytes, s;
+	uchar *sense;
+	int acount;
+
+	if((tp = scsitarget(d)) == 0)
+		panic("scsiprobe: device = %Z", d);
+
+	acount = 0;
+again:
+	s = scsitest(tp, d->wren.lun);
+	if(s < STok){
+		print("%s: test, status %d\n", tp->id, s);
+		return;
+	}
+
+	/*
+	 * Determine if the drive exists and is not ready or
+	 * is simply not responding.
+	 * If the status is OK but the drive came back with a 'power on' or
+	 * 'reset' status, try the test again to make sure the drive is really
+	 * ready.
+	 * If the drive is not ready and requires intervention, try to spin it
+	 * up.
+	 */
+	s = scsireqsense(tp, d->wren.lun, &nbytes, acount);
+	sense = tp->sense;
+	switch(s){
+	case STok:
+		if ((sense[2] & 0x0F) == 0x06 &&
+		    (sense[12] == 0x28 || sense[12] == 0x29))
+			if(acount == 0){
+				acount = 1;
+				goto again;
+			}
+		break;
+	case STcheck:
+		if((sense[2] & 0x0F) == 0x02){
+			if(sense[12] == 0x3A)
+				break;
+			if(sense[12] == 0x04 && sense[13] == 0x02){
+				print("%s: starting...\n", tp->id);
+				if(scsistart(tp, d->wren.lun, 1) == STok)
+					break;
+				s = scsireqsense(tp, d->wren.lun, &nbytes, 0);
+			}
+		}
+		/*FALLTHROUGH*/
+	default:
+		print("%s: unavailable, status %d\n", tp->id, s);
+		return;
+	}
+
+	/*
+	 * Inquire to find out what the device is.
+	 * Hardware drivers may need some of the info.
+	 */
+	s = scsiinquiry(tp, d->wren.lun, &nbytes);
+	if(s != STok) {
+		print("%s: inquiry failed, status %d\n", tp->id, s);
+		return;
+	}
+	print("%s: %s\n", tp->id, (char*)tp->inquiry+8);
+	tp->ok = 1;
+}
+
+int
+scsiio(Device* d, int rw, uchar* cmd, int cbytes, void* data, int dbytes)
+{
+	Target *tp;
+	int e, nbytes, s;
+
+	if((tp = scsitarget(d)) == 0)
+		panic("scsiio: device = %Z", d);
+
+	qlock(tp);
+	if(tp->ok == 0)
+		scsiprobe(d);
+	qunlock(tp);
+
+	s = STinit;
+	for(e = 0; e < 10; e++){
+		for(;;){
+			nbytes = dbytes;
+			s = scsiexec(tp, rw, cmd, cbytes, data, &nbytes);
+			if(s == STok)
+				break;
+			s = scsireqsense(tp, d->wren.lun, &nbytes, 0);
+			if(s == STblank && rw == SCSIread) {
+				memset(data, 0, dbytes);
+				return STok;
+			}
+			if(s != STok)
+				break;
+		}
+		if(s == STok)
+			break;
+	}
+	if(e)
+		print("%s: retry %d cmd #%x\n", tp->id, e, cmd[0]);
+	return s;
+}
+
+void
+newscsi(Device *d, Scsi *sc)
+{
+	Target *tp;
+
+	if((tp = scsitarget(d)) == nil)
+		panic("newscsi: device = %Z", d);
+	tp->sc = sc;		/* connect Target to Scsi */
+}

+ 1537 - 0
sys/src/cmd/cwfs/sub.c

@@ -0,0 +1,1537 @@
+#include "all.h"
+#include "io.h"
+
+enum {
+	Slop = 256,	/* room at the start of a message buf for proto hdrs */
+};
+
+Filsys*
+fsstr(char *p)
+{
+	Filsys *fs;
+
+	for(fs=filsys; fs->name; fs++)
+		if(strcmp(fs->name, p) == 0)
+			return fs;
+	return 0;
+}
+
+Filsys*
+dev2fs(Device *dev)
+{
+	Filsys *fs;
+
+	for(fs=filsys; fs->name; fs++)
+		if(fs->dev == dev)
+			return fs;
+	return 0;
+}
+
+/*
+ * allocate 'count' contiguous channels
+ * of type 'type' and return pointer to base
+ */
+Chan*
+fs_chaninit(int type, int count, int data)
+{
+	uchar *p;
+	Chan *cp, *icp;
+	int i;
+
+	p = malloc(count * (sizeof(Chan)+data));
+	icp = (Chan*)p;
+	for(i = 0; i < count; i++) {
+		cp = (Chan*)p;
+		cp->next = chans;
+		chans = cp;
+		cp->type = type;
+		cp->chan = cons.chano;
+		cons.chano++;
+		strncpy(cp->whoname, "<none>", sizeof cp->whoname);
+		wlock(&cp->reflock);
+		wunlock(&cp->reflock);
+		rlock(&cp->reflock);
+		runlock(&cp->reflock);
+
+		p += sizeof(Chan);
+		if(data){
+			cp->pdata = p;
+			p += data;
+		}
+	}
+	return icp;
+}
+
+void
+fileinit(Chan *cp)
+{
+	File *f, *prev;
+	Tlock *t;
+	int h;
+
+loop:
+	lock(&flock);
+	for (h=0; h < nelem(flist); h++)
+		for (prev=0, f = flist[h]; f; prev=f, f=f->next) {
+			if(f->cp != cp)
+				continue;
+			if(prev) {
+				prev->next = f->next;
+				f->next = flist[h];
+				flist[h] = f;
+			}
+			flist[h] = f->next;
+			unlock(&flock);
+
+			qlock(f);
+			if(t = f->tlock) {
+				if(t->file == f)
+					t->time = 0;	/* free the lock */
+				f->tlock = 0;
+			}
+			if(f->open & FREMOV)
+				doremove(f, 0);
+			freewp(f->wpath);
+			f->open = 0;
+			authfree(f->auth);
+			f->auth = 0;
+			f->cp = 0;
+			qunlock(f);
+			goto loop;
+		}
+	unlock(&flock);
+}
+
+enum { NOFID = (ulong)~0 };
+
+/*
+ * returns a locked file structure
+ */
+File*
+filep(Chan *cp, ulong fid, int flag)
+{
+	File *f;
+	int h;
+
+	if(fid == NOFID)
+		return 0;
+
+	h = (long)cp + fid;
+	if(h < 0)
+		h = ~h;
+	h %= nelem(flist);
+
+loop:
+	lock(&flock);
+	for(f=flist[h]; f; f=f->next)
+		if(f->fid == fid && f->cp == cp){
+			/*
+			 * Already in use is an error
+			 * when called from attach or clone (walk
+			 * in 9P2000). The console uses FID[12] and
+			 * never clunks them so catch that case.
+			 */
+			if(flag == 0 || cp == cons.chan)
+				goto out;
+			unlock(&flock);
+			return 0;
+		}
+
+	if(flag) {
+		f = newfp();
+		if(f) {
+			f->fid = fid;
+			f->cp = cp;
+			f->wpath = 0;
+			f->tlock = 0;
+			f->doffset = 0;
+			f->dslot = 0;
+			f->auth = 0;
+			f->next = flist[h];
+			flist[h] = f;
+			goto out;
+		}
+	}
+	unlock(&flock);
+	return 0;
+
+out:
+	unlock(&flock);
+	qlock(f);
+	if(f->fid == fid && f->cp == cp)
+		return f;
+	qunlock(f);
+	goto loop;
+}
+
+/*
+ * always called with flock locked
+ */
+File*
+newfp(void)
+{
+	static int first;
+	File *f;
+	int start, i;
+
+	i = first;
+	start = i;
+	do {
+		f = &files[i];
+		i++;
+		if(i >= conf.nfile)
+			i = 0;
+		if(f->cp)
+			continue;
+		first = i;
+		return f;
+	} while(i != start);
+
+	print("out of files\n");
+	return 0;
+}
+
+void
+freefp(File *fp)
+{
+	Chan *cp;
+	File *f, *prev;
+	int h;
+
+	if(!fp || !(cp = fp->cp))
+		return;
+
+	h = (long)cp + fp->fid;
+	if(h < 0)
+		h = ~h;
+	h %= nelem(flist);
+
+	lock(&flock);
+	for(prev=0,f=flist[h]; f; prev=f,f=f->next)
+		if(f == fp) {
+			if(prev)
+				prev->next = f->next;
+			else
+				flist[h] = f->next;
+			break;
+		}
+	fp->cp = 0;
+	unlock(&flock);
+}
+
+int
+iaccess(File *f, Dentry *d, int m)
+{
+	/* uid none gets only other permissions */
+	if(f->uid != 0) {
+		/*
+		 * owner
+		 */
+		if(f->uid == d->uid)
+			if((m<<6) & d->mode)
+				return 0;
+		/*
+		 * group membership
+		 */
+		if(ingroup(f->uid, d->gid))
+			if((m<<3) & d->mode)
+				return 0;
+	}
+
+	/*
+	 * other
+	 */
+	if(m & d->mode) {
+		if((d->mode & DDIR) && (m == DEXEC))
+			return 0;
+		if(!ingroup(f->uid, 9999))
+			return 0;
+	}
+
+	/*
+	 * various forms of superuser
+	 */
+	if(wstatallow)
+		return 0;
+	if(duallow != 0 && duallow == f->uid)
+		if((d->mode & DDIR) && (m == DREAD || m == DEXEC))
+			return 0;
+
+	return 1;
+}
+
+Tlock*
+tlocked(Iobuf *p, Dentry *d)
+{
+	Tlock *t, *t1;
+	Off qpath;
+	Timet tim;
+	Device *dev;
+
+	tim = toytime();
+	qpath = d->qid.path;
+	dev = p->dev;
+
+again:
+	t1 = 0;
+	for(t=tlocks+NTLOCK-1; t>=tlocks; t--) {
+		if(t->qpath == qpath)
+		if(t->time >= tim)
+		if(t->dev == dev)
+			return nil;		/* its locked */
+		if(t1 != nil && t->time == 0)
+			t1 = t;			/* remember free lock */
+	}
+	if(t1 == 0) {
+		// reclaim old locks
+		lock(&tlocklock);
+		for(t=tlocks+NTLOCK-1; t>=tlocks; t--)
+			if(t->time < tim) {
+				t->time = 0;
+				t1 = t;
+			}
+		unlock(&tlocklock);
+	}
+	if(t1) {
+		lock(&tlocklock);
+		if(t1->time != 0) {
+			unlock(&tlocklock);
+			goto again;
+		}
+		t1->dev = dev;
+		t1->qpath = qpath;
+		t1->time = tim + TLOCK;
+		unlock(&tlocklock);
+	}
+	/* botch
+	 * out of tlock nodes simulates
+	 * a locked file
+	 */
+	return t1;
+}
+
+Wpath*
+newwp(void)
+{
+	static int si = 0;
+	int i;
+	Wpath *w, *sw, *ew;
+
+	i = si + 1;
+	if(i < 0 || i >= conf.nwpath)
+		i = 0;
+	si = i;
+	sw = &wpaths[i];
+	ew = &wpaths[conf.nwpath];
+	for(w=sw;;) {
+		w++;
+		if(w >= ew)
+			w = &wpaths[0];
+		if(w == sw) {
+			print("out of wpaths\n");
+			return 0;
+		}
+		if(w->refs)
+			continue;
+		lock(&wpathlock);
+		if(w->refs) {
+			unlock(&wpathlock);
+			continue;
+		}
+		w->refs = 1;
+		w->up = 0;
+		unlock(&wpathlock);
+		return w;
+	}
+
+}
+
+void
+freewp(Wpath *w)
+{
+	lock(&wpathlock);
+	for(; w; w=w->up)
+		w->refs--;
+	unlock(&wpathlock);
+}
+
+Off
+qidpathgen(Device *dev)
+{
+	Iobuf *p;
+	Superb *sb;
+	Off path;
+
+	p = getbuf(dev, superaddr(dev), Brd|Bmod);
+	if(!p || checktag(p, Tsuper, QPSUPER))
+		panic("newqid: super block");
+	sb = (Superb*)p->iobuf;
+	sb->qidgen++;
+	path = sb->qidgen;
+	putbuf(p);
+	return path;
+}
+
+/* truncating to length > 0 */
+static void
+truncfree(Truncstate *ts, Device *dev, int d, Iobuf *p, int i)
+{
+	int pastlast;
+	Off a;
+
+	pastlast = ts->pastlast;
+	a = ((Off *)p->iobuf)[i];
+	if (d > 0 || pastlast)
+		buffree(dev, a, d, ts);
+	if (pastlast) {
+		((Off *)p->iobuf)[i] = 0;
+		p->flags |= Bmod|Bimm;
+	} else if (d == 0 && ts->relblk == ts->lastblk)
+		ts->pastlast = 1;
+	if (d == 0)
+		ts->relblk++;
+}
+
+/*
+ * free the block at `addr' on dev.
+ * if it's an indirect block (d [depth] > 0),
+ * first recursively free all the blocks it names.
+ *
+ * ts->relblk is the block number within the file of this
+ * block (or the first data block eventually pointed to via
+ * this indirect block).
+ */
+void
+buffree(Device *dev, Off addr, int d, Truncstate *ts)
+{
+	Iobuf *p;
+	Off a;
+	int i, pastlast;
+
+	if(!addr)
+		return;
+	pastlast = (ts == nil? 1: ts->pastlast);
+	/*
+	 * if this is an indirect block, recurse and free any
+	 * suitable blocks within it (possibly via further indirect blocks).
+	 */
+	if(d > 0) {
+		d--;
+		p = getbuf(dev, addr, Brd);
+		if(p) {
+			if (ts == nil)		/* common case: create */
+				for(i=INDPERBUF-1; i>=0; i--) {
+					a = ((Off *)p->iobuf)[i];
+					buffree(dev, a, d, nil);
+				}
+			else			/* wstat truncation */
+				for (i = 0; i < INDPERBUF; i++)
+					truncfree(ts, dev, d, p, i);
+			putbuf(p);
+		}
+	}
+	if (!pastlast)
+		return;
+	/*
+	 * having zeroed the pointer to this block, add it to the free list.
+	 * stop outstanding i/o
+	 */
+	p = getbuf(dev, addr, Bprobe);
+	if(p) {
+		p->flags &= ~(Bmod|Bimm);
+		putbuf(p);
+	}
+	/*
+	 * dont put written worm
+	 * blocks into free list
+	 */
+	if(dev->type == Devcw) {
+		i = cwfree(dev, addr);
+		if(i)
+			return;
+	}
+	p = getbuf(dev, superaddr(dev), Brd|Bmod);
+	if(!p || checktag(p, Tsuper, QPSUPER))
+		panic("buffree: super block");
+	addfree(dev, addr, (Superb*)p->iobuf);
+	putbuf(p);
+}
+
+Off
+bufalloc(Device *dev, int tag, long qid, int uid)
+{
+	Iobuf *bp, *p;
+	Superb *sb;
+	Off a, n;
+
+	p = getbuf(dev, superaddr(dev), Brd|Bmod);
+	if(!p || checktag(p, Tsuper, QPSUPER)) {
+		print("bufalloc: super block\n");
+		if(p)
+			putbuf(p);
+		return 0;
+	}
+	sb = (Superb*)p->iobuf;
+
+loop:
+	n = --sb->fbuf.nfree;
+	sb->tfree--;
+	if(n < 0 || n >= FEPERBUF) {
+		print("bufalloc: %Z: bad freelist\n", dev);
+		n = 0;
+		sb->fbuf.free[0] = 0;
+	}
+	a = sb->fbuf.free[n];
+	if(n <= 0) {
+		if(a == 0) {
+			sb->tfree = 0;
+			sb->fbuf.nfree = 1;
+			if(dev->type == Devcw) {
+				n = uid;
+				if(n < 0 || n >= nelem(growacct))
+					n = 0;
+				growacct[n]++;
+				if(cwgrow(dev, sb, uid))
+					goto loop;
+			}
+			putbuf(p);
+			print("fs %Z full uid=%d\n", dev, uid);
+			return 0;
+		}
+		bp = getbuf(dev, a, Brd);
+		if(!bp || checktag(bp, Tfree, QPNONE)) {
+			if(bp)
+				putbuf(bp);
+			putbuf(p);
+			return 0;
+		}
+		sb->fbuf = *(Fbuf*)bp->iobuf;
+		putbuf(bp);
+	}
+
+	bp = getbuf(dev, a, Bmod);
+	memset(bp->iobuf, 0, RBUFSIZE);
+	settag(bp, tag, qid);
+	if(tag == Tind1 || tag == Tind2 || tag == Tdir)
+		bp->flags |= Bimm;
+	putbuf(bp);
+	putbuf(p);
+	return a;
+}
+
+/*
+ * what are legal characters in a name?
+ * only disallow control characters.
+ * a) utf avoids control characters.
+ * b) '/' may not be the separator
+ */
+int
+checkname(char *n)
+{
+	int i, c;
+
+	for(i=0; i<NAMELEN; i++) {
+		c = *n & 0xff;
+		if(c == 0) {
+			if(i == 0)
+				return 1;
+			memset(n, 0, NAMELEN-i);
+			return 0;
+		}
+		if(c <= 040)
+			return 1;
+		n++;
+	}
+	return 1;	/* too long */
+}
+
+void
+addfree(Device *dev, Off addr, Superb *sb)
+{
+	int n;
+	Iobuf *p;
+
+	n = sb->fbuf.nfree;
+	if(n < 0 || n > FEPERBUF)
+		panic("addfree: bad freelist");
+	if(n >= FEPERBUF) {
+		p = getbuf(dev, addr, Bmod|Bimm);
+		if(p == 0)
+			panic("addfree: getbuf");
+		*(Fbuf*)p->iobuf = sb->fbuf;
+		settag(p, Tfree, QPNONE);
+		putbuf(p);
+		n = 0;
+	}
+	sb->fbuf.free[n++] = addr;
+	sb->fbuf.nfree = n;
+	sb->tfree++;
+	if(addr >= sb->fsize)
+		sb->fsize = addr+1;
+}
+
+/*
+static int
+Yfmt(Fmt* fmt)
+{
+	Chan *cp;
+	char s[20];
+
+	cp = va_arg(fmt->args, Chan*);
+	sprint(s, "C%d.%.3d", cp->type, cp->chan);
+	return fmtstrcpy(fmt, s);
+}
+ */
+
+static int
+Zfmt(Fmt* fmt)
+{
+	Device *d;
+	int c, c1;
+	char s[100];
+
+	d = va_arg(fmt->args, Device*);
+	if(d == nil) {
+		sprint(s, "Z***");
+		goto out;
+	}
+	c = c1 = '\0';
+	switch(d->type) {
+	default:
+		sprint(s, "D%d", d->type);
+		break;
+	case Devwren:
+		c = 'w';
+		/* fallthrough */
+	case Devworm:
+		if (c == '\0')
+			c = 'r';
+		/* fallthrough */
+	case Devlworm:
+		if (c == '\0')
+			c = 'l';
+		if(d->wren.ctrl == 0 && d->wren.lun == 0)
+			sprint(s, "%c%d", c, d->wren.targ);
+		else
+			sprint(s, "%c%d.%d.%d", c, d->wren.ctrl, d->wren.targ,
+				d->wren.lun);
+		break;
+	case Devmcat:
+		c = '(';
+		c1 = ')';
+		/* fallthrough */
+	case Devmlev:
+		if (c == '\0') {
+			c = '[';
+			c1 = ']';
+		}
+		/* fallthrough */
+	case Devmirr:
+		if (c == '\0') {
+			c = '{';
+			c1 = '}';
+		}
+		if(d->cat.first == d->cat.last)
+			sprint(s, "%c%Z%c", c, d->cat.first, c1);
+		else if(d->cat.first->link == d->cat.last)
+			sprint(s, "%c%Z%Z%c", c, d->cat.first, d->cat.last, c1);
+		else
+			sprint(s, "%c%Z-%Z%c", c, d->cat.first, d->cat.last, c1);
+		break;
+	case Devro:
+		sprint(s, "o%Z%Z", d->ro.parent->cw.c, d->ro.parent->cw.w);
+		break;
+	case Devcw:
+		sprint(s, "c%Z%Z", d->cw.c, d->cw.w);
+		break;
+	case Devjuke:
+		sprint(s, "j%Z%Z", d->j.j, d->j.m);
+		break;
+	case Devfworm:
+		sprint(s, "f%Z", d->fw.fw);
+		break;
+	case Devpart:
+		sprint(s, "p(%Z)%ld.%ld", d->part.d, d->part.base, d->part.size);
+		break;
+	case Devswab:
+		sprint(s, "x%Z", d->swab.d);
+		break;
+	case Devnone:
+		sprint(s, "n");
+		break;
+	}
+out:
+	return fmtstrcpy(fmt, s);
+}
+
+static int
+Gfmt(Fmt* fmt)
+{
+	int t;
+	char *s;
+
+	t = va_arg(fmt->args, int);
+	s = "<badtag>";
+	if(t >= 0 && t < MAXTAG)
+		s = tagnames[t];
+	return fmtstrcpy(fmt, s);
+}
+
+void
+formatinit(void)
+{
+	quotefmtinstall();
+//	fmtinstall('Y', Yfmt);	/* print channels */
+	fmtinstall('Z', Zfmt);	/* print devices */
+	fmtinstall('G', Gfmt);	/* print tags */
+	fmtinstall('T', Tfmt);	/* print times */
+//	fmtinstall('E', eipfmt);	/* print ether addresses */
+	fmtinstall('I', eipfmt);	/* print ip addresses */
+}
+
+void
+rootream(Device *dev, Off addr)
+{
+	Iobuf *p;
+	Dentry *d;
+
+	p = getbuf(dev, addr, Bmod|Bimm);
+	memset(p->iobuf, 0, RBUFSIZE);
+	settag(p, Tdir, QPROOT);
+	d = getdir(p, 0);
+	strcpy(d->name, "/");
+	d->uid = -1;
+	d->gid = -1;
+	d->mode = DALLOC | DDIR |
+		((DREAD|DEXEC) << 6) |
+		((DREAD|DEXEC) << 3) |
+		((DREAD|DEXEC) << 0);
+	d->qid = QID9P1(QPROOT|QPDIR,0);
+	d->atime = time(nil);
+	d->mtime = d->atime;
+	d->muid = 0;
+	putbuf(p);
+}
+
+void
+superream(Device *dev, Off addr)
+{
+	Iobuf *p;
+	Superb *s;
+	Off i;
+
+	p = getbuf(dev, addr, Bmod|Bimm);
+	memset(p->iobuf, 0, RBUFSIZE);
+	settag(p, Tsuper, QPSUPER);
+
+	s = (Superb*)p->iobuf;
+	s->fstart = 2;
+	s->fsize = devsize(dev);
+	s->fbuf.nfree = 1;
+	s->qidgen = 10;
+	for(i = s->fsize-1; i >= addr+2; i--)
+		addfree(dev, i, s);
+	putbuf(p);
+}
+
+struct
+{
+	Lock;
+	Msgbuf	*smsgbuf;
+	Msgbuf	*lmsgbuf;
+} msgalloc;
+
+/*
+ * pre-allocate some message buffers at boot time.
+ * if this supply is exhausted, more will be allocated as needed.
+ */
+void
+mbinit(void)
+{
+	Msgbuf *mb;
+	Rabuf *rb;
+	int i;
+
+	lock(&msgalloc);
+	unlock(&msgalloc);
+	msgalloc.lmsgbuf = 0;
+	msgalloc.smsgbuf = 0;
+	for(i=0; i<conf.nlgmsg; i++) {
+		mb = malloc(sizeof(Msgbuf));
+		mb->magic = Mbmagic;
+		mb->xdata = malloc(LARGEBUF+Slop);
+		mb->flags = LARGE;
+		mbfree(mb);
+		cons.nlarge++;
+	}
+	for(i=0; i<conf.nsmmsg; i++) {
+		mb = malloc(sizeof(Msgbuf));
+		mb->magic = Mbmagic;
+		mb->xdata = malloc(SMALLBUF+Slop);
+		mb->flags = 0;
+		mbfree(mb);
+		cons.nsmall++;
+	}
+	memset(mballocs, 0, sizeof(mballocs));
+
+	lock(&rabuflock);
+	unlock(&rabuflock);
+	rabuffree = 0;
+	for(i=0; i<1000; i++) {
+		rb = malloc(sizeof(*rb));
+		rb->link = rabuffree;
+		rabuffree = rb;
+	}
+}
+
+Msgbuf*
+mballoc(int count, Chan *cp, int category)
+{
+	Msgbuf *mb;
+
+	lock(&msgalloc);
+	if(count > SMALLBUF) {
+		if(count > LARGEBUF)
+			panic("msgbuf count");
+		mb = msgalloc.lmsgbuf;
+		if(mb == nil) {
+			mb = malloc(sizeof(Msgbuf));
+			mb->xdata = malloc(LARGEBUF+Slop);
+			cons.nlarge++;
+		} else
+			msgalloc.lmsgbuf = mb->next;
+		mb->flags = LARGE;
+	} else {
+		mb = msgalloc.smsgbuf;
+		if(mb == nil) {
+			mb = malloc(sizeof(Msgbuf));
+			mb->xdata = malloc(SMALLBUF+Slop);
+			cons.nsmall++;
+		} else
+			msgalloc.smsgbuf = mb->next;
+		mb->flags = 0;
+	}
+	mballocs[category]++;
+	unlock(&msgalloc);
+	mb->magic = Mbmagic;
+	mb->count = count;
+	mb->chan = cp;
+	mb->next = 0;
+	mb->param = 0;
+	mb->category = category;
+	mb->data = mb->xdata+Slop;
+	return mb;
+}
+
+void
+mbfree(Msgbuf *mb)
+{
+	if(mb == nil)
+		return;
+	assert(mb->magic == Mbmagic);
+	if (mb->magic != Mbmagic)
+		panic("mbfree: bad magic 0x%lux", mb->magic);
+	if(mb->flags & BTRACE)
+		print("mbfree: BTRACE cat=%d flags=%ux, caller %#p\n",
+			mb->category, mb->flags, getcallerpc(&mb));
+
+	if(mb->flags & FREE)
+		panic("mbfree already free");
+
+	lock(&msgalloc);
+	mballocs[mb->category]--;
+	mb->flags |= FREE;
+	if(mb->flags & LARGE) {
+		mb->next = msgalloc.lmsgbuf;
+		msgalloc.lmsgbuf = mb;
+	} else {
+		mb->next = msgalloc.smsgbuf;
+		msgalloc.smsgbuf = mb;
+	}
+	mb->data = 0;
+	mb->magic = 0;
+	unlock(&msgalloc);
+}
+
+/*
+ * returns 1 if n is prime
+ * used for adjusting lengths
+ * of hashing things.
+ * there is no need to be clever
+ */
+int
+prime(vlong n)
+{
+	long i;
+
+	if((n%2) == 0)
+		return 0;
+	for(i=3;; i+=2) {
+		if((n%i) == 0)
+			return 0;
+		if((vlong)i*i >= n)
+			return 1;
+	}
+}
+
+char*
+getwrd(char *word, char *line)
+{
+	int c, n;
+
+	while(isascii(*line) && isspace(*line) && *line != '\n')
+		line++;
+	for(n = 0; n < Maxword; n++) {
+		c = *line;
+		if(c == '\0' || isascii(c) && isspace(c))
+			break;
+		line++;
+		*word++ = c;
+	}
+	*word = 0;
+	return line;
+}
+
+void
+hexdump(void *a, int n)
+{
+	char s1[30], s2[4];
+	uchar *p;
+	int i;
+
+	p = a;
+	s1[0] = 0;
+	for(i = 0; i < n; i++) {
+		sprint(s2, " %.2ux", p[i]);
+		strcat(s1, s2);
+		if((i&7) == 7) {
+			print("%s\n", s1);
+			s1[0] = 0;
+		}
+	}
+	if(s1[0])
+		print("%s\n", s1);
+}
+
+void*
+fs_recv(Queue *q, int)
+{
+	void *a;
+	int i, c;
+
+	if(q == nil)
+		panic("recv null q");
+	qlock(q);
+	q->waitedfor = 1;
+	while((c = q->count) <= 0)
+		rsleep(&q->empty);
+	i = q->loc;
+	a = q->args[i];
+	i++;
+	if(i >= q->size)
+		i = 0;
+	q->loc = i;
+	q->count = c-1;
+	rwakeup(&q->full);			/* no longer full */
+	qunlock(q);
+	return a;
+}
+
+void
+fs_send(Queue *q, void *a)
+{
+	int i, c;
+
+	if(q == nil)
+		panic("send null q");
+	if(!q->waitedfor) {
+		for (i = 0; i < 5 && !q->waitedfor; i++)
+			sleep(1000);
+		if(!q->waitedfor) {
+			/* likely a bug; don't wait forever */
+			print("no readers yet for %s q\n", q->name);
+			abort();
+		}
+	}
+	qlock(q);
+	while((c = q->count) >= q->size)
+		rsleep(&q->full);
+	i = q->loc + c;
+	if(i >= q->size)
+		i -= q->size;
+	q->args[i] = a;
+	q->count = c+1;
+	rwakeup(&q->empty);			/* no longer empty */
+	qunlock(q);
+}
+
+Queue*
+newqueue(int size, char *name)
+{
+	Queue *q;
+
+	q = malloc(sizeof(Queue) + (size-1)*sizeof(void*));
+	q->size = size;
+	q->full.l = q->empty.l = &q->QLock;
+	q->name = name;
+	return q;
+}
+
+int
+devread(Device *d, Off b, void *c)
+{
+	int e;
+
+	for (;;)
+		switch(d->type) {
+		case Devcw:
+			return cwread(d, b, c);
+
+		case Devjuke:
+			d = d->j.m;
+			break;
+
+		case Devro:
+			return roread(d, b, c);
+
+		case Devwren:
+			return wrenread(d, b, c);
+
+		case Devworm:
+		case Devlworm:
+			return wormread(d, b, c);
+
+		case Devfworm:
+			return fwormread(d, b, c);
+
+		case Devmcat:
+			return mcatread(d, b, c);
+
+		case Devmlev:
+			return mlevread(d, b, c);
+
+		case Devmirr:
+			return mirrread(d, b, c);
+
+		case Devpart:
+			return partread(d, b, c);
+
+		case Devswab:
+			e = devread(d->swab.d, b, c);
+			if(e == 0)
+				swab(c, 0);
+			return e;
+
+		case Devnone:
+			print("read from device none(%lld)\n", (Wideoff)b);
+			return 1;
+		default:
+			panic("illegal device in devread: %Z %lld",
+				d, (Wideoff)b);
+			return 1;
+		}
+}
+
+int
+devwrite(Device *d, Off b, void *c)
+{
+	int e;
+
+	/*
+	 * set readonly to non-0 to prevent all writes;
+	 * mainly for trying dangerous experiments.
+	 */
+	if (readonly)
+		return 0;
+	for (;;)
+		switch(d->type) {
+		case Devcw:
+			return cwwrite(d, b, c);
+
+		case Devjuke:
+			d = d->j.m;
+			break;
+
+		case Devro:
+			print("write to ro device %Z(%lld)\n", d, (Wideoff)b);
+			return 1;
+
+		case Devwren:
+			return wrenwrite(d, b, c);
+
+		case Devworm:
+		case Devlworm:
+			return wormwrite(d, b, c);
+
+		case Devfworm:
+			return fwormwrite(d, b, c);
+
+		case Devmcat:
+			return mcatwrite(d, b, c);
+
+		case Devmlev:
+			return mlevwrite(d, b, c);
+
+		case Devmirr:
+			return mirrwrite(d, b, c);
+
+		case Devpart:
+			return partwrite(d, b, c);
+
+		case Devswab:
+			swab(c, 1);
+			e = devwrite(d->swab.d, b, c);
+			swab(c, 0);
+			return e;
+
+		case Devnone:
+			/* checktag() can generate blocks with type devnone */
+			return 0;
+		default:
+			panic("illegal device in devwrite: %Z %lld",
+				d, (Wideoff)b);
+			return 1;
+		}
+}
+
+Devsize
+devsize(Device *d)
+{
+	for (;;)
+		switch(d->type) {
+		case Devcw:
+		case Devro:
+			return cwsize(d);
+
+		case Devjuke:
+			d = d->j.m;
+			break;
+
+		case Devwren:
+			return wrensize(d);
+
+		case Devworm:
+		case Devlworm:
+			return wormsize(d);
+
+		case Devfworm:
+			return fwormsize(d);
+
+		case Devmcat:
+			return mcatsize(d);
+
+		case Devmlev:
+			return mlevsize(d);
+
+		case Devmirr:
+			return mirrsize(d);
+
+		case Devpart:
+			return partsize(d);
+
+		case Devswab:
+			d = d->swab.d;
+			break;
+		default:
+			panic("illegal device in devsize: %Z", d);
+			return 0;
+		}
+}
+
+/* result is malloced */
+char *
+sdof(Device *d)
+{
+	static char name[256];
+
+	for (;;)
+		switch(d->type) {
+		case Devjuke:
+			d = d->j.j;		/* robotics */
+			break;
+		case Devwren:
+			snprint(name, sizeof name, "/dev/sd%d%d", d->wren.ctrl,
+				d->wren.targ);
+			return strdup(name);
+		case Devswab:
+			d = d->swab.d;
+			break;
+		default:
+			panic("illegal device in sdof: %Z", d);
+			return nil;
+		}
+}
+
+Off
+superaddr(Device *d)
+{
+	for (;;)
+		switch(d->type) {
+		default:
+			return SUPER_ADDR;
+		case Devcw:
+		case Devro:
+			return cwsaddr(d);
+		case Devswab:
+			d = d->swab.d;
+			break;
+		}
+}
+
+Off
+getraddr(Device *d)
+{
+	for (;;)
+		switch(d->type) {
+		default:
+			return ROOT_ADDR;
+		case Devcw:
+		case Devro:
+			return cwraddr(d);
+		case Devswab:
+			d = d->swab.d;
+			break;
+		}
+}
+
+void
+devream(Device *d, int top)
+{
+	Device *l;
+
+loop:
+	print("\tdevream: %Z %d\n", d, top);
+	switch(d->type) {
+	default:
+		print("ream: unknown dev type %Z\n", d);
+		return;
+
+	case Devcw:
+		devream(d->cw.w, 0);
+		devream(d->cw.c, 0);
+		if(top) {
+			wlock(&mainlock);
+			cwream(d);
+			wunlock(&mainlock);
+		}
+		devinit(d);
+		return;
+
+	case Devfworm:
+		devream(d->fw.fw, 0);
+		fwormream(d);
+		break;
+
+	case Devpart:
+		devream(d->part.d, 0);
+		break;
+
+	case Devmlev:
+	case Devmcat:
+	case Devmirr:
+		for(l=d->cat.first; l; l=l->link)
+			devream(l, 0);
+		break;
+
+	case Devjuke:
+	case Devworm:
+	case Devlworm:
+	case Devwren:
+		break;
+
+	case Devswab:
+		d = d->swab.d;
+		goto loop;
+	}
+	devinit(d);
+	if(top) {
+		wlock(&mainlock);
+		rootream(d, ROOT_ADDR);
+		superream(d, SUPER_ADDR);
+		wunlock(&mainlock);
+	}
+}
+
+void
+devrecover(Device *d)
+{
+	for (;;) {
+		print("recover: %Z\n", d);
+		switch(d->type) {
+		default:
+			print("recover: unknown dev type %Z\n", d);
+			return;
+
+		case Devcw:
+			wlock(&mainlock);	/* recover */
+			cwrecover(d);
+			wunlock(&mainlock);
+			return;
+
+		case Devswab:
+			d = d->swab.d;
+			break;
+		}
+	}
+}
+
+void
+devinit(Device *d)
+{
+	for (;;) {
+		if(d->init)
+			return;
+		d->init = 1;
+		print("\tdevinit %Z\n", d);
+		switch(d->type) {
+		default:
+			print("devinit unknown device %Z\n", d);
+			return;
+
+		case Devro:
+			cwinit(d->ro.parent);
+			return;
+
+		case Devcw:
+			cwinit(d);
+			return;
+
+		case Devjuke:
+			jukeinit(d);
+			return;
+
+		case Devwren:
+			wreninit(d);
+			return;
+
+		case Devworm:
+		case Devlworm:
+			return;
+
+		case Devfworm:
+			fworminit(d);
+			return;
+
+		case Devmcat:
+			mcatinit(d);
+			return;
+
+		case Devmlev:
+			mlevinit(d);
+			return;
+
+		case Devmirr:
+			mirrinit(d);
+			return;
+
+		case Devpart:
+			partinit(d);
+			return;
+
+		case Devswab:
+			d = d->swab.d;
+			break;
+
+		case Devnone:
+			print("devinit of Devnone\n");
+			return;
+		}
+	}
+}
+
+void
+swab2(void *c)
+{
+	uchar *p;
+	int t;
+
+	p = c;
+
+	t = p[0];
+	p[0] = p[1];
+	p[1] = t;
+}
+
+void
+swab4(void *c)
+{
+	uchar *p;
+	int t;
+
+	p = c;
+
+	t = p[0];
+	p[0] = p[3];
+	p[3] = t;
+
+	t = p[1];
+	p[1] = p[2];
+	p[2] = t;
+}
+
+void
+swab8(void *c)
+{
+	uchar *p;
+	int t;
+
+	p = c;
+
+	t = p[0];
+	p[0] = p[7];
+	p[7] = t;
+
+	t = p[1];
+	p[1] = p[6];
+	p[6] = t;
+
+	t = p[2];
+	p[2] = p[5];
+	p[5] = t;
+
+	t = p[3];
+	p[3] = p[4];
+	p[4] = t;
+}
+
+/*
+ * swab a block
+ *	flag = 0 -- convert from foreign to native
+ *	flag = 1 -- convert from native to foreign
+ */
+void
+swab(void *c, int flag)
+{
+	uchar *p;
+	Tag *t;
+	int i, j;
+	Dentry *d;
+	Cache *h;
+	Bucket *b;
+	Superb *s;
+	Fbuf *f;
+	Off *l;
+
+	/* swab the tag */
+	p = (uchar*)c;
+	t = (Tag*)(p + BUFSIZE);
+	if(!flag) {
+		swab2(&t->pad);
+		swab2(&t->tag);
+		swaboff(&t->path);
+	}
+
+	/* swab each block type */
+	switch(t->tag) {
+
+	default:
+		print("no swab for tag=%G rw=%d\n", t->tag, flag);
+		for(j=0; j<16; j++)
+			print(" %.2x", p[BUFSIZE+j]);
+		print("\n");
+		for(i=0; i<16; i++) {
+			print("%.4x", i*16);
+			for(j=0; j<16; j++)
+				print(" %.2x", p[i*16+j]);
+			print("\n");
+		}
+		panic("swab");
+		break;
+
+	case Tsuper:
+		s = (Superb*)p;
+		swaboff(&s->fbuf.nfree);
+		for(i=0; i<FEPERBUF; i++)
+			swaboff(&s->fbuf.free[i]);
+		swaboff(&s->fstart);
+		swaboff(&s->fsize);
+		swaboff(&s->tfree);
+		swaboff(&s->qidgen);
+		swaboff(&s->cwraddr);
+		swaboff(&s->roraddr);
+		swaboff(&s->last);
+		swaboff(&s->next);
+		break;
+
+	case Tdir:
+		for(i=0; i<DIRPERBUF; i++) {
+			d = (Dentry*)p + i;
+			swab2(&d->uid);
+			swab2(&d->gid);
+			swab2(&d->mode);
+			swab2(&d->muid);
+			swaboff(&d->qid.path);
+			swab4(&d->qid.version);
+			swaboff(&d->size);
+			for(j=0; j<NDBLOCK; j++)
+				swaboff(&d->dblock[j]);
+			for (j = 0; j < NIBLOCK; j++)
+				swaboff(&d->iblocks[j]);
+			swab4(&d->atime);
+			swab4(&d->mtime);
+		}
+		break;
+
+	case Tind1:
+	case Tind2:
+#ifndef COMPAT32
+	case Tind3:
+	case Tind4:
+	/* add more Tind tags here ... */
+#endif
+		l = (Off *)p;
+		for(i=0; i<INDPERBUF; i++) {
+			swaboff(l);
+			l++;
+		}
+		break;
+
+	case Tfree:
+		f = (Fbuf*)p;
+		swaboff(&f->nfree);
+		for(i=0; i<FEPERBUF; i++)
+			swaboff(&f->free[i]);
+		break;
+
+	case Tbuck:
+		for(i=0; i<BKPERBLK; i++) {
+			b = (Bucket*)p + i;
+			swab4(&b->agegen);
+			for(j=0; j<CEPERBK; j++) {
+				swab2(&b->entry[j].age);
+				swab2(&b->entry[j].state);
+				swaboff(&b->entry[j].waddr);
+			}
+		}
+		break;
+
+	case Tcache:
+		h = (Cache*)p;
+		swaboff(&h->maddr);
+		swaboff(&h->msize);
+		swaboff(&h->caddr);
+		swaboff(&h->csize);
+		swaboff(&h->fsize);
+		swaboff(&h->wsize);
+		swaboff(&h->wmax);
+		swaboff(&h->sbaddr);
+		swaboff(&h->cwraddr);
+		swaboff(&h->roraddr);
+		swab4(&h->toytime);
+		swab4(&h->time);
+		break;
+
+	case Tnone:	// unitialized
+	case Tfile:	// someone elses problem
+	case Tvirgo:	// bit map -- all bytes
+	case Tconfig:	// configuration string -- all bytes
+		break;
+	}
+
+	/* swab the tag */
+	if(flag) {
+		swab2(&t->pad);
+		swab2(&t->tag);
+		swaboff(&t->path);
+	}
+}

+ 117 - 0
sys/src/cmd/cwfs/time.c

@@ -0,0 +1,117 @@
+#include "all.h"
+
+Timet
+toytime(void)
+{
+	return time(nil);
+}
+
+void
+datestr(char *s, Timet t)
+{
+	Tm *tm;
+
+	tm = localtime(t);
+	sprint(s, "%.4d%.2d%.2d", tm->year+1900, tm->mon+1, tm->mday);
+}
+
+void
+prdate(void)
+{
+	print("%T\n", time(nil));
+}
+
+static void
+ct_numb(char *cp, int n)
+{
+	if(n >= 10)
+		cp[0] = (n/10)%10 + '0';
+	else
+		cp[0] = ' ';
+	cp[1] = n%10 + '0';
+}
+
+int
+Tfmt(Fmt* fmt)
+{
+	char s[30];
+	char *cp;
+	Timet t;
+	Tm *tm;
+
+	t = va_arg(fmt->args, Timet);
+	if(t == 0)
+		return fmtstrcpy(fmt, "The Epoch");
+
+	tm = localtime(t);
+	strcpy(s, "Day Mon 00 00:00:00 1900");
+	cp = &"SunMonTueWedThuFriSat"[tm->wday*3];
+	s[0] = cp[0];
+	s[1] = cp[1];
+	s[2] = cp[2];
+	cp = &"JanFebMarAprMayJunJulAugSepOctNovDec"[tm->mon*3];
+	s[4] = cp[0];
+	s[5] = cp[1];
+	s[6] = cp[2];
+	ct_numb(s+8, tm->mday);
+	ct_numb(s+11, tm->hour+100);
+	ct_numb(s+14, tm->min+100);
+	ct_numb(s+17, tm->sec+100);
+	if(tm->year >= 100) {
+		s[20] = '2';
+		s[21] = '0';
+	}
+	ct_numb(s+22, tm->year+100);
+
+	return fmtstrcpy(fmt, s);
+}
+
+/*
+ * compute the next time after t
+ * that has hour hr and is not on
+ * day in bitpattern --
+ * for automatic dumps
+ */
+Timet
+nextime(Timet t, int hr, int day)
+{
+	int nhr;
+	Tm *tm;
+
+	if(hr < 0 || hr >= 24)
+		hr = 5;
+	if((day&0x7f) == 0x7f)
+		day = 0;
+	for (;;) {
+		tm = localtime(t);
+		t -= tm->sec;
+		t -= tm->min*60;
+		nhr = tm->hour;
+		do {
+			t += 60*60;
+			nhr++;
+		} while(nhr%24 != hr);
+		tm = localtime(t);
+		if(tm->hour != hr) {
+			t += 60*60;
+			tm = localtime(t);
+			if(tm->hour != hr) {
+				t -= 60*60;
+				tm = localtime(t);
+			}
+		}
+		if(day & (1<<tm->wday))
+			t += 12*60*60;
+		else
+			return t;
+	}
+}
+
+/*
+ *  delay for l milliseconds more or less.
+ */
+void
+delay(int l)
+{
+	sleep(l);
+}

+ 582 - 0
sys/src/cmd/cwfs/uidgid.c

@@ -0,0 +1,582 @@
+#include "all.h"
+
+struct {
+	char*	name;
+	Userid	uid;
+	Userid	lead;
+} minusers[] = {
+	"adm",		-1,	-1,
+	"none",		0,	-1,
+	"tor",		1,	1,
+	"sys",		10000,	0,
+	"map",		10001,	10001,
+	"doc",		10002,	0,
+	"upas",		10003,	10003,
+	"font",		10004,	0,
+	"bootes",	10005,	10005,
+	0
+};
+
+static char buf[4096];
+static Rune ichar[] = L"?=+-/:";
+
+Uid*	chkuid(char *name, int chk);
+void	do_newuser(int, char*[]);
+char*	getword(char*, Rune, char*, int);
+void	pentry(char*, Uid*);
+int	readln(char*, int);
+void	setminusers(void);
+Uid*	uidtop(int);
+
+void
+cmd_users(int argc, char *argv[])
+{
+	Uid *ui;
+	int u, g, o, line;
+	char *file, *p, *uname, *ulead, *unext;
+
+	file = "/adm/users";
+	if(argc > 1)
+		file = argv[1];
+
+	if(strcmp(file, "default") == 0) {
+		setminusers();
+		return;
+	}
+
+	uidgc.uidbuf = getbuf(devnone, Cuidbuf, 0);
+	if(walkto(file) || con_open(FID2, 0)) {
+		print("cmd_users: cannot access %s\n", file);
+		putbuf(uidgc.uidbuf);
+		return;
+	}
+
+	uidgc.flen = 0;
+	uidgc.find = 0;
+	cons.offset = 0;
+	cons.nuid = 0;
+
+	u = 0;
+	line = 0;
+	while(readln(buf, sizeof buf) != 0) {
+		line++;
+		p = getword(buf, L':', "no : after number", line);
+		if(p == nil)
+			continue;
+		ulead = getword(p, L':', "no : after name", line);
+		if(ulead == nil)
+			continue;
+
+		if(strlen(p) > NAMELEN-1) {
+			print("%s: name too long\n", p);
+			continue;
+		}
+		strcpy(uid[u].name, p);
+		uid[u].uid = number(buf, 0, 10);
+		uid[u].lead = 0;
+		uid[u].ngrp = 0;
+		u++;
+		if(u >= conf.nuid) {
+			print("conf.nuid too small (%ld)\n", conf.nuid);
+			break;
+		}
+	}
+
+	/* Sorted by uid for use in uidtostr */
+	wlock(&uidgc.uidlock);
+	qsort(uid, u, sizeof(uid[0]), byuid);
+	cons.nuid = u;
+	wunlock(&uidgc.uidlock);
+
+	/* Parse group table */
+	uidgc.flen = 0;
+	uidgc.find = 0;
+	cons.offset = 0;
+	cons.ngid = 0;
+
+	g = 0;
+	line = 0;
+	while(readln(buf, sizeof buf) != 0) {
+		line++;
+		uname = getword(buf, L':', 0, 0);	/* skip number */
+		if(uname == nil)
+			continue;
+
+		ulead = getword(uname, L':', 0, 0);	/* skip name */
+		if(ulead == nil)
+			continue;
+
+		p = getword(ulead, L':', "no : after leader", line);
+		if(p == nil)
+			continue;
+
+		ui = uidpstr(uname);
+		if(ui == nil)
+			continue;
+
+		/* set to owner if name not known */
+		ui->lead = 0;
+		if(ulead[0]) {
+			o = strtouid(ulead);
+			if(o >= 0)
+				ui->lead = o;
+			else
+				ui->lead = ui->uid;
+		}
+		ui->gtab = &gidspace[g];
+		ui->ngrp = 0;
+		while (p != nil) {
+			unext = getword(p, L',', 0, 0);
+			o = strtouid(p);
+			if(o >= 0) {
+				gidspace[g++] = o;
+				ui->ngrp++;
+			}
+			p = unext;
+		}
+	}
+
+	cons.ngid = g;
+
+	putbuf(uidgc.uidbuf);
+	print("%d uids read, %d groups used\n", cons.nuid, cons.ngid);
+}
+
+void
+cmd_newuser(int argc, char *argv[])
+{
+	if(argc <= 1) {
+		print("usage: newuser args\n");
+		print("\tname -- create a new user\n");
+		print("\tname : -- create a new group\n");
+		print("\tname ? -- show entry for user\n");
+		print("\tname name -- rename\n");
+		print("\tname =[name] -- add/alter/remove leader\n");
+		print("\tname +name -- add member\n");
+		print("\tname -name -- delete member\n");
+		return;
+	}
+	do_newuser(argc, argv);
+}
+
+void
+do_newuser(int argc, char *argv[])
+{
+	int i, l, n, nuid;
+	char *p, *md, *q;
+	Rune *r;
+	Userid *s;
+	Uid *ui, *u2;
+
+	nuid = 10000;
+	md = 0;
+	if(argc == 2) {
+		nuid = 1;
+		argv[2] = ":";
+	}
+
+	for(r = ichar; *r; r++)
+		if(utfrune(argv[1], *r)) {
+			print("illegal character in name\n");
+			return;
+		}
+	if(strlen(argv[1]) > NAMELEN-1) {
+		print("name %s too long\n", argv[1]);
+		return;
+	}
+
+	p = argv[2];
+	switch(*p) {
+	case '?':
+		ui = chkuid(argv[1], 1);
+		if(ui == 0)
+			return;
+		pentry(buf, ui);
+		n = strlen(buf);
+		p = buf;
+		while(n > PRINTSIZE-5) {
+			q = p;
+			p += PRINTSIZE-5;
+			n -= PRINTSIZE-5;
+			i = *p;
+			*p = 0;
+			print("%s", q);
+			*p = i;
+		}
+		print("%s\n", p);
+		return;
+
+	case ':':
+		if(chkuid(argv[1], 0))
+			return;
+		while(uidtop(nuid) != 0)
+			nuid++;
+		if(cons.nuid >= conf.nuid) {
+			print("conf.nuid too small (%ld)\n", conf.nuid);
+			return;
+		}
+
+		wlock(&uidgc.uidlock);
+		ui = &uid[cons.nuid++];
+		ui->uid = nuid;
+		ui->lead = 0;
+		if(nuid < 10000) {
+			ui->lead = ui->uid;
+			md = argv[1];
+		}
+		strcpy(ui->name, argv[1]);
+		ui->ngrp = 0;
+		qsort(uid, cons.nuid, sizeof(uid[0]), byuid);
+		wunlock(&uidgc.uidlock);
+		break;
+
+	case '=':
+		ui = chkuid(argv[1], 1);
+		if(ui == 0)
+			return;
+		p++;
+		if(*p == '\0') {
+			ui->lead = 0;
+			break;
+		}
+		u2 = chkuid(p, 1);
+		if(u2 == 0)
+			return;
+		ui->lead = u2->uid;
+		break;
+
+	case '+':
+		ui = chkuid(argv[1], 1);
+		if(ui == 0)
+			return;
+		p++;
+		u2 = chkuid(p, 1);
+		if(u2 == 0)
+			return;
+		if(u2->uid == ui->uid)
+			return;
+		if(cons.ngid+ui->ngrp+1 >= conf.gidspace) {
+			print("conf.gidspace too small (%ld)\n", conf.gidspace);
+			return;
+		}
+		for(i = 0; i < ui->ngrp; i++) {
+			if(ui->gtab[i] == u2->uid) {
+				print("member already in group\n");
+				return;
+			}
+		}
+
+		wlock(&uidgc.uidlock);
+		s = gidspace+cons.ngid;
+		memmove(s, ui->gtab, ui->ngrp*sizeof(*s));
+		ui->gtab = s;
+		s[ui->ngrp++] = u2->uid;
+		cons.ngid += ui->ngrp+1;
+		wunlock(&uidgc.uidlock);
+		break;
+
+	case '-':
+		ui = chkuid(argv[1], 1);
+		if(ui == 0)
+			return;
+		p++;
+		u2 = chkuid(p, 1);
+		if(u2 == 0)
+			return;
+		for(i = 0; i < ui->ngrp; i++)
+			if(ui->gtab[i] == u2->uid)
+				break;
+
+		if(i == ui->ngrp) {
+			print("%s not in group\n", p);
+			return;
+		}
+
+		wlock(&uidgc.uidlock);
+		s = ui->gtab+i;
+		ui->ngrp--;
+		memmove(s, s+1, (ui->ngrp-i)*sizeof(*s));
+		wunlock(&uidgc.uidlock);
+		break;
+
+	default:
+		if(chkuid(argv[2], 0))
+			return;
+
+		for(r = ichar; *r; r++)
+			if(utfrune(argv[2], *r)) {
+				print("illegal character in name\n");
+				return;
+			}
+
+		ui = chkuid(argv[1], 1);
+		if(ui == 0)
+			return;
+
+		if(strlen(argv[2]) > NAMELEN-1) {
+			print("name %s too long\n", argv[2]);
+			return;
+		}
+
+		wlock(&uidgc.uidlock);
+		strcpy(ui->name, argv[2]);
+		wunlock(&uidgc.uidlock);
+		break;
+	}
+
+
+	if(walkto("/adm/users") || con_open(FID2, OWRITE|OTRUNC)) {
+		print("can't open /adm/users for write\n");
+		return;
+	}
+
+	cons.offset = 0;
+	for(i = 0; i < cons.nuid; i++) {
+		pentry(buf, &uid[i]);
+		l = strlen(buf);
+		n = con_write(FID2, buf, cons.offset, l);
+		if(l != n)
+			print("short write on /adm/users\n");
+		cons.offset += n;
+	}
+
+	if(md != 0) {
+		sprint(buf, "create /usr/%s %s %s 755 d", md, md, md);
+		print("%s\n", buf);
+		cmd_exec(buf);
+	}
+}
+
+Uid*
+chkuid(char *name, int chk)
+{
+	Uid *u;
+
+	u = uidpstr(name);
+	if(chk == 1) {
+		if(u == 0)
+			print("%s does not exist\n", name);
+	}
+	else {
+		if(u != 0)
+			print("%s already exists\n", name);
+	}
+	return u;
+}
+
+void
+pentry(char *buf, Uid *u)
+{
+	int i, posn;
+	Uid *p;
+
+	posn = sprint(buf, "%d:%s:", u->uid, u->name);
+	p = uidtop(u->lead);
+	if(p && u->lead != 0)
+		posn += sprint(buf+posn, "%s", p->name);
+
+	posn += sprint(buf+posn, ":");
+	for(i = 0; i < u->ngrp; i++) {
+		p = uidtop(u->gtab[i]);
+		if(i != 0)
+			posn += sprint(buf+posn, ",");
+		if(p != 0)
+			posn += sprint(buf+posn, "%s", p->name);
+		else
+			posn += sprint(buf+posn, "%d", u->gtab[i]);
+	}
+	sprint(buf+posn, "\n");
+}
+
+void
+setminusers(void)
+{
+	int u;
+
+	for(u = 0; minusers[u].name; u++) {
+		strcpy(uid[u].name, minusers[u].name);
+		uid[u].uid = minusers[u].uid;
+		uid[u].lead = minusers[u].lead;
+	}
+	cons.nuid = u;
+	qsort(uid, u, sizeof(uid[0]), byuid);
+}
+
+Uid*
+uidpstr(char *name)
+{
+	Uid *s, *e;
+
+	s = uid;
+	for(e = s+cons.nuid; s < e; s++) {
+		if(strcmp(name, s->name) == 0)
+			return s;
+	}
+	return 0;
+}
+
+char*
+getword(char *buf, Rune delim, char *error, int line)
+{
+	char *p;
+
+	p = utfrune(buf, delim);
+	if(p == 0) {
+		if(error)
+			print("cmd_users: %s line %d\n", error, line);
+		return 0;
+	}
+	*p = '\0';
+	return p+1;
+}
+
+int
+strtouid(char *name)
+{
+	Uid *u;
+	int id;
+
+	rlock(&uidgc.uidlock);
+
+	u = uidpstr(name);
+	id = -2;
+	if(u != 0)
+		id = u->uid;
+
+	runlock(&uidgc.uidlock);
+
+	return id;
+}
+
+Uid*
+uidtop(int id)
+{
+	Uid *bot, *top, *new;
+
+	bot = uid;
+	top = bot + cons.nuid-1;
+
+	while(bot <= top){
+		new = bot + (top - bot)/2;
+		if(new->uid == id)
+			return new;
+		if(new->uid < id)
+			bot = new + 1;
+		else
+			top = new - 1;
+	}
+	return 0;
+}
+
+void
+uidtostr(char *name, int id, int dolock)
+{
+	Uid *p;
+
+	if(dolock)
+		rlock(&uidgc.uidlock);
+
+	p = uidtop(id);
+	if(p == 0)
+		strcpy(name, "none");
+	else
+		strcpy(name, p->name);
+
+	if(dolock)
+		runlock(&uidgc.uidlock);
+}
+
+int
+ingroup(int u, int g)
+{
+	Uid *p;
+	Userid *s, *e;
+
+	if(u == g)
+		return 1;
+
+	rlock(&uidgc.uidlock);
+	p = uidtop(g);
+	if(p != 0) {
+		s = p->gtab;
+		for(e = s + p->ngrp; s < e; s++) {
+			if(*s == u) {
+				runlock(&uidgc.uidlock);
+				return 1;
+			}
+		}
+	}
+	runlock(&uidgc.uidlock);
+	return 0;
+}
+
+int
+leadgroup(int ui, int gi)
+{
+	int i;
+	Uid *u;
+
+	/* user 'none' cannot be a group leader */
+	if(ui == 0)
+		return 0;
+
+	rlock(&uidgc.uidlock);
+	u = uidtop(gi);
+	if(u == 0) {
+		runlock(&uidgc.uidlock);
+		return 0;
+	}
+	i = u->lead;
+	runlock(&uidgc.uidlock);
+	if(i == ui)
+		return 1;
+	if(i == 0)
+		return ingroup(ui, gi);
+
+	return 0;
+}
+
+int
+byuid(void *a1, void *a2)
+{
+	Uid *u1, *u2;
+
+	u1 = a1;
+	u2 = a2;
+	return u1->uid - u2->uid;
+}
+
+int
+fchar(void)
+{
+	int n;
+
+	n = BUFSIZE;
+	if(n > MAXDAT)
+		n = MAXDAT;
+	if(uidgc.find >= uidgc.flen) {
+		uidgc.find = 0;
+		uidgc.flen = con_read(FID2, uidgc.uidbuf->iobuf, cons.offset, n);
+		if(uidgc.flen <= 0)
+			return -1;
+		cons.offset += uidgc.flen;
+	}
+	return (uchar)uidgc.uidbuf->iobuf[uidgc.find++];
+}
+
+int
+readln(char *p, int len)
+{
+	int n, c;
+
+	n = 0;
+	while(len--) {
+		c = fchar();
+		if(c == -1 || c == '\n')
+			break;
+		n++;
+		*p++ = c;
+	}
+	*p = '\0';
+	return n;
+}

+ 122 - 0
sys/src/cmd/cwfs/wren.c

@@ -0,0 +1,122 @@
+/*
+ * drive disks
+ * used to be just scsi disks, and issued scsi commands directly to the host
+ * adapter, but now it just does normal i/o.
+ */
+#include "all.h"
+
+enum { Sectorsz = 512, };		/* usual disk sector size */
+
+typedef	struct	Wren	Wren;
+struct	Wren
+{
+	long	block;			/* size of a block -- from config */
+	Devsize	nblock;			/* number of blocks -- from config */
+	long	mult;			/* multiplier to get physical blocks */
+	Devsize	max;			/* number of logical blocks */
+
+//	char	*sddir;			/* /dev/sdXX name */
+};
+
+char *
+dataof(char *file)
+{
+	char *datanm;
+	Dir *dir;
+
+	dir = dirstat(file);
+	if (dir != nil && dir->mode & DMDIR)
+		datanm = smprint("%s/data", file);
+	else
+		datanm = strdup(file);
+	free(dir);
+	return datanm;
+}
+
+void
+wreninit(Device *d)
+{
+	Wren *dr;
+	Dir *dir;
+
+	if(d->private)
+		return;
+	d->private = dr = malloc(sizeof(Wren));
+
+	if (d->wren.file)
+		d->wren.sddata = dataof(d->wren.file);
+	else {
+		d->wren.sddir = sdof(d);
+		d->wren.sddata = smprint("%s/data", d->wren.sddir);
+	}
+
+	assert(d->wren.fd <= 0);
+	d->wren.fd = open(d->wren.sddata, ORDWR);
+	if (d->wren.fd < 0)
+		panic("wreninit: can't open %s for %Z: %r", d->wren.sddata, d);
+
+	dr->block = inqsize(d->wren.sddata);
+	if(dr->block <= 0 || dr->block >= 16*1024) {
+		print("\twreninit %Z block size %ld, setting to %d\n",
+			d, dr->block, Sectorsz);
+		dr->block = Sectorsz;
+	}
+
+	dir = dirfstat(d->wren.fd);
+	dr->nblock = dir->length / dr->block;
+	free(dir);
+
+	dr->mult = (RBUFSIZE + dr->block - 1) / dr->block;
+	dr->max = (dr->nblock + 1) / dr->mult;
+	print("\tdisk drive %Z: %,lld %ld-byte sectors, ",
+		d, (Wideoff)dr->nblock, dr->block);
+	print("%,lld %d-byte blocks\n", (Wideoff)dr->max, RBUFSIZE);
+	print("\t\t%ld multiplier\n", dr->mult);
+}
+
+Devsize
+wrensize(Device *d)
+{
+	return ((Wren *)d->private)->max;
+}
+
+int
+wrenread(Device *d, Off b, void *c)
+{
+	int r = 0;
+	Wren *dr = d->private;
+
+	if (dr == nil)
+		panic("wrenread: no drive (%Z) block %lld", d, (Wideoff)b);
+	if(b >= dr->max) {
+		print("wrenread: block out of range %Z(%lld)\n", d, (Wideoff)b);
+		r = 0x040;
+	} else if (seek(d->wren.fd, (vlong)b*RBUFSIZE, 0) < 0 ||
+	    read(d->wren.fd, c, RBUFSIZE) != RBUFSIZE) {
+		print("wrenread: error on %Z(%lld): %r\n", d, (Wideoff)b);
+		cons.nwrenre++;
+		r = 1;
+	}
+	return r;
+}
+
+int
+wrenwrite(Device *d, Off b, void *c)
+{
+	int r = 0;
+	Wren *dr = d->private;
+
+	if (dr == nil)
+		panic("wrenwrite: no drive (%Z) block %lld", d, (Wideoff)b);
+	if(b >= dr->max) {
+		print("wrenwrite: block out of range %Z(%lld)\n",
+			d, (Wideoff)b);
+		r = 0x040;
+	} else if (seek(d->wren.fd, (vlong)b*RBUFSIZE, 0) < 0 ||
+	    write(d->wren.fd, c, RBUFSIZE) != RBUFSIZE) {
+		print("wrenwrite: error on %Z(%lld): %r\n", d, (Wideoff)b);
+		cons.nwrenwe++;
+		r = 1;
+	}
+	return r;
+}

+ 2 - 2
sys/src/libauthsrv/readnvram.c

@@ -266,8 +266,8 @@ readnvram(Nvrsafe *safep, int flag)
 					sizeof safe->authid);
 			readcons("authdom", nil, 0, safe->authdom,
 					sizeof safe->authdom);
-			readcons("secstore key (or fs config)", nil, 1,
-					safe->config, sizeof safe->config);
+			readcons("secstore key", nil, 1, safe->config,
+					sizeof safe->config);
 			for(;;){
 				if(readcons("password", nil, 1, in, sizeof in)
 				    == nil)