Browse Source

Plan 9 from Bell Labs 2003-02-19

David du Colombier 21 years ago
parent
commit
4428380291
45 changed files with 11508 additions and 319 deletions
  1. 47 23
      dist/replica/plan9.db
  2. 48 0
      dist/replica/plan9.log
  3. 811 0
      sys/include/nfs3.h
  4. 395 0
      sys/include/sunrpc.h
  5. 146 0
      sys/man/4/nfs
  6. 29 7
      sys/man/8/fs
  7. 1 1
      sys/src/cmd/acme/util.c
  8. 2 1
      sys/src/cmd/aux/mkfile
  9. 334 0
      sys/src/cmd/aux/nfsmount.c
  10. 204 0
      sys/src/cmd/aux/portmap.c
  11. 54 22
      sys/src/cmd/fossil/9.h
  12. 11 4
      sys/src/cmd/fossil/9auth.c
  13. 18 1
      sys/src/cmd/fossil/9fid.c
  14. 11 29
      sys/src/cmd/fossil/9p.c
  15. 536 206
      sys/src/cmd/fossil/9proc.c
  16. 17 10
      sys/src/cmd/fossil/9srv.c
  17. 7 9
      sys/src/cmd/fossil/9user.c
  18. 1 1
      sys/src/cmd/fossil/Ccmd.c
  19. 1 1
      sys/src/cmd/fossil/archive.c
  20. 51 0
      sys/src/cmd/fossil/epoch.c
  21. 2 1
      sys/src/cmd/fossil/fossil.c
  22. 1 1
      sys/src/cmd/fossil/fs.c
  23. 9 0
      sys/src/cmd/fossil/history
  24. 0 1
      sys/src/cmd/fossil/mkfile
  25. 7 0
      sys/src/cmd/fossil/source.c
  26. 1602 0
      sys/src/cmd/nfs.c
  27. 8 0
      sys/src/cmd/page/page.h
  28. 1 1
      sys/src/cmd/vnc/vncs.c
  29. 29 0
      sys/src/libsunrpc/COPYING
  30. 60 0
      sys/src/libsunrpc/authunix.c
  31. 478 0
      sys/src/libsunrpc/client.c
  32. 34 0
      sys/src/libsunrpc/emalloc.c
  33. 37 0
      sys/src/libsunrpc/error.c
  34. 107 0
      sys/src/libsunrpc/fd.c
  35. 64 0
      sys/src/libsunrpc/fmt.c
  36. 29 0
      sys/src/libsunrpc/mkfile
  37. 727 0
      sys/src/libsunrpc/mount3.c
  38. 57 0
      sys/src/libsunrpc/net.c
  39. 4045 0
      sys/src/libsunrpc/nfs3.c
  40. 498 0
      sys/src/libsunrpc/portmap.c
  41. 71 0
      sys/src/libsunrpc/prog.c
  42. 528 0
      sys/src/libsunrpc/rpc.c
  43. 277 0
      sys/src/libsunrpc/server.c
  44. 112 0
      sys/src/libsunrpc/udp.c
  45. 1 0
      sys/src/mkfile

+ 47 - 23
dist/replica/plan9.db

@@ -23,7 +23,7 @@
 386/bin/9660srv - 775 sys sys 1045537932 105209
 386/bin/aan - 775 sys sys 1045537933 128318
 386/bin/acid - 775 sys sys 1045537934 377954
-386/bin/acme - 775 sys sys 1045537935 416883
+386/bin/acme - 775 sys sys 1045546062 416883
 386/bin/ape - 20000000775 sys sys 1016944144 0
 386/bin/ape/basename - 775 sys sys 1038443079 135228
 386/bin/ape/cc - 775 sys sys 1045537935 68930
@@ -298,7 +298,7 @@
 386/bin/nntpfs - 775 sys sys 1045538038 161729
 386/bin/ns - 775 sys sys 1039758580 63830
 386/bin/p - 775 sys sys 1043589869 63785
-386/bin/page - 775 sys sys 1045538038 215658
+386/bin/page - 775 sys sys 1045606971 215429
 386/bin/paqfs - 775 sys sys 1045538039 108076
 386/bin/passwd - 775 sys sys 1039758581 81058
 386/bin/pbd - 775 sys sys 1038443175 4663
@@ -3188,7 +3188,7 @@ sys/doc/venti/venti.pdf - 755 sys sys 1020384352 139090
 sys/doc/venti/venti.ps - 664 sys sys 1019852320 2012620
 sys/games - 20000000775 sys sys 952648872 0
 sys/games/lib - 20000000775 sys sys 952648879 0
-sys/games/lib/fortunes - 664 sys sys 1045504123 238318
+sys/games/lib/fortunes - 664 sys sys 1045543258 238358
 sys/include - 20000000775 sys sys 1016902416 0
 sys/include/9p.h - 664 sys sys 1044836327 4309
 sys/include/String.h - 664 sys sys 1014929061 1133
@@ -3276,6 +3276,7 @@ sys/include/memlayer.h - 664 sys sys 1014929065 1810
 sys/include/mouse.h - 664 sys sys 1035232010 1003
 sys/include/mp.h - 664 sys sys 1014929065 4610
 sys/include/ndb.h - 664 sys sys 1032058245 3781
+sys/include/nfs3.h - 664 sys sys 1045589438 15082
 sys/include/plumb.h - 664 sys sys 1014929065 989
 sys/include/pool.h - 664 sys sys 1032058257 1156
 sys/include/rdbg.h - 664 sys sys 1014929066 95
@@ -3283,6 +3284,7 @@ sys/include/realtime.h - 664 sys sys 1037669237 869
 sys/include/regexp.h - 664 sys sys 1014929066 1308
 sys/include/scribble.h - 664 sys sys 1014929066 679
 sys/include/stdio.h - 664 sys sys 1014929066 4145
+sys/include/sunrpc.h - 664 sys sys 1045589438 7174
 sys/include/thread.h - 664 sys sys 1045504289 3562
 sys/include/venti.h - 664 sys sys 1045504293 6956
 sys/lib - 20000000775 sys sys 1018580948 0
@@ -4748,6 +4750,7 @@ sys/man/4/kfs - 664 sys sys 1019058716 2159
 sys/man/4/lnfs - 664 sys sys 1018973955 1006
 sys/man/4/mntgen - 664 sys sys 1043680799 473
 sys/man/4/namespace - 664 sys sys 1018386777 6916
+sys/man/4/nfs - 664 sys sys 1045589694 2739
 sys/man/4/nntpfs - 664 sys sys 1032632346 2771
 sys/man/4/paqfs - 664 sys sys 1017723482 1541
 sys/man/4/plumber - 664 sys sys 944959699 2748
@@ -4838,7 +4841,7 @@ sys/man/8/cron - 664 sys sys 944959679 1750
 sys/man/8/dhcpd - 664 sys sys 1032654987 5237
 sys/man/8/drawterm - 664 sys sys 958419689 2458
 sys/man/8/fossilcons - 664 sys sys 1044478450 13360
-sys/man/8/fs - 664 sys sys 1045501599 14286
+sys/man/8/fs - 664 sys sys 1045570953 14693
 sys/man/8/fsconfig - 664 sys sys 1045501600 8142
 sys/man/8/httpd - 664 sys sys 1037690024 4516
 sys/man/8/init - 664 sys sys 944959679 1430
@@ -6401,7 +6404,7 @@ sys/src/cmd/acme/rows.c - 664 sys sys 1016833877 14726
 sys/src/cmd/acme/scrl.c - 664 sys sys 1014926095 3072
 sys/src/cmd/acme/text.c - 664 sys sys 1045504840 24133
 sys/src/cmd/acme/time.c - 664 sys sys 1014926095 1783
-sys/src/cmd/acme/util.c - 664 sys sys 1044626080 6433
+sys/src/cmd/acme/util.c - 664 sys sys 1045546057 6435
 sys/src/cmd/acme/wind.c - 664 sys sys 1016833877 11063
 sys/src/cmd/acme/xfid.c - 664 sys sys 1044626081 19191
 sys/src/cmd/ar.c - 664 sys sys 1014926693 23801
@@ -6554,7 +6557,7 @@ sys/src/cmd/aux/lines.c - 664 sys sys 1015008782 546
 sys/src/cmd/aux/lis - 775 sys sys 944960794 45
 sys/src/cmd/aux/listen.c - 664 sys sys 1015008684 8139
 sys/src/cmd/aux/listen1.c - 664 sys sys 1024375142 2016
-sys/src/cmd/aux/mkfile - 664 sys sys 1037575305 962
+sys/src/cmd/aux/mkfile - 664 sys sys 1045589265 956
 sys/src/cmd/aux/mklatinkbd.c - 664 sys sys 953253425 3813
 sys/src/cmd/aux/mnihongo - 20000000775 sys sys 944960789 0
 sys/src/cmd/aux/mnihongo/README - 664 sys sys 944960789 475
@@ -6568,8 +6571,10 @@ sys/src/cmd/aux/na - 20000000775 sys sys 955036627 0
 sys/src/cmd/aux/na/mkfile - 664 sys sys 955036627 121
 sys/src/cmd/aux/na/na.h - 775 sys sys 955036627 267
 sys/src/cmd/aux/na/na.y - 775 sys sys 1015008889 25158
+sys/src/cmd/aux/nfsmount.c - 664 sys sys 1045589268 6202
 sys/src/cmd/aux/olefs.c - 664 sys sys 1014925094 9627
 sys/src/cmd/aux/pcmcia.c - 664 sys sys 1014925094 8223
+sys/src/cmd/aux/portmap.c - 664 sys sys 1045589269 3617
 sys/src/cmd/aux/rdwr.c - 664 sys sys 1016833876 811
 sys/src/cmd/aux/reboot.c - 664 sys sys 1014925091 1411
 sys/src/cmd/aux/searchfs.c - 664 sys sys 1014925091 18176
@@ -6999,23 +7004,23 @@ sys/src/cmd/file.c - 664 sys sys 1044560289 20196
 sys/src/cmd/fmt.c - 664 sys sys 1025298248 3897
 sys/src/cmd/fortune.c - 664 sys sys 1035832953 1674
 sys/src/cmd/fossil - 20000000775 sys sys 1042005512 0
-sys/src/cmd/fossil/9.h - 664 sys sys 1042311685 3412
-sys/src/cmd/fossil/9auth.c - 664 sys sys 1042311686 3023
+sys/src/cmd/fossil/9.h - 664 sys sys 1045576809 3987
+sys/src/cmd/fossil/9auth.c - 664 sys sys 1045600016 3149
 sys/src/cmd/fossil/9dir.c - 664 sys sys 1042005502 1995
 sys/src/cmd/fossil/9excl.c - 664 sys sys 1042005502 1887
-sys/src/cmd/fossil/9fid.c - 664 sys sys 1042005502 5236
+sys/src/cmd/fossil/9fid.c - 664 sys sys 1045600016 5521
 sys/src/cmd/fossil/9fsys.c - 664 sys sys 1044906157 26913
 sys/src/cmd/fossil/9lstn.c - 664 sys sys 1042005503 2865
-sys/src/cmd/fossil/9p.c - 664 sys sys 1042005503 21328
+sys/src/cmd/fossil/9p.c - 664 sys sys 1045600099 21080
 sys/src/cmd/fossil/9ping.c - 664 sys sys 1042005503 1563
-sys/src/cmd/fossil/9proc.c - 664 sys sys 1042005503 7358
-sys/src/cmd/fossil/9srv.c - 664 sys sys 1042005504 3215
-sys/src/cmd/fossil/9user.c - 664 sys sys 1042311687 17473
+sys/src/cmd/fossil/9proc.c - 664 sys sys 1045576808 13707
+sys/src/cmd/fossil/9srv.c - 664 sys sys 1045600018 3291
+sys/src/cmd/fossil/9user.c - 664 sys sys 1045600019 17457
 sys/src/cmd/fossil/Ccli.c - 664 sys sys 1042005504 1624
-sys/src/cmd/fossil/Ccmd.c - 664 sys sys 1042005504 7169
+sys/src/cmd/fossil/Ccmd.c - 664 sys sys 1045600019 7160
 sys/src/cmd/fossil/Ccons.c - 664 sys sys 1042005504 6524
 sys/src/cmd/fossil/Clog.c - 664 sys sys 1042005505 591
-sys/src/cmd/fossil/archive.c - 664 sys sys 1042005505 9083
+sys/src/cmd/fossil/archive.c - 664 sys sys 1045600020 9112
 sys/src/cmd/fossil/build - 664 sys sys 1042005505 449
 sys/src/cmd/fossil/buildsh - 775 sys sys 1042005505 561
 sys/src/cmd/fossil/bwatch.c - 664 sys sys 1042005505 6754
@@ -7024,6 +7029,7 @@ sys/src/cmd/fossil/dat.h - 664 sys sys 1042311689 7790
 sys/src/cmd/fossil/deadlock - 775 sys sys 1042005506 413
 sys/src/cmd/fossil/disk.c - 664 sys sys 1042497769 5736
 sys/src/cmd/fossil/dump.c - 664 sys sys 1042005506 1340
+sys/src/cmd/fossil/epoch.c - 664 sys sys 1045600021 997
 sys/src/cmd/fossil/error.c - 664 sys sys 1042005507 1367
 sys/src/cmd/fossil/error.h - 664 sys sys 1042005507 744
 sys/src/cmd/fossil/file.c - 664 sys sys 1044906157 27633
@@ -7032,16 +7038,16 @@ sys/src/cmd/fossil/flfmt.c - 664 sys sys 1042005507 10314
 sys/src/cmd/fossil/flproto - 664 sys sys 1042005508 210
 sys/src/cmd/fossil/fns.h - 664 sys sys 1042311690 2958
 sys/src/cmd/fossil/fossil-acid - 664 sys sys 1042005508 3965
-sys/src/cmd/fossil/fossil.c - 664 sys sys 1042005508 1256
-sys/src/cmd/fossil/fs.c - 664 sys sys 1042311691 17030
+sys/src/cmd/fossil/fossil.c - 664 sys sys 1045600029 1267
+sys/src/cmd/fossil/fs.c - 664 sys sys 1045600030 17059
 sys/src/cmd/fossil/fs.h - 664 sys sys 1042005509 1222
-sys/src/cmd/fossil/history - 664 sys sys 1042311694 881
+sys/src/cmd/fossil/history - 664 sys sys 1045600133 1150
 sys/src/cmd/fossil/invariants - 664 sys sys 1042005509 4073
-sys/src/cmd/fossil/mkfile - 664 sys sys 1042005509 1589
+sys/src/cmd/fossil/mkfile - 664 sys sys 1045576809 1567
 sys/src/cmd/fossil/nobwatch.c - 664 sys sys 1042005509 329
 sys/src/cmd/fossil/pack.c - 664 sys sys 1042005510 4683
 sys/src/cmd/fossil/periodic.c - 664 sys sys 1042005510 1091
-sys/src/cmd/fossil/source.c - 664 sys sys 1042311692 18406
+sys/src/cmd/fossil/source.c - 664 sys sys 1045576810 18668
 sys/src/cmd/fossil/srcload.c - 664 sys sys 1042005510 4178
 sys/src/cmd/fossil/stdinc.h - 664 sys sys 1042005510 155
 sys/src/cmd/fossil/trunc.c - 664 sys sys 1042005511 280
@@ -9212,6 +9218,7 @@ sys/src/cmd/ndb/query.c - 664 sys sys 957402054 1133
 sys/src/cmd/ndb/time.c - 664 sys sys 957402055 321
 sys/src/cmd/netstat.c - 664 sys sys 1038237140 3796
 sys/src/cmd/news.c - 664 sys sys 1014926614 3778
+sys/src/cmd/nfs.c - 664 sys sys 1045589249 30446
 sys/src/cmd/nm.c - 664 sys sys 1014926696 4908
 sys/src/cmd/nntpfs.c - 664 sys sys 1037404000 18808
 sys/src/cmd/ns.c - 664 sys sys 984717934 3558
@@ -9223,7 +9230,7 @@ sys/src/cmd/page/gs.c - 664 sys sys 1038517813 6544
 sys/src/cmd/page/mkfile - 664 sys sys 1035998247 411
 sys/src/cmd/page/nrotate.c - 664 sys sys 944961365 5806
 sys/src/cmd/page/page.c - 664 sys sys 1035695486 4292
-sys/src/cmd/page/page.h - 664 sys sys 1038517816 1641
+sys/src/cmd/page/page.h - 664 sys sys 1045606937 2000
 sys/src/cmd/page/pdf.c - 664 sys sys 1038517814 2884
 sys/src/cmd/page/pdfprolog.ps - 664 sys sys 1035695487 699
 sys/src/cmd/page/ps.c - 664 sys sys 1045505467 9176
@@ -10504,7 +10511,7 @@ sys/src/cmd/vnc/rre.c - 664 sys sys 1044880750 11496
 sys/src/cmd/vnc/screen.c - 664 sys sys 1044880750 7537
 sys/src/cmd/vnc/screen.h - 664 sys sys 1044880750 880
 sys/src/cmd/vnc/vnc.h - 664 sys sys 1044880750 2626
-sys/src/cmd/vnc/vncs.c - 664 sys sys 1044880750 20915
+sys/src/cmd/vnc/vncs.c - 664 sys sys 1045597638 20914
 sys/src/cmd/vnc/vncs.h - 664 sys sys 1044880750 890
 sys/src/cmd/vnc/vncv.c - 664 sys sys 1044880750 3093
 sys/src/cmd/vnc/vncv.h - 664 sys sys 1044880750 643
@@ -11661,6 +11668,23 @@ sys/src/libstdio/vfscanf.c - 664 sys sys 944961754 9042
 sys/src/libstdio/vprintf.c - 664 sys sys 944961754 134
 sys/src/libstdio/vsnprintf.c - 664 sys sys 1022112161 256
 sys/src/libstdio/vsprintf.c - 664 sys sys 1022112162 246
+sys/src/libsunrpc - 20000000775 sys sys 1045589225 0
+sys/src/libsunrpc/COPYING - 664 sys sys 1045589219 1269
+sys/src/libsunrpc/authunix.c - 664 sys sys 1045589219 1477
+sys/src/libsunrpc/client.c - 664 sys sys 1045589220 8843
+sys/src/libsunrpc/emalloc.c - 664 sys sys 1045589220 397
+sys/src/libsunrpc/error.c - 664 sys sys 1045589221 819
+sys/src/libsunrpc/fd.c - 664 sys sys 1045589221 1734
+sys/src/libsunrpc/fmt.c - 664 sys sys 1045589222 1146
+sys/src/libsunrpc/mkfile - 664 sys sys 1045589222 318
+sys/src/libsunrpc/mount3.c - 664 sys sys 1045589223 15217
+sys/src/libsunrpc/net.c - 664 sys sys 1045589223 792
+sys/src/libsunrpc/nfs3.c - 664 sys sys 1045589224 93376
+sys/src/libsunrpc/portmap.c - 664 sys sys 1045589224 9758
+sys/src/libsunrpc/prog.c - 664 sys sys 1045589225 1586
+sys/src/libsunrpc/rpc.c - 664 sys sys 1045589225 9235
+sys/src/libsunrpc/server.c - 664 sys sys 1045589225 5187
+sys/src/libsunrpc/udp.c - 664 sys sys 1045589225 2158
 sys/src/libthread - 20000000775 sys sys 1014928161 0
 sys/src/libthread/386.c - 664 sys sys 1014928156 448
 sys/src/libthread/alpha.c - 664 sys sys 1014928156 664
@@ -11722,7 +11746,7 @@ sys/src/libventi/session.h - 664 sys sys 1036470162 913
 sys/src/libventi/strdup.c - 664 sys sys 1045502096 203
 sys/src/libventi/venti.txt - 664 sys sys 1045502097 4347
 sys/src/libventi/zero.c - 664 sys sys 1045502097 1509
-sys/src/mkfile - 664 sys sys 1032061508 980
+sys/src/mkfile - 664 sys sys 1045589241 992
 sys/src/mkfile.proto - 664 sys sys 1045502130 269
 tmp - 20000000555 sys sys 953999902 0
 usr - 20000000775 sys sys 953406542 0

+ 48 - 0
dist/replica/plan9.log

@@ -17872,3 +17872,51 @@
 1045538237 308 c sys/man/1/INDEX - 664 sys sys 1045538129 2900
 1045538237 309 c sys/man/2/INDEX - 664 sys sys 1045538129 20216
 1045538237 310 c sys/man/8/INDEX - 664 sys sys 1045538129 2512
+1045544479 0 c sys/games/lib/fortunes - 664 sys sys 1045543258 238358
+1045546278 0 c 386/bin/acme - 775 sys sys 1045546062 416883
+1045546278 1 c sys/src/cmd/acme/util.c - 664 sys sys 1045546057 6435
+1045571408 0 c sys/man/8/fs - 664 sys sys 1045570953 14693
+1045576810 0 c sys/src/cmd/fossil/9.h - 664 sys sys 1045576809 3987
+1045576810 1 c sys/src/cmd/fossil/9proc.c - 664 sys sys 1045576808 13707
+1045576810 2 c sys/src/cmd/fossil/history - 664 sys sys 1045576810 1085
+1045576810 3 c sys/src/cmd/fossil/mkfile - 664 sys sys 1045576809 1567
+1045576810 4 c sys/src/cmd/fossil/source.c - 664 sys sys 1045576810 18668
+1045589416 0 c sys/src/cmd/aux/mkfile - 664 sys sys 1045589265 956
+1045589416 1 a sys/src/cmd/aux/nfsmount.c - 664 sys sys 1045589268 6202
+1045589416 2 a sys/src/cmd/aux/portmap.c - 664 sys sys 1045589269 3617
+1045589416 3 a sys/src/cmd/nfs.c - 664 sys sys 1045589249 30446
+1045589416 4 a sys/src/libsunrpc - 20000000775 sys sys 1045589225 0
+1045589416 5 a sys/src/libsunrpc/COPYING - 664 sys sys 1045589219 1269
+1045589416 6 a sys/src/libsunrpc/authunix.c - 664 sys sys 1045589219 1477
+1045589416 7 a sys/src/libsunrpc/client.c - 664 sys sys 1045589220 8843
+1045589416 8 a sys/src/libsunrpc/emalloc.c - 664 sys sys 1045589220 397
+1045589416 9 a sys/src/libsunrpc/error.c - 664 sys sys 1045589221 819
+1045589416 10 a sys/src/libsunrpc/fd.c - 664 sys sys 1045589221 1734
+1045589416 11 a sys/src/libsunrpc/fmt.c - 664 sys sys 1045589222 1146
+1045589416 12 a sys/src/libsunrpc/mkfile - 664 sys sys 1045589222 318
+1045589416 13 a sys/src/libsunrpc/mount3.c - 664 sys sys 1045589223 15217
+1045589416 14 a sys/src/libsunrpc/net.c - 664 sys sys 1045589223 792
+1045589416 15 a sys/src/libsunrpc/nfs3.c - 664 sys sys 1045589224 93376
+1045589416 16 a sys/src/libsunrpc/portmap.c - 664 sys sys 1045589224 9758
+1045589416 17 a sys/src/libsunrpc/prog.c - 664 sys sys 1045589225 1586
+1045589416 18 a sys/src/libsunrpc/rpc.c - 664 sys sys 1045589225 9235
+1045589416 19 a sys/src/libsunrpc/server.c - 664 sys sys 1045589225 5187
+1045589416 20 a sys/src/libsunrpc/udp.c - 664 sys sys 1045589225 2158
+1045589416 21 c sys/src/mkfile - 664 sys sys 1045589241 992
+1045591217 0 a sys/include/nfs3.h - 664 sys sys 1045589438 15082
+1045591217 1 a sys/include/sunrpc.h - 664 sys sys 1045589438 7174
+1045591217 2 a sys/man/4/nfs - 664 sys sys 1045589694 2739
+1045598426 0 c sys/src/cmd/vnc/vncs.c - 664 sys sys 1045597638 20914
+1045600227 0 c sys/src/cmd/fossil/9auth.c - 664 sys sys 1045600016 3149
+1045600227 1 c sys/src/cmd/fossil/9fid.c - 664 sys sys 1045600016 5521
+1045600227 2 c sys/src/cmd/fossil/9p.c - 664 sys sys 1045600099 21080
+1045600227 3 c sys/src/cmd/fossil/9srv.c - 664 sys sys 1045600018 3291
+1045600227 4 c sys/src/cmd/fossil/9user.c - 664 sys sys 1045600019 17457
+1045600227 5 c sys/src/cmd/fossil/Ccmd.c - 664 sys sys 1045600019 7160
+1045600227 6 c sys/src/cmd/fossil/archive.c - 664 sys sys 1045600020 9112
+1045600227 7 a sys/src/cmd/fossil/epoch.c - 664 sys sys 1045600021 997
+1045600227 8 c sys/src/cmd/fossil/fossil.c - 664 sys sys 1045600029 1267
+1045600227 9 c sys/src/cmd/fossil/fs.c - 664 sys sys 1045600030 17059
+1045600227 10 c sys/src/cmd/fossil/history - 664 sys sys 1045600133 1150
+1045607430 0 c 386/bin/page - 775 sys sys 1045606971 215429
+1045607430 1 c sys/src/cmd/page/page.h - 664 sys sys 1045606937 2000

+ 811 - 0
sys/include/nfs3.h

@@ -0,0 +1,811 @@
+/* 
+ * NFS mounter V3;  see RFC 1813
+ */
+
+#pragma lib "libsunrpc.a"
+#pragma src "/sys/src/libsunrpc"
+
+enum {
+	NfsMount1HandleSize = 32,
+	NfsMount3MaxPathSize = 1024,
+	NfsMount3MaxNameSize = 255,
+	NfsMount3MaxHandleSize = 64,
+	NfsMount3Program = 100005,
+	NfsMount3Version = 3,
+	NfsMount1Program = 100005,
+	NfsMount1Version = 1
+};
+typedef struct NfsMount3TNull NfsMount3TNull;
+typedef struct NfsMount3RNull NfsMount3RNull;
+typedef struct NfsMount3TMnt NfsMount3TMnt;
+typedef struct NfsMount3RMnt NfsMount3RMnt;
+typedef struct NfsMount3TDump NfsMount3TDump;
+typedef struct NfsMount3Entry NfsMount3Entry;
+typedef struct NfsMount3RDump NfsMount3RDump;
+typedef struct NfsMount3TUmnt NfsMount3TUmnt;
+typedef struct NfsMount3RUmnt NfsMount3RUmnt;
+typedef struct NfsMount3Export NfsMount3Export;
+typedef struct NfsMount3TUmntall NfsMount3TUmntall;
+typedef struct NfsMount3RUmntall NfsMount3RUmntall;
+typedef struct NfsMount3TExport NfsMount3TExport;
+typedef struct NfsMount3RExport NfsMount3RExport;
+
+typedef enum
+{
+	NfsMount3CallTNull,
+	NfsMount3CallRNull,
+	NfsMount3CallTMnt,
+	NfsMount3CallRMnt,
+	NfsMount3CallTDump,
+	NfsMount3CallRDump,
+	NfsMount3CallTUmnt,
+	NfsMount3CallRUmnt,
+	NfsMount3CallTUmntall,
+	NfsMount3CallRUmntall,
+	NfsMount3CallTExport,
+	NfsMount3CallRExport
+} NfsMount3CallType;
+
+typedef struct NfsMount3Call NfsMount3Call;
+struct NfsMount3Call {
+	SunRpc rpc;
+	NfsMount3CallType type;
+};
+
+struct NfsMount3TNull {
+	NfsMount3Call call;
+};
+
+struct NfsMount3RNull {
+	NfsMount3Call call;
+};
+
+struct NfsMount3TMnt {
+	NfsMount3Call call;
+	char *path;
+};
+
+struct NfsMount3RMnt {
+	NfsMount3Call call;
+	uint status;
+	uchar *handle;
+	uint len;
+	u32int *auth;
+	u32int nauth;
+};
+
+struct NfsMount3TDump {
+	NfsMount3Call call;
+};
+
+struct NfsMount3Entry {
+	char *host;
+	char *path;
+};
+
+struct NfsMount3RDump {
+	NfsMount3Call call;
+	uchar *data;
+	u32int count;
+};
+
+struct NfsMount3TUmnt {
+	NfsMount3Call call;
+	char *path;
+};
+
+struct NfsMount3RUmnt {
+	NfsMount3Call call;
+};
+
+struct NfsMount3Export {
+	char *path;
+	char **g;
+	u32int ng;
+};
+
+struct NfsMount3TUmntall {
+	NfsMount3Call call;
+};
+
+struct NfsMount3RUmntall {
+	NfsMount3Call call;
+};
+
+struct NfsMount3TExport {
+	NfsMount3Call call;
+};
+
+struct NfsMount3RExport {
+	NfsMount3Call call;
+	uchar *data;
+	u32int count;
+};
+
+uint nfsMount3ExportGroupSize(uchar*);
+uint nfsMount3ExportSize(NfsMount3Export*);
+int nfsMount3ExportPack(uchar*, uchar*, uchar**, NfsMount3Export*);
+int nfsMount3ExportUnpack(uchar*, uchar*, uchar**, char**, char***, NfsMount3Export*);
+int nfsMount3EntryPack(uchar*, uchar*, uchar**, NfsMount3Entry*);
+int nfsMount3EntryUnpack(uchar*, uchar*, uchar**, NfsMount3Entry*);
+uint nfsMount3EntrySize(NfsMount3Entry*);
+
+extern SunProg nfsMount3Prog;
+
+/*
+ * NFS V3; see RFC 1813
+ */
+enum {
+	Nfs3MaxHandleSize = 64,
+	Nfs3CookieVerfSize = 8,
+	Nfs3CreateVerfSize = 8,
+	Nfs3WriteVerfSize = 8,
+	Nfs3AccessRead = 1,
+	Nfs3AccessLookup = 2,
+	Nfs3AccessModify = 4,
+	Nfs3AccessExtend = 8,
+	Nfs3AccessDelete = 16,
+	Nfs3AccessExecute = 32,
+	Nfs3FsHasLinks = 1,
+	Nfs3FsHasSymlinks = 2,
+	Nfs3FsHomogeneous = 8,
+	Nfs3FsCanSetTime = 16,
+
+	Nfs3Version = 3,	
+	Nfs3Program = 100003,
+};
+typedef enum
+{
+	Nfs3Ok = 0,
+	Nfs3ErrNotOwner = 1,
+	Nfs3ErrNoEnt = 2,
+	Nfs3ErrIo = 5,
+	Nfs3ErrNxio = 6,
+	Nfs3ErrNoMem = 12,
+	Nfs3ErrAcces = 13,
+	Nfs3ErrExist = 17,
+	Nfs3ErrXDev = 18,
+	Nfs3ErrNoDev = 19,
+	Nfs3ErrNotDir = 20,
+	Nfs3ErrIsDir = 21,
+	Nfs3ErrInval = 22,
+	Nfs3ErrFbig = 27,
+	Nfs3ErrNoSpc = 28,
+	Nfs3ErrRoFs = 30,
+	Nfs3ErrMLink = 31,
+	Nfs3ErrNameTooLong = 63,
+	Nfs3ErrNotEmpty = 66,
+	Nfs3ErrDQuot = 69,
+	Nfs3ErrStale = 70,
+	Nfs3ErrRemote = 71,
+	Nfs3ErrBadHandle = 10001,
+	Nfs3ErrNotSync = 10002,
+	Nfs3ErrBadCookie = 10003,
+	Nfs3ErrNotSupp = 10004,
+	Nfs3ErrTooSmall = 10005,
+	Nfs3ErrServerFault = 10006,
+	Nfs3ErrBadType = 10007,
+	Nfs3ErrJukebox = 10008,
+	Nfs3ErrFprintNotFound = 10009,
+	Nfs3ErrAborted = 10010,
+} Nfs3Status;
+
+void nfs3Errstr(Nfs3Status);
+
+typedef enum
+{
+	Nfs3FileReg = 1,
+	Nfs3FileDir = 2,
+	Nfs3FileBlock = 3,
+	Nfs3FileChar = 4,
+	Nfs3FileSymlink = 5,
+	Nfs3FileSocket = 6,
+	Nfs3FileFifo = 7,
+} Nfs3FileType;
+
+enum
+{
+	Nfs3ModeSetUid = 0x800,
+	Nfs3ModeSetGid = 0x400,
+	Nfs3ModeSticky = 0x200,
+};
+
+typedef enum
+{
+	Nfs3CallTNull,
+	Nfs3CallRNull,
+	Nfs3CallTGetattr,
+	Nfs3CallRGetattr,
+	Nfs3CallTSetattr,
+	Nfs3CallRSetattr,
+	Nfs3CallTLookup,
+	Nfs3CallRLookup,
+	Nfs3CallTAccess,
+	Nfs3CallRAccess,
+	Nfs3CallTReadlink,
+	Nfs3CallRReadlink,
+	Nfs3CallTRead,
+	Nfs3CallRRead,
+	Nfs3CallTWrite,
+	Nfs3CallRWrite,
+	Nfs3CallTCreate,
+	Nfs3CallRCreate,
+	Nfs3CallTMkdir,
+	Nfs3CallRMkdir,
+	Nfs3CallTSymlink,
+	Nfs3CallRSymlink,
+	Nfs3CallTMknod,
+	Nfs3CallRMknod,
+	Nfs3CallTRemove,
+	Nfs3CallRRemove,
+	Nfs3CallTRmdir,
+	Nfs3CallRRmdir,
+	Nfs3CallTRename,
+	Nfs3CallRRename,
+	Nfs3CallTLink,
+	Nfs3CallRLink,
+	Nfs3CallTReadDir,
+	Nfs3CallRReadDir,
+	Nfs3CallTReadDirPlus,
+	Nfs3CallRReadDirPlus,
+	Nfs3CallTFsStat,
+	Nfs3CallRFsStat,
+	Nfs3CallTFsInfo,
+	Nfs3CallRFsInfo,
+	Nfs3CallTPathconf,
+	Nfs3CallRPathconf,
+	Nfs3CallTCommit,
+	Nfs3CallRCommit,
+} Nfs3CallType;
+
+typedef struct Nfs3Call Nfs3Call;
+typedef struct Nfs3Handle Nfs3Handle;
+typedef struct Nfs3Time Nfs3Time;
+typedef struct Nfs3Attr Nfs3Attr;
+typedef struct Nfs3WccAttr Nfs3WccAttr;
+typedef struct Nfs3Wcc Nfs3Wcc;
+typedef enum
+{
+	Nfs3SetTimeDont = 0,
+	Nfs3SetTimeServer = 1,
+	Nfs3SetTimeClient = 2,
+} Nfs3SetTime;
+
+typedef struct Nfs3SetAttr Nfs3SetAttr;
+typedef struct Nfs3TNull Nfs3TNull;
+typedef struct Nfs3RNull Nfs3RNull;
+typedef struct Nfs3TGetattr Nfs3TGetattr;
+typedef struct Nfs3RGetattr Nfs3RGetattr;
+typedef struct Nfs3TSetattr Nfs3TSetattr;
+typedef struct Nfs3RSetattr Nfs3RSetattr;
+typedef struct Nfs3TLookup Nfs3TLookup;
+typedef struct Nfs3RLookup Nfs3RLookup;
+typedef struct Nfs3TAccess Nfs3TAccess;
+typedef struct Nfs3RAccess Nfs3RAccess;
+typedef struct Nfs3TReadlink Nfs3TReadlink;
+typedef struct Nfs3RReadlink Nfs3RReadlink;
+typedef struct Nfs3TRead Nfs3TRead;
+typedef struct Nfs3RRead Nfs3RRead;
+typedef enum
+{
+	Nfs3SyncNone = 0,
+	Nfs3SyncData = 1,
+	Nfs3SyncFile = 2,
+} Nfs3Sync;
+
+typedef struct Nfs3TWrite Nfs3TWrite;
+typedef struct Nfs3RWrite Nfs3RWrite;
+typedef enum
+{
+	Nfs3CreateUnchecked = 0,
+	Nfs3CreateGuarded = 1,
+	Nfs3CreateExclusive = 2,
+} Nfs3Create;
+
+typedef struct Nfs3TCreate Nfs3TCreate;
+typedef struct Nfs3RCreate Nfs3RCreate;
+typedef struct Nfs3TMkdir Nfs3TMkdir;
+typedef struct Nfs3RMkdir Nfs3RMkdir;
+typedef struct Nfs3TSymlink Nfs3TSymlink;
+typedef struct Nfs3RSymlink Nfs3RSymlink;
+typedef struct Nfs3TMknod Nfs3TMknod;
+typedef struct Nfs3RMknod Nfs3RMknod;
+typedef struct Nfs3TRemove Nfs3TRemove;
+typedef struct Nfs3RRemove Nfs3RRemove;
+typedef struct Nfs3TRmdir Nfs3TRmdir;
+typedef struct Nfs3RRmdir Nfs3RRmdir;
+typedef struct Nfs3TRename Nfs3TRename;
+typedef struct Nfs3RRename Nfs3RRename;
+typedef struct Nfs3TLink Nfs3TLink;
+typedef struct Nfs3RLink Nfs3RLink;
+typedef struct Nfs3TReadDir Nfs3TReadDir;
+typedef struct Nfs3Entry Nfs3Entry;
+typedef struct Nfs3RReadDir Nfs3RReadDir;
+typedef struct Nfs3TReadDirPlus Nfs3TReadDirPlus;
+typedef struct Nfs3EntryPlus Nfs3EntryPlus;
+typedef struct Nfs3RReadDirPlus Nfs3RReadDirPlus;
+typedef struct Nfs3TFsStat Nfs3TFsStat;
+typedef struct Nfs3RFsStat Nfs3RFsStat;
+typedef struct Nfs3TFsInfo Nfs3TFsInfo;
+typedef struct Nfs3RFsInfo Nfs3RFsInfo;
+typedef struct Nfs3TPathconf Nfs3TPathconf;
+typedef struct Nfs3RPathconf Nfs3RPathconf;
+typedef struct Nfs3TCommit Nfs3TCommit;
+typedef struct Nfs3RCommit Nfs3RCommit;
+
+struct Nfs3Call {
+	SunRpc rpc;
+	Nfs3CallType type;
+};
+
+struct Nfs3Handle {
+	uchar h[Nfs3MaxHandleSize];
+	u32int len;
+};
+
+struct Nfs3Time {
+	u32int sec;
+	u32int nsec;
+};
+
+struct Nfs3Attr {
+	Nfs3FileType type;
+	u32int mode;
+	u32int nlink;
+	u32int uid;
+	u32int gid;
+	u64int size;
+	u64int used;
+	u32int major;
+	u32int minor;
+	u64int fsid;
+	u64int fileid;
+	Nfs3Time atime;
+	Nfs3Time mtime;
+	Nfs3Time ctime;
+};
+
+struct Nfs3WccAttr {
+	u64int size;
+	Nfs3Time mtime;
+	Nfs3Time ctime;
+};
+
+struct Nfs3Wcc {
+	u1int haveWccAttr;
+	Nfs3WccAttr wccAttr;
+	u1int haveAttr;
+	Nfs3Attr attr;
+};
+
+struct Nfs3SetAttr {
+	u1int setMode;
+	u32int mode;
+	u1int setUid;
+	u32int uid;
+	u1int setGid;
+	u32int gid;
+	u1int setSize;
+	u64int size;
+	Nfs3SetTime setAtime;
+	Nfs3Time atime;
+	Nfs3SetTime setMtime;
+	Nfs3Time mtime;
+};
+
+struct Nfs3TNull {
+	Nfs3Call call;
+};
+
+struct Nfs3RNull {
+	Nfs3Call call;
+};
+
+struct Nfs3TGetattr {
+	Nfs3Call call;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RGetattr {
+	Nfs3Call call;
+	Nfs3Status status;
+	Nfs3Attr attr;
+};
+
+struct Nfs3TSetattr {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	Nfs3SetAttr attr;
+	u1int checkCtime;
+	Nfs3Time ctime;
+};
+
+struct Nfs3RSetattr {
+	Nfs3Call call;
+	Nfs3Status status;
+	Nfs3Wcc wcc;
+};
+
+struct Nfs3TLookup {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	char *name;
+};
+
+struct Nfs3RLookup {
+	Nfs3Call call;
+	Nfs3Status status;
+	Nfs3Handle handle;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u1int haveDirAttr;
+	Nfs3Attr dirAttr;
+};
+
+struct Nfs3TAccess {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	u32int access;
+};
+
+struct Nfs3RAccess {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u32int access;
+};
+
+struct Nfs3TReadlink {
+	Nfs3Call call;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RReadlink {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	char *data;
+};
+
+struct Nfs3TRead {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	u64int offset;
+	u32int count;
+};
+
+struct Nfs3RRead {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u32int count;
+	u1int eof;
+	uchar *data;
+	u32int ndata;
+};
+
+struct Nfs3TWrite {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	u64int offset;
+	u32int count;
+	Nfs3Sync stable;
+	uchar *data;
+	u32int ndata;
+};
+
+struct Nfs3RWrite {
+	Nfs3Call call;
+	Nfs3Status status;
+	Nfs3Wcc wcc;
+	u32int count;
+	Nfs3Sync committed;
+	uchar verf[Nfs3WriteVerfSize];
+};
+
+struct Nfs3TCreate {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	char *name;
+	Nfs3Create mode;
+	Nfs3SetAttr attr;
+	uchar verf[Nfs3CreateVerfSize];
+};
+
+struct Nfs3RCreate {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveHandle;
+	Nfs3Handle handle;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	Nfs3Wcc dirWcc;
+};
+
+struct Nfs3TMkdir {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	char *name;
+	Nfs3SetAttr attr;
+};
+
+struct Nfs3RMkdir {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveHandle;
+	Nfs3Handle handle;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	Nfs3Wcc dirWcc;
+};
+
+struct Nfs3TSymlink {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	char *name;
+	Nfs3SetAttr attr;
+	char *data;
+};
+
+struct Nfs3RSymlink {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveHandle;
+	Nfs3Handle handle;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	Nfs3Wcc dirWcc;
+};
+
+struct Nfs3TMknod {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	char *name;
+	Nfs3FileType type;
+	Nfs3SetAttr attr;
+	u32int major;
+	u32int minor;
+};
+
+struct Nfs3RMknod {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveHandle;
+	Nfs3Handle handle;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	Nfs3Wcc dirWcc;
+};
+
+struct Nfs3TRemove {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	char *name;
+};
+
+struct Nfs3RRemove {
+	Nfs3Call call;
+	Nfs3Status status;
+	Nfs3Wcc wcc;
+};
+
+struct Nfs3TRmdir {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	char *name;
+};
+
+struct Nfs3RRmdir {
+	Nfs3Call call;
+	Nfs3Status status;
+	Nfs3Wcc wcc;
+};
+
+struct Nfs3TRename {
+	Nfs3Call call;
+	struct {
+		Nfs3Handle handle;
+		char *name;
+	} from;
+	struct {
+		Nfs3Handle handle;
+		char *name;
+	} to;
+};
+
+struct Nfs3RRename {
+	Nfs3Call call;
+	Nfs3Status status;
+	Nfs3Wcc fromWcc;
+	Nfs3Wcc toWcc;
+};
+
+struct Nfs3TLink {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	struct {
+		Nfs3Handle handle;
+		char *name;
+	} link;
+};
+
+struct Nfs3RLink {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	Nfs3Wcc dirWcc;
+};
+
+struct Nfs3TReadDir {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	u64int cookie;
+	uchar verf[Nfs3CookieVerfSize];
+	u32int count;
+};
+
+struct Nfs3RReadDir {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	uchar verf[Nfs3CookieVerfSize];
+	uchar *data;
+	u32int count;
+	u1int eof;
+};
+
+struct Nfs3TReadDirPlus {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	u64int cookie;
+	uchar verf[Nfs3CookieVerfSize];
+	u32int dirCount;
+	u32int maxCount;
+};
+
+struct Nfs3Entry {
+	u64int fileid;
+	char *name;
+	u64int cookie;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u1int haveHandle;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RReadDirPlus {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	uchar verf[Nfs3CookieVerfSize];
+	uchar *data;
+	u32int count;
+	u1int eof;
+};
+
+struct Nfs3TFsStat {
+	Nfs3Call call;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RFsStat {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u64int totalBytes;
+	u64int freeBytes;
+	u64int availBytes;
+	u64int totalFiles;
+	u64int freeFiles;
+	u64int availFiles;
+	u32int invarSec;
+};
+
+struct Nfs3TFsInfo {
+	Nfs3Call call;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RFsInfo {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u32int readMax;
+	u32int readPref;
+	u32int readMult;
+	u32int writeMax;
+	u32int writePref;
+	u32int writeMult;
+	u32int readDirPref;
+	u64int maxFileSize;
+	Nfs3Time timePrec;
+	u32int flags;
+};
+
+struct Nfs3TPathconf {
+	Nfs3Call call;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RPathconf {
+	Nfs3Call call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u32int maxLink;
+	u32int maxName;
+	u1int noTrunc;
+	u1int chownRestricted;
+	u1int caseInsensitive;
+	u1int casePreserving;
+};
+
+struct Nfs3TCommit {
+	Nfs3Call call;
+	Nfs3Handle handle;
+	u64int offset;
+	u32int count;
+};
+
+struct Nfs3RCommit {
+	Nfs3Call call;
+	Nfs3Status status;
+	Nfs3Wcc wcc;
+	uchar verf[Nfs3WriteVerfSize];
+};
+
+char *nfs3StatusStr(Nfs3Status);
+char *nfs3TypeStr(Nfs3CallType);
+char *nfs3SetTimeStr(Nfs3SetTime);
+char *nfs3SyncStr(Nfs3Sync);
+
+void nfs3HandlePrint(Fmt*, Nfs3Handle*);
+u32int nfs3HandleSize(Nfs3Handle*);
+int nfs3HandlePack(uchar*, uchar*, uchar**, Nfs3Handle*);
+int nfs3HandleUnpack(uchar*, uchar*, uchar**, Nfs3Handle*);
+
+void nfs3TimePrint(Fmt*, Nfs3Time*);
+u32int nfs3TimeSize(Nfs3Time*);
+int nfs3TimePack(uchar*, uchar*, uchar**, Nfs3Time*);
+int nfs3TimeUnpack(uchar*, uchar*, uchar**, Nfs3Time*);
+
+void nfs3AttrPrint(Fmt*, Nfs3Attr*);
+u32int nfs3AttrSize(Nfs3Attr*);
+int nfs3AttrPack(uchar*, uchar*, uchar**, Nfs3Attr*);
+int nfs3AttrUnpack(uchar*, uchar*, uchar**, Nfs3Attr*);
+
+void nfs3WccAttrPrint(Fmt*, Nfs3WccAttr*);
+u32int nfs3WccAttrSize(Nfs3WccAttr*);
+int nfs3WccAttrPack(uchar*, uchar*, uchar**, Nfs3WccAttr*);
+int nfs3WccAttrUnpack(uchar*, uchar*, uchar**, Nfs3WccAttr*);
+
+void nfs3WccPrint(Fmt*, Nfs3Wcc*);
+u32int nfs3WccSize(Nfs3Wcc*);
+int nfs3WccPack(uchar*, uchar*, uchar**, Nfs3Wcc*);
+int nfs3WccUnpack(uchar*, uchar*, uchar**, Nfs3Wcc*);
+
+void nfs3SetAttrPrint(Fmt*, Nfs3SetAttr*);
+u32int nfs3SetAttrSize(Nfs3SetAttr*);
+int nfs3SetAttrPack(uchar*, uchar*, uchar**, Nfs3SetAttr*);
+int nfs3SetAttrUnpack(uchar*, uchar*, uchar**, Nfs3SetAttr*);
+
+extern SunProg nfs3Prog;
+
+void nfs3EntryPrint(Fmt*, Nfs3Entry*);
+u32int nfs3EntrySize(Nfs3Entry*);
+int nfs3EntryPack(uchar*, uchar*, uchar**, Nfs3Entry*);
+int nfs3EntryUnpack(uchar*, uchar*, uchar**, Nfs3Entry*);
+
+void nfs3EntryPlusPrint(Fmt*, Nfs3Entry*);
+u32int nfs3EntryPlusSize(Nfs3Entry*);
+int nfs3EntryPlusPack(uchar*, uchar*, uchar**, Nfs3Entry*);
+int nfs3EntryPlusUnpack(uchar*, uchar*, uchar**, Nfs3Entry*);
+

+ 395 - 0
sys/include/sunrpc.h

@@ -0,0 +1,395 @@
+/*
+ * Sun RPC; see RFC 1057
+ */
+
+#pragma lib "libsunrpc.a"
+#pragma src "/sys/src/libsunrpc"
+
+typedef uchar u1int;
+
+typedef struct SunAuthInfo SunAuthInfo;
+typedef struct SunAuthUnix SunAuthUnix;
+typedef struct SunRpc SunRpc;
+typedef struct SunCall SunCall;
+
+enum
+{
+	/* Authinfo.flavor */
+	SunAuthNone = 0,
+	SunAuthSys,
+	SunAuthShort,
+	SunAuthDes,
+};
+
+typedef enum {
+	SunAcceptError = 0x10000,
+	SunRejectError = 0x20000,
+	SunAuthError = 0x40000,
+
+	/* Reply.status */
+	SunSuccess = 0,
+
+	SunProgUnavail = SunAcceptError | 1,
+	SunProgMismatch,
+	SunProcUnavail,
+	SunGarbageArgs,
+	SunSystemErr,
+
+	SunRpcMismatch = SunRejectError | 0,
+
+	SunAuthBadCred = SunAuthError | 1,
+	SunAuthRejectedCred,
+	SunAuthBadVerf,
+	SunAuthRejectedVerf,
+	SunAuthTooWeak,
+	SunAuthInvalidResp,
+	SunAuthFailed,
+} SunStatus;
+
+struct SunAuthInfo
+{
+	uint flavor;
+	uchar *data;
+	uint ndata;
+};
+
+struct SunAuthUnix
+{
+	u32int stamp;
+	char *sysname;
+	u32int uid;
+	u32int gid;
+	u32int g[16];
+	u32int ng;
+};
+
+struct SunRpc
+{
+	u32int xid;
+	uint iscall;
+
+	/*
+	 * only sent on wire in call
+	 * caller fills in for the reply unpackers.
+	 */
+	u32int proc;
+
+	/* call */
+	// uint proc;
+	u32int prog, vers;
+	SunAuthInfo cred;
+	SunAuthInfo verf;
+	uchar *data;
+	uint ndata;
+
+	/* reply */
+	u32int status;
+	// SunAuthInfo verf;
+	u32int low, high;
+	// uchar *data;
+	// uint ndata;
+};
+
+typedef enum
+{
+	SunCallTypeTNull,
+	SunCallTypeRNull,
+} SunCallType;
+
+struct SunCall
+{
+	SunRpc rpc;
+	SunCallType type;
+};
+
+void sunErrstr(SunStatus);
+
+void sunRpcPrint(Fmt*, SunRpc*);
+uint sunRpcSize(SunRpc*);
+SunStatus sunRpcPack(uchar*, uchar*, uchar**, SunRpc*);
+SunStatus sunRpcUnpack(uchar*, uchar*, uchar**, SunRpc*);
+
+void sunAuthInfoPrint(Fmt*, SunAuthInfo*);
+uint sunAuthInfoSize(SunAuthInfo*);
+int sunAuthInfoPack(uchar*, uchar*, uchar**, SunAuthInfo*);
+int sunAuthInfoUnpack(uchar*, uchar*, uchar**, SunAuthInfo*);
+
+void sunAuthUnixPrint(Fmt*, SunAuthUnix*);
+uint sunAuthUnixSize(SunAuthUnix*);
+int sunAuthUnixPack(uchar*, uchar*, uchar**, SunAuthUnix*);
+int sunAuthUnixUnpack(uchar*, uchar*, uchar**, SunAuthUnix*);
+
+int sunEnumPack(uchar*, uchar*, uchar**, int*);
+int sunEnumUnpack(uchar*, uchar*, uchar**, int*);
+int sunUint1Pack(uchar*, uchar*, uchar**, u1int*);
+int sunUint1Unpack(uchar*, uchar*, uchar**, u1int*);
+
+int sunStringPack(uchar*, uchar*, uchar**, char**, u32int);
+int sunStringUnpack(uchar*, uchar*, uchar**, char**, u32int);
+uint sunStringSize(char*);
+
+int sunUint32Pack(uchar*, uchar*, uchar**, u32int*);
+int sunUint32Unpack(uchar*, uchar*, uchar**, u32int*);
+int sunUint64Pack(uchar*, uchar*, uchar**, u64int*);
+int sunUint64Unpack(uchar*, uchar*, uchar**, u64int*);
+
+int sunVarOpaquePack(uchar*, uchar*, uchar**, uchar**, u32int*, u32int);
+int sunVarOpaqueUnpack(uchar*, uchar*, uchar**, uchar**, u32int*, u32int);
+uint sunVarOpaqueSize(u32int);
+
+int sunFixedOpaquePack(uchar*, uchar*, uchar**, uchar*, u32int);
+int sunFixedOpaqueUnpack(uchar*, uchar*, uchar**, uchar*, u32int);
+uint sunFixedOpaqueSize(u32int);
+
+/*
+ * Sun RPC Program
+ */
+typedef struct SunProc SunProc;
+typedef struct SunProg SunProg;
+struct SunProg
+{
+	uint prog;
+	uint vers;
+	SunProc *proc;
+	int nproc;
+};
+
+struct SunProc
+{
+	int (*pack)(uchar*, uchar*, uchar**, SunCall*);
+	int (*unpack)(uchar*, uchar*, uchar**, SunCall*);
+	uint (*size)(SunCall*);
+	void (*fmt)(Fmt*, SunCall*);
+	uint sizeoftype;
+};
+
+SunStatus sunCallPack(SunProg*, uchar*, uchar*, uchar**, SunCall*);
+SunStatus sunCallUnpack(SunProg*, uchar*, uchar*, uchar**, SunCall*);
+SunStatus sunCallUnpackAlloc(SunProg*, SunCallType, uchar*, uchar*, uchar**, SunCall**);
+uint sunCallSize(SunProg*, SunCall*);
+
+/*
+ * Formatting
+ */
+#pragma varargck type "B" SunRpc*
+#pragma varargck type "C" SunCall*
+
+int	sunRpcFmt(Fmt*);
+int	sunCallFmt(Fmt*);
+void	sunFmtInstall(SunProg*);
+
+
+/*
+ * Sun RPC Server
+ */
+typedef struct SunMsg SunMsg;
+typedef struct SunSrv SunSrv;
+
+enum
+{
+	SunStackSize = 8192,
+};
+
+struct SunMsg
+{
+	uchar *data;
+	int count;
+	SunSrv *srv;
+	SunRpc rpc;
+	SunProg *pg;
+	SunCall *call;
+	Channel *creply;	/* chan(SunMsg*) */
+};
+
+struct SunSrv
+{
+	int chatty;
+	int cacheReplies;
+	int alwaysReject;
+	SunProg **map;
+	Channel *crequest;
+
+/* implementation use only */
+	Channel **cdispatch;
+	SunProg **prog;
+	int nprog;
+	void *cache;
+	Channel *creply;
+	Channel *cthread;
+};
+
+SunSrv *sunSrv(void);
+
+void	sunSrvProg(SunSrv *srv, SunProg *prog, Channel *c);
+int	sunSrvAnnounce(SunSrv *srv, char *address);
+int	sunSrvUdp(SunSrv *srv, char *address);
+int	sunSrvNet(SunSrv *srv, char *address);
+int	sunSrvFd(SunSrv *srv, int fd);
+void	sunSrvThreadCreate(SunSrv *srv, void (*fn)(void*), void*);
+void	sunSrvClose(SunSrv*);
+
+int	sunMsgReply(SunMsg*, SunCall*);
+int	sunMsgDrop(SunMsg*);
+int	sunMsgReplyError(SunMsg*, SunStatus);
+
+/*
+ * Sun RPC Client
+ */
+typedef struct SunClient SunClient;
+
+struct SunClient
+{
+	int		fd;
+	int		chatty;
+	int		needcount;
+	ulong	maxwait;
+	ulong	xidgen;
+	int		nsend;
+	int		nresend;
+	struct {
+		ulong min;
+		ulong max;
+		ulong avg;
+	} rtt;
+	Channel	*dying;
+	Channel	*rpcchan;
+	Channel	*timerchan;
+	Channel	*flushchan;
+	Channel	*readchan;
+	SunProg	**prog;
+	int		nprog;
+	int 		timertid;
+	int 		nettid;
+};
+
+SunClient	*sunDial(char*);
+
+int	sunClientRpc(SunClient*, ulong, SunCall*, SunCall*, uchar**);
+void	sunClientClose(SunClient*);
+void	sunClientFlushRpc(SunClient*, ulong);
+void	sunClientProg(SunClient*, SunProg*);
+
+
+/*
+ * Provided by callers.
+ * Should remove dependence on this, but hard.
+ */
+void	*emalloc(ulong);
+void *erealloc(void*, ulong);
+
+
+/*
+ * Sun RPC port mapper; see RFC 1057 Appendix A
+ */
+
+typedef struct PortMap PortMap;
+typedef struct PortTNull PortTNull;
+typedef struct PortRNull PortRNull;
+typedef struct PortTSet PortTSet;
+typedef struct PortRSet PortRSet;
+typedef struct PortTUnset PortTUnset;
+typedef struct PortRUnset PortRUnset;
+typedef struct PortTGetport PortTGetport;
+typedef struct PortRGetport PortRGetport;
+typedef struct PortTDump PortTDump;
+typedef struct PortRDump PortRDump;
+typedef struct PortTCallit PortTCallit;
+typedef struct PortRCallit PortRCallit;
+
+typedef enum
+{
+	PortCallTNull,
+	PortCallRNull,
+	PortCallTSet,
+	PortCallRSet,
+	PortCallTUnset,
+	PortCallRUnset,
+	PortCallTGetport,
+	PortCallRGetport,
+	PortCallTDump,
+	PortCallRDump,
+	PortCallTCallit,
+	PortCallRCallit,
+} PortCallType;
+
+enum
+{
+	PortProgram	= 100000,
+	PortVersion	= 2,
+
+	PortProtoTcp	= 6,	/* protocol number for TCP/IP */
+	PortProtoUdp	= 17	/* protocol number for UDP/IP */
+};
+
+struct PortMap {
+	u32int prog;
+	u32int vers;
+	u32int prot;
+	u32int port;
+};
+
+struct PortTNull {
+	SunCall call;
+};
+
+struct PortRNull {
+	SunCall call;
+};
+
+struct PortTSet {
+	SunCall call;
+	PortMap map;
+};
+
+struct PortRSet {
+	SunCall call;
+	u1int b;
+};
+
+struct PortTUnset {
+	SunCall call;
+	PortMap map;
+};
+
+struct PortRUnset {
+	SunCall call;
+	u1int b;
+};
+
+struct PortTGetport {
+	SunCall call;
+	PortMap map;
+};
+
+struct PortRGetport {
+	SunCall call;
+	u32int port;
+};
+
+struct PortTDump {
+	SunCall call;
+};
+
+struct PortRDump {
+	SunCall call;
+	PortMap *map;
+	int nmap;
+};
+
+struct PortTCallit {
+	SunCall call;
+	u32int prog;
+	u32int vers;
+	u32int proc;
+	uchar *data;
+	u32int count;
+};
+
+struct PortRCallit {
+	SunCall call;
+	u32int port;
+	uchar *data;
+	u32int count;
+};
+
+extern SunProg portProg;

+ 146 - 0
sys/man/4/nfs

@@ -0,0 +1,146 @@
+.TH NFS 4
+.SH NAME
+nfs \- Sun network file system client
+.SH SYNOPSIS
+.B nfs
+[
+.B -DRv
+]
+[
+.B -p
+.I perm
+]
+[
+.B -s
+.I srvname
+]
+[
+.B -u
+.I passwd
+.I group
+]
+.I addr1
+[
+.I addr2
+]
+.PP
+.B aux/portmap
+[
+.B -R
+]
+.I host
+.I cmd
+...
+.PP
+.B aux/nfsmonut
+[
+.B -R
+]
+.I host
+.I cmd
+...
+.SH DESCRIPTION
+.I Nfs
+translates between the Sun network file system protocol (NFS)
+and 9P, allowing 9P clients to mount file systems on NFS servers.
+NFS servers comprise two separate services: a mount service used to
+obtain the initial file handle, and a file service used to perform
+actual file system operations.
+The Sun port mapper service is typically used to find these two services.
+If one address is given, it is taken to be the address of a port mapper service;
+.I nfs
+queries the port mapper to find the addresses
+of the NFS mount service and file service.
+If two addresses are given, the port mapper is bypasswd;
+.I addr1
+is used as the address of the NFS mount service,
+and
+.I addr2
+is used as the address of the file service.
+.PP
+The options are:
+.TP
+.B -D
+print all 9P messages.
+.TP
+.B -R
+print all NFS messages.
+.TP
+.B -v
+print verbose information about session startup.
+.TP
+.B -p \fIperm
+set the posted service file to have mode
+.IR perm ,
+which is assumed to be octal;
+the default is
+.BR 600 .
+.TP
+.B -s \fIsrvname
+post the service as
+.BI /srv/ srvname \fR;
+the default is
+.BI /srv/ addr1 \fR.
+.TP
+.B -u \fIpasswd\fR \fIgroup
+translate user and group names using the 
+.I passwd
+and
+.I group
+files, which are in the traditional Unix format.
+The translation is used to present names for
+user and group in
+.I stat (5)
+and
+.I wstat
+messages.
+The translation is also used to
+choose the user and group credentials
+to present for a user.
+Without this option, users and groups are presented
+as decimal numbers, and everyone attaches as uid \-1
+.RB ( nobody
+on most Unix systems).
+.PD
+.I Portmap
+and
+.I nfsmount
+are test programs to perform port mapper and NFS mount RPCs.
+Run
+.B aux/portmap
+.B -?
+and
+.B aux/nfsmount
+.B -?
+for lists of commands.
+.SH EXAMPLE
+We use this in our
+.B /rc/bin/9fs
+script to mount all the home directories served by
+.IR bopp :
+.IP
+.EX
+case bopp
+	if(! test -f /srv/bopp)
+		nfs -p 666 -u /lib/ndb/1127.passwd /lib/ndb/1127.group bopp
+	unmount /n/bopp >[2]/dev/null
+	for(i in u0 u1 u2 u3 u4 u5 u6 u7 u8 u9)
+		mount -a /srv/bopp /n/bopp /$i
+.EE
+.SH SOURCE
+.B /sys/src/cmd/nfs.c
+.br
+.B /sys/src/libsunrpc
+.SH "SEE ALSO
+.IR nfsserver (8),
+.IR srv (4)
+.SH BUGS
+The authentication employed by NFS is laughable.
+The server simply trusts the uid, gid, and group list
+presented by the client.
+.PP
+.I Nfs
+speaks only NFS version 3.
+Older operating systems typically
+have reasonable NFS version 2 servers
+but crash when serving version 3.

+ 29 - 7
sys/man/8/fs

@@ -124,6 +124,14 @@ fs, exsort \- file server maintenance
 [
 .I tunit
 ]
+.PP
+.B wormoffline
+.I drive
+.PP
+.B wormonline
+.I drive
+.PP
+.B wormreset
 .PD
 .PP
 .B disk/exsort
@@ -742,13 +750,23 @@ are given the output selects connections owned by those users.
 .I Wormeject
 moves the WORM disk in slot
 .I tunit
-to the output shelf.
+of the first jukebox to the output shelf.
 .PP
 .I Wormingest
-moves the WORM disk from the input shelf to
-slot
+moves the WORM disk from the input shelf of the first jukebox to slot
 .IR tunit .
 .PP
+.I Wormoffline
+takes
+.I drive
+of the first jukebox out of service;
+.I wormonline
+puts it back in service.
+.PP
+.I Wormreset
+put discs back where the jukebox thinks they belong,
+and does this for all jukeboxes.
+.PP
 When the file server boots, it prints the message
 .IP
 .EX
@@ -778,12 +796,16 @@ With option
 .B -w
 it writes the sorted data back to
 .IR file .
-.SH SOURCE
-.B /sys/src/fs
-.br
-.B /sys/src/cmd/disk/exsort.c
 .SH SEE ALSO
 .IR fs (4)
 .br
 Ken Thompson,
 ``The Plan 9 File Server''.
+.SH SOURCE
+.B /sys/src/fs
+.br
+.B /sys/src/cmd/disk/exsort.c
+.SH BUGS
+The
+.B worm*
+commands should accept an argument identifying a jukebox.

+ 1 - 1
sys/src/cmd/acme/util.c

@@ -201,7 +201,7 @@ runetobyte(Rune *r, int n)
 {
 	char *s;
 
-	if(n == 0)
+	if(r == nil)
 		return nil;
 	s = emalloc(n*UTFmax+1);
 	setmalloctag(s, getcallerpc(&r));

+ 2 - 1
sys/src/cmd/aux/mkfile

@@ -19,8 +19,10 @@ TARG=\
 	ms2\
 	mswordstrings\
 	mouse\
+	nfsmount\
 	olefs\
 	pcmcia\
+	portmap\
 	rdwr\
 	reboot\
 	searchfs\
@@ -38,7 +40,6 @@ UPDATE=\
 	$HFILES\
 	${OFILES:%.$O=%.c}\
 	${TARG:%=%.c}\
-	${TARG:%=/386/bin/aux/%}\
 
 </sys/src/cmd/mkmany
 

+ 334 - 0
sys/src/cmd/aux/nfsmount.c

@@ -0,0 +1,334 @@
+/* Copyright © 2003 Russ Cox, MIT; see /sys/src/libsunrpc/COPYING */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+#include <nfs3.h>
+
+int chatty;
+SunClient *client;
+
+void
+usage(void)
+{
+	fprint(2, "usage: nfsmount address [cmd]\n"
+		"cmd is one of:\n"
+		"\tnull\n"
+		"\tmnt path\n"
+		"\tdump\n"
+		"\tumnt path\n"
+		"\tumntall\n"
+		"\texport (default)\n");
+	threadexitsall("usage");
+}
+
+void
+portCall(SunCall *c, PortCallType type)
+{
+	c->rpc.prog = PortProgram;
+	c->rpc.vers = PortVersion;
+	c->rpc.proc = type>>1;
+	c->rpc.iscall = !(type&1);
+	c->type = type;
+}
+
+int
+getport(SunClient *client, uint prog, uint vers, uint prot, uint *port)
+{
+	PortTGetport tx;
+	PortRGetport rx;
+
+	memset(&tx, 0, sizeof tx);
+	portCall(&tx.call, PortCallTGetport);
+	tx.map.prog = prog;
+	tx.map.vers = vers;
+	tx.map.prot = prot;
+
+	memset(&rx, 0, sizeof rx);
+	portCall(&rx.call, PortCallRGetport);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	*port = rx.port;
+	return 0;
+}
+
+uchar unixauth[] = {
+	0x12, 0x23, 0x34, 0x45,	/* stamp */
+	0x00, 0x00, 0x00, 0x04,	/* gnot */
+	0x67, 0x6e, 0x6f, 0x74,
+	0x00, 0x00, 0x03, 0xE9,	/* 1001 */
+	0x00, 0x00, 0x03, 0xE9,	/* 1001 */
+	0x00, 0x00, 0x00, 0x00,	/* gid list */
+};
+void
+mountCall(SunCall *c, NfsMount3CallType type)
+{
+	c->rpc.prog = NfsMount3Program;
+	c->rpc.vers = NfsMount3Version;
+	c->rpc.proc = type>>1;
+	c->rpc.iscall = !(type&1);
+	if(c->rpc.iscall){
+		c->rpc.cred.flavor = SunAuthSys;
+		c->rpc.cred.data = unixauth;
+		c->rpc.cred.ndata = sizeof unixauth;
+	}
+	c->type = type;
+}
+
+void
+tnull(char **argv)
+{
+	NfsMount3TNull tx;
+	NfsMount3RNull rx;
+
+	USED(argv);
+
+	memset(&tx, 0, sizeof tx);
+	mountCall(&tx.call, NfsMount3CallTNull);
+
+	memset(&rx, 0, sizeof rx);
+	mountCall(&rx.call, NfsMount3CallRNull);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		sysfatal("rpc: %r");
+}
+
+void
+tmnt(char **argv)
+{
+	int i;
+	NfsMount3TMnt tx;
+	NfsMount3RMnt rx;
+
+	memset(&tx, 0, sizeof tx);
+	mountCall(&tx.call, NfsMount3CallTMnt);
+	tx.path = argv[0];
+
+	memset(&rx, 0, sizeof rx);
+	mountCall(&rx.call, NfsMount3CallRMnt);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		sysfatal("rpc: %r");
+
+	if(rx.status != 0){
+		nfs3Errstr(rx.status);
+		sysfatal("mnt: %r");
+	}
+
+	print("handle %.*H\n", rx.len, rx.handle);
+	print("auth:");
+	for(i=0; i<rx.nauth; i++)
+		print(" %ud", (uint)rx.auth[i]);
+	print("\n");
+}
+
+void
+tdump(char **argv)
+{
+	uchar *p, *ep;
+	NfsMount3TDump tx;
+	NfsMount3RDump rx;
+	NfsMount3Entry e;
+
+	memset(&tx, 0, sizeof tx);
+	mountCall(&tx.call, NfsMount3CallTDump);
+	USED(argv);
+
+	memset(&rx, 0, sizeof rx);
+	mountCall(&rx.call, NfsMount3CallRDump);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		sysfatal("rpc: %r");
+
+	p = rx.data;
+	ep = p+rx.count;
+	while(p < ep){
+		if(nfsMount3EntryUnpack(p, ep, &p, &e) < 0)
+			sysfatal("unpack entry structure failed");
+		print("%s %s\n", e.host, e.path);
+	}
+}
+
+void
+tumnt(char **argv)
+{
+	NfsMount3TUmnt tx;
+	NfsMount3RUmnt rx;
+
+	memset(&tx, 0, sizeof tx);
+	mountCall(&tx.call, NfsMount3CallTUmnt);
+	tx.path = argv[0];
+
+	memset(&rx, 0, sizeof rx);
+	mountCall(&rx.call, NfsMount3CallRUmnt);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		sysfatal("rpc: %r");
+
+	print("\n");
+}
+
+void
+tumntall(char **argv)
+{
+	NfsMount3TUmntall tx;
+	NfsMount3RUmntall rx;
+
+	memset(&tx, 0, sizeof tx);
+	mountCall(&tx.call, NfsMount3CallTUmntall);
+	USED(argv);
+
+	memset(&rx, 0, sizeof rx);
+	mountCall(&rx.call, NfsMount3CallRUmntall);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		sysfatal("rpc: %r");
+
+	print("\n");
+}
+
+void
+texport(char **argv)
+{
+	uchar *p, *ep, *tofree;
+	char **g, **gg;
+	int ng, i, n;
+	NfsMount3TDump tx;
+	NfsMount3RDump rx;
+	NfsMount3Export e;
+
+	memset(&tx, 0, sizeof tx);
+	mountCall(&tx.call, NfsMount3CallTExport);
+	USED(argv);
+
+	memset(&rx, 0, sizeof rx);
+	mountCall(&rx.call, NfsMount3CallRExport);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, &tofree) < 0)
+		sysfatal("rpc: %r");
+
+	p = rx.data;
+	ep = p+rx.count;
+	g = nil;
+	ng = 0;
+	while(p < ep){
+		n = nfsMount3ExportGroupSize(p);
+		if(n > ng){
+			ng = n;
+			g = erealloc(g, sizeof(g[0])*ng);
+		}
+		if(nfsMount3ExportUnpack(p, ep, &p, g, &gg, &e) < 0)
+			sysfatal("unpack export structure failed");
+		print("%s", e.path);
+		for(i=0; i<e.ng; i++)
+			print(" %s", e.g[i]);
+		print("\n");
+	}
+	free(tofree);
+}
+
+static struct {
+	char *cmd;
+	int narg;
+	void (*fn)(char**);
+} tab[] = {
+	"null",	0,	tnull,
+	"mnt",	1,	tmnt,
+	"dump",	0,	tdump,
+	"umnt",	1,	tumnt,
+	"umntall",	1,	tumntall,
+	"export",	0,	texport,
+};
+
+char*
+netchangeport(char *addr, char *port)
+{
+	static char buf[256];
+	char *r;
+
+	strecpy(buf, buf+sizeof buf, addr);
+	r = strrchr(buf, '!');
+	if(r == nil)
+		return nil;
+	r++;
+	strecpy(r, buf+sizeof buf, port);
+	return buf;
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	char *dflt[] = { "export", };
+	char *addr, *cmd;
+	int i, proto;
+	uint port;
+	char buf[32];
+	int mapit;
+
+	mapit = 1;
+	ARGBEGIN{
+	case 'R':
+		chatty++;
+		break;
+	case 'm':
+		mapit = 0;
+		break;
+	}ARGEND
+
+	if(argc < 1)
+		usage();
+
+	fmtinstall('B', sunRpcFmt);
+	fmtinstall('C', sunCallFmt);
+	fmtinstall('H', encodefmt);
+	sunFmtInstall(&portProg);
+	sunFmtInstall(&nfsMount3Prog);
+
+	addr = netmkaddr(argv[0], "udp", "portmap");
+	if(mapit){
+		/* translate with port mapper */
+		fprint(2, "connecting to %s\n", addr);
+		if((client = sunDial(addr)) == nil)
+			sysfatal("dial %s: %r", addr);
+		client->chatty = chatty;
+		sunClientProg(client, &portProg);
+		if(strstr(addr, "udp!"))
+			proto = PortProtoUdp;
+		else
+			proto = PortProtoTcp;
+		if(getport(client, NfsMount3Program, NfsMount3Version, proto, &port) < 0)
+			sysfatal("getport: %r");
+		snprint(buf, sizeof buf, "%ud!r", port);
+		addr = netchangeport(addr, buf);
+		sunClientClose(client);
+	}
+
+	fprint(2, "connecting to %s\n", addr);
+	if((client = sunDial(addr)) == nil)
+		sysfatal("dial %s: %r", addr);
+
+	client->chatty = chatty;
+	sunClientProg(client, &nfsMount3Prog);
+
+	argv++;
+	argc--;
+
+	if(argc == 0){
+		argc = 1;
+		argv = dflt;
+	}
+	cmd = argv[0];
+	argv++;
+	argc--;
+
+	for(i=0; i<nelem(tab); i++){
+		if(strcmp(tab[i].cmd, cmd) == 0){
+			if(tab[i].narg != argc)
+				usage();
+			(*tab[i].fn)(argv);
+			threadexitsall(nil);
+		}
+	}
+	usage();
+}

+ 204 - 0
sys/src/cmd/aux/portmap.c

@@ -0,0 +1,204 @@
+/* Copyright © 2003 Russ Cox, MIT; see /sys/src/libsunrpc/COPYING */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+int chatty;
+SunClient *client;
+
+void
+usage(void)
+{
+	fprint(2, "usage: portmap address [cmd]\n"
+		"cmd is one of:\n"
+		"\tnull\n"
+		"\tset prog vers proto port\n"
+		"\tunset prog vers proto port\n"
+		"\tgetport prog vers proto\n"
+		"\tdump (default)\n");
+	threadexitsall("usage");
+}
+
+void
+portCall(SunCall *c, PortCallType type)
+{
+	c->rpc.prog = PortProgram;
+	c->rpc.vers = PortVersion;
+	c->rpc.proc = type>>1;
+	c->rpc.iscall = !(type&1);
+	c->type = type;
+}
+
+void
+tnull(char **argv)
+{
+	PortTNull tx;
+	PortRNull rx;
+
+	USED(argv);
+
+	memset(&tx, 0, sizeof tx);
+	portCall(&tx.call, PortCallTNull);
+
+	memset(&rx, 0, sizeof rx);
+	portCall(&rx.call, PortCallRNull);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		sysfatal("rpc: %r");
+}
+
+void
+tset(char **argv)
+{
+	PortTSet tx;
+	PortRSet rx;
+
+	memset(&tx, 0, sizeof tx);
+	portCall(&tx.call, PortCallTSet);
+	tx.map.prog = strtol(argv[0], 0, 0);
+	tx.map.vers = strtol(argv[1], 0, 0);
+	tx.map.prot = strtol(argv[2], 0, 0);
+	tx.map.port = strtol(argv[3], 0, 0);
+
+	memset(&rx, 0, sizeof rx);
+	portCall(&rx.call, PortCallRSet);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		sysfatal("rpc: %r");
+
+	if(rx.b == 0)
+		print("rejected\n");
+}
+
+void
+tunset(char **argv)
+{
+	PortTUnset tx;
+	PortRUnset rx;
+
+	memset(&tx, 0, sizeof tx);
+	portCall(&tx.call, PortCallTUnset);
+	tx.map.prog = strtol(argv[0], 0, 0);
+	tx.map.vers = strtol(argv[1], 0, 0);
+	tx.map.prot = strtol(argv[2], 0, 0);
+	tx.map.port = strtol(argv[3], 0, 0);
+
+	memset(&rx, 0, sizeof rx);
+	portCall(&rx.call, PortCallRUnset);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		sysfatal("rpc: %r");
+
+	if(rx.b == 0)
+		print("rejected\n");
+}
+
+void
+tgetport(char **argv)
+{
+	PortTGetport tx;
+	PortRGetport rx;
+
+	memset(&tx, 0, sizeof tx);
+	portCall(&tx.call, PortCallTGetport);
+	tx.map.prog = strtol(argv[0], 0, 0);
+	tx.map.vers = strtol(argv[1], 0, 0);
+	tx.map.prot = strtol(argv[2], 0, 0);
+
+	memset(&rx, 0, sizeof rx);
+	portCall(&rx.call, PortCallRGetport);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		sysfatal("rpc: %r");
+
+	print("%d\n", rx.port);
+}
+
+void
+tdump(char **argv)
+{
+	int i;
+	uchar *p;
+	PortTDump tx;
+	PortRDump rx;
+	PortMap *m;
+
+	USED(argv);
+
+	memset(&tx, 0, sizeof tx);
+	portCall(&tx.call, PortCallTDump);
+
+	memset(&rx, 0, sizeof rx);
+	portCall(&rx.call, PortCallRDump);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, &p) < 0)
+		sysfatal("rpc: %r");
+
+	for(i=0, m=rx.map; i<rx.nmap; i++, m++)
+		print("%ud %ud %ud %ud\n", (uint)m->prog, (uint)m->vers, (uint)m->prot, (uint)m->port);
+
+	free(p);
+}
+
+static struct {
+	char *cmd;
+	int narg;
+	void (*fn)(char**);
+} tab[] = {
+	"null",	0,	tnull,
+	"set",	4,	tset,
+	"unset",	4,	tunset,
+	"getport",	3,	tgetport,
+	"dump",	0,	tdump,
+};
+
+void
+threadmain(int argc, char **argv)
+{
+	char *dflt[] = { "dump", };
+	char *addr, *cmd;
+	int i;
+
+	ARGBEGIN{
+	case 'R':
+		chatty++;
+		break;
+	}ARGEND
+
+	if(argc < 2)
+		usage();
+
+	fmtinstall('B', sunRpcFmt);
+	fmtinstall('C', sunCallFmt);
+	fmtinstall('H', encodefmt);
+	sunFmtInstall(&portProg);
+
+	addr = netmkaddr(argv[0], "udp", "portmap");
+	if((client = sunDial(addr)) == nil)
+		sysfatal("dial %s: %r", addr);
+
+	client->chatty = chatty;
+	sunClientProg(client, &portProg);
+
+	argv++;
+	argc--;
+
+	if(argc == 0){
+		argc = 1;
+		argv = dflt;
+	}
+	cmd = argv[0];
+	argv++;
+	argc--;
+
+	for(i=0; i<nelem(tab); i++){
+		if(strcmp(tab[i].cmd, cmd) == 0){
+			if(tab[i].narg != argc)
+				usage();
+			(*tab[i].fn)(argv);
+			threadexitsall(nil);
+		}
+	}
+	usage();
+}

+ 54 - 22
sys/src/cmd/fossil/9.h

@@ -18,27 +18,56 @@ struct Msg {
 	Fcall	t;
 	Fcall	r;
 	Con*	con;
-	int	flush;
 
-	Msg*	next;
-	Msg*	prev;
+	Msg*	anext;			/* alloc */
+
+	Msg*	mnext;			/* Con */
+	Msg* 	mprev;
+
+	Msg*	rwnext;			/* proc read/write */
+
+	int	state;
+
+	Msg*	fnext;			/* proc flush */
+	Msg*	fprev;
+};
+
+enum {
+	MsgN		= 0,
+	MsgR		= 1,
+	Msg9		= 2,
+	MsgW		= 3,
+	MsgF		= 4,
 };
 
 struct Con {
-	VtLock*	lock;
-	int	fd;
 	char*	name;
 	uchar*	data;			/* max, not negotiated */
-	u32int	msize;			/* negotiated with Tversion */
+	int	isconsole;		/* immutable */
+
+	VtLock*	lock;
 	int	state;
-	int	aok;
+	int	fd;
 	Msg*	version;
-	int	isconsole;
+	u32int	msize;			/* negotiated with Tversion */
+	VtRendez* rendez;
+
+	Con*	anext;			/* alloc */
+	Con*	cnext;			/* in use */
+	Con*	cprev;
+
+	VtLock*	alock;
+	int	aok;			/* authentication done */
 
-	Msg*	mhead;			/* active queue */
+	VtLock*	mlock;
+	Msg*	mhead;			/* all Msgs on this connection */
 	Msg*	mtail;
-	VtRendez* active;
-	int	nmsg;
+	VtRendez* mrendez;
+
+	VtLock*	wlock;
+	Msg*	whead;			/* write queue */
+	Msg*	wtail;
+	VtRendez* wrendez;
 
 	VtLock*	fidlock;		/* */
 	Fid*	fidhash[NFidHash];
@@ -48,12 +77,12 @@ struct Con {
 };
 
 enum {
-	CsDead,
-	CsNew,
-	CsDown,
-	CsInit,
-	CsUp,
-	CsMoribund,
+	ConDead		= 0,
+	ConNew		= 1,
+	ConDown		= 2,
+	ConInit		= 3,
+	ConUp		= 4,
+	ConMoribund	= 5,
 };
 
 struct Fid {
@@ -64,6 +93,7 @@ struct Fid {
 	int	flags;
 
 	int	open;
+	Fsys*	fsys;
 	File*	file;
 	Qid	qid;
 	char*	uid;
@@ -73,7 +103,6 @@ struct Fid {
 
 	VtLock*	alock;			/* Tauth/Tattach */
 	AuthRpc* rpc;
-	Fsys*	fsys;
 	char*	cuname;
 
 	Fid*	hash;			/* lookup by fidno */
@@ -125,6 +154,7 @@ extern int exclUpdate(Fid*);
  * 9fid.c
  */
 extern void fidClunk(Fid*);
+extern void fidClunkAll(Con*);
 extern Fid* fidGet(Con*, u32int, int);
 extern void fidInit(void);
 extern void fidPut(Fid*);
@@ -132,10 +162,11 @@ extern void fidPut(Fid*);
 /*
  * 9fsys.c
  */
-extern Fsys* fsysGet(char*);
-extern Fs* fsysGetFs(Fsys*);
 extern void fsysFsRlock(Fsys*);
 extern void fsysFsRUnlock(Fsys*);
+extern Fs* fsysGetFs(Fsys*);
+extern Fsys* fsysGet(char*);
+extern char* fsysGetName(Fsys*);
 extern File* fsysGetRoot(Fsys*, char*);
 extern Fsys* fsysIncRef(Fsys*);
 extern int fsysInit(void);
@@ -143,7 +174,6 @@ extern int fsysNoAuthCheck(Fsys*);
 extern int fsysNoPermCheck(Fsys*);
 extern void fsysPut(Fsys*);
 extern int fsysWstatAllow(Fsys*);
-extern char* fsysGetName(Fsys*);
 
 /*
  * 9lstn.c
@@ -154,7 +184,9 @@ extern int lstnInit(void);
  * 9proc.c
  */
 extern Con* conAlloc(int, char*);
-extern void procInit(void);
+extern void conInit(void);
+extern void msgFlush(Msg*);
+extern void msgInit(void);
 
 /*
  * 9srv.c

+ 11 - 4
sys/src/cmd/fossil/9auth.c

@@ -53,6 +53,7 @@ authWrite(Fid* afid, void* data, int count)
 int
 authCheck(Fcall* t, Fid* fid, Fs* fsys)
 {
+	Con *con;
 	Fid *afid;
 	uchar buf[1];
 
@@ -61,6 +62,7 @@ authCheck(Fcall* t, Fid* fid, Fs* fsys)
 	 * protocol to do. Use a separate lock to protect altering
 	 * the auth information inside afid.
 	 */
+	con = fid->con;
 	if(t->afid == NOFID){
 		/*
 		 * If no authentication is asked for, allow
@@ -70,11 +72,14 @@ authCheck(Fcall* t, Fid* fid, Fs* fsys)
 		 * The console is allowed to attach without
 		 * authentication.
 		 */
-		if(!fid->con->isconsole &&
-		(strcmp(fid->uname, unamenone) != 0 || !fid->con->aok)){
+		vtRLock(con->alock);
+		if(!con->isconsole &&
+		(strcmp(fid->uname, unamenone) != 0 || !con->aok)){
+			vtRUnlock(con->alock);
 			consPrint("attach %s as %s: connection not authenticated, not console\n", fsysGetName(fsys), fid->uname);
 			return 0;
 		}
+		vtRUnlock(con->alock);
 
 		if((fid->uid = uidByUname(fid->uname)) == nil){
 			consPrint("attach %s as %s: unknown uname\n", fsysGetName(fsys), fid->uname);
@@ -83,7 +88,7 @@ authCheck(Fcall* t, Fid* fid, Fs* fsys)
 		return 1;
 	}
 
-	if((afid = fidGet(fid->con, t->afid, 0)) == nil){
+	if((afid = fidGet(con, t->afid, 0)) == nil){
 		consPrint("attach %s as %s: bad afid\n", fsysGetName(fsys), fid->uname);
 		return 0;
 	}
@@ -128,7 +133,9 @@ authCheck(Fcall* t, Fid* fid, Fs* fsys)
 	/*
 	 * Allow "none" once the connection has been authenticated.
 	 */
-	fid->con->aok = 1;
+	vtLock(con->alock);
+	con->aok = 1;
+	vtUnlock(con->alock);
 
 	return 1;
 }

+ 18 - 1
sys/src/cmd/fossil/9fid.c

@@ -77,6 +77,7 @@ fidAlloc(void)
 	fid->ref = 0;
 	fid->flags = 0;
 	fid->open = FidOCreate;
+	assert(fid->fsys == nil);
 	assert(fid->file == nil);
 	fid->qid = (Qid){0, 0, 0};
 	assert(fid->uid == nil);
@@ -84,7 +85,6 @@ fidAlloc(void)
 	assert(fid->db == nil);
 	assert(fid->excl == nil);
 	assert(fid->rpc == nil);
-	assert(fid->fsys == nil);
 	assert(fid->cuname == nil);
 	fid->hash = fid->next = fid->prev = nil;
 
@@ -279,6 +279,23 @@ fidClunk(Fid* fid)
 	fidFree(fid);
 }
 
+void
+fidClunkAll(Con* con)
+{
+	Fid *fid;
+	u32int fidno;
+
+	vtLock(con->fidlock);
+	while(con->fhead != nil){
+		fidno = con->fhead->fidno;
+		vtUnlock(con->fidlock);
+		if((fid = fidGet(con, fidno, FidFWlock)) != nil)
+			fidClunk(fid);
+		vtLock(con->fidlock);
+	}
+	vtUnlock(con->fidlock);
+}
+
 void
 fidInit(void)
 {

+ 11 - 29
sys/src/cmd/fossil/9p.c

@@ -627,6 +627,8 @@ rTcreate(Msg* m)
 	}
 	fileDecRef(fid->file);
 
+	fid->qid.vers = fileGetMcount(file);
+	fid->qid.path = fileGetId(file);
 	fid->file = file;
 	mode = fileGetMode(fid->file);
 	if(mode & ModeDir)
@@ -639,8 +641,6 @@ rTcreate(Msg* m)
 		fid->qid.type |= QTEXCL;
 		assert(exclAlloc(fid) != 0);
 	}
-	fid->qid.vers = fileGetMcount(file);
-	fid->qid.path = fileGetId(file);
 	if(m->t.mode & ORCLOSE)
 		open |= FidORclose;
 	fid->open = open;
@@ -883,23 +883,8 @@ rTwalk(Msg* m)
 static int
 rTflush(Msg* m)
 {
-	Msg *mp;
-	Con *con;
-	u32int oldtag;
-
-	if((oldtag = m->t.oldtag) == NOTAG)
-		return 1;
-
-	con = m->con;
-	vtLock(con->lock);
-	for(mp = con->mhead; mp != nil; mp = mp->next){
-		if(mp->t.tag == oldtag){
-			mp->flush = 1;
-			break;
-		}
-	}
-	vtUnlock(con->lock);
-
+	if(m->t.oldtag != NOTAG)
+		msgFlush(m);
 	return 1;
 }
 
@@ -1038,7 +1023,6 @@ rTversion(Msg* m)
 {
 	int v;
 	Con *con;
-	Fid *fid;
 	Fcall *r, *t;
 
 	t = &m->t;
@@ -1046,21 +1030,19 @@ rTversion(Msg* m)
 	con = m->con;
 
 	vtLock(con->lock);
-	if(con->state != CsInit){
+	if(con->state != ConInit){
 		vtUnlock(con->lock);
 		vtSetError("Tversion: down");
 		return 0;
 	}
-	con->state = CsNew;
+	con->state = ConNew;
 
 	/*
 	 * Release the karma of past lives and suffering.
+	 * Should this be done before or after checking the
+	 * validity of the Tversion?
 	 */
-	while(con->fhead != nil){
-		fid = fidGet(con, con->fhead->fidno, FidFWlock);
-		assert(fid == con->fhead);
-		fidClunk(fid);
-	}
+	fidClunkAll(con);
 
 	if(t->tag != NOTAG){
 		vtUnlock(con->lock);
@@ -1088,12 +1070,12 @@ rTversion(Msg* m)
 		if(v >= 2000){
 			r->version = VERSION9P;
 			con->msize = r->msize;
-			con->state = CsUp;
+			con->state = ConUp;
 		}
 		else if(strcmp(t->version, "9PEoF") == 0){
 			r->version = "9PEoF";
 			con->msize = r->msize;
-			con->state = CsMoribund;
+			con->state = ConMoribund;
 		}
 	}
 	vtUnlock(con->lock);

+ 536 - 206
sys/src/cmd/fossil/9proc.c

@@ -12,125 +12,332 @@ enum {
 };
 
 static struct {
-	VtLock*	lock;
-	Con**	con;			/* arena */
-	int	ncon;			/* how many in arena */
-	int	hi;			/* high watermark */
-	int	cur;			/* hint for allocation */
-
-	u32int	msize;
-} cbox;
-
-static struct {
-	VtLock*	lock;
-
-	Msg*	free;
-	VtRendez* alloc;
-
-	Msg*	head;
-	Msg*	tail;
-	VtRendez* work;
+	VtLock*	alock;			/* alloc */
+	Msg*	ahead;
+	VtRendez* arendez;
 
 	int	maxmsg;
 	int	nmsg;
+	int	nmsgstarve;
+
+	VtLock*	rlock;			/* read */
+	Msg*	rhead;
+	Msg*	rtail;
+	VtRendez* rrendez;
+
 	int	maxproc;
 	int	nproc;
+	int	nprocstarve;
 
 	u32int	msize;			/* immutable */
 } mbox;
 
+static struct {
+	VtLock*	alock;			/* alloc */
+	Con*	ahead;
+	VtRendez* arendez;
+
+	VtLock*	clock;
+	Con*	chead;
+	Con*	ctail;
+
+	int	maxcon;
+	int	ncon;
+	int	nconstarve;
+
+	u32int	msize;
+} cbox;
+
+static void
+conFree(Con* con)
+{
+	assert(con->version == nil);
+	assert(con->mhead == nil);
+	assert(con->whead == nil);
+	assert(con->nfid == 0);
+	assert(con->state == ConMoribund);
+
+	if(con->fd >= 0){
+		close(con->fd);
+		con->fd = -1;
+	}
+	con->state = ConDead;
+
+	vtLock(cbox.alock);
+	if(con->cprev != nil)
+		con->cprev->cnext = con->cnext;
+	else
+		cbox.chead = con->cnext;
+	if(con->cnext != nil)
+		con->cnext->cprev = con->cprev;
+	else
+		cbox.ctail = con->cprev;
+	con->cprev = con->cnext = nil;
+
+	if(cbox.ncon > cbox.maxcon){
+		if(con->name != nil)
+			vtMemFree(con->name);
+		vtLockFree(con->fidlock);
+		vtMemFree(con->data);
+		vtRendezAlloc(con->wrendez);
+		vtLockFree(con->wlock);
+		vtRendezAlloc(con->mrendez);
+		vtLockFree(con->mlock);
+		vtRendezAlloc(con->rendez);
+		vtLockFree(con->lock);
+		vtMemFree(con);
+		cbox.ncon--;
+		vtUnlock(cbox.alock);
+		return;
+	}
+	con->anext = cbox.ahead;
+	cbox.ahead = con;
+	if(con->anext == nil)
+		vtWakeup(cbox.arendez);
+	vtUnlock(cbox.alock);
+}
+
 static void
 msgFree(Msg* m)
 {
-	vtLock(mbox.lock);
+	assert(m->rwnext == nil);
+	assert(m->fnext == nil && m->fprev == nil);
+
+	vtLock(mbox.alock);
 	if(mbox.nmsg > mbox.maxmsg){
 		vtMemFree(m->data);
 		vtMemFree(m);
 		mbox.nmsg--;
-		vtUnlock(mbox.lock);
+		vtUnlock(mbox.alock);
 		return;
 	}
-	m->next = mbox.free;
-	mbox.free = m;
-	if(m->next == nil)
-		vtWakeup(mbox.alloc);
-	vtUnlock(mbox.lock);
+	m->anext = mbox.ahead;
+	mbox.ahead = m;
+	if(m->anext == nil)
+		vtWakeup(mbox.arendez);
+	vtUnlock(mbox.alock);
+}
+
+static Msg*
+msgAlloc(Con* con)
+{
+	Msg *m;
+
+	vtLock(mbox.alock);
+	while(mbox.ahead == nil){
+		if(mbox.nmsg >= mbox.maxmsg){
+			mbox.nmsgstarve++;
+			vtSleep(mbox.arendez);
+			continue;
+		}
+		m = vtMemAllocZ(sizeof(Msg));
+		m->data = vtMemAlloc(mbox.msize);
+		m->msize = mbox.msize;
+		mbox.nmsg++;
+		mbox.ahead = m;
+		break;
+	}
+	m = mbox.ahead;
+	mbox.ahead = m->anext;
+	m->anext = nil;
+	vtUnlock(mbox.alock);
+
+	m->con = con;
+	m->state = MsgR;
+
+	return m;
 }
 
 static void
-conFree(Con* con)
+msgMunlink(Msg* m)
 {
-	if(con->fd >= 0){
-		close(con->fd);
-		con->fd = -1;
+	Con *con;
+
+	con = m->con;
+
+	if(m->mprev != nil)
+		m->mprev->mnext = m->mnext;
+	else
+		con->mhead = m->mnext;
+	if(m->mnext != nil)
+		m->mnext->mprev = m->mprev;
+	else
+		con->mtail = m->mprev;
+	m->mprev = m->mnext = nil;
+}
+
+static void
+msgUnlinkUnlockAndFree(Msg* m)
+{
+	/*
+	 * Unlink the message from the flush and message queues,
+	 * unlock the connection message lock and free the message.
+	 * Called with con->mlock locked.
+	 */
+	if(m->fprev != nil)
+		m->fprev->fnext = m->fnext;
+	if(m->fnext != nil)
+		m->fnext->fprev = m->fprev;
+	m->fprev = m->fnext = nil;
+
+	msgMunlink(m);
+	vtUnlock(m->con->mlock);
+	msgFree(m);
+}
+
+void
+msgFlush(Msg* m)
+{
+	Msg *old;
+	Con *con;
+
+	con = m->con;
+
+	/*
+	 * Look for the message to be flushed in the
+	 * queue of all messages still on this connection.
+	 */
+	vtLock(con->mlock);
+	for(old = con->mhead; old != nil; old = old->mnext)
+		if(old->t.tag == m->t.oldtag)
+			break;
+	if(old == nil){
+		if(Dflag)
+			fprint(2, "msgFlush: cannot find %d\n", m->t.oldtag);
+		vtUnlock(con->mlock);
+		return;
 	}
 
-	assert(con->version == nil);
-	assert(con->mhead == nil);
-	assert(con->nmsg == 0);
-	assert(con->nfid == 0);
-	assert(con->state == CsMoribund);
+	/*
+	 * Found it.
+	 *
+	 * Easy case is no 9P processing done yet,
+	 * message is on the read queue.
+	 * Mark the message as flushed and let the read
+	 * process throw it away after after pulling
+	 * it off the read queue.
+	 */
+	if(old->state == MsgR){
+		old->state = MsgF;
+		if(Dflag)
+			fprint(2, "msgFlush: change %d from MsgR to MsgF\n", m->t.oldtag);
+		vtUnlock(con->mlock);
+		return;
+	}
 
-	con->state = CsDead;
+	/*
+	 * Flushing flushes.
+	 * Since they don't affect the server state, flushes
+	 * can be deleted when in Msg9 or MsgW state.
+	 */
+	if(old->t.type == Tflush){
+		/*
+		 * For Msg9 state, the old message may
+		 * or may not be on the write queue.
+		 * Mark the message as flushed and let
+		 * the write process throw it away after
+		 * after pulling it off the write queue.
+		 */
+		if(old->state == Msg9){
+			old->state = MsgF;
+			if(Dflag)
+				fprint(2, "msgFlush: change %d from Msg9 to MsgF\n", m->t.oldtag);
+			vtUnlock(con->mlock);
+			return;
+		}
+		assert(old->state == MsgW);
+
+		/*
+		 * A flush in MsgW state implies it is waiting
+		 * for its corresponding old message to be written,
+		 * so it can be deleted right here, right now...
+		 * right here, right now... right here, right now...
+		 * right about now... the funk soul brother.
+		 */
+		if(Dflag)
+			fprint(2, "msgFlush: delete pending flush %F\n", &old->t);
+		msgUnlinkUnlockAndFree(old);
+		return;
+	}
+
+	/*
+	 * Must wait for the old message to be written.
+	 * Add m to old's flush queue.
+	 * Old is the head of its own flush queue.
+	 */
+	m->fprev = old;
+	m->fnext = old->fnext;
+	if(m->fnext)
+		m->fnext->fprev = m;
+	old->fnext = m;
+	if(Dflag)
+		fprint(2, "msgFlush: add %d to %d queue\n", m->t.tag, old->t.tag);
+	vtUnlock(con->mlock);
 }
 
 static void
 msgProc(void*)
 {
-	int n;
 	Msg *m;
 	char *e;
 	Con *con;
 
-	vtThreadSetName("msg");
+	vtThreadSetName("msgProc");
 
-	vtLock(mbox.lock);
-	while(mbox.nproc <= mbox.maxproc){
-		while(mbox.head == nil)
-			vtSleep(mbox.work);
-		m = mbox.head;
-		mbox.head = m->next;
-		m->next = nil;
+	for(;;){
+		/*
+		 * If surplus to requirements, exit.
+		 * If not, wait for and pull a message off
+		 * the read queue.
+		 */
+		vtLock(mbox.rlock);
+		if(mbox.nproc > mbox.maxproc){
+			mbox.nproc--;
+			vtUnlock(mbox.rlock);
+			break;
+		}
+		while(mbox.rhead == nil)
+			vtSleep(mbox.rrendez);
+		m = mbox.rhead;
+		mbox.rhead = m->rwnext;
+		m->rwnext = nil;
+		vtUnlock(mbox.rlock);
 
+		con = m->con;
 		e = nil;
 
-		con = m->con;
-		vtLock(con->lock);
-		assert(con->state != CsDead);
-		con->nmsg++;
+		/*
+		 * If the message has been flushed before any
+		 * 9P processing has started, just throw it away.
+		 */
+		vtLock(con->mlock);
+		if(m->state == MsgF){
+			msgUnlinkUnlockAndFree(m);
+			continue;
+		}
+		m->state = Msg9;
+		vtUnlock(con->mlock);
 
+		/*
+		 * explain this
+		 */
+		vtLock(con->lock);
 		if(m->t.type == Tversion){
 			con->version = m;
-			con->state = CsDown;
-			while(con->mhead != nil)
-				vtSleep(con->active);
-			assert(con->state == CsDown);
+			con->state = ConDown;
+			while(con->mhead != m)
+				vtSleep(con->rendez);
+			assert(con->state == ConDown);
 			if(con->version == m){
 				con->version = nil;
-				con->state = CsInit;
+				con->state = ConInit;
 			}
 			else
 				e = "Tversion aborted";
 		}
-		else if(con->state != CsUp)	
+		else if(con->state != ConUp)	
 			e = "connection not ready";
-
-		/*
-		 * Add Msg to end of active list.
-		 */
-		if(con->mtail != nil){
-			m->prev = con->mtail;
-			con->mtail->next = m;
-		}
-		else{
-			con->mhead = m;
-			m->prev = nil;
-		}
-		con->mtail = m;
-		m->next = nil;
-
 		vtUnlock(con->lock);
-		vtUnlock(mbox.lock);
 
 		/*
 		 * Dispatch if not error already.
@@ -145,93 +352,37 @@ msgProc(void*)
 		else
 			m->r.type = m->t.type+1;
 
-		vtLock(con->lock);
+
 		/*
-		 * Remove Msg from active list.
+		 * Put the message (with reply) on the
+		 * write queue and wakeup the write process.
 		 */
-		if(m->prev != nil)
-			m->prev->next = m->next;
+		vtLock(con->wlock);
+		if(con->whead == nil)
+			con->whead = m;
 		else
-			con->mhead = m->next;
-		if(m->next != nil)
-			m->next->prev = m->prev;
-		else
-			con->mtail = m->prev;
-		m->prev = m->next = nil;
-		if(con->mhead == nil)
-			vtWakeup(con->active);
-
-		if(Dflag)
-			fprint(2, "msgProc: r %F\n", &m->r);
-
-		if((con->state == CsNew || con->state == CsUp) && !m->flush){
-			/*
-			 * TODO: optimise this copy away somehow for
-			 * read, stat, etc.
-			 */
-			assert(n = convS2M(&m->r, con->data, con->msize));
-			if(write(con->fd, con->data, n) != n){
-				if(con->fd >= 0){
-					close(con->fd);
-					con->fd = -1;
-				}
-			}
-		}
-
-		con->nmsg--;
-		if(con->state == CsMoribund && con->nmsg == 0){
-			vtUnlock(con->lock);
-			conFree(con);
-		}
-		else
-			vtUnlock(con->lock);
-
-		vtLock(mbox.lock);
-		m->next = mbox.free;
-		mbox.free = m;
-		if(m->next == nil)
-			vtWakeup(mbox.alloc);
+			con->wtail->rwnext = m;
+		con->wtail = m;
+		vtWakeup(con->wrendez);
+		vtUnlock(con->wlock);
 	}
-	mbox.nproc--;
-	vtUnlock(mbox.lock);
 }
 
 static void
-conProc(void* v)
+msgRead(void* v)
 {
 	Msg *m;
 	Con *con;
 	int eof, fd, n;
 
-	vtThreadSetName("con");
+	vtThreadSetName("msgRead");
 
 	con = v;
-	if(Dflag)
-		fprint(2, "conProc: con->fd %d\n", con->fd);
 	fd = con->fd;
 	eof = 0;
 
-	vtLock(mbox.lock);
 	while(!eof){
-		while(mbox.free == nil){
-			if(mbox.nmsg >= mbox.maxmsg){
-				vtSleep(mbox.alloc);
-				continue;
-			}
-			m = vtMemAllocZ(sizeof(Msg));
-			m->data = vtMemAlloc(mbox.msize);
-			m->msize = mbox.msize;
-			mbox.nmsg++;
-			mbox.free = m;
-			break;
-		}
-		m = mbox.free;
-		mbox.free = m->next;
-		m->next = nil;
-		vtUnlock(mbox.lock);
-
-		m->con = con;
-		m->flush = 0;
+		m = msgAlloc(con);
 
 		while((n = read9pmsg(fd, m->data, con->msize)) == 0)
 			;
@@ -245,92 +396,204 @@ conProc(void* v)
 		}
 		else if(convM2S(m->data, n, &m->t) != n){
 			if(Dflag)
-				fprint(2, "conProc: convM2S error: %s\n",
+				fprint(2, "msgRead: convM2S error: %s\n",
 					con->name);
 			msgFree(m);
-			vtLock(mbox.lock);
 			continue;
 		}
 		if(Dflag)
-			fprint(2, "conProc: t %F\n", &m->t);
-
-		vtLock(mbox.lock);
-		if(mbox.head == nil){
-			mbox.head = m;
-			if(!vtWakeup(mbox.work) && mbox.nproc < mbox.maxproc){
-				if(vtThread(msgProc, nil) > 0)
-					mbox.nproc++;
+			fprint(2, "msgRead: t %F\n", &m->t);
+
+		vtLock(con->mlock);
+		if(con->mtail != nil){
+			m->mprev = con->mtail;
+			con->mtail->mnext = m;
+		}
+		else{
+			con->mhead = m;
+			m->mprev = nil;
+		}
+		con->mtail = m;
+		vtUnlock(con->mlock);
+
+		vtLock(mbox.rlock);
+		if(mbox.rhead == nil){
+			mbox.rhead = m;
+			if(!vtWakeup(mbox.rrendez)){
+				if(mbox.nproc < mbox.maxproc){
+					if(vtThread(msgProc, nil) > 0)
+						mbox.nproc++;
+				}
+				else
+					mbox.nprocstarve++;
 			}
-			vtWakeup(mbox.work);
+			/*
+			 * don't need this surely?
+			vtWakeup(mbox.rrendez);
+			 */
 		}
 		else
-			mbox.tail->next = m;
-		mbox.tail = m;
+			mbox.rtail->rwnext = m;
+		mbox.rtail = m;
+		vtUnlock(mbox.rlock);
 	}
-	vtUnlock(mbox.lock);
 }
 
-Con*
-conAlloc(int fd, char* name)
+static int
+_msgWrite(Msg* m)
 {
 	Con *con;
-	int cur, i;
+	int eof, n;
+
+	con = m->con;
 
-	vtLock(cbox.lock);
-	cur = cbox.cur;
-	for(i = 0; i < cbox.hi; i++){
+	/*
+	 * An Rflush with a .fprev implies it is on a flush queue waiting for
+	 * its corresponding 'oldtag' message to go out first, so punt
+	 * until the 'oldtag' message goes out (see below).
+	 */
+	if(m->r.type == Rflush && m->fprev != nil){
+		fprint(2, "msgWrite: delay r %F\n", &m->r);
+		return 0;
+	}
+
+	msgMunlink(m);
+	vtUnlock(con->mlock);
+
+	/*
+	 * TODO: optimise this copy away somehow for
+	 * read, stat, etc.
+	 */
+	assert(n = convS2M(&m->r, con->data, con->msize));
+	if(write(con->fd, con->data, n) != n)
+		eof = 1;
+	else
+		eof = 0;
+
+	if(Dflag)
+		fprint(2, "msgWrite: r %F\n", &m->r);
+
+	/*
+	 * Just wrote a reply. If it has any flushes waiting
+	 * for it to have gone out, recurse down the list writing
+	 * them out too.
+	 */
+	vtLock(con->mlock);
+	if(m->fnext != nil){
+		m->fnext->fprev = nil;
+		eof += _msgWrite(m->fnext);
+		m->fnext = nil;
+	}
+	msgFree(m);
+
+	return eof;
+}
+
+static void
+msgWrite(void* v)
+{
+	Msg *m;
+	int eof;
+	Con *con;
+
+	vtThreadSetName("msgWrite");
+
+	con = v;
+	if(vtThread(msgRead, con) < 0){
+		conFree(con);
+		return;
+	}
+
+	for(;;){
 		/*
-		 * Look for any unallocated or CsDead up to the
-		 * high watermark; cur is a hint where to start.
-		 * Wrap around the whole arena.
+		 * Wait for and pull a message off the write queue.
 		 */
-		if(cbox.con[cur] == nil || cbox.con[cur]->state == CsDead)
-			break;
-		if(++cur >= cbox.hi)
-			cur = 0;
-	}
-	if(i >= cbox.hi){
+		vtLock(con->wlock);
+		while(con->whead == nil)
+			vtSleep(con->wrendez);
+		m = con->whead;
+		con->whead = m->rwnext;
+		m->rwnext = nil;
+		vtUnlock(con->wlock);
+
 		/*
-		 * None found.
-		 * If the high watermark is up to the limit of those
-		 * allocated, increase the size of the arena.
-		 * Bump up the watermark and take the next.
+		 * Throw the message away if it's a flushed flush,
+		 * otherwise change its state and try to write it out.
 		 */
-		if(cbox.hi >= cbox.ncon){
-			cbox.con = vtMemRealloc(cbox.con,
-					(cbox.ncon+NConInit)*sizeof(Con*));
-			memset(&cbox.con[cbox.ncon], 0, NConInit*sizeof(Con*));
-			cbox.ncon += NConInit;
+		vtLock(con->mlock);
+		if(m->state == MsgF){
+			assert(m->t.type == Tflush);
+			msgUnlinkUnlockAndFree(m);
+			continue;
 		}
-		cur = cbox.hi++;
+		m->state = MsgW;
+		eof = _msgWrite(m);
+		vtUnlock(con->mlock);
+
+		vtLock(con->lock);
+		if(eof && con->fd >= 0){
+			close(con->fd);
+			con->fd = -1;
+		}
+		if(con->state == ConDown)
+			vtWakeup(con->rendez);
+		if(con->state == ConMoribund && con->mhead == nil){
+			vtUnlock(con->lock);
+			conFree(con);
+			break;
+		}
+		vtUnlock(con->lock);
 	}
+}
 
-	/*
-	 * Do one-time initialisation if necessary.
-	 * Put back a new hint.
-	 * Do specific initialisation and start the proc.
-	 */
-	con = cbox.con[cur];
-	if(con == nil){
+Con*
+conAlloc(int fd, char* name)
+{
+	Con *con;
+
+	vtLock(cbox.alock);
+	while(cbox.ahead == nil){
+		if(cbox.ncon >= cbox.maxcon){
+			cbox.nconstarve++;
+			vtSleep(cbox.arendez);
+			continue;
+		}
 		con = vtMemAllocZ(sizeof(Con));
 		con->lock = vtLockAlloc();
+		con->rendez = vtRendezAlloc(con->lock);
 		con->data = vtMemAlloc(cbox.msize);
 		con->msize = cbox.msize;
-		con->active = vtRendezAlloc(con->lock);
+		con->alock = vtLockAlloc();
+		con->mlock = vtLockAlloc();
+		con->mrendez = vtRendezAlloc(con->mlock);
+		con->wlock = vtLockAlloc();
+		con->wrendez = vtRendezAlloc(con->wlock);
 		con->fidlock = vtLockAlloc();
-		cbox.con[cur] = con;
+
+		cbox.ncon++;
+		cbox.ahead = con;
+		break;
+	}
+	con = cbox.ahead;
+	cbox.ahead = con->anext;
+	con->anext = nil;
+
+	if(cbox.ctail != nil){
+		con->cprev = cbox.ctail;
+		cbox.ctail->cnext = con;
+	}
+	else{
+		cbox.chead = con;
+		con->cprev = nil;
 	}
+	cbox.ctail = con;
+
 	assert(con->mhead == nil);
-	assert(con->nmsg == 0);
+	assert(con->whead == nil);
 	assert(con->fhead == nil);
 	assert(con->nfid == 0);
 
-	con->state = CsNew;
-
-	if(++cur >= cbox.hi)
-		cur = 0;
-	cbox.cur = cur;
-
+	con->state = ConNew;
 	con->fd = fd;
 	if(con->name != nil){
 		vtMemFree(con->name);
@@ -338,10 +601,12 @@ conAlloc(int fd, char* name)
 	}
 	if(name != nil)
 		con->name = vtStrDup(name);
+	else
+		con->name = vtStrDup("unknown");
 	con->aok = 0;
-	vtUnlock(cbox.lock);
+	vtUnlock(cbox.alock);
 
-	if(vtThread(conProc, con) < 0){
+	if(vtThread(msgWrite, con) < 0){
 		conFree(con);
 		return nil;
 	}
@@ -353,8 +618,8 @@ static int
 cmdMsg(int argc, char* argv[])
 {
 	char *p;
-	int maxmsg, maxproc;
 	char *usage = "usage: msg [-m nmsg] [-p nproc]";
+	int maxmsg, nmsg, nmsgstarve, maxproc, nproc, nprocstarve;
 
 	maxmsg = maxproc = 0;
 
@@ -381,35 +646,100 @@ cmdMsg(int argc, char* argv[])
 	if(argc)
 		return cliError(usage);
 
-	vtLock(mbox.lock);
+	vtLock(mbox.alock);
 	if(maxmsg)
 		mbox.maxmsg = maxmsg;
 	maxmsg = mbox.maxmsg;
+	nmsg = mbox.nmsg;
+	nmsgstarve = mbox.nmsgstarve;
+	vtUnlock(mbox.alock);
+
+	vtLock(mbox.rlock);
 	if(maxproc)
 		mbox.maxproc = maxproc;
 	maxproc = mbox.maxproc;
-	vtUnlock(mbox.lock);
+	nproc = mbox.nproc;
+	nprocstarve = mbox.nprocstarve;
+	vtUnlock(mbox.rlock);
 
 	consPrint("\tmsg -m %d -p %d\n", maxmsg, maxproc);
+	consPrint("\tnmsg %d nmsgstarve %d nproc %d nprocstarve %d\n",
+		nmsg, nmsgstarve, nproc, nprocstarve);
 
 	return 1;
 }
 
 void
-procInit(void)
+msgInit(void)
 {
-	mbox.lock = vtLockAlloc();
-	mbox.alloc = vtRendezAlloc(mbox.lock);
-	mbox.work = vtRendezAlloc(mbox.lock);
+	mbox.alock = vtLockAlloc();
+	mbox.arendez = vtRendezAlloc(mbox.alock);
+
+	mbox.rlock = vtLockAlloc();
+	mbox.rrendez = vtRendezAlloc(mbox.rlock);
 
 	mbox.maxmsg = NMsgInit;
 	mbox.maxproc = NMsgProcInit;
 	mbox.msize = NMsizeInit;
 
 	cliAddCmd("msg", cmdMsg);
+}
+
+static int
+cmdCon(int argc, char* argv[])
+{
+	char *p;
+	Con *con;
+	char *usage = "usage: con [-m ncon]";
+	int maxcon, ncon, nconstarve;
+
+	maxcon = 0;
+
+	ARGBEGIN{
+	default:
+		return cliError(usage);
+	case 'm':
+		p = ARGF();
+		if(p == nil)
+			return cliError(usage);
+		maxcon = strtol(argv[0], &p, 0);
+		if(maxcon <= 0 || p == argv[0] || *p != '\0')
+			return cliError(usage);
+		break;
+	}ARGEND
+	if(argc)
+		return cliError(usage);
+
+	vtLock(cbox.clock);
+	if(maxcon)
+		cbox.maxcon = maxcon;
+	maxcon = cbox.maxcon;
+	ncon = cbox.ncon;
+	nconstarve = cbox.nconstarve;
+	vtUnlock(cbox.clock);
 
-	cbox.lock = vtLockAlloc();
-	cbox.con = nil;
-	cbox.ncon = 0;
+	consPrint("\tcon -m %d\n", maxcon);
+	consPrint("\tncon %d nconstarve %d\n", ncon, nconstarve);
+
+	vtRLock(cbox.clock);
+	for(con = cbox.chead; con != nil; con = con->cnext){
+		consPrint("\t%s\n", con->name);
+	}
+	vtRUnlock(cbox.clock);
+
+	return 1;
+}
+
+void
+conInit(void)
+{
+	cbox.alock = vtLockAlloc();
+	cbox.arendez = vtRendezAlloc(cbox.alock);
+
+	cbox.clock = vtLockAlloc();
+
+	cbox.maxcon = NConInit;
 	cbox.msize = NMsizeInit;
+
+	cliAddCmd("con", cmdCon);
 }

+ 17 - 10
sys/src/cmd/fossil/9srv.c

@@ -21,21 +21,23 @@ static struct {
 } sbox;
 
 static int
-srvFd(char* name, int mode, int fd)
+srvFd(char* name, int mode, int fd, char** mntpnt)
 {
 	int n, srvfd;
-	char buf[10], srv[VtMaxStringSize];
+	char *p, buf[10];
 
 	/*
 	 * Drop a file descriptor with given name and mode into /srv.
 	 * Create with ORCLOSE and don't close srvfd so it will be removed
 	 * automatically on process exit.
 	 */
-	snprint(srv, sizeof(srv), "/srv/%s", name);
-	if((srvfd = create(srv, ORCLOSE|OWRITE, mode)) < 0){
-		snprint(srv, sizeof(srv), "#s/%s", name);
-		if((srvfd = create(srv, ORCLOSE|OWRITE, mode)) < 0){
-			vtSetError("create %s: %r", srv);
+	p = smprint("/srv/%s", name);
+	if((srvfd = create(p, ORCLOSE|OWRITE, mode)) < 0){
+		vtMemFree(p);
+		p = smprint("#s/%s", name);
+		if((srvfd = create(p, ORCLOSE|OWRITE, mode)) < 0){
+			vtSetError("create %s: %r", p);
+			vtMemFree(p);
 			return -1;
 		}
 	}
@@ -43,10 +45,13 @@ srvFd(char* name, int mode, int fd)
 	n = snprint(buf, sizeof(buf), "%d", fd);
 	if(write(srvfd, buf, n) < 0){
 		close(srvfd);
-		vtSetError("write %s: %r", srv);
+		vtSetError("write %s: %r", p);
+		vtMemFree(p);
 		return -1;
 	}
 
+	*mntpnt = p;
+
 	return srvfd;
 }
 
@@ -74,6 +79,7 @@ srvAlloc(char* service, int mode, int fd)
 {
 	Srv *srv;
 	int srvfd;
+	char *mntpnt;
 
 	vtLock(sbox.lock);
 	for(srv = sbox.head; srv != nil; srv = srv->next){
@@ -84,7 +90,7 @@ srvAlloc(char* service, int mode, int fd)
 		return nil;
 	}
 
-	if((srvfd = srvFd(service, mode, fd)) < 0){
+	if((srvfd = srvFd(service, mode, fd, &mntpnt)) < 0){
 		vtUnlock(sbox.lock);
 		return nil;
 	}
@@ -93,6 +99,7 @@ srvAlloc(char* service, int mode, int fd)
 	srv = vtMemAllocZ(sizeof(Srv));
 	srv->srvfd = srvfd;
 	srv->service = vtStrDup(service);
+	srv->mntpnt = mntpnt;
 
 	if(sbox.tail != nil){
 		srv->prev = sbox.tail;
@@ -173,7 +180,7 @@ cmdSrv(int argc, char* argv[])
 	if(pflag)
 		r = consOpen(fd[1], srv->srvfd, -1);
 	else
-		r = (conAlloc(fd[1], argv[0]) != nil);
+		r = (conAlloc(fd[1], srv->mntpnt) != nil);
 	if(r == 0){
 		close(fd[1]);
 		vtLock(sbox.lock);

+ 7 - 9
sys/src/cmd/fossil/9user.c

@@ -713,7 +713,7 @@ cmdUname(int argc, char* argv[])
 	int d, dflag, i, r;
 	char *p, *uid, *uname;
 	char *createfmt = "fsys main create /active/usr/%s %s %s d775";
-	char *usage = "usage: uname uname [uid|:uid|%%newname|=leader|+member|-member]";
+	char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]";
 
 	dflag = 0;
 
@@ -726,14 +726,12 @@ cmdUname(int argc, char* argv[])
 	}ARGEND
 
 	if(argc < 1){
-		if(dflag){
-			vtRLock(ubox.lock);
-			if(dflag)
-				uboxDump(ubox.box);
-			vtRUnlock(ubox.lock);
-			return 1;
-		}
-		return cliError(usage);
+		if(!dflag)
+			return cliError(usage);
+		vtRLock(ubox.lock);
+		uboxDump(ubox.box);
+		vtRUnlock(ubox.lock);
+		return 1;
 	}
 
 	uname = argv[0];

+ 1 - 1
sys/src/cmd/fossil/Ccmd.c

@@ -404,7 +404,7 @@ cmdInit(void)
 
 	if(pipe(cbox.confd) < 0)
 		return 0;
-	if((cbox.con = conAlloc(cbox.confd[1], "internal-command")) == nil){
+	if((cbox.con = conAlloc(cbox.confd[1], "console")) == nil){
 		close(cbox.confd[0]);
 		close(cbox.confd[1]);
 		cbox.confd[0] = cbox.confd[1] = -1;

+ 1 - 1
sys/src/cmd/fossil/archive.c

@@ -390,7 +390,7 @@ sleep(10*1000);	/* window of opportunity to provoke races */
 		/* tie up vac root */
 		memset(&root, 0, sizeof root);
 		root.version = VtRootVersion;
-		strcpy(root.type, "vac");
+		strecpy(root.type, root.type+sizeof root.type, "vac");
 		strecpy(root.name, root.name+sizeof root.name, "fossil");
 		memmove(root.score, p.score, VtScoreSize);
 		memmove(root.prev, super.last, VtScoreSize);

+ 51 - 0
sys/src/cmd/fossil/epoch.c

@@ -0,0 +1,51 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+
+uchar buf[65536];
+
+void
+usage(void)
+{
+	fprint(2, "usage: fossil/epoch fs [new-low-epoch]\n");
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int fd;
+	Header h;
+	Super s;
+
+	ARGBEGIN{
+	default:
+		usage();
+	}ARGEND
+
+	if(argc == 0 || argc > 2)
+		usage();
+
+	if((fd = open(argv[0], argc==2 ? ORDWR : OREAD)) < 0)
+		sysfatal("open %s: %r", argv[0]);
+
+	if(pread(fd, buf, HeaderSize, HeaderOffset) != HeaderSize)
+		sysfatal("reading header: %r");
+	if(!headerUnpack(&h, buf))
+		sysfatal("unpacking header: %r");
+
+	if(pread(fd, buf, h.blockSize, (vlong)h.super*h.blockSize) != h.blockSize)
+		sysfatal("reading super block: %r");
+
+	if(!superUnpack(&s, buf))
+		sysfatal("unpacking super block: %r");
+
+	print("epoch %d\n", s.epochLow);
+	if(argc == 2){
+		s.epochLow = strtoul(argv[1], 0, 0);
+		superPack(&s, buf);
+		if(pwrite(fd, buf, h.blockSize, (vlong)h.super*h.blockSize) != h.blockSize)
+			sysfatal("writing super block: %r");
+	}
+	exits(0);
+}

+ 2 - 1
sys/src/cmd/fossil/fossil.c

@@ -70,7 +70,8 @@ main(int argc, char* argv[])
 
 	consInit();
 	cliInit();
-	procInit();
+	msgInit();
+	conInit();
 	cmdInit();
 	fsysInit();
 	exclInit();

+ 1 - 1
sys/src/cmd/fossil/fs.c

@@ -677,7 +677,7 @@ mkVac(VtSession *z, uint blockSize, Entry *pe, Entry *pee, DirEntry *pde, uchar
 	 * Save root.
 	 */
 	root.version = VtRootVersion;
-	strcpy(root.type, "vac");
+	strecpy(root.type, root.type+sizeof root.type, "vac");
 	strecpy(root.name, root.name+sizeof root.name, de.elem);
 	root.blockSize = blockSize;
 	vtRootPack(&root, buf);

+ 9 - 0
sys/src/cmd/fossil/history

@@ -25,3 +25,12 @@ changes since initial alpha release
 		not get updated when you change it right before a reboot,
 		and i don't understand why.
 
+10 feb 2003
+	better error messages for fossil console functions
+
+18 feb 2003
+	correct handling of flush messages
+	add msgWrite procs to handle output queues
+	comment out an overeager assert in source.c.
+	move setting of fid->qid.path higher in rTcreate for exclAlloc.
+

+ 0 - 1
sys/src/cmd/fossil/mkfile

@@ -49,7 +49,6 @@ UPDATE=\
 	mkfile\
 	$CFILES\
 	$HFILES\
-	${TARG:%=/386/bin/%}
 
 default:V: all
 

+ 7 - 0
sys/src/cmd/fossil/source.c

@@ -786,7 +786,14 @@ sourceLoadBlock(Source *r, int mode)
 		assert(0);
 	case OReadWrite:
 		assert(r->mode == OReadWrite);
+		/*
+		 * This needn't be true -- we might bump the low epoch
+		 * to reclaim some old blocks, but since this score is 
+		 * OReadWrite, the blocks must all still be open, so none
+		 * are reclaimed.  Thus it's okay that the epoch is so low.
+		 * Proceed.
 		assert(r->epoch >= r->fs->elo);
+		 */
 		if(r->epoch == r->fs->ehi){
 			b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, OReadWrite);
 			assert(r->epoch == b->l.epoch);

+ 1602 - 0
sys/src/cmd/nfs.c

@@ -0,0 +1,1602 @@
+/* Copyright © 2003 Russ Cox, MIT; see /sys/src/libsunrpc/COPYING */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <sunrpc.h>
+#include <nfs3.h>
+
+SunClient *nfscli;
+SunClient *mntcli;
+char *defaultpath = "/";
+Channel *fschan;
+char *sys;
+int verbose;
+int readplus = 0;
+
+
+typedef struct Auth Auth;
+struct Auth
+{
+	int ref;
+	uchar *data;
+	int ndata;
+};
+
+typedef struct FidAux FidAux;
+struct FidAux
+{
+	Nfs3Handle handle;
+
+	u64int cookie;	/* for continuing directory reads */
+	char *name;	/* botch: for remove and rename */
+	Nfs3Handle parent;	/* botch: for remove and rename */
+	char err[ERRMAX];	/* for walk1 */
+	Auth *auth;
+};
+
+/*
+ * various RPCs.  here is where we'd insert support for NFS v2
+ */
+
+void
+portCall(SunCall *c, PortCallType type)
+{
+	c->rpc.prog = PortProgram;
+	c->rpc.vers = PortVersion;
+	c->rpc.proc = type>>1;
+	c->rpc.iscall = !(type&1);
+	c->type = type;
+}
+
+int
+getport(SunClient *client, uint prog, uint vers, uint prot, uint *port)
+{
+	PortTGetport tx;
+	PortRGetport rx;
+
+	memset(&tx, 0, sizeof tx);
+	portCall(&tx.call, PortCallTGetport);
+	tx.map.prog = prog;
+	tx.map.vers = vers;
+	tx.map.prot = prot;
+
+	memset(&rx, 0, sizeof rx);
+	portCall(&rx.call, PortCallRGetport);
+
+	if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	*port = rx.port;
+	return 0;
+}
+
+void
+mountCall(Auth *a, SunCall *c, NfsMount3CallType type)
+{
+	c->rpc.iscall = !(type&1);
+	c->rpc.proc = type>>1;
+	c->rpc.prog = NfsMount3Program;
+	c->rpc.vers = NfsMount3Version;
+	if(c->rpc.iscall && a){
+		c->rpc.cred.flavor = SunAuthSys;
+		c->rpc.cred.data = a->data;
+		c->rpc.cred.ndata = a->ndata;
+	}
+	c->type = type;
+}
+
+int
+mountNull(ulong tag)
+{
+	NfsMount3TNull tx;
+	NfsMount3RNull rx;
+
+	memset(&tx, 0, sizeof tx);
+	mountCall(nil, &tx.call, NfsMount3CallTNull);
+
+	memset(&rx, 0, sizeof rx);
+	mountCall(nil, &rx.call, NfsMount3CallTNull);
+
+	return sunClientRpc(mntcli, tag, &tx.call, &rx.call, nil);
+}
+
+int
+mountMnt(Auth *a, ulong tag, char *path, Nfs3Handle *h)
+{
+	uchar *freeme;
+	NfsMount3TMnt tx;
+	NfsMount3RMnt rx;
+
+	memset(&tx, 0, sizeof tx);
+	mountCall(a, &tx.call, NfsMount3CallTMnt);
+	tx.path = path;
+
+	memset(&rx, 0, sizeof rx);
+	mountCall(a, &rx.call, NfsMount3CallRMnt);
+	if(sunClientRpc(mntcli, tag, &tx.call, &rx.call, &freeme) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+if(verbose)print("handle %.*H\n", rx.len, rx.handle);
+	if(rx.len >= Nfs3MaxHandleSize){
+		free(freeme);
+		werrstr("server-returned handle too long");
+		return -1;
+	}
+	memmove(h->h, rx.handle, rx.len);
+	h->len = rx.len;
+	free(freeme);
+	return 0;
+}
+
+void
+nfs3Call(Auth *a, SunCall *c, Nfs3CallType type)
+{
+	c->rpc.iscall = !(type&1);
+	c->rpc.proc = type>>1;
+	c->rpc.prog = Nfs3Program;
+	c->rpc.vers = Nfs3Version;
+	if(c->rpc.iscall && a){
+		c->rpc.cred.flavor = SunAuthSys;
+		c->rpc.cred.data = a->data;
+		c->rpc.cred.ndata = a->ndata;
+	}
+	c->type = type;
+}
+
+int
+nfsNull(ulong tag)
+{
+	Nfs3TNull tx;
+	Nfs3RNull rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(nil, &tx.call, Nfs3CallTNull);
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(nil, &rx.call, Nfs3CallTNull);
+
+	return sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil);
+}
+
+int
+nfsGetattr(Auth *a, ulong tag, Nfs3Handle *h, Nfs3Attr *attr)
+{
+	Nfs3TGetattr tx;
+	Nfs3RGetattr rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTGetattr);
+	tx.handle = *h;	
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRGetattr);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+
+	*attr = rx.attr;
+	return 0;
+}
+
+int
+nfsAccess(Auth *a, ulong tag, Nfs3Handle *h, ulong want, ulong *got, u1int *have, Nfs3Attr *attr)
+{
+	Nfs3TAccess tx;
+	Nfs3RAccess rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTAccess);
+	tx.handle = *h;
+	tx.access = want;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRAccess);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+
+	*got = rx.access;
+
+	*have = rx.haveAttr;
+	if(rx.haveAttr)
+		*attr = rx.attr;
+	return 0;
+}
+
+int
+nfsMkdir(Auth *a, ulong tag, Nfs3Handle *h, char *name, Nfs3Handle *nh, ulong mode, uint gid,
+	u1int *have, Nfs3Attr *attr)
+{
+	Nfs3TMkdir tx;
+	Nfs3RMkdir rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTMkdir);
+	tx.handle = *h;
+	tx.name = name;
+	tx.attr.setMode = 1;
+	tx.attr.mode = mode;
+	tx.attr.setGid = 1;
+	tx.attr.gid = gid;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRMkdir);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+
+	if(!rx.haveHandle){
+		werrstr("nfs mkdir did not return handle");
+		return -1;
+	}
+	*nh = rx.handle;
+
+	*have = rx.haveAttr;
+	if(rx.haveAttr)
+		*attr = rx.attr;
+	return 0;
+}
+
+int
+nfsCreate(Auth *a, ulong tag, Nfs3Handle *h, char *name, Nfs3Handle *nh, ulong mode, uint gid,
+	u1int *have, Nfs3Attr *attr)
+{
+	Nfs3TCreate tx;
+	Nfs3RCreate rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTCreate);
+	tx.handle = *h;
+	tx.name = name;
+	tx.attr.setMode = 1;
+	tx.attr.mode = mode;
+	tx.attr.setGid = 1;
+	tx.attr.gid = gid;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRCreate);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+
+	if(!rx.haveHandle){
+		werrstr("nfs create did not return handle");
+		return -1;
+	}
+	*nh = rx.handle;
+
+	*have = rx.haveAttr;
+	if(rx.haveAttr)
+		*attr = rx.attr;
+	return 0;
+}
+
+int
+nfsRead(Auth *a, ulong tag, Nfs3Handle *h, u32int count, u64int offset,
+	uchar **pp, u32int *pcount, uchar **pfreeme)
+{
+	uchar *freeme;
+	Nfs3TRead tx;
+	Nfs3RRead rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTRead);
+	tx.handle = *h;
+	tx.count = count;
+	tx.offset = offset;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRRead);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, &freeme) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+	if(rx.count != rx.ndata){
+		werrstr("nfs read returned count=%ud ndata=%ud", (uint)rx.count, (uint)rx.ndata);
+		free(freeme);
+		return -1;
+	}
+	*pfreeme = freeme;
+	*pcount = rx.count;
+	*pp = rx.data;
+	return 0;
+}
+
+int
+nfsWrite(Auth *a, ulong tag, Nfs3Handle *h, uchar *data, u32int count, u64int offset, u32int *pcount)
+{
+	Nfs3TWrite tx;
+	Nfs3RWrite rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTWrite);
+	tx.handle = *h;
+	tx.count = count;
+	tx.offset = offset;
+	tx.data = data;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRWrite);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+
+	*pcount = rx.count;
+	return 0;
+}
+
+int
+nfsRmdir(Auth *a, ulong tag, Nfs3Handle *h, char *name)
+{
+	Nfs3TRmdir tx;
+	Nfs3RRmdir rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTRmdir);
+	tx.handle = *h;
+	tx.name = name;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRRmdir);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+	return 0;
+}
+
+int
+nfsRemove(Auth *a, ulong tag, Nfs3Handle *h, char *name)
+{
+	Nfs3TRemove tx;
+	Nfs3RRemove rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTRemove);
+	tx.handle = *h;
+	tx.name = name;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRRemove);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+	return 0;
+}
+
+int
+nfsRename(Auth *a, ulong tag, Nfs3Handle *h, char *name, Nfs3Handle *th, char *tname)
+{
+	Nfs3TRename tx;
+	Nfs3RRename rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTRename);
+	tx.from.handle = *h;
+	tx.from.name = name;
+	tx.to.handle = *th;
+	tx.to.name = tname;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRRename);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+	return 0;
+}
+
+int
+nfsSetattr(Auth *a, ulong tag, Nfs3Handle *h, Nfs3SetAttr *attr)
+{
+	Nfs3TSetattr tx;
+	Nfs3RSetattr rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTSetattr);
+	tx.handle = *h;
+	tx.attr = *attr;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRSetattr);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+	return 0;
+}
+
+int
+nfsCommit(Auth *a, ulong tag, Nfs3Handle *h)
+{
+	Nfs3TCommit tx;
+	Nfs3RCommit rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTCommit);
+	tx.handle = *h;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRCommit);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+	return 0;
+}
+
+int
+nfsLookup(Auth *a, ulong tag, Nfs3Handle *h, char *name, Nfs3Handle *nh, u1int *have, Nfs3Attr *attr)
+{
+	Nfs3TLookup tx;
+	Nfs3RLookup rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTLookup);
+	tx.handle = *h;
+	tx.name = name;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRLookup);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+		return -1;
+
+	if(rx.status != Nfs3Ok){
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+	*nh = rx.handle;
+	*have = rx.haveAttr;
+	if(rx.haveAttr)
+		*attr = rx.attr;
+	return 0;
+}
+
+int
+nfsReadDirPlus(Auth *a, ulong tag, Nfs3Handle *h, u32int count, u64int cookie, uchar **pp,
+	u32int *pcount, int (**unpack)(uchar*, uchar*, uchar**, Nfs3Entry*), uchar **pfreeme)
+{
+	Nfs3TReadDirPlus tx;
+	Nfs3RReadDirPlus rx;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTReadDirPlus);
+	tx.handle = *h;
+	tx.maxCount = count;
+	tx.dirCount = 1000;
+	tx.cookie = cookie;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRReadDirPlus);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, pfreeme) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		free(*pfreeme);
+		*pfreeme = 0;
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+
+	*unpack = nfs3EntryPlusUnpack;
+	*pcount = rx.count;
+	*pp = rx.data;
+	return 0;
+}
+
+int
+nfsReadDir(Auth *a, ulong tag, Nfs3Handle *h, u32int count, u64int cookie, uchar **pp,
+	u32int *pcount, int (**unpack)(uchar*, uchar*, uchar**, Nfs3Entry*), uchar **pfreeme)
+{
+	/* BUG: try readdirplus */
+	char e[ERRMAX];
+	Nfs3TReadDir tx;
+	Nfs3RReadDir rx;
+
+	if(readplus!=-1){
+		if(nfsReadDirPlus(a, tag, h, count, cookie, pp, pcount, unpack, pfreeme) == 0){
+			readplus = 1;
+			return 0;
+		}
+fprint(2, "readdirplus: %r\n");
+		if(readplus == 0){
+			rerrstr(e, sizeof e);
+			if(strstr(e, "procedure unavailable"))
+				readplus = -1;
+		}
+	}
+	if(readplus == 1)
+		return -1;
+
+	memset(&tx, 0, sizeof tx);
+	nfs3Call(a, &tx.call, Nfs3CallTReadDir);
+	tx.handle = *h;
+	tx.count = count;
+	tx.cookie = cookie;
+
+	memset(&rx, 0, sizeof rx);
+	nfs3Call(a, &rx.call, Nfs3CallRReadDir);
+
+	if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, pfreeme) < 0)
+		return -1;
+	if(rx.status != Nfs3Ok){
+		free(*pfreeme);
+		*pfreeme = 0;
+		nfs3Errstr(rx.status);
+		return -1;
+	}
+
+	/* readplus failed but read succeeded */
+	readplus = -1;
+
+	*unpack = nfs3EntryUnpack;
+	*pcount = rx.count;
+	*pp = rx.data;
+	return 0;
+}
+
+/*
+ * name <-> int translation
+ */
+typedef struct Map Map;
+typedef struct User User;
+typedef struct Group Group;
+
+Map *map;
+Map emptymap;
+
+struct User
+{
+	char *name;
+	uint uid;
+	uint gid;
+	uint g[16];
+	uint ng;
+	uchar *auth;
+	int nauth;
+};
+
+struct Group
+{
+	char *name;	/* same pos as in User struct */
+	uint gid;	/* same pos as in User struct */
+};
+
+struct Map
+{
+	int nuser;
+	int ngroup;
+	User *user;
+	User **ubyname;
+	User **ubyid;
+	Group *group;
+	Group **gbyname;
+	Group **gbyid;
+};
+
+User*
+finduser(User **u, int nu, char *s)
+{
+	int lo, hi, mid, n;
+
+	hi = nu;
+	lo = 0;
+	while(hi > lo){
+		mid = (lo+hi)/2;
+		n = strcmp(u[mid]->name, s);
+		if(n == 0)
+			return u[mid];
+		if(n < 0)
+			lo = mid+1;
+		else
+			hi = mid;
+	}
+	return nil;
+}
+
+int
+strtoid(User **u, int nu, char *s, u32int *id)
+{
+	u32int x;
+	char *p;
+	User *uu;
+
+	x = strtoul(s, &p, 10);
+	if(*s != 0 && *p == 0){
+		*id = x;
+		return 0;
+	}
+
+	uu = finduser(u, nu, s);
+	if(uu == nil)
+		return -1;
+	*id = uu->uid;
+	return 0;
+}
+
+char*
+idtostr(User **u, int nu, u32int id)
+{
+	char buf[32];
+	int lo, hi, mid;
+
+	hi = nu;
+	lo = 0;
+	while(hi > lo){
+		mid = (lo+hi)/2;
+		if(u[mid]->uid == id)
+			return estrdup9p(u[mid]->name);
+		if(u[mid]->uid < id)
+			lo = mid+1;
+		else
+			hi = mid;
+	}
+	snprint(buf, sizeof buf, "%ud", id);	
+	return estrdup9p(buf);
+}		
+char*
+uidtostr(u32int uid)
+{
+	return idtostr(map->ubyid, map->nuser, uid);
+}
+
+char*
+gidtostr(u32int gid)
+{
+	return idtostr((User**)map->gbyid, map->ngroup, gid);
+}
+
+int
+strtouid(char *s, u32int *id)
+{
+	return strtoid(map->ubyname, map->nuser, s, id);
+}
+
+int
+strtogid(char *s, u32int *id)
+{
+	return strtoid((User**)map->gbyid, map->ngroup, s, id);
+}
+
+
+int
+idcmp(const void *va, const void *vb)
+{
+	User **a, **b;
+
+	a = (User**)va;
+	b = (User**)vb;
+	return (*a)->uid - (*b)->uid;
+}
+
+int
+namecmp(const void *va, const void *vb)
+{
+	User **a, **b;
+
+	a = (User**)va;
+	b = (User**)vb;
+	return strcmp((*a)->name, (*b)->name);
+}
+
+void
+closemap(Map *m)
+{
+	int i;
+
+	for(i=0; i<m->nuser; i++){
+		free(m->user[i].name);
+		free(m->user[i].auth);
+	}
+	for(i=0; i<m->ngroup; i++)
+		free(m->group[i].name);
+	free(m->user);
+	free(m->group);
+	free(m->ubyid);
+	free(m->ubyname);
+	free(m->gbyid);
+	free(m->gbyname);
+	free(m);
+}
+
+Map*
+readmap(char *passwd, char *group)
+{
+	char *s, *f[10], *p, *nextp, *name;
+	uchar *q, *eq;
+	int i, n, nf, line, uid, gid;
+	Biobuf *b;
+	Map *m;
+	User *u;
+	Group *g;
+	SunAuthUnix au;
+
+	m = emalloc(sizeof(Map));
+
+	if((b = Bopen(passwd, OREAD)) == nil){
+		free(m);
+		return nil;
+	}
+	line = 0;
+	for(; (s = Brdstr(b, '\n', 1)) != nil; free(s)){
+		line++;
+		if(s[0] == '#')
+			continue;
+		nf = getfields(s, f, nelem(f), 0, ":");
+		if(nf < 4)
+			continue;
+		name = f[0];
+		uid = strtol(f[2], &p, 10);
+		if(f[2][0] == 0 || *p != 0){
+			fprint(2, "%s:%d: non-numeric id in third field\n", passwd, line);
+			continue;
+		}
+		gid = strtol(f[3], &p, 10);
+		if(f[3][0] == 0 || *p != 0){
+			fprint(2, "%s:%d: non-numeric id in fourth field\n", passwd, line);
+			continue;
+		}
+		if(m->nuser%32 == 0)
+			m->user = erealloc(m->user, (m->nuser+32)*sizeof(m->user[0]));
+		u = &m->user[m->nuser++];
+		u->name = estrdup9p(name);
+		u->uid = uid;
+		u->gid = gid;
+		u->ng = 0;
+		u->auth = 0;
+		u->nauth = 0;
+	}
+	Bterm(b);
+	m->ubyname = emalloc(m->nuser*sizeof(User*));
+	m->ubyid = emalloc(m->nuser*sizeof(User*));
+	for(i=0; i<m->nuser; i++){
+		m->ubyname[i] = &m->user[i];
+		m->ubyid[i] = &m->user[i];
+	}
+	qsort(m->ubyname, m->nuser, sizeof(m->ubyname[0]), namecmp);
+	qsort(m->ubyid, m->nuser, sizeof(m->ubyid[0]), idcmp);
+
+	if((b = Bopen(group, OREAD)) == nil){
+		closemap(m);
+		return nil;
+	}
+	line = 0;
+	for(; (s = Brdstr(b, '\n', 1)) != nil; free(s)){
+		line++;
+		if(s[0] == '#')
+			continue;
+		nf = getfields(s, f, nelem(f), 0, ":");
+		if(nf < 4)
+			continue;
+		name = f[0];
+		gid = strtol(f[2], &p, 10);
+		if(f[2][0] == 0 || *p != 0){
+			fprint(2, "%s:%d: non-numeric id in third field\n", group, line);
+			continue;
+		}
+		if(m->ngroup%32 == 0)
+			m->group = erealloc(m->group, (m->ngroup+32)*sizeof(m->group[0]));
+		g = &m->group[m->ngroup++];
+		g->name = estrdup9p(name);
+		g->gid = gid;
+
+		for(p=f[3]; *p; p=nextp){
+			if((nextp = strchr(p, ',')) != nil)
+				*nextp++ = 0;
+			else
+				nextp = p+strlen(p);
+			u = finduser(m->ubyname, m->nuser, p);
+			if(u == nil){
+				if(verbose)
+					fprint(2, "%s:%d: unknown user %s\n", group, line, p);
+				continue;
+			}
+			if(u->ng >= nelem(u->g)){
+				fprint(2, "%s:%d: user %s is in too many groups; ignoring %s\n", group, line, p, name);
+				continue;
+			}
+			u->g[u->ng++] = gid;
+		}
+	}
+	Bterm(b);
+	m->gbyname = emalloc(m->ngroup*sizeof(Group*));
+	m->gbyid = emalloc(m->ngroup*sizeof(Group*));
+	for(i=0; i<m->ngroup; i++){
+		m->gbyname[i] = &m->group[i];
+		m->gbyid[i] = &m->group[i];
+	}
+	qsort(m->gbyname, m->ngroup, sizeof(m->gbyname[0]), namecmp);
+	qsort(m->gbyid, m->ngroup, sizeof(m->gbyid[0]), idcmp);
+
+	for(i=0; i<m->nuser; i++){
+		au.stamp = 0;
+		au.sysname = sys;
+		au.uid = m->user[i].uid;
+		au.gid = m->user[i].gid;
+		memmove(au.g, m->user[i].g, sizeof au.g);
+		au.ng = m->user[i].ng;
+		n = sunAuthUnixSize(&au);
+		q = emalloc(n);
+		eq = q+n;
+		m->user[i].auth = q;
+		m->user[i].nauth = n;
+		if(sunAuthUnixPack(q, eq, &q, &au) < 0 || q != eq){
+			fprint(2, "sunAuthUnixPack failed for %s\n", m->user[i].name);
+			free(m->user[i].auth);
+			m->user[i].auth = 0;
+			m->user[i].nauth = 0;
+		}
+	}
+
+	return m;
+}
+
+Auth*
+mkauth(char *user)
+{
+	Auth *a;
+	uchar *p;
+	int n;
+	SunAuthUnix au;
+	User *u;
+
+	u = finduser(map->ubyname, map->nuser, user);
+	if(u == nil || u->nauth == 0){
+		/* nobody */
+		au.stamp = 0;
+		au.uid = -1;
+		au.gid = -1;
+		au.ng = 0;
+		au.sysname = sys;
+		n = sunAuthUnixSize(&au);
+		a = emalloc(sizeof(Auth)+n);
+		a->data = (uchar*)&a[1];
+		a->ndata = n;
+		if(sunAuthUnixPack(a->data, a->data+a->ndata, &p, &au) < 0
+		|| p != a->data+a->ndata){
+			free(a);
+			return nil;
+		}
+		a->ref = 1;
+if(verbose)print("creds for %s: %.*H\n", user, a->ndata, a->data);
+		return a;
+	}
+
+	a = emalloc(sizeof(Auth)+u->nauth);
+	a->data = (uchar*)&a[1];
+	a->ndata = u->nauth;
+	memmove(a->data, u->auth, a->ndata);
+	a->ref = 1;
+if(verbose)print("creds for %s: %.*H\n", user, a->ndata, a->data);
+	return a;
+}
+
+void
+freeauth(Auth *a)
+{
+	if(--a->ref > 0)
+		return;
+	free(a);
+}
+
+/*
+ * 9P server
+ */
+void
+responderrstr(Req *r)
+{
+	char e[ERRMAX];
+
+	rerrstr(e, sizeof e);
+	respond(r, e);
+}
+
+void
+fsdestroyfid(Fid *fid)
+{
+	FidAux *aux;
+
+	aux = fid->aux;
+	if(aux == nil)
+		return;
+	freeauth(aux->auth);
+	free(aux->name);
+	free(aux);
+}
+
+void
+attrToQid(Nfs3Attr *attr, Qid *qid)
+{
+	qid->path = attr->fileid;
+	qid->vers = attr->mtime.sec;
+	qid->type = 0;
+	if(attr->type == Nfs3FileDir)
+		qid->type |= QTDIR;
+}
+
+void
+attrToDir(Nfs3Attr *attr, Dir *d)
+{
+	d->mode = attr->mode & 0777;
+	if(attr->type == Nfs3FileDir)
+		d->mode |= DMDIR;
+	d->uid = uidtostr(attr->uid);
+	d->gid = gidtostr(attr->gid);
+	d->length = attr->size;
+	attrToQid(attr, &d->qid);
+	d->mtime = attr->mtime.sec;
+	d->atime = attr->atime.sec;
+	d->muid = nil;
+}
+
+void
+fsattach(Req *r)
+{
+	char *path;
+	Auth *auth;
+	FidAux *aux;
+	Nfs3Attr attr;
+	Nfs3Handle h;
+
+	path = r->ifcall.aname;
+	if(path==nil || path[0]==0)
+		path = defaultpath;
+
+	auth = mkauth(r->ifcall.uname);
+
+	if(mountMnt(auth, r->tag, path, &h) < 0
+	|| nfsGetattr(auth, r->tag, &h, &attr) < 0){
+		freeauth(auth);
+		responderrstr(r);
+		return;
+	}
+
+	aux = emalloc(sizeof(FidAux));
+	aux->auth = auth;
+	aux->handle = h;
+	aux->cookie = 0;
+	aux->name = nil;
+	memset(&aux->parent, 0, sizeof aux->parent);
+	r->fid->aux = aux;
+	attrToQid(&attr, &r->fid->qid);
+	r->ofcall.qid = r->fid->qid;
+	respond(r, nil);
+}
+
+void
+fsopen(Req *r)
+{
+	FidAux *aux;
+	Nfs3Attr attr;
+	u1int have;
+	ulong a, b;
+
+	aux = r->fid->aux;
+	a = 0;
+	switch(r->ifcall.mode&OMASK){
+	case OREAD:
+		a = 1;
+		break;
+	case OWRITE:
+		a = 2;
+		break;
+	case ORDWR:
+		a = 3;
+		break;
+	case OEXEC:
+		a = 0x20;
+		break;
+	}
+	if(r->ifcall.mode&OTRUNC)
+		a = 0x10;
+
+	if(nfsAccess(aux->auth, r->tag, &aux->handle, a, &b, &have, &attr) < 0
+	|| (!have && nfsGetattr(aux->auth, r->tag, &aux->handle, &attr) < 0)){
+		responderrstr(r);
+		return;
+	}
+	attrToQid(&attr, &r->fid->qid);
+	r->ofcall.qid = r->fid->qid;
+	respond(r, nil);
+}
+
+void
+fscreate(Req *r)
+{
+	FidAux *aux;
+	u1int have;
+	Nfs3Attr attr;
+	Nfs3Handle h;
+	ulong mode;
+	uint gid;
+	int (*mk)(Auth*, ulong, Nfs3Handle*, char*, Nfs3Handle*, ulong, uint, u1int*, Nfs3Attr*);
+
+	aux = r->fid->aux;
+
+	/*
+	 * Plan 9 has no umask, so let's use the
+	 * parent directory bits like Plan 9 does.
+	 * What the heck, let's inherit the group too.
+	 * (Unix will let us set the group to anything
+	 * since we're the owner!)
+	 */
+	if(nfsGetattr(aux->auth, r->tag, &aux->handle, &attr) < 0){
+		responderrstr(r);
+		return;
+	}
+	mode = r->ifcall.perm&0777;
+	if(r->ifcall.perm&DMDIR)
+		mode &= (attr.mode&0666) | ~0666;
+	else
+		mode &= (attr.mode&0777) | ~0777;
+	gid = attr.gid;
+
+	if(r->ifcall.perm&DMDIR)
+		mk = nfsMkdir;
+	else
+		mk = nfsCreate;
+
+	if((*mk)(aux->auth, r->tag, &aux->handle, r->ifcall.name, &h, mode, gid, &have, &attr) < 0
+	|| (!have && nfsGetattr(aux->auth, r->tag, &h, &attr) < 0)){
+		responderrstr(r);
+		return;
+	}
+	attrToQid(&attr, &r->fid->qid);
+	aux->parent = aux->handle;
+	aux->handle = h;
+	free(aux->name);
+	aux->name = estrdup9p(r->ifcall.name);
+	r->ofcall.qid = r->fid->qid;
+	respond(r, nil);
+}
+
+void
+fsreaddir(Req *r)
+{
+	FidAux *aux;
+	uchar *p, *freeme, *ep, *p9, *ep9;
+	uint count;
+	int n, (*unpack)(uchar*, uchar*, uchar**, Nfs3Entry*);
+	Nfs3Entry e;
+	u64int cookie;
+	Dir d;
+
+	aux = r->fid->aux;
+	/*
+	 * r->ifcall.count seems a reasonable estimate to
+	 * how much NFS entry data we want.  is it?
+	 */
+	if(r->ifcall.offset)
+		cookie = aux->cookie;
+	else
+		cookie = 0;
+	if(nfsReadDir(aux->auth, r->tag, &aux->handle, r->ifcall.count, cookie,
+		&p, &count, &unpack, &freeme) < 0){
+		responderrstr(r);
+		return;
+	}
+	ep = p+count;
+
+	p9 = (uchar*)r->ofcall.data;
+	ep9 = p9+r->ifcall.count;
+
+	/*
+	 * BUG: Issue all of the stat requests in parallel.
+	 */
+	while(p < ep && p9 < ep9){
+		if((*unpack)(p, ep, &p, &e) < 0)
+			break;
+		aux->cookie = e.cookie;
+		if(strcmp(e.name, ".") == 0 || strcmp(e.name, "..") == 0)
+			continue;
+		if(!e.haveAttr && !e.haveHandle)
+			if(nfsLookup(aux->auth, r->tag, &aux->handle, e.name, &e.handle, &e.haveAttr, &e.attr) < 0)
+				continue;
+		if(!e.haveAttr)
+			if(nfsGetattr(aux->auth, r->tag, &e.handle, &e.attr) < 0)
+				continue;
+		memset(&d, 0, sizeof d);
+		attrToDir(&e.attr, &d);
+		d.name = e.name;
+		if((n = convD2M(&d, p9, ep9-p9)) <= BIT16SZ)
+			break;
+		p9 += n;
+	}
+	free(freeme);
+	r->ofcall.count = p9 - (uchar*)r->ofcall.data;
+	respond(r, nil);
+}
+	
+void
+fsread(Req *r)
+{
+	uchar *p, *freeme;
+	uint count;
+	FidAux *aux;
+
+	if(r->fid->qid.type&QTDIR){
+		fsreaddir(r);
+		return;
+	}
+
+	aux = r->fid->aux;
+	if(nfsRead(aux->auth, r->tag, &aux->handle, r->ifcall.count, r->ifcall.offset, &p, &count, &freeme) < 0){
+		responderrstr(r);
+		return;
+	}
+	r->ofcall.data = (char*)p;
+	r->ofcall.count = count;
+	respond(r, nil);
+	free(freeme);
+}
+
+void
+fswrite(Req *r)
+{
+	uint count;
+	FidAux *aux;
+
+	aux = r->fid->aux;
+	if(nfsWrite(aux->auth, r->tag, &aux->handle, (uchar*)r->ifcall.data, r->ifcall.count, r->ifcall.offset, &count) < 0){
+		responderrstr(r);
+		return;
+	}
+	r->ofcall.count = count;
+	respond(r, nil);
+}
+
+void
+fsremove(Req *r)
+{
+	int n;
+	FidAux *aux;
+
+	aux = r->fid->aux;
+	if(aux->name == nil){
+		respond(r, "nfs3client botch -- don't know parent handle in remove");
+		return;
+	}
+	if(r->fid->qid.type&QTDIR)
+		n = nfsRmdir(aux->auth, r->tag, &aux->parent, aux->name);
+	else
+		n = nfsRemove(aux->auth, r->tag, &aux->parent, aux->name);
+	if(n < 0){
+		responderrstr(r);
+		return;
+	}
+	respond(r, nil);
+}
+
+void
+fsstat(Req *r)
+{
+	FidAux *aux;
+	Nfs3Attr attr;
+
+	aux = r->fid->aux;
+	if(nfsGetattr(aux->auth, r->tag, &aux->handle, &attr) < 0){
+		responderrstr(r);
+		return;
+	}
+	memset(&r->d, 0, sizeof r->d);
+	attrToDir(&attr, &r->d);
+	r->d.name = estrdup9p(aux->name ? aux->name : "???");
+	respond(r, nil);
+}
+
+void
+fswstat(Req *r)
+{
+	int op, sync;
+	FidAux *aux;
+	Nfs3SetAttr attr;
+
+	aux = r->fid->aux;
+
+	/* Fill out stat first to catch errors */
+	op = 0;
+	sync = 1;
+	if(~r->d.mode){
+		if(r->d.mode&(DMAPPEND|DMEXCL)){
+			respond(r, "wstat -- DMAPPEND and DMEXCL bits not supported");
+			return;
+		}
+		op = 1;
+		sync = 0;
+		attr.setMode = 1;
+		attr.mode = r->d.mode & 0777;
+	}
+	if(r->d.uid && r->d.uid[0]){
+		attr.setUid = 1;
+		if(strtouid(r->d.uid, &attr.uid) < 0){
+			respond(r, "wstat -- unknown uid");
+			return;
+		}
+		op = 1;
+		sync = 0;
+	}
+	if(r->d.gid && r->d.gid[0]){
+		attr.setGid = 1;
+		if(strtogid(r->d.gid, &attr.gid) < 0){
+			respond(r, "wstat -- unknown gid");
+			return;
+		}
+		op = 1;
+		sync = 0;
+	}
+	if(~r->d.length){
+		attr.setSize = 1;
+		attr.size = r->d.length;
+	}
+	if(~r->d.mtime){
+		attr.setMtime = 1;
+		attr.mtime.sec = r->d.mtime;
+	}
+
+	/* Try rename first because it's more likely to fail (?) */
+	if(r->d.name && r->d.name[0]){
+		if(aux->name == nil){
+			respond(r, "nfsclient botch -- don't know parent handle in rename");
+			return;
+		}
+		if(nfsRename(aux->auth, r->tag, &aux->parent, aux->name, &aux->parent, r->d.name) < 0){
+			responderrstr(r);
+			return;
+		}
+		free(aux->name);
+		aux->name = estrdup9p(r->d.name);
+		sync = 0;
+	}
+
+	/*
+	 * Now we have a problem.  The rename succeeded
+	 * but the setattr could fail.  Sic transit atomicity.
+	 */
+	if(op){
+		if(nfsSetattr(aux->auth, r->tag, &aux->handle, &attr) < 0){
+			responderrstr(r);
+			return;
+		}
+	}
+
+	if(sync){
+		/* NFS commit */
+		if(nfsCommit(aux->auth, r->tag, &aux->handle) < 0){
+			responderrstr(r);
+			return;
+		}
+	}
+
+	respond(r, nil);
+}
+
+char*
+fswalk1(Fid *fid, char *name, void *v)
+{
+	u1int have;
+	ulong tag;
+	FidAux *aux;
+	Nfs3Attr attr;
+	Nfs3Handle h;
+
+	tag = *(ulong*)v;
+	aux = fid->aux;
+
+	if(nfsLookup(aux->auth, tag, &aux->handle, name, &h, &have, &attr) < 0
+	|| (!have && nfsGetattr(aux->auth, tag, &h, &attr) < 0)){
+		rerrstr(aux->err, sizeof aux->err);
+		return aux->err;
+	}
+
+	aux->parent = aux->handle;
+	aux->handle = h;
+	free(aux->name);
+	if(strcmp(name, "..") == 0)
+		aux->name = nil;
+	else
+		aux->name = estrdup9p(name);
+	attrToQid(&attr, &fid->qid);
+	return nil;
+}
+
+char*
+fsclone(Fid *fid, Fid *newfid, void*)
+{
+	FidAux *a, *na;
+
+	a = fid->aux;
+	na = emalloc9p(sizeof(FidAux));
+	*na = *a;
+	if(na->name)
+		na->name = estrdup9p(na->name);
+	newfid->aux = na;
+	if(na->auth)
+		na->auth->ref++;
+	return nil;
+}
+
+void
+fswalk(Req *r)
+{
+	walkandclone(r, fswalk1, fsclone, &r->tag);
+}
+
+void
+fsflush(Req *r)
+{
+	Req *or;
+
+	/*
+	 * Send on the flush channel(s).
+	 * The library will make sure the response
+	 * is delayed as necessary.
+	 */
+	or = r->oldreq;
+	if(nfscli)
+		sendul(nfscli->flushchan, (ulong)or->tag);
+	if(mntcli)
+		sendul(mntcli->flushchan, (ulong)or->tag);
+	respond(r, nil);
+}
+
+void
+fsdispatch(void *v)
+{
+	Req *r;
+
+	r = v;
+	switch(r->ifcall.type){
+	default:	respond(r, "unknown type");	break;
+	case Tattach:	fsattach(r);	break;
+	case Topen:	fsopen(r);	break;
+	case Tcreate:	fscreate(r);	break;
+	case Tread:	fsread(r);	break;
+	case Twrite:	fswrite(r);	break;
+	case Tremove:	fsremove(r);	break;
+	case Tflush:	fsflush(r);	break;
+	case Tstat:	fsstat(r);	break;
+	case Twstat:	fswstat(r);	break;
+	case Twalk:	fswalk(r);	break;
+	}
+}
+
+void
+fsthread(void*)
+{
+	Req *r;
+
+	while((r = recvp(fschan)) != nil)
+		threadcreate(fsdispatch, r, SunStackSize);
+}
+
+void
+fssend(Req *r)
+{
+	sendp(fschan, r);
+}
+
+void
+fsdie(Srv*)
+{
+	threadexitsall(nil);
+}
+
+Srv fs =
+{
+.destroyfid =	fsdestroyfid,
+.attach=		fssend,
+.open=		fssend,
+.create=		fssend,
+.read=		fssend,
+.write=		fssend,
+.remove=		fssend,
+.flush=		fssend,
+.stat=		fssend,
+.wstat=		fssend,
+.walk=		fssend,
+.end=		fsdie
+};
+
+void
+usage(void)
+{
+	fprint(2, "usage: nfs [-DRv] [-p perm] [-s srvname] [-u passwd group] addr [addr]\n");
+	fprint(2, "\taddr - address of portmapper server\n");
+	fprint(2, "\taddr addr - addresses of mount server and nfs server\n");
+	exits("usage");
+}
+
+char*
+netchangeport(char *addr, uint port, char *buf, uint nbuf)
+{
+	char *r;
+
+	strecpy(buf, buf+nbuf, addr);
+	r = strrchr(buf, '!');
+	if(r == nil)
+		return nil;
+	r++;
+	seprint(r, buf+nbuf, "%ud", port);
+	return buf;
+}
+
+char mbuf[256], nbuf[256];
+char *mountaddr, *nfsaddr;
+Channel *csync;
+int chattyrpc;
+void dialproc(void*);
+
+void
+threadmain(int argc, char **argv)
+{
+	char *srvname, *passwd, *group, *addr, *p;
+	SunClient *cli;
+	int proto;
+	uint mport, nport;
+	ulong perm;
+	Dir d;
+
+	perm = 0600;
+	passwd = nil;
+	group = nil;
+	srvname = nil;
+	sys = sysname();
+	if(sys == nil)
+		sys = "plan9";
+	ARGBEGIN{
+	default:
+		usage();
+	case 'D':
+		chatty9p++;
+		break;
+	case 'R':
+		chattyrpc++;
+		break;
+	case 'p':
+		perm = strtol(EARGF(usage()), &p, 8);
+		if(perm==0 || *p != 0)
+			usage();
+		break;
+	case 's':
+		srvname = EARGF(usage());
+		break;
+	case 'u':
+		passwd = EARGF(usage());
+		group = EARGF(usage());
+		break;
+	case 'v':
+		verbose++;
+		break;
+	}ARGEND
+
+	if(argc != 1 && argc != 2)
+		usage();
+
+	if(srvname == nil)
+		srvname = argv[0];
+
+	fmtinstall('B', sunRpcFmt);
+	fmtinstall('C', sunCallFmt);
+	fmtinstall('H', encodefmt);
+	sunFmtInstall(&portProg);
+	sunFmtInstall(&nfs3Prog);
+	sunFmtInstall(&nfsMount3Prog);
+
+	if(passwd && (map = readmap(passwd, group)) == nil)
+		fprint(2, "warning: reading %s and %s: %r\n", passwd, group);
+
+	if(map == nil)
+		map = &emptymap;
+
+	if(argc == 1){
+		addr = netmkaddr(argv[0], "udp", "portmap");
+		if((cli = sunDial(addr)) == nil)
+			sysfatal("dial %s: %r", addr);
+		cli->chatty = chattyrpc;
+		sunClientProg(cli, &portProg);
+		if(strstr(addr, "udp!"))
+			proto = PortProtoUdp;
+		else
+			proto = PortProtoTcp;
+		if(getport(cli, NfsMount3Program, NfsMount3Version, proto, &mport) < 0)
+			sysfatal("lookup mount program port: %r");
+		if(getport(cli, Nfs3Program, Nfs3Version, proto, &nport) < 0)
+			sysfatal("lookup nfs program port: %r");
+		sunClientClose(cli);
+		mountaddr = netchangeport(addr, mport, mbuf, sizeof mbuf);
+		nfsaddr = netchangeport(addr, nport, nbuf, sizeof nbuf);
+		strcat(mountaddr, "!r");
+		strcat(nfsaddr, "!r");
+		if(verbose)
+			fprint(2, "nfs %s %s\n", mountaddr, nfsaddr);
+	}else{
+		mountaddr = argv[0];
+		nfsaddr = argv[1];
+	}
+
+	/* have to dial in another proc because it creates threads */
+	csync = chancreate(sizeof(void*), 0);
+	proccreate(dialproc, nil, SunStackSize);
+	recvp(csync);
+
+	threadpostmountsrv(&fs, srvname, nil, 0);
+	if(perm != 0600){
+		p = smprint("/srv/%s", srvname);
+		if(p){
+			nulldir(&d);
+			d.mode = perm;
+			dirwstat(p, &d);
+		}
+	}
+	threadexits(nil);
+}
+
+void
+dialproc(void*)
+{
+	rfork(RFNAMEG);
+	rfork(RFNOTEG);
+	if((mntcli = sunDial(mountaddr)) == nil)
+		sysfatal("dial mount program at %s: %r", mountaddr);
+	mntcli->chatty = chattyrpc;
+	sunClientProg(mntcli, &nfsMount3Prog);
+	if(mountNull(0) < 0)
+		sysfatal("execute nop with mnt server at %s: %r", mountaddr);
+	
+	if((nfscli = sunDial(nfsaddr)) == nil)
+		sysfatal("dial nfs program at %s: %r", nfsaddr);
+	nfscli->chatty = chattyrpc;
+	sunClientProg(nfscli, &nfs3Prog);
+	if(nfsNull(0) < 0)
+		sysfatal("execute nop with nfs server at %s: %r", nfsaddr);
+
+	fschan = chancreate(sizeof(Req*), 0);
+	threadcreate(fsthread, nil, SunStackSize);
+	sendp(csync, 0);
+}

+ 8 - 0
sys/src/cmd/page/page.h

@@ -68,3 +68,11 @@ int	opentemp(char *template);
 
 extern int stdinfd;
 extern int truecolor;
+
+/* BUG BUG BUG BUG BUG: cannot use new draw operations in drawterm,
+ * or in vncs, and there is a bug in the kernel for copying images
+ * from cpu memory -> video memory (memmove is not being used).
+ * until all that is settled, ignore the draw operators.
+ */
+#define drawop(a,b,c,d,e,f) draw(a,b,c,d,e)
+#define gendrawop(a,b,c,d,e,f,g) gendraw(a,b,c,d,e,f)

+ 1 - 1
sys/src/cmd/vnc/vncs.c

@@ -77,7 +77,7 @@ static void vncname(char*, ...);
 void
 usage(void)
 {
-	fprint(2, "usage: vncs [-nv] [-c cert] [-d :display] [-g widthXheight] [-p pixelfmt] [cmd [args]...]\n");
+	fprint(2, "usage: vncs [-v] [-c cert] [-d :display] [-g widthXheight] [-p pixelfmt] [cmd [args]...]\n");
 	fprint(2, "\tto kill a server: vncs [-v] -k :display\n");
 	exits("usage");
 }

+ 29 - 0
sys/src/libsunrpc/COPYING

@@ -0,0 +1,29 @@
+
+This software was developed as part of a project at MIT:
+	/sys/src/libsunrpc/*
+	/sys/src/cmd/nfs.c
+	/sys/src/cmd/aux/nfsmount.c
+	/sys/src/cmd/aux/portmap.c
+
+Copyright (c) 2003 Russ Cox,
+                   Massachusetts Institute of Technology
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+

+ 60 - 0
sys/src/libsunrpc/authunix.c

@@ -0,0 +1,60 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+uint
+sunAuthUnixSize(SunAuthUnix *x)
+{
+	return 4 + sunStringSize(x->sysname) + 4 + 4 + 4 + 4*x->ng;
+}
+int
+sunAuthUnixUnpack(uchar *a, uchar *ea, uchar **pa, SunAuthUnix *x)
+{
+	int i;
+
+	if(sunUint32Unpack(a, ea, &a, &x->stamp) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->sysname, 256) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->uid) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->gid) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->ng) < 0 || x->ng > nelem(x->g)) goto Err;
+	for(i=0; i<x->ng; i++)
+		if(sunUint32Unpack(a, ea, &a, &x->g[i]) < 0) goto Err;
+
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+sunAuthUnixPack(uchar *a, uchar *ea, uchar **pa, SunAuthUnix *x)
+{
+	int i;
+
+	if(sunUint32Pack(a, ea, &a, &x->stamp) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->sysname, 256) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->uid) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->gid) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->ng) < 0 || x->ng > nelem(x->g)) goto Err;
+	for(i=0; i<x->ng; i++)
+		if(sunUint32Pack(a, ea, &a, &x->g[i]) < 0) goto Err;
+
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+sunAuthUnixPrint(Fmt *fmt, SunAuthUnix *x)
+{
+	int i;
+	fmtprint(fmt, "unix %.8lux %s %lud %lud (", (ulong)x->stamp,
+		x->sysname, (ulong)x->uid, (ulong)x->gid);
+	for(i=0; i<x->ng; i++)
+		fmtprint(fmt, "%s%lud", i ? " ":"", (ulong)x->g[i]);
+	fmtprint(fmt, ")");
+}

+ 478 - 0
sys/src/libsunrpc/client.c

@@ -0,0 +1,478 @@
+/*
+ * Sun RPC client.
+ */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+typedef struct Out Out;
+struct Out
+{
+	char err[ERRMAX];	/* error string */
+	Channel *creply;	/* send to finish rpc */
+	uchar *p;			/* pending request packet */
+	int n;				/* size of request */
+	ulong tag;			/* flush tag of pending request */
+	ulong xid;			/* xid of pending request */
+	ulong st;			/* first send time */
+	ulong t;			/* resend time */
+	int nresend;		/* number of resends */
+	SunRpc rpc;		/* response rpc */
+};
+
+static void
+udpThread(void *v)
+{
+	uchar *p, *buf;
+	Ioproc *io;
+	int n;
+	SunClient *cli;
+	enum { BufSize = 65536 };
+
+	cli = v;
+	buf = emalloc(BufSize);
+	io = ioproc();
+	p = nil;
+	for(;;){
+		n = ioread(io, cli->fd, buf, BufSize);
+		if(n <= 0)
+			break;
+		p = emalloc(4+n);
+		memmove(p+4, buf, n);
+		p[0] = n>>24;
+		p[1] = n>>16;
+		p[2] = n>>8;
+		p[3] = n;
+		if(sendp(cli->readchan, p) == 0)
+			break;
+		p = nil;
+	}
+	free(p);
+	closeioproc(io);
+	while(send(cli->dying, nil) == -1)
+		;
+}
+
+static void
+netThread(void *v)
+{
+	uchar *p, buf[4];
+	Ioproc *io;
+	uint n, tot;
+	int done;
+	SunClient *cli;
+
+	cli = v;
+	io = ioproc();
+	tot = 0;
+	p = nil;
+	for(;;){
+		n = ioreadn(io, cli->fd, buf, 4);
+		if(n != 4)
+			break;
+		n = (buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
+		if(cli->chatty)
+			fprint(2, "%.8ux...", n);
+		done = n&0x80000000;
+		n &= ~0x80000000;
+		if(tot == 0){
+			p = emalloc(4+n);
+			tot = 4;
+		}else
+			p = erealloc(p, tot+n);
+		if(ioreadn(io, cli->fd, p+tot, n) != n)
+			break;
+		tot += n;
+		if(done){
+			p[0] = tot>>24;
+			p[1] = tot>>16;
+			p[2] = tot>>8;
+			p[3] = tot;
+			if(sendp(cli->readchan, p) == 0)
+				break;
+			p = nil;
+			tot = 0;
+		}
+	}
+	free(p);
+	closeioproc(io);
+	while(send(cli->dying, 0) == -1)
+		;
+}
+
+static void
+timerThread(void *v)
+{
+	Ioproc *io;
+	SunClient *cli;
+
+	cli = v;
+	io = ioproc();
+	for(;;){
+		if(iosleep(io, 200) < 0)
+			break;
+		if(sendul(cli->timerchan, 0) == 0)
+			break;
+	}
+	closeioproc(io);
+	while(send(cli->dying, 0) == -1)
+		;
+}
+
+static ulong
+msec(void)
+{
+	return nsec()/1000000;
+}
+
+static ulong
+twait(ulong rtt, int nresend)
+{
+	ulong t;
+
+	t = rtt;
+	if(nresend <= 1)
+		{}
+	else if(nresend <= 3)
+		t *= 2;
+	else if(nresend <= 18)
+		t <<= nresend-2;
+	else
+		t = 60*1000;
+	if(t > 60*1000)
+		t = 60*1000;
+
+	return t;
+}
+
+static void
+rpcMuxThread(void *v)
+{
+	uchar *buf, *p, *ep;
+	int i, n, nout, mout;
+	ulong t, xidgen, tag;
+	Alt a[5];
+	Out *o, **out;
+	SunRpc rpc;
+	SunClient *cli;
+
+	cli = v;
+	mout = 16;
+	nout = 0;
+	out = emalloc(mout*sizeof(out[0]));
+	xidgen = truerand();
+
+	a[0].op = CHANRCV;
+	a[0].c = cli->rpcchan;
+	a[0].v = &o;
+	a[1].op = CHANNOP;
+	a[1].c = cli->timerchan;
+	a[1].v = nil;
+	a[2].op = CHANRCV;
+	a[2].c = cli->flushchan;
+	a[2].v = &tag;
+	a[3].op = CHANRCV;
+	a[3].c = cli->readchan;
+	a[3].v = &buf;
+	a[4].op = CHANEND;
+
+	for(;;){
+		switch(alt(a)){
+		case 0:	/* rpcchan */
+			if(o == nil)
+				goto Done;
+			cli->nsend++;
+			/* set xid */
+			o->xid = ++xidgen;
+			if(cli->needcount)
+				p = o->p+4;
+			else
+				p = o->p;
+			p[0] = xidgen>>24;
+			p[1] = xidgen>>16;
+			p[2] = xidgen>>8;
+			p[3] = xidgen;
+			if(write(cli->fd, o->p, o->n) != o->n){
+				free(o->p);
+				o->p = nil;
+				snprint(o->err, sizeof o->err, "write: %r");
+				sendp(o->creply, 0);
+				break;
+			}
+			if(nout >= mout){
+				mout *= 2;
+				out = erealloc(out, mout*sizeof(out[0]));
+			}
+			o->st = msec();
+			o->nresend = 0;
+			o->t = o->st + twait(cli->rtt.avg, 0);
+if(cli->chatty) print("send %lux %lud %lud\n", o->xid, o->st, o->t);
+			out[nout++] = o;
+			a[1].op = CHANRCV;
+			break;
+
+		case 1:	/* timerchan */
+			t = msec();
+			for(i=0; i<nout; i++){
+				o = out[i];
+				if((int)(t - o->t) > 0){
+if(cli->chatty) print("resend %lux %lud %lud\n", o->xid, t, o->t);
+					if(cli->maxwait && t - o->st >= cli->maxwait){
+						free(o->p);
+						o->p = nil;
+						strcpy(o->err, "timeout");
+						sendp(o->creply, 0);
+						out[i--] = out[--nout];
+						continue;
+					}
+					cli->nresend++;
+					o->nresend++;
+					o->t = t + twait(cli->rtt.avg, o->nresend);
+					if(write(cli->fd, o->p, o->n) != o->n){
+						free(o->p);
+						o->p = nil;
+						snprint(o->err, sizeof o->err, "rewrite: %r");
+						sendp(o->creply, 0);
+						out[i--] = out[--nout];
+						continue;
+					}
+				}
+			}
+			/* stop ticking if no work; rpcchan will turn it back on */
+			if(nout == 0)
+				a[1].op = CHANNOP;
+			break;
+			
+		case 2:	/* flushchan */
+			for(i=0; i<nout; i++){
+				o = out[i];
+				if(o->tag == tag){
+					o[i--] = o[--nout];
+					strcpy(o->err, "flushed");
+					free(o->p);
+					o->p = nil;
+					sendp(o->creply, 0);
+				}
+			}
+			break;
+
+		case 3:	/* readchan */
+			p = buf;
+			n = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3];
+			p += 4;
+			ep = p+n;
+			if(sunRpcUnpack(p, ep, &p, &rpc) < 0){
+				print("in: %.*H unpack failed\n", n, buf+4);
+				free(buf);
+				break;
+			}
+			if(cli->chatty)
+				print("in: %B\n", &rpc);
+			if(rpc.iscall){
+				print("did not get reply\n");
+				free(buf);
+				break;
+			}
+			for(i=0; i<nout; i++)
+				if(o->xid == rpc.xid)
+					break;
+			if(i==nout){
+				if(cli->chatty) print("did not find waiting request\n");
+				free(buf);
+				break;
+			}
+			out[i] = out[--nout];
+			free(o->p);
+			if(rpc.status == SunSuccess){
+				o->p = buf;
+				o->rpc = rpc;
+			}else{
+				o->p = nil;
+				free(buf);
+				sunErrstr(rpc.status);
+				rerrstr(o->err, sizeof o->err);
+			}
+			sendp(o->creply, 0);
+			break;
+		}
+	}
+Done:
+	free(out);
+	sendp(cli->dying, 0);
+}
+
+SunClient*
+sunDial(char *address)
+{
+	int fd;
+	SunClient *cli;
+
+	if((fd = dial(address, 0, 0, 0)) < 0)
+		return nil;
+
+	cli = emalloc(sizeof(SunClient));
+	cli->fd = fd;
+	cli->maxwait = 15000;
+	cli->rtt.avg = 1000;
+	cli->dying = chancreate(sizeof(void*), 0);
+	cli->rpcchan = chancreate(sizeof(Out*), 0);
+	cli->timerchan = chancreate(sizeof(ulong), 0);
+	cli->flushchan = chancreate(sizeof(ulong), 0);
+	cli->readchan = chancreate(sizeof(uchar*), 0);
+	if(strstr(address, "udp!")){
+		cli->needcount = 0;
+		cli->nettid = threadcreate(udpThread, cli, SunStackSize);
+		cli->timertid = threadcreate(timerThread, cli, SunStackSize);
+	}else{
+		cli->needcount = 1;
+		cli->nettid = threadcreate(netThread, cli, SunStackSize);
+		/* assume reliable: don't need timer */
+		/* BUG: netThread should know how to redial */
+	}
+	threadcreate(rpcMuxThread, cli, SunStackSize);
+
+	return cli;
+}
+
+void
+sunClientClose(SunClient *cli)
+{
+	int n;
+
+	/*
+	 * Threadints get you out of any stuck system calls
+	 * or thread rendezvouses, but do nothing if the thread
+	 * is in the ready state.  Keep interrupting until it takes.
+	 */
+	n = 0;
+	if(!cli->timertid)
+		n++;
+	while(n < 2){
+		threadint(cli->nettid);
+		if(cli->timertid)
+			threadint(cli->timertid);
+		yield();
+		while(nbrecv(cli->dying, nil) == 1)
+			n++;
+	}
+
+	sendp(cli->rpcchan, 0);
+	recvp(cli->dying);
+
+	/* everyone's gone: clean up */
+	close(cli->fd);
+	chanfree(cli->flushchan);
+	chanfree(cli->readchan);
+	chanfree(cli->timerchan);
+	free(cli);
+}
+	
+void
+sunClientFlushRpc(SunClient *cli, ulong tag)
+{
+	sendul(cli->flushchan, tag);
+}
+
+void
+sunClientProg(SunClient *cli, SunProg *p)
+{
+	if(cli->nprog%16 == 0)
+		cli->prog = erealloc(cli->prog, (cli->nprog+16)*sizeof(cli->prog[0]));
+	cli->prog[cli->nprog++] = p;
+}
+
+int
+sunClientRpc(SunClient *cli, ulong tag, SunCall *tx, SunCall *rx, uchar **tofree)
+{
+	uchar *bp, *p, *ep;
+	int i, n1, n2, n, nn;
+	Out o;
+	SunProg *prog;
+	SunStatus ok;
+
+	for(i=0; i<cli->nprog; i++)
+		if(cli->prog[i]->prog == tx->rpc.prog && cli->prog[i]->vers == tx->rpc.vers)
+			break;
+	if(i==cli->nprog){
+		werrstr("unknown sun rpc program %d version %d", tx->rpc.prog, tx->rpc.vers);
+		return -1;
+	}
+	prog = cli->prog[i];
+
+	if(cli->chatty){
+		print("out: %B\n", &tx->rpc);
+		print("\t%C\n", tx);
+	}
+
+	n1 = sunRpcSize(&tx->rpc);
+	n2 = sunCallSize(prog, tx);
+
+	n = n1+n2;
+	if(cli->needcount)
+		n += 4;
+
+	bp = emalloc(n);
+	ep = bp+n;
+	p = bp;
+	if(cli->needcount){
+		nn = n-4;
+		p[0] = (nn>>24)|0x80;
+		p[1] = nn>>16;
+		p[2] = nn>>8;
+		p[3] = nn;
+		p += 4;
+	}
+	if((ok = sunRpcPack(p, ep, &p, &tx->rpc)) != SunSuccess
+	|| (ok = sunCallPack(prog, p, ep, &p, tx)) != SunSuccess){
+		sunErrstr(ok);
+		free(bp);
+		return -1;
+	}
+	if(p != ep){
+		werrstr("rpc: packet size mismatch");
+		free(bp);
+		return -1;
+	}
+
+	memset(&o, 0, sizeof o);
+	o.creply = chancreate(sizeof(void*), 0);
+	o.tag = tag;
+	o.p = bp;
+	o.n = n;
+
+	sendp(cli->rpcchan, &o);
+	recvp(o.creply);
+	chanfree(o.creply);
+
+	if(o.p == nil){
+		werrstr("%s", o.err);
+		return -1;
+	}
+
+	p = o.rpc.data;
+	ep = p+o.rpc.ndata;
+	rx->rpc = o.rpc;
+	rx->rpc.proc = tx->rpc.proc;
+	rx->rpc.prog = tx->rpc.prog;
+	rx->rpc.vers = tx->rpc.vers;
+	rx->type = (rx->rpc.proc<<1)|1;
+	if((ok = sunCallUnpack(prog, p, ep, &p, rx)) != SunSuccess){
+		sunErrstr(ok);
+		werrstr("unpack: %r");
+		free(o.p);
+		return -1;
+	}
+
+	if(cli->chatty){
+		print("in: %B\n", &rx->rpc);
+		print("in:\t%C\n", rx);
+	}
+
+	if(tofree)
+		*tofree = o.p;
+	else
+		free(o.p);
+
+	return 0;
+}

+ 34 - 0
sys/src/libsunrpc/emalloc.c

@@ -0,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+void*
+emalloc(ulong n)
+{
+	void *v;
+
+	v = mallocz(n, 1);
+	if(v == nil)
+{
+abort();
+		sysfatal("out of memory");
+}
+	setmalloctag(v, getcallerpc(&n));
+	return v;
+}
+
+void*
+erealloc(void *v, ulong n)
+{
+	v = realloc(v, n);
+	if(v == nil)
+{
+abort();
+		sysfatal("out of memory");
+}
+	setrealloctag(v, getcallerpc(&n));
+	return v;
+}
+
+

+ 37 - 0
sys/src/libsunrpc/error.c

@@ -0,0 +1,37 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+static struct {
+	SunStatus status;
+	char *msg;
+} tab[] = {
+	SunProgUnavail,	"program unavailable",
+	SunProgMismatch,	"program mismatch",
+	SunProcUnavail,	"procedure unavailable",
+	SunGarbageArgs,	"garbage args",
+	SunSystemErr,		"system error",
+	SunRpcMismatch,	"rpc mismatch",
+	SunAuthBadCred,	"bad auth cred",
+	SunAuthRejectedCred,	"rejected auth cred",
+	SunAuthBadVerf,	"bad auth verf",
+	SunAuthRejectedVerf,	"rejected auth verf",
+	SunAuthTooWeak,	"auth too weak",
+	SunAuthInvalidResp,	"invalid auth response",
+	SunAuthFailed,		"auth failed",
+};
+
+void
+sunErrstr(SunStatus status)
+{
+	int i;
+
+	for(i=0; i<nelem(tab); i++){
+		if(tab[i].status == status){
+			werrstr(tab[i].msg);
+			return;
+		}
+	}
+	werrstr("unknown sun error %d", (int)status);
+}

+ 107 - 0
sys/src/libsunrpc/fd.c

@@ -0,0 +1,107 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+enum
+{
+	MaxRead = 17000,
+};
+
+typedef struct SunMsgFd SunMsgFd;
+struct SunMsgFd
+{
+	SunMsg msg;
+	int fd;
+};
+
+typedef struct Arg Arg;
+struct Arg
+{
+	SunSrv *srv;
+	Channel *creply;
+	Channel *csync;
+	int fd;
+};
+
+static void
+sunFdRead(void *v)
+{
+	uint n, tot;
+	int done;
+	uchar buf[4], *p;
+	Arg arg = *(Arg*)v;
+	SunMsgFd *msg;
+
+	sendp(arg.csync, 0);
+
+	p = nil;
+	tot = 0;
+	for(;;){
+		n = readn(arg.fd, buf, 4);
+		if(n != 4)
+			break;
+		n = (buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
+if(arg.srv->chatty) fprint(2, "%.8ux...", n);
+		done = n&0x80000000;
+		n &= ~0x80000000;
+		p = erealloc(p, tot+n);
+		if(readn(arg.fd, p+tot, n) != n)
+			break;
+		tot += n;
+		if(done){
+			msg = emalloc(sizeof(SunMsgFd));
+			msg->msg.data = p;
+			msg->msg.count = tot;
+			msg->msg.creply = arg.creply;
+			sendp(arg.srv->crequest, msg);
+			p = nil;
+			tot = 0;
+		}
+	}
+}
+
+static void
+sunFdWrite(void *v)
+{
+	uchar buf[4];
+	u32int n;
+	Arg arg = *(Arg*)v;
+	SunMsgFd *msg;
+
+	sendp(arg.csync, 0);
+
+	while((msg = recvp(arg.creply)) != nil){
+		n = msg->msg.count;
+		buf[0] = (n>>24)|0x80;
+		buf[1] = n>>16;
+		buf[2] = n>>8;
+		buf[3] = n;
+		if(write(arg.fd, buf, 4) != 4
+		|| write(arg.fd, msg->msg.data, msg->msg.count) != msg->msg.count)
+			fprint(2, "sunFdWrite: %r\n");
+		free(msg->msg.data);
+		free(msg);
+	}
+}
+
+int
+sunSrvFd(SunSrv *srv, int fd)
+{
+	Arg *arg;
+
+	arg = emalloc(sizeof(Arg));
+	arg->fd = fd;
+	arg->srv = srv;
+	arg->csync = chancreate(sizeof(void*), 0);
+	arg->creply = chancreate(sizeof(SunMsg*), 10);
+
+	proccreate(sunFdRead, arg, SunStackSize);
+	proccreate(sunFdWrite, arg, SunStackSize);
+	recvp(arg->csync);
+	recvp(arg->csync);
+
+	chanfree(arg->csync);
+	free(arg);
+	return 1;
+}

+ 64 - 0
sys/src/libsunrpc/fmt.c

@@ -0,0 +1,64 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+/*
+ * print formatters
+ */
+int
+sunRpcFmt(Fmt *f)
+{
+	SunRpc *rpc;
+
+	rpc = va_arg(f->args, SunRpc*);
+	sunRpcPrint(f, rpc);
+	return 0;
+}
+
+static SunProg **fmtProg;
+static int nfmtProg;
+static RWLock fmtLock;
+
+void
+sunFmtInstall(SunProg *p)
+{
+	int i;
+
+	wlock(&fmtLock);
+	for(i=0; i<nfmtProg; i++){
+		if(fmtProg[i] == p){
+			wunlock(&fmtLock);
+			return;
+		}
+	}
+	if(nfmtProg%16 == 0)
+		fmtProg = erealloc(fmtProg, sizeof(fmtProg[0])*(nfmtProg+16));
+	fmtProg[nfmtProg++] = p;
+	wunlock(&fmtLock);
+}
+
+int
+sunCallFmt(Fmt *f)
+{
+	int i;
+	void (*fmt)(Fmt*, SunCall*);
+	SunCall *c;
+	SunProg *p;
+
+	c = va_arg(f->args, SunCall*);
+	rlock(&fmtLock);
+	for(i=0; i<nfmtProg; i++){
+		p = fmtProg[i];
+		if(p->prog == c->rpc.prog && p->vers == c->rpc.vers){
+			runlock(&fmtLock);
+			if(c->type < 0 || c->type >= p->nproc || (fmt=p->proc[c->type].fmt) == nil)
+				return fmtprint(f, "unknown proc %c%d", "TR"[c->type&1], c->type>>1);
+			(*fmt)(f, c);
+			return 0;
+		}
+	}
+	runlock(&fmtLock);
+	fmtprint(f, "<sunrpc %d %d %c%d>", c->rpc.prog, c->rpc.vers, "TR"[c->type&1], c->type>>1);
+	return 0;
+}

+ 29 - 0
sys/src/libsunrpc/mkfile

@@ -0,0 +1,29 @@
+</$objtype/mkfile
+
+PROTO=\
+	mount3.$O\
+	nfs3.$O\
+	portmap.$O\
+
+OFILES=\
+	authunix.$O\
+	client.$O\
+	emalloc.$O\
+	error.$O\
+	fd.$O\
+	fmt.$O\
+	net.$O\
+	prog.$O\
+	rpc.$O\
+	server.$O\
+	udp.$O\
+	$PROTO\
+
+HFILES=\
+	/sys/include/sunrpc.h\
+
+LIB=/$objtype/lib/libsunrpc.a
+
+</sys/src/cmd/mksyslib
+
+mount3.$O: /sys/include/nfs3.h

+ 727 - 0
sys/src/libsunrpc/mount3.c

@@ -0,0 +1,727 @@
+/*
+ * SUN NFSv3 Mounter.  See RFC 1813
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+#include <nfs3.h>
+
+void
+nfsMount3TNullPrint(Fmt *fmt, NfsMount3TNull *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "NfsMount3TNull");
+}
+uint
+nfsMount3TNullSize(NfsMount3TNull *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+int
+nfsMount3TNullPack(uchar *a, uchar *ea, uchar **pa, NfsMount3TNull *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+int
+nfsMount3TNullUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3TNull *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+void
+nfsMount3RNullPrint(Fmt *fmt, NfsMount3RNull *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "NfsMount3RNull");
+}
+uint
+nfsMount3RNullSize(NfsMount3RNull *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+int
+nfsMount3RNullPack(uchar *a, uchar *ea, uchar **pa, NfsMount3RNull *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+int
+nfsMount3RNullUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3RNull *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+void
+nfsMount3TMntPrint(Fmt *fmt, NfsMount3TMnt *x)
+{
+	fmtprint(fmt, "%s\n", "NfsMount3TMnt");
+	fmtprint(fmt, "\t%s=", "path");
+	fmtprint(fmt, "\"%s\"", x->path);
+	fmtprint(fmt, "\n");
+}
+uint
+nfsMount3TMntSize(NfsMount3TMnt *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + sunStringSize(x->path);
+	return a;
+}
+int
+nfsMount3TMntPack(uchar *a, uchar *ea, uchar **pa, NfsMount3TMnt *x)
+{
+	if(sunStringPack(a, ea, &a, &x->path, 1024) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfsMount3TMntUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3TMnt *x)
+{
+	if(sunStringUnpack(a, ea, &a, &x->path, 1024) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfsMount3RMntPrint(Fmt *fmt, NfsMount3RMnt *x)
+{
+	fmtprint(fmt, "%s\n", "NfsMount3RMnt");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%ud", x->status);
+	fmtprint(fmt, "\n");
+	switch(x->status){
+	case 0:
+		fmtprint(fmt, "\t%s=", "handle");
+		fmtprint(fmt, "%.*H", x->len, x->handle);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfsMount3RMntSize(NfsMount3RMnt *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	switch(x->status){
+	case 0:
+		a = a + sunVarOpaqueSize(x->len);
+		a = a + 4 + 4 * x->nauth;
+		break;
+	}
+	a = a;
+	return a;
+}
+uint
+nfsMount1RMntSize(NfsMount3RMnt *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	switch(x->status){
+	case 0:
+		a = a + NfsMount1HandleSize;
+		break;
+	}
+	return a;
+}
+
+int
+nfsMount3RMntPack(uchar *a, uchar *ea, uchar **pa, NfsMount3RMnt *x)
+{
+	int i;
+	if(sunUint32Pack(a, ea, &a, &x->status) < 0) goto Err;
+	switch(x->status){
+	case 0:
+		if(sunVarOpaquePack(a, ea, &a, &x->handle, &x->len, NfsMount3MaxHandleSize) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->nauth) < 0) goto Err;
+		for(i=0; i<x->nauth; i++)
+			if(sunUint32Pack(a, ea, &a, &x->auth[i]) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfsMount1RMntPack(uchar *a, uchar *ea, uchar **pa, NfsMount3RMnt *x)
+{
+	if(sunUint32Pack(a, ea, &a, &x->status) < 0) goto Err;
+	switch(x->status){
+	case 0:
+		if(x->len != NfsMount1HandleSize)
+			goto Err;
+		if(sunFixedOpaquePack(a, ea, &a, x->handle, NfsMount1HandleSize) < 0) goto Err;
+		if(x->nauth != 0)
+			goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfsMount1RMntUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3RMnt *x)
+{
+	if(sunUint32Unpack(a, ea, &a, &x->status) < 0) goto Err;
+	switch(x->status){
+	case 0:
+		x->len = NfsMount1HandleSize;
+		x->nauth = 0;
+		x->auth = 0;
+		if(sunFixedOpaqueUnpack(a, ea, &a, x->handle, NfsMount1HandleSize) < 0) goto Err;
+		if(x->nauth != 0)
+			goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+
+int
+nfsMount3RMntUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3RMnt *x)
+{
+	int i;
+
+	if(sunUint32Unpack(a, ea, &a, &x->status) < 0) goto Err;
+	switch(x->status){
+	case 0:
+		if(sunVarOpaqueUnpack(a, ea, &a, &x->handle, &x->len, NfsMount3MaxHandleSize) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->nauth) < 0) goto Err;
+		x->auth = (u32int*)a;
+		for(i=0; i<x->nauth; i++)
+			if(sunUint32Unpack(a, ea, &a, &x->auth[i]) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfsMount3TDumpPrint(Fmt *fmt, NfsMount3TDump *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "NfsMount3TDump");
+}
+uint
+nfsMount3TDumpSize(NfsMount3TDump *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+int
+nfsMount3TDumpPack(uchar *a, uchar *ea, uchar **pa, NfsMount3TDump *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+int
+nfsMount3TDumpUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3TDump *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+void
+nfsMount3EntryPrint(Fmt *fmt, NfsMount3Entry *x)
+{
+	fmtprint(fmt, "%s\n", "NfsMount3Entry");
+	fmtprint(fmt, "\t%s=", "host");
+	fmtprint(fmt, "\"%s\"", x->host);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "path");
+	fmtprint(fmt, "\"%s\"", x->path);
+	fmtprint(fmt, "\n");
+}
+uint
+nfsMount3EntrySize(NfsMount3Entry *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + sunStringSize(x->host) + sunStringSize(x->path);
+	return a;
+}
+int
+nfsMount3EntryPack(uchar *a, uchar *ea, uchar **pa, NfsMount3Entry *x)
+{
+	u1int one;
+
+	one = 1;
+	if(sunUint1Pack(a, ea, &a, &one) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->host, 255) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->path, 1024) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfsMount3EntryUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3Entry *x)
+{
+	u1int one;
+
+	if(sunUint1Unpack(a, ea, &a, &one) < 0 || one != 1) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->host, NfsMount3MaxNameSize) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->path, NfsMount3MaxPathSize) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfsMount3RDumpPrint(Fmt *fmt, NfsMount3RDump *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "NfsMount3RDump");
+}
+uint
+nfsMount3RDumpSize(NfsMount3RDump *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	a += x->count;
+	a += 4;
+	return a;
+}
+int
+nfsMount3RDumpPack(uchar *a, uchar *ea, uchar **pa, NfsMount3RDump *x)
+{
+	u1int zero;
+
+	zero = 0;
+	if(a+x->count > ea) goto Err;
+	memmove(a, x->data, x->count);
+	a += x->count;
+	if(sunUint1Pack(a, ea, &a, &zero) < 0)
+		goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfsMount3RDumpUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3RDump *x)
+{
+	int i;
+	uchar *oa;
+	u1int u1;
+	u32int u32;
+
+	oa = a;
+	for(i=0;; i++){
+		if(sunUint1Unpack(a, ea, &a, &u1) < 0)
+			goto Err;
+		if(u1 == 0)
+			break;
+		if(sunUint32Unpack(a, ea, &a, &u32) < 0
+		|| u32 > NfsMount3MaxNameSize
+		|| (a+=u32) >= ea
+		|| sunUint32Unpack(a, ea, &a, &u32) < 0
+		|| u32 > NfsMount3MaxPathSize
+		|| (a+=u32) >= ea)
+			goto Err;
+	}
+	x->count = (a-4) - oa;
+	x->data = oa;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfsMount3TUmntPrint(Fmt *fmt, NfsMount3TUmnt *x)
+{
+	fmtprint(fmt, "%s\n", "NfsMount3TUmnt");
+	fmtprint(fmt, "\t%s=", "path");
+	fmtprint(fmt, "\"%s\"", x->path);
+	fmtprint(fmt, "\n");
+}
+uint
+nfsMount3TUmntSize(NfsMount3TUmnt *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + sunStringSize(x->path);
+	return a;
+}
+int
+nfsMount3TUmntPack(uchar *a, uchar *ea, uchar **pa, NfsMount3TUmnt *x)
+{
+	if(sunStringPack(a, ea, &a, &x->path, 1024) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfsMount3TUmntUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3TUmnt *x)
+{
+	if(sunStringUnpack(a, ea, &a, &x->path, 1024) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfsMount3RUmntPrint(Fmt *fmt, NfsMount3RUmnt *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "NfsMount3RUmnt");
+}
+uint
+nfsMount3RUmntSize(NfsMount3RUmnt *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+int
+nfsMount3RUmntPack(uchar *a, uchar *ea, uchar **pa, NfsMount3RUmnt *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+int
+nfsMount3RUmntUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3RUmnt *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+void
+nfsMount3TUmntallPrint(Fmt *fmt, NfsMount3TUmntall *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "NfsMount3TUmntall");
+}
+uint
+nfsMount3TUmntallSize(NfsMount3TUmntall *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+int
+nfsMount3TUmntallPack(uchar *a, uchar *ea, uchar **pa, NfsMount3TUmntall *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+int
+nfsMount3TUmntallUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3TUmntall *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+void
+nfsMount3RUmntallPrint(Fmt *fmt, NfsMount3RUmntall *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "NfsMount3RUmntall");
+}
+uint
+nfsMount3RUmntallSize(NfsMount3RUmntall *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+int
+nfsMount3RUmntallPack(uchar *a, uchar *ea, uchar **pa, NfsMount3RUmntall *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+int
+nfsMount3RUmntallUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3RUmntall *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+void
+nfsMount3TExportPrint(Fmt *fmt, NfsMount3TExport *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "NfsMount3TExport");
+}
+uint
+nfsMount3TExportSize(NfsMount3TExport *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+int
+nfsMount3TExportPack(uchar *a, uchar *ea, uchar **pa, NfsMount3TExport *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+int
+nfsMount3TExportUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3TExport *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+void
+nfsMount3RExportPrint(Fmt *fmt, NfsMount3RExport *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "NfsMount3RExport");
+	fmtprint(fmt, "\n");
+}
+uint
+nfsMount3RExportSize(NfsMount3RExport *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	a += x->count;
+	a += 4;	/* end of export list */
+	return a;
+}
+int
+nfsMount3RExportPack(uchar *a, uchar *ea, uchar **pa, NfsMount3RExport *x)
+{
+	u1int zero;
+
+	zero = 0;
+	if(a+x->count > ea) goto Err;
+	memmove(a, x->data, x->count);
+	a += x->count;
+	if(sunUint1Pack(a, ea, &a, &zero) < 0)
+		goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfsMount3RExportUnpack(uchar *a, uchar *ea, uchar **pa, NfsMount3RExport *x)
+{
+	int ng, ne;
+	uchar *oa;
+	u1int u1;
+	u32int u32;
+
+	oa = a;
+	ng = 0;
+	for(ne=0;; ne++){
+		if(sunUint1Unpack(a, ea, &a, &u1) < 0)
+			goto Err;
+		if(u1 == 0)
+			break;
+		if(sunUint32Unpack(a, ea, &a, &u32) < 0
+		|| (a += (u32+3)&~3) >= ea)
+			goto Err;
+		for(;; ng++){
+			if(sunUint1Unpack(a, ea, &a, &u1) < 0)
+				goto Err;
+			if(u1 == 0)
+				break;
+			if(sunUint32Unpack(a, ea, &a, &u32) < 0
+			|| (a += (u32+3)&~3) >= ea)
+				goto Err;
+		}
+	}
+	x->data = oa;
+	x->count = (a-4) - oa;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+uint
+nfsMount3ExportGroupSize(uchar *a)
+{
+	int ng;
+	u1int have;
+	u32int n;
+
+	a += 4;
+	sunUint32Unpack(a, a+4, &a, &n);
+	a += (n+3)&~3;
+	ng = 0;
+	for(;;){
+		sunUint1Unpack(a, a+4, &a, &have);
+		if(have == 0)
+			break;
+		ng++;
+		sunUint32Unpack(a, a+4, &a, &n);
+		a += (n+3)&~3;
+	}
+	return ng;
+}
+int
+nfsMount3ExportUnpack(uchar *a, uchar *ea, uchar **pa, char **gp, char ***pgp, NfsMount3Export *x)
+{
+	int ng;
+	u1int u1;
+
+	if(sunUint1Unpack(a, ea, &a, &u1) < 0 || u1 != 1) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->path, NfsMount3MaxPathSize) < 0) goto Err;
+	x->g = gp;
+	ng = 0;
+	for(;;){
+		if(sunUint1Unpack(a, ea, &a, &u1) < 0) goto Err;
+		if(u1 == 0)
+			break;
+		if(sunStringUnpack(a, ea, &a, &gp[ng++], NfsMount3MaxNameSize) < 0) goto Err;
+	}
+	x->ng = ng;
+	*pgp = gp+ng;
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+uint
+nfsMount3ExportSize(NfsMount3Export *x)
+{
+	int i;
+	uint a;
+
+	a = 4 + sunStringSize(x->path);
+	for(i=0; i<x->ng; i++)
+		a += 4 + sunStringSize(x->g[i]);
+	a += 4;
+	return a;
+}
+int
+nfsMount3ExportPack(uchar *a, uchar *ea, uchar **pa, NfsMount3Export *x)
+{
+	int i;
+	u1int u1;
+
+	u1 = 1;
+	if(sunUint1Pack(a, ea, &a, &u1) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->path, NfsMount3MaxPathSize) < 0) goto Err;
+	for(i=0; i<x->ng; i++){
+		if(sunUint1Pack(a, ea, &a, &u1) < 0) goto Err;
+		if(sunStringPack(a, ea, &a, &x->g[i], NfsMount3MaxNameSize) < 0) goto Err;
+	}
+	u1 = 0;
+	if(sunUint1Pack(a, ea, &a, &u1) < 0) goto Err;
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+
+typedef int (*P)(uchar*, uchar*, uchar**, SunCall*);
+typedef void (*F)(Fmt*, SunCall*);
+typedef uint (*S)(SunCall*);
+
+static SunProc proc3[] = {
+	(P)nfsMount3TNullPack, (P)nfsMount3TNullUnpack, (S)nfsMount3TNullSize, (F)nfsMount3TNullPrint, sizeof(NfsMount3TNull),
+	(P)nfsMount3RNullPack, (P)nfsMount3RNullUnpack, (S)nfsMount3RNullSize, (F)nfsMount3RNullPrint, sizeof(NfsMount3RNull),
+	(P)nfsMount3TMntPack, (P)nfsMount3TMntUnpack, (S)nfsMount3TMntSize, (F)nfsMount3TMntPrint, sizeof(NfsMount3TMnt),
+	(P)nfsMount3RMntPack, (P)nfsMount3RMntUnpack, (S)nfsMount3RMntSize, (F)nfsMount3RMntPrint, sizeof(NfsMount3RMnt),
+	(P)nfsMount3TDumpPack, (P)nfsMount3TDumpUnpack, (S)nfsMount3TDumpSize, (F)nfsMount3TDumpPrint, sizeof(NfsMount3TDump),
+	(P)nfsMount3RDumpPack, (P)nfsMount3RDumpUnpack, (S)nfsMount3RDumpSize, (F)nfsMount3RDumpPrint, sizeof(NfsMount3RDump),
+	(P)nfsMount3TUmntPack, (P)nfsMount3TUmntUnpack, (S)nfsMount3TUmntSize, (F)nfsMount3TUmntPrint, sizeof(NfsMount3TUmnt),
+	(P)nfsMount3RUmntPack, (P)nfsMount3RUmntUnpack, (S)nfsMount3RUmntSize, (F)nfsMount3RUmntPrint, sizeof(NfsMount3RUmnt),
+	(P)nfsMount3TUmntallPack, (P)nfsMount3TUmntallUnpack, (S)nfsMount3TUmntallSize, (F)nfsMount3TUmntallPrint, sizeof(NfsMount3TUmntall),
+	(P)nfsMount3RUmntallPack, (P)nfsMount3RUmntallUnpack, (S)nfsMount3RUmntallSize, (F)nfsMount3RUmntallPrint, sizeof(NfsMount3RUmntall),
+	(P)nfsMount3TExportPack, (P)nfsMount3TExportUnpack, (S)nfsMount3TExportSize, (F)nfsMount3TExportPrint, sizeof(NfsMount3TExport),
+	(P)nfsMount3RExportPack, (P)nfsMount3RExportUnpack, (S)nfsMount3RExportSize, (F)nfsMount3RExportPrint, sizeof(NfsMount3RExport),
+};
+
+static SunProc proc1[] = {
+	(P)nfsMount3TNullPack, (P)nfsMount3TNullUnpack, (S)nfsMount3TNullSize, (F)nfsMount3TNullPrint, sizeof(NfsMount3TNull),
+	(P)nfsMount3RNullPack, (P)nfsMount3RNullUnpack, (S)nfsMount3RNullSize, (F)nfsMount3RNullPrint, sizeof(NfsMount3RNull),
+	(P)nfsMount3TMntPack, (P)nfsMount3TMntUnpack, (S)nfsMount3TMntSize, (F)nfsMount3TMntPrint, sizeof(NfsMount3TMnt),
+	(P)nfsMount1RMntPack, (P)nfsMount1RMntUnpack, (S)nfsMount1RMntSize, (F)nfsMount3RMntPrint, sizeof(NfsMount3RMnt),
+	(P)nfsMount3TDumpPack, (P)nfsMount3TDumpUnpack, (S)nfsMount3TDumpSize, (F)nfsMount3TDumpPrint, sizeof(NfsMount3TDump),
+	(P)nfsMount3RDumpPack, (P)nfsMount3RDumpUnpack, (S)nfsMount3RDumpSize, (F)nfsMount3RDumpPrint, sizeof(NfsMount3RDump),
+	(P)nfsMount3TUmntPack, (P)nfsMount3TUmntUnpack, (S)nfsMount3TUmntSize, (F)nfsMount3TUmntPrint, sizeof(NfsMount3TUmnt),
+	(P)nfsMount3RUmntPack, (P)nfsMount3RUmntUnpack, (S)nfsMount3RUmntSize, (F)nfsMount3RUmntPrint, sizeof(NfsMount3RUmnt),
+	(P)nfsMount3TUmntallPack, (P)nfsMount3TUmntallUnpack, (S)nfsMount3TUmntallSize, (F)nfsMount3TUmntallPrint, sizeof(NfsMount3TUmntall),
+	(P)nfsMount3RUmntallPack, (P)nfsMount3RUmntallUnpack, (S)nfsMount3RUmntallSize, (F)nfsMount3RUmntallPrint, sizeof(NfsMount3RUmntall),
+	(P)nfsMount3TExportPack, (P)nfsMount3TExportUnpack, (S)nfsMount3TExportSize, (F)nfsMount3TExportPrint, sizeof(NfsMount3TExport),
+	(P)nfsMount3RExportPack, (P)nfsMount3RExportUnpack, (S)nfsMount3RExportSize, (F)nfsMount3RExportPrint, sizeof(NfsMount3RExport),
+};
+
+SunProg nfsMount3Prog = 
+{
+	NfsMount3Program,
+	NfsMount3Version,
+	proc3,
+	nelem(proc3),
+};
+
+SunProg nfsMount1Prog =
+{
+	NfsMount1Program,
+	NfsMount1Version,
+	proc1,
+	nelem(proc1),
+};

+ 57 - 0
sys/src/libsunrpc/net.c

@@ -0,0 +1,57 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+typedef struct Arg Arg;
+struct Arg
+{
+	int fd;
+	char adir[40];
+	SunSrv *srv;
+};
+
+static void
+sunNetListen(void *v)
+{
+	int fd, lcfd;
+	char ldir[40];
+	Arg *a = v;
+
+	for(;;){
+		lcfd = listen(a->adir, ldir);
+		if(lcfd < 0)
+			break;
+		fd = accept(lcfd, ldir);
+		close(lcfd);
+		if(fd < 0)
+			continue;
+		if(!sunSrvFd(a->srv, fd))
+			close(fd);
+	}
+	free(a);
+	close(a->fd);
+}
+
+int
+sunSrvNet(SunSrv *srv, char *addr)
+{
+	Arg *a;
+
+	a = emalloc(sizeof(Arg));
+	if((a->fd = announce(addr, a->adir)) < 0)
+		return 0;
+	a->srv = srv;
+
+	proccreate(sunNetListen, a, SunStackSize);
+	return 1;
+}
+
+int
+sunSrvAnnounce(SunSrv *srv, char *addr)
+{
+	if(strstr(addr, "udp!"))
+		return sunSrvUdp(srv, addr);
+	else
+		return sunSrvNet(srv, addr);
+}

+ 4045 - 0
sys/src/libsunrpc/nfs3.c

@@ -0,0 +1,4045 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+#include <nfs3.h>
+
+char*
+nfs3StatusStr(Nfs3Status x)
+{
+	switch(x){
+	case Nfs3Ok:
+		return "Nfs3Ok";
+	case Nfs3ErrNotOwner:
+		return "Nfs3ErrNotOwner";
+	case Nfs3ErrNoEnt:
+		return "Nfs3ErrNoEnt";
+	case Nfs3ErrNoMem:
+		return "Nfs3ErrNoMem";
+	case Nfs3ErrIo:
+		return "Nfs3ErrIo";
+	case Nfs3ErrNxio:
+		return "Nfs3ErrNxio";
+	case Nfs3ErrAcces:
+		return "Nfs3ErrAcces";
+	case Nfs3ErrExist:
+		return "Nfs3ErrExist";
+	case Nfs3ErrXDev:
+		return "Nfs3ErrXDev";
+	case Nfs3ErrNoDev:
+		return "Nfs3ErrNoDev";
+	case Nfs3ErrNotDir:
+		return "Nfs3ErrNotDir";
+	case Nfs3ErrIsDir:
+		return "Nfs3ErrIsDir";
+	case Nfs3ErrInval:
+		return "Nfs3ErrInval";
+	case Nfs3ErrFbig:
+		return "Nfs3ErrFbig";
+	case Nfs3ErrNoSpc:
+		return "Nfs3ErrNoSpc";
+	case Nfs3ErrRoFs:
+		return "Nfs3ErrRoFs";
+	case Nfs3ErrMLink:
+		return "Nfs3ErrMLink";
+	case Nfs3ErrNameTooLong:
+		return "Nfs3ErrNameTooLong";
+	case Nfs3ErrNotEmpty:
+		return "Nfs3ErrNotEmpty";
+	case Nfs3ErrDQuot:
+		return "Nfs3ErrDQuot";
+	case Nfs3ErrStale:
+		return "Nfs3ErrStale";
+	case Nfs3ErrRemote:
+		return "Nfs3ErrRemote";
+	case Nfs3ErrBadHandle:
+		return "Nfs3ErrBadHandle";
+	case Nfs3ErrNotSync:
+		return "Nfs3ErrNotSync";
+	case Nfs3ErrBadCookie:
+		return "Nfs3ErrBadCookie";
+	case Nfs3ErrNotSupp:
+		return "Nfs3ErrNotSupp";
+	case Nfs3ErrTooSmall:
+		return "Nfs3ErrTooSmall";
+	case Nfs3ErrServerFault:
+		return "Nfs3ErrServerFault";
+	case Nfs3ErrBadType:
+		return "Nfs3ErrBadType";
+	case Nfs3ErrJukebox:
+		return "Nfs3ErrJukebox";
+	case Nfs3ErrFprintNotFound:
+		return "Nfs3ErrFprintNotFound";
+	case Nfs3ErrAborted:
+		return "Nfs3ErrAborted";
+	default:
+		return "unknown";
+	}
+}
+
+static struct {
+	SunStatus status;
+	char *msg;
+} etab[] = {
+	Nfs3ErrNotOwner,	"not owner",
+	Nfs3ErrNoEnt,		"entry not found",
+	Nfs3ErrIo,			"i/o error",
+	Nfs3ErrNxio,		"no such device",
+	Nfs3ErrNoMem,	"out of memory",
+	Nfs3ErrAcces,		"access denied",
+	Nfs3ErrExist,		"file or directory exists",
+	Nfs3ErrXDev,		"cross-device operation",
+	Nfs3ErrNoDev,		"no such device",
+	Nfs3ErrNotDir,		"not a directory",
+	Nfs3ErrIsDir,		"is a directory",
+	Nfs3ErrInval,		"invalid arguments",
+	Nfs3ErrFbig,		"file too big",
+	Nfs3ErrNoSpc,		"no space left on device",
+	Nfs3ErrRoFs,		"read-only file system",
+	Nfs3ErrMLink,		"too many links",
+	Nfs3ErrNameTooLong,	"name too long",
+	Nfs3ErrNotEmpty,	"directory not empty",
+	Nfs3ErrDQuot,		"dquot",
+	Nfs3ErrStale,		"stale handle",
+	Nfs3ErrRemote,	"remote error",
+	Nfs3ErrBadHandle,	"bad handle",
+	Nfs3ErrNotSync,	"out of sync with server",
+	Nfs3ErrBadCookie,	"bad cookie",
+	Nfs3ErrNotSupp,	"not supported",
+	Nfs3ErrTooSmall,	"too small",
+	Nfs3ErrServerFault,	"server fault",
+	Nfs3ErrBadType,	"bad type",
+	Nfs3ErrJukebox,	"jukebox -- try again later",
+	Nfs3ErrFprintNotFound,	"fprint not found",
+	Nfs3ErrAborted,	"aborted",
+};
+
+void
+nfs3Errstr(SunStatus status)
+{
+	int i;
+
+	for(i=0; i<nelem(etab); i++){
+		if(etab[i].status == status){
+			werrstr(etab[i].msg);
+			return;
+		}
+	}
+	werrstr("unknown nfs3 error %d", (int)status);
+}
+
+char*
+nfs3FileTypeStr(Nfs3FileType x)
+{
+	switch(x){
+	case Nfs3FileReg:
+		return "Nfs3FileReg";
+	case Nfs3FileDir:
+		return "Nfs3FileDir";
+	case Nfs3FileBlock:
+		return "Nfs3FileBlock";
+	case Nfs3FileChar:
+		return "Nfs3FileChar";
+	case Nfs3FileSymlink:
+		return "Nfs3FileSymlink";
+	case Nfs3FileSocket:
+		return "Nfs3FileSocket";
+	case Nfs3FileFifo:
+		return "Nfs3FileFifo";
+	default:
+		return "unknown";
+	}
+}
+
+void
+nfs3HandlePrint(Fmt *fmt, Nfs3Handle *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3Handle");
+	fmtprint(fmt, "\t%s=", "handle");
+	if(x->len > 64)
+		fmtprint(fmt, "%.*H... (%d)", 64, x->h, x->len);
+	else
+		fmtprint(fmt, "%.*H", x->len, x->h);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3HandleSize(Nfs3Handle *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + sunVarOpaqueSize(x->len);
+	return a;
+}
+int
+nfs3HandlePack(uchar *a, uchar *ea, uchar **pa, Nfs3Handle *x)
+{
+	if(x->len > Nfs3MaxHandleSize || sunUint32Pack(a, ea, &a, &x->len) < 0
+	|| sunFixedOpaquePack(a, ea, &a, x->h, x->len) < 0)
+		goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3HandleUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3Handle *x)
+{
+	uchar *ha;
+	u32int n;
+
+	if(sunUint32Unpack(a, ea, &a, &n) < 0 || n > Nfs3MaxHandleSize)
+		goto Err;
+	ha = a;
+	a += (n+3)&~3;
+	if(a > ea)
+		goto Err;
+	memmove(x->h, ha, n);
+	x->len = n;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TimePrint(Fmt *fmt, Nfs3Time *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3Time");
+	fmtprint(fmt, "\t%s=", "sec");
+	fmtprint(fmt, "%ud", x->sec);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "nsec");
+	fmtprint(fmt, "%ud", x->nsec);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TimeSize(Nfs3Time *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4;
+	return a;
+}
+int
+nfs3TimePack(uchar *a, uchar *ea, uchar **pa, Nfs3Time *x)
+{
+	if(sunUint32Pack(a, ea, &a, &x->sec) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->nsec) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TimeUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3Time *x)
+{
+	if(sunUint32Unpack(a, ea, &a, &x->sec) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->nsec) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3AttrPrint(Fmt *fmt, Nfs3Attr *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3Attr");
+	fmtprint(fmt, "\t%s=", "type");
+	fmtprint(fmt, "%s", nfs3FileTypeStr(x->type));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "mode");
+	fmtprint(fmt, "%ud", x->mode);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "nlink");
+	fmtprint(fmt, "%ud", x->nlink);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "uid");
+	fmtprint(fmt, "%ud", x->uid);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "gid");
+	fmtprint(fmt, "%ud", x->gid);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "size");
+	fmtprint(fmt, "%llud", x->size);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "used");
+	fmtprint(fmt, "%llud", x->used);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "major");
+	fmtprint(fmt, "%ud", x->major);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "minor");
+	fmtprint(fmt, "%ud", x->minor);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "fsid");
+	fmtprint(fmt, "%llud", x->fsid);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "fileid");
+	fmtprint(fmt, "%llud", x->fileid);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "atime");
+	nfs3TimePrint(fmt, &x->atime);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "mtime");
+	nfs3TimePrint(fmt, &x->mtime);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "ctime");
+	nfs3TimePrint(fmt, &x->ctime);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3AttrSize(Nfs3Attr *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4 + 4 + 4 + 4 + 8 + 8 + 4 + 4 + 8 + 8 + nfs3TimeSize(&x->atime) + nfs3TimeSize(&x->mtime) + nfs3TimeSize(&x->ctime);
+	return a;
+}
+int
+nfs3AttrPack(uchar *a, uchar *ea, uchar **pa, Nfs3Attr *x)
+{
+	int i;
+
+	if(i=x->type, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->mode) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->nlink) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->uid) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->gid) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->size) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->used) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->major) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->minor) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->fsid) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->fileid) < 0) goto Err;
+	if(nfs3TimePack(a, ea, &a, &x->atime) < 0) goto Err;
+	if(nfs3TimePack(a, ea, &a, &x->mtime) < 0) goto Err;
+	if(nfs3TimePack(a, ea, &a, &x->ctime) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3AttrUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3Attr *x)
+{
+	int i;
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->type = i;
+	if(sunUint32Unpack(a, ea, &a, &x->mode) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->nlink) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->uid) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->gid) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->size) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->used) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->major) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->minor) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->fsid) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->fileid) < 0) goto Err;
+	if(nfs3TimeUnpack(a, ea, &a, &x->atime) < 0) goto Err;
+	if(nfs3TimeUnpack(a, ea, &a, &x->mtime) < 0) goto Err;
+	if(nfs3TimeUnpack(a, ea, &a, &x->ctime) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3WccAttrPrint(Fmt *fmt, Nfs3WccAttr *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3WccAttr");
+	fmtprint(fmt, "\t%s=", "size");
+	fmtprint(fmt, "%llud", x->size);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "mtime");
+	nfs3TimePrint(fmt, &x->mtime);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "ctime");
+	nfs3TimePrint(fmt, &x->ctime);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3WccAttrSize(Nfs3WccAttr *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 8 + nfs3TimeSize(&x->mtime) + nfs3TimeSize(&x->ctime);
+	return a;
+}
+int
+nfs3WccAttrPack(uchar *a, uchar *ea, uchar **pa, Nfs3WccAttr *x)
+{
+	if(sunUint64Pack(a, ea, &a, &x->size) < 0) goto Err;
+	if(nfs3TimePack(a, ea, &a, &x->mtime) < 0) goto Err;
+	if(nfs3TimePack(a, ea, &a, &x->ctime) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3WccAttrUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3WccAttr *x)
+{
+	if(sunUint64Unpack(a, ea, &a, &x->size) < 0) goto Err;
+	if(nfs3TimeUnpack(a, ea, &a, &x->mtime) < 0) goto Err;
+	if(nfs3TimeUnpack(a, ea, &a, &x->ctime) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3WccPrint(Fmt *fmt, Nfs3Wcc *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3Wcc");
+	fmtprint(fmt, "\t%s=", "haveWccAttr");
+	fmtprint(fmt, "%d", x->haveWccAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveWccAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "wccAttr");
+		nfs3WccAttrPrint(fmt, &x->wccAttr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3WccSize(Nfs3Wcc *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	switch(x->haveWccAttr){
+	case 1:
+		a = a + nfs3WccAttrSize(&x->wccAttr);
+		break;
+	}
+	a = a + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	return a;
+}
+int
+nfs3WccPack(uchar *a, uchar *ea, uchar **pa, Nfs3Wcc *x)
+{
+	if(sunUint1Pack(a, ea, &a, &x->haveWccAttr) < 0) goto Err;
+	switch(x->haveWccAttr){
+	case 1:
+		if(nfs3WccAttrPack(a, ea, &a, &x->wccAttr) < 0) goto Err;
+		break;
+	}
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3WccUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3Wcc *x)
+{
+	if(sunUint1Unpack(a, ea, &a, &x->haveWccAttr) < 0) goto Err;
+	switch(x->haveWccAttr){
+	case 1:
+		if(nfs3WccAttrUnpack(a, ea, &a, &x->wccAttr) < 0) goto Err;
+		break;
+	}
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+char*
+nfs3SetTimeStr(Nfs3SetTime x)
+{
+	switch(x){
+	case Nfs3SetTimeDont:
+		return "Nfs3SetTimeDont";
+	case Nfs3SetTimeServer:
+		return "Nfs3SetTimeServer";
+	case Nfs3SetTimeClient:
+		return "Nfs3SetTimeClient";
+	default:
+		return "unknown";
+	}
+}
+
+void
+nfs3SetAttrPrint(Fmt *fmt, Nfs3SetAttr *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3SetAttr");
+	fmtprint(fmt, "\t%s=", "setMode");
+	fmtprint(fmt, "%d", x->setMode);
+	fmtprint(fmt, "\n");
+	switch(x->setMode){
+	case 1:
+		fmtprint(fmt, "\t%s=", "mode");
+		fmtprint(fmt, "%ud", x->mode);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "setUid");
+	fmtprint(fmt, "%d", x->setUid);
+	fmtprint(fmt, "\n");
+	switch(x->setUid){
+	case 1:
+		fmtprint(fmt, "\t%s=", "uid");
+		fmtprint(fmt, "%ud", x->uid);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "setGid");
+	fmtprint(fmt, "%d", x->setGid);
+	fmtprint(fmt, "\n");
+	switch(x->setGid){
+	case 1:
+		fmtprint(fmt, "\t%s=", "gid");
+		fmtprint(fmt, "%ud", x->gid);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "setSize");
+	fmtprint(fmt, "%d", x->setSize);
+	fmtprint(fmt, "\n");
+	switch(x->setSize){
+	case 1:
+		fmtprint(fmt, "\t%s=", "size");
+		fmtprint(fmt, "%llud", x->size);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "setAtime");
+	fmtprint(fmt, "%s", nfs3SetTimeStr(x->setAtime));
+	fmtprint(fmt, "\n");
+	switch(x->setAtime){
+	case Nfs3SetTimeClient:
+		fmtprint(fmt, "\t%s=", "atime");
+		nfs3TimePrint(fmt, &x->atime);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "setMtime");
+	fmtprint(fmt, "%s", nfs3SetTimeStr(x->setMtime));
+	fmtprint(fmt, "\n");
+	switch(x->setMtime){
+	case Nfs3SetTimeClient:
+		fmtprint(fmt, "\t%s=", "mtime");
+		nfs3TimePrint(fmt, &x->mtime);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3SetAttrSize(Nfs3SetAttr *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	switch(x->setMode){
+	case 1:
+		a = a + 4;
+		break;
+	}
+	a = a + 4;
+	switch(x->setUid){
+	case 1:
+		a = a + 4;
+		break;
+	}
+	a = a + 4;
+	switch(x->setGid){
+	case 1:
+		a = a + 4;
+		break;
+	}
+	a = a + 4;
+	switch(x->setSize){
+	case 1:
+		a = a + 8;
+		break;
+	}
+	a = a + 4;
+	switch(x->setAtime){
+	case Nfs3SetTimeClient:
+		a = a + nfs3TimeSize(&x->atime);
+		break;
+	}
+	a = a + 4;
+	switch(x->setMtime){
+	case Nfs3SetTimeClient:
+		a = a + nfs3TimeSize(&x->mtime);
+		break;
+	}
+	return a;
+}
+int
+nfs3SetAttrPack(uchar *a, uchar *ea, uchar **pa, Nfs3SetAttr *x)
+{
+	int i;
+
+	if(sunUint1Pack(a, ea, &a, &x->setMode) < 0) goto Err;
+	switch(x->setMode){
+	case 1:
+		if(sunUint32Pack(a, ea, &a, &x->mode) < 0) goto Err;
+		break;
+	}
+	if(sunUint1Pack(a, ea, &a, &x->setUid) < 0) goto Err;
+	switch(x->setUid){
+	case 1:
+		if(sunUint32Pack(a, ea, &a, &x->uid) < 0) goto Err;
+		break;
+	}
+	if(sunUint1Pack(a, ea, &a, &x->setGid) < 0) goto Err;
+	switch(x->setGid){
+	case 1:
+		if(sunUint32Pack(a, ea, &a, &x->gid) < 0) goto Err;
+		break;
+	}
+	if(sunUint1Pack(a, ea, &a, &x->setSize) < 0) goto Err;
+	switch(x->setSize){
+	case 1:
+		if(sunUint64Pack(a, ea, &a, &x->size) < 0) goto Err;
+		break;
+	}
+	if(i=x->setAtime, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	switch(x->setAtime){
+	case Nfs3SetTimeClient:
+		if(nfs3TimePack(a, ea, &a, &x->atime) < 0) goto Err;
+		break;
+	}
+	if(i=x->setMtime, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	switch(x->setMtime){
+	case Nfs3SetTimeClient:
+		if(nfs3TimePack(a, ea, &a, &x->mtime) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3SetAttrUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3SetAttr *x)
+{
+	int i;
+
+	if(sunUint1Unpack(a, ea, &a, &x->setMode) < 0) goto Err;
+	switch(x->setMode){
+	case 1:
+		if(sunUint32Unpack(a, ea, &a, &x->mode) < 0) goto Err;
+		break;
+	}
+	if(sunUint1Unpack(a, ea, &a, &x->setUid) < 0) goto Err;
+	switch(x->setUid){
+	case 1:
+		if(sunUint32Unpack(a, ea, &a, &x->uid) < 0) goto Err;
+		break;
+	}
+	if(sunUint1Unpack(a, ea, &a, &x->setGid) < 0) goto Err;
+	switch(x->setGid){
+	case 1:
+		if(sunUint32Unpack(a, ea, &a, &x->gid) < 0) goto Err;
+		break;
+	}
+	if(sunUint1Unpack(a, ea, &a, &x->setSize) < 0) goto Err;
+	switch(x->setSize){
+	case 1:
+		if(sunUint64Unpack(a, ea, &a, &x->size) < 0) goto Err;
+		break;
+	}
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->setAtime = i;
+	switch(x->setAtime){
+	case Nfs3SetTimeClient:
+		if(nfs3TimeUnpack(a, ea, &a, &x->atime) < 0) goto Err;
+		break;
+	}
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->setMtime = i;
+	switch(x->setMtime){
+	case Nfs3SetTimeClient:
+		if(nfs3TimeUnpack(a, ea, &a, &x->mtime) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TNullPrint(Fmt *fmt, Nfs3TNull *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "Nfs3TNull");
+}
+uint
+nfs3TNullSize(Nfs3TNull *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+int
+nfs3TNullPack(uchar *a, uchar *ea, uchar **pa, Nfs3TNull *x)
+{
+	USED(x);
+	USED(ea);
+	*pa = a;
+	return 0;
+}
+int
+nfs3TNullUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TNull *x)
+{
+	USED(x);
+	USED(ea);
+	*pa = a;
+	return 0;
+}
+void
+nfs3RNullPrint(Fmt *fmt, Nfs3RNull *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s\n", "Nfs3RNull");
+}
+uint
+nfs3RNullSize(Nfs3RNull *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+int
+nfs3RNullPack(uchar *a, uchar *ea, uchar **pa, Nfs3RNull *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+int
+nfs3RNullUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RNull *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+void
+nfs3TGetattrPrint(Fmt *fmt, Nfs3TGetattr *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TGetattr");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TGetattrSize(Nfs3TGetattr *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle);
+	return a;
+}
+int
+nfs3TGetattrPack(uchar *a, uchar *ea, uchar **pa, Nfs3TGetattr *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TGetattrUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TGetattr *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RGetattrPrint(Fmt *fmt, Nfs3RGetattr *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RGetattr");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RGetattrSize(Nfs3RGetattr *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	return a;
+}
+int
+nfs3RGetattrPack(uchar *a, uchar *ea, uchar **pa, Nfs3RGetattr *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	switch(x->status){
+	case Nfs3Ok:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RGetattrUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RGetattr *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	switch(x->status){
+	case Nfs3Ok:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TSetattrPrint(Fmt *fmt, Nfs3TSetattr *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TSetattr");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "attr");
+	nfs3SetAttrPrint(fmt, &x->attr);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "checkCtime");
+	fmtprint(fmt, "%d", x->checkCtime);
+	fmtprint(fmt, "\n");
+	switch(x->checkCtime){
+	case 1:
+		fmtprint(fmt, "\t%s=", "ctime");
+		nfs3TimePrint(fmt, &x->ctime);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3TSetattrSize(Nfs3TSetattr *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + nfs3SetAttrSize(&x->attr) + 4;
+	switch(x->checkCtime){
+	case 1:
+		a = a + nfs3TimeSize(&x->ctime);
+		break;
+	}
+	return a;
+}
+int
+nfs3TSetattrPack(uchar *a, uchar *ea, uchar **pa, Nfs3TSetattr *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(nfs3SetAttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->checkCtime) < 0) goto Err;
+	switch(x->checkCtime){
+	case 1:
+		if(nfs3TimePack(a, ea, &a, &x->ctime) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TSetattrUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TSetattr *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(nfs3SetAttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+	if(sunUint1Unpack(a, ea, &a, &x->checkCtime) < 0) goto Err;
+	switch(x->checkCtime){
+	case 1:
+		if(nfs3TimeUnpack(a, ea, &a, &x->ctime) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RSetattrPrint(Fmt *fmt, Nfs3RSetattr *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RSetattr");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "wcc");
+	nfs3WccPrint(fmt, &x->wcc);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3RSetattrSize(Nfs3RSetattr *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + nfs3WccSize(&x->wcc);
+	return a;
+}
+int
+nfs3RSetattrPack(uchar *a, uchar *ea, uchar **pa, Nfs3RSetattr *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(nfs3WccPack(a, ea, &a, &x->wcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RSetattrUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RSetattr *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(nfs3WccUnpack(a, ea, &a, &x->wcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TLookupPrint(Fmt *fmt, Nfs3TLookup *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TLookup");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->name);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TLookupSize(Nfs3TLookup *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + sunStringSize(x->name);
+	return a;
+}
+int
+nfs3TLookupPack(uchar *a, uchar *ea, uchar **pa, Nfs3TLookup *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TLookupUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TLookup *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RLookupPrint(Fmt *fmt, Nfs3RLookup *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RLookup");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "handle");
+		nfs3HandlePrint(fmt, &x->handle);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "haveAttr");
+		fmtprint(fmt, "%d", x->haveAttr);
+		fmtprint(fmt, "\n");
+		switch(x->haveAttr){
+		case 1:
+			fmtprint(fmt, "\t%s=", "attr");
+			nfs3AttrPrint(fmt, &x->attr);
+			fmtprint(fmt, "\n");
+			break;
+		}
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "haveDirAttr");
+	fmtprint(fmt, "%d", x->haveDirAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveDirAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "dirAttr");
+		nfs3AttrPrint(fmt, &x->dirAttr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RLookupSize(Nfs3RLookup *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + nfs3HandleSize(&x->handle) + 4;
+		switch(x->haveAttr){
+		case 1:
+			a = a + nfs3AttrSize(&x->attr);
+			break;
+		}
+			break;
+	}
+	a = a + 4;
+	switch(x->haveDirAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->dirAttr);
+		break;
+	}
+	return a;
+}
+int
+nfs3RLookupPack(uchar *a, uchar *ea, uchar **pa, Nfs3RLookup *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	switch(x->status){
+	case Nfs3Ok:
+		if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+		if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+		switch(x->haveAttr){
+		case 1:
+			if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+			break;
+		}
+		break;
+	}
+	if(sunUint1Pack(a, ea, &a, &x->haveDirAttr) < 0) goto Err;
+	switch(x->haveDirAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->dirAttr) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RLookupUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RLookup *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	switch(x->status){
+	case Nfs3Ok:
+		if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+		if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+		switch(x->haveAttr){
+		case 1:
+			if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+			break;
+		}
+		break;
+	}
+	if(sunUint1Unpack(a, ea, &a, &x->haveDirAttr) < 0) goto Err;
+	switch(x->haveDirAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->dirAttr) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TAccessPrint(Fmt *fmt, Nfs3TAccess *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TAccess");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "access");
+	fmtprint(fmt, "%ud", x->access);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TAccessSize(Nfs3TAccess *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + 4;
+	return a;
+}
+int
+nfs3TAccessPack(uchar *a, uchar *ea, uchar **pa, Nfs3TAccess *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->access) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TAccessUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TAccess *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->access) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RAccessPrint(Fmt *fmt, Nfs3RAccess *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RAccess");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "access");
+		fmtprint(fmt, "%ud", x->access);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RAccessSize(Nfs3RAccess *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + 4;
+		break;
+	}
+	return a;
+}
+int
+nfs3RAccessPack(uchar *a, uchar *ea, uchar **pa, Nfs3RAccess *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint32Pack(a, ea, &a, &x->access) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RAccessUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RAccess *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint32Unpack(a, ea, &a, &x->access) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TReadlinkPrint(Fmt *fmt, Nfs3TReadlink *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TReadlink");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TReadlinkSize(Nfs3TReadlink *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle);
+	return a;
+}
+int
+nfs3TReadlinkPack(uchar *a, uchar *ea, uchar **pa, Nfs3TReadlink *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TReadlinkUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TReadlink *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RReadlinkPrint(Fmt *fmt, Nfs3RReadlink *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RReadlink");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "data");
+		fmtprint(fmt, "\"%s\"", x->data);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RReadlinkSize(Nfs3RReadlink *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + sunStringSize(x->data);
+		break;
+	}
+	return a;
+}
+int
+nfs3RReadlinkPack(uchar *a, uchar *ea, uchar **pa, Nfs3RReadlink *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunStringPack(a, ea, &a, &x->data, -1) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RReadlinkUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RReadlink *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunStringUnpack(a, ea, &a, &x->data, -1) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TReadPrint(Fmt *fmt, Nfs3TRead *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TRead");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "offset");
+	fmtprint(fmt, "%llud", x->offset);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "count");
+	fmtprint(fmt, "%ud", x->count);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TReadSize(Nfs3TRead *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + 8 + 4;
+	return a;
+}
+int
+nfs3TReadPack(uchar *a, uchar *ea, uchar **pa, Nfs3TRead *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->offset) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->count) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TReadUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TRead *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->offset) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->count) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RReadPrint(Fmt *fmt, Nfs3RRead *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RRead");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "count");
+		fmtprint(fmt, "%ud", x->count);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "eof");
+		fmtprint(fmt, "%d", x->eof);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "data");
+		if(x->ndata <= 32)
+			fmtprint(fmt, "%.*H", x->ndata, x->data);
+		else
+			fmtprint(fmt, "%.32H...", x->data);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RReadSize(Nfs3RRead *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + 4 + 4 + sunVarOpaqueSize(x->ndata);
+		break;
+	}
+	return a;
+}
+int
+nfs3RReadPack(uchar *a, uchar *ea, uchar **pa, Nfs3RRead *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint32Pack(a, ea, &a, &x->count) < 0) goto Err;
+		if(sunUint1Pack(a, ea, &a, &x->eof) < 0) goto Err;
+		if(sunVarOpaquePack(a, ea, &a, &x->data, &x->ndata, x->count) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RReadUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RRead *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint32Unpack(a, ea, &a, &x->count) < 0) goto Err;
+		if(sunUint1Unpack(a, ea, &a, &x->eof) < 0) goto Err;
+		if(sunVarOpaqueUnpack(a, ea, &a, &x->data, &x->ndata, x->count) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+char*
+nfs3SyncStr(Nfs3Sync x)
+{
+	switch(x){
+	case Nfs3SyncNone:
+		return "Nfs3SyncNone";
+	case Nfs3SyncData:
+		return "Nfs3SyncData";
+	case Nfs3SyncFile:
+		return "Nfs3SyncFile";
+	default:
+		return "unknown";
+	}
+}
+
+void
+nfs3TWritePrint(Fmt *fmt, Nfs3TWrite *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TWrite");
+	fmtprint(fmt, "\t%s=", "file");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "offset");
+	fmtprint(fmt, "%llud", x->offset);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "count");
+	fmtprint(fmt, "%ud", x->count);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "stable");
+	fmtprint(fmt, "%s", nfs3SyncStr(x->stable));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "data");
+	if(x->ndata > 32)
+		fmtprint(fmt, "%.32H... (%d)", x->data, x->ndata);
+	else
+		fmtprint(fmt, "%.*H", x->ndata, x->data);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TWriteSize(Nfs3TWrite *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + 8 + 4 + 4 + sunVarOpaqueSize(x->ndata);
+	return a;
+}
+int
+nfs3TWritePack(uchar *a, uchar *ea, uchar **pa, Nfs3TWrite *x)
+{
+	int i;
+
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->offset) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->count) < 0) goto Err;
+	if(i=x->stable, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunVarOpaquePack(a, ea, &a, &x->data, &x->ndata, x->count) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TWriteUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TWrite *x)
+{
+	int i;
+
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->offset) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->count) < 0) goto Err;
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->stable = i;
+	if(sunVarOpaqueUnpack(a, ea, &a, &x->data, &x->ndata, x->count) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RWritePrint(Fmt *fmt, Nfs3RWrite *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RWrite");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "wcc");
+	nfs3WccPrint(fmt, &x->wcc);
+	fmtprint(fmt, "\n");
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "count");
+		fmtprint(fmt, "%ud", x->count);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "committed");
+		fmtprint(fmt, "%s", nfs3SyncStr(x->committed));
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "verf");
+		fmtprint(fmt, "%.*H", Nfs3WriteVerfSize, x->verf);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RWriteSize(Nfs3RWrite *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + nfs3WccSize(&x->wcc);
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + 4 + 4 + Nfs3WriteVerfSize;
+		break;
+	}
+	return a;
+}
+int
+nfs3RWritePack(uchar *a, uchar *ea, uchar **pa, Nfs3RWrite *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(nfs3WccPack(a, ea, &a, &x->wcc) < 0) goto Err;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint32Pack(a, ea, &a, &x->count) < 0) goto Err;
+		if(i=x->committed, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+		if(sunFixedOpaquePack(a, ea, &a, x->verf, Nfs3WriteVerfSize) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RWriteUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RWrite *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(nfs3WccUnpack(a, ea, &a, &x->wcc) < 0) goto Err;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint32Unpack(a, ea, &a, &x->count) < 0) goto Err;
+		if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->committed = i;
+		if(sunFixedOpaqueUnpack(a, ea, &a, x->verf, Nfs3WriteVerfSize) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+char*
+nfs3CreateStr(Nfs3Create x)
+{
+	switch(x){
+	case Nfs3CreateUnchecked:
+		return "Nfs3CreateUnchecked";
+	case Nfs3CreateGuarded:
+		return "Nfs3CreateGuarded";
+	case Nfs3CreateExclusive:
+		return "Nfs3CreateExclusive";
+	default:
+		return "unknown";
+	}
+}
+
+void
+nfs3TCreatePrint(Fmt *fmt, Nfs3TCreate *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TCreate");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->name);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "mode");
+	fmtprint(fmt, "%s", nfs3CreateStr(x->mode));
+	fmtprint(fmt, "\n");
+	switch(x->mode){
+	case Nfs3CreateUnchecked:
+	case Nfs3CreateGuarded:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3SetAttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	case Nfs3CreateExclusive:
+		fmtprint(fmt, "\t%s=", "verf");
+		fmtprint(fmt, "%.*H", Nfs3CreateVerfSize, x->verf);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3TCreateSize(Nfs3TCreate *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + sunStringSize(x->name) + 4;
+	switch(x->mode){
+	case Nfs3CreateUnchecked:
+	case Nfs3CreateGuarded:
+		a = a + nfs3SetAttrSize(&x->attr);
+		break;
+	case Nfs3CreateExclusive:
+		a = a + Nfs3CreateVerfSize;
+		break;
+	}
+	return a;
+}
+int
+nfs3TCreatePack(uchar *a, uchar *ea, uchar **pa, Nfs3TCreate *x)
+{
+	int i;
+
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(i=x->mode, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	switch(x->mode){
+	case Nfs3CreateUnchecked:
+	case Nfs3CreateGuarded:
+		if(nfs3SetAttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	case Nfs3CreateExclusive:
+		if(sunFixedOpaquePack(a, ea, &a, x->verf, Nfs3CreateVerfSize) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TCreateUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TCreate *x)
+{
+	int i;
+
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->mode = i;
+	switch(x->mode){
+	case Nfs3CreateUnchecked:
+	case Nfs3CreateGuarded:
+		if(nfs3SetAttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	case Nfs3CreateExclusive:
+		if(sunFixedOpaqueUnpack(a, ea, &a, x->verf, Nfs3CreateVerfSize) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RCreatePrint(Fmt *fmt, Nfs3RCreate *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RCreate");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "haveHandle");
+		fmtprint(fmt, "%d", x->haveHandle);
+		fmtprint(fmt, "\n");
+		switch(x->haveHandle){
+		case 1:
+			fmtprint(fmt, "\t%s=", "handle");
+			nfs3HandlePrint(fmt, &x->handle);
+			fmtprint(fmt, "\n");
+			break;
+		}
+		fmtprint(fmt, "\t%s=", "haveAttr");
+		fmtprint(fmt, "%d", x->haveAttr);
+		fmtprint(fmt, "\n");
+		switch(x->haveAttr){
+		case 1:
+			fmtprint(fmt, "\t%s=", "attr");
+			nfs3AttrPrint(fmt, &x->attr);
+			fmtprint(fmt, "\n");
+			break;
+		}
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "dirWcc");
+	nfs3WccPrint(fmt, &x->dirWcc);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3RCreateSize(Nfs3RCreate *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + 4;
+		switch(x->haveHandle){
+		case 1:
+			a = a + nfs3HandleSize(&x->handle);
+			break;
+		}
+		a = a + 4;
+		switch(x->haveAttr){
+		case 1:
+			a = a + nfs3AttrSize(&x->attr);
+			break;
+		}
+			break;
+	}
+	a = a + nfs3WccSize(&x->dirWcc);
+	return a;
+}
+int
+nfs3RCreatePack(uchar *a, uchar *ea, uchar **pa, Nfs3RCreate *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint1Pack(a, ea, &a, &x->haveHandle) < 0) goto Err;
+		switch(x->haveHandle){
+		case 1:
+			if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+			break;
+		}
+		if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+		switch(x->haveAttr){
+		case 1:
+			if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+			break;
+		}
+		break;
+	}
+	if(nfs3WccPack(a, ea, &a, &x->dirWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RCreateUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RCreate *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint1Unpack(a, ea, &a, &x->haveHandle) < 0) goto Err;
+		switch(x->haveHandle){
+		case 1:
+			if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+			break;
+		}
+		if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+		switch(x->haveAttr){
+		case 1:
+			if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+			break;
+		}
+		break;
+	}
+	if(nfs3WccUnpack(a, ea, &a, &x->dirWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TMkdirPrint(Fmt *fmt, Nfs3TMkdir *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TMkdir");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->name);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "attr");
+	nfs3SetAttrPrint(fmt, &x->attr);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TMkdirSize(Nfs3TMkdir *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + sunStringSize(x->name) + nfs3SetAttrSize(&x->attr);
+	return a;
+}
+int
+nfs3TMkdirPack(uchar *a, uchar *ea, uchar **pa, Nfs3TMkdir *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(nfs3SetAttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TMkdirUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TMkdir *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(nfs3SetAttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RMkdirPrint(Fmt *fmt, Nfs3RMkdir *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RMkdir");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "haveHandle");
+		fmtprint(fmt, "%d", x->haveHandle);
+		fmtprint(fmt, "\n");
+		switch(x->haveHandle){
+		case 1:
+			fmtprint(fmt, "\t%s=", "handle");
+			nfs3HandlePrint(fmt, &x->handle);
+			fmtprint(fmt, "\n");
+			break;
+		}
+		fmtprint(fmt, "\t%s=", "haveAttr");
+		fmtprint(fmt, "%d", x->haveAttr);
+		fmtprint(fmt, "\n");
+		switch(x->haveAttr){
+		case 1:
+			fmtprint(fmt, "\t%s=", "attr");
+			nfs3AttrPrint(fmt, &x->attr);
+			fmtprint(fmt, "\n");
+			break;
+		}
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "dirWcc");
+	nfs3WccPrint(fmt, &x->dirWcc);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3RMkdirSize(Nfs3RMkdir *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + 4;
+		switch(x->haveHandle){
+		case 1:
+			a = a + nfs3HandleSize(&x->handle);
+			break;
+		}
+		a = a + 4;
+		switch(x->haveAttr){
+		case 1:
+			a = a + nfs3AttrSize(&x->attr);
+			break;
+		}
+			break;
+	}
+	a = a + nfs3WccSize(&x->dirWcc);
+	return a;
+}
+int
+nfs3RMkdirPack(uchar *a, uchar *ea, uchar **pa, Nfs3RMkdir *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint1Pack(a, ea, &a, &x->haveHandle) < 0) goto Err;
+		switch(x->haveHandle){
+		case 1:
+			if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+			break;
+		}
+		if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+		switch(x->haveAttr){
+		case 1:
+			if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+			break;
+		}
+		break;
+	}
+	if(nfs3WccPack(a, ea, &a, &x->dirWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RMkdirUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RMkdir *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint1Unpack(a, ea, &a, &x->haveHandle) < 0) goto Err;
+		switch(x->haveHandle){
+		case 1:
+			if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+			break;
+		}
+		if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+		switch(x->haveAttr){
+		case 1:
+			if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+			break;
+		}
+		break;
+	}
+	if(nfs3WccUnpack(a, ea, &a, &x->dirWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TSymlinkPrint(Fmt *fmt, Nfs3TSymlink *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TSymlink");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->name);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "attr");
+	nfs3SetAttrPrint(fmt, &x->attr);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "data");
+	fmtprint(fmt, "\"%s\"", x->data);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TSymlinkSize(Nfs3TSymlink *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + sunStringSize(x->name) + nfs3SetAttrSize(&x->attr) + sunStringSize(x->data);
+	return a;
+}
+int
+nfs3TSymlinkPack(uchar *a, uchar *ea, uchar **pa, Nfs3TSymlink *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(nfs3SetAttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->data, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TSymlinkUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TSymlink *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(nfs3SetAttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->data, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RSymlinkPrint(Fmt *fmt, Nfs3RSymlink *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RSymlink");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "haveHandle");
+		fmtprint(fmt, "%d", x->haveHandle);
+		fmtprint(fmt, "\n");
+		switch(x->haveHandle){
+		case 1:
+			fmtprint(fmt, "\t%s=", "handle");
+			nfs3HandlePrint(fmt, &x->handle);
+			fmtprint(fmt, "\n");
+			break;
+		}
+		fmtprint(fmt, "\t%s=", "haveAttr");
+		fmtprint(fmt, "%d", x->haveAttr);
+		fmtprint(fmt, "\n");
+		switch(x->haveAttr){
+		case 1:
+			fmtprint(fmt, "\t%s=", "attr");
+			nfs3AttrPrint(fmt, &x->attr);
+			fmtprint(fmt, "\n");
+			break;
+		}
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "dirWcc");
+	nfs3WccPrint(fmt, &x->dirWcc);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3RSymlinkSize(Nfs3RSymlink *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + 4;
+		switch(x->haveHandle){
+		case 1:
+			a = a + nfs3HandleSize(&x->handle);
+			break;
+		}
+		a = a + 4;
+		switch(x->haveAttr){
+		case 1:
+			a = a + nfs3AttrSize(&x->attr);
+			break;
+		}
+			break;
+	}
+	a = a + nfs3WccSize(&x->dirWcc);
+	return a;
+}
+int
+nfs3RSymlinkPack(uchar *a, uchar *ea, uchar **pa, Nfs3RSymlink *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint1Pack(a, ea, &a, &x->haveHandle) < 0) goto Err;
+		switch(x->haveHandle){
+		case 1:
+			if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+			break;
+		}
+		if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+		switch(x->haveAttr){
+		case 1:
+			if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+			break;
+		}
+		break;
+	}
+	if(nfs3WccPack(a, ea, &a, &x->dirWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RSymlinkUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RSymlink *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint1Unpack(a, ea, &a, &x->haveHandle) < 0) goto Err;
+		switch(x->haveHandle){
+		case 1:
+			if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+			break;
+		}
+		if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+		switch(x->haveAttr){
+		case 1:
+			if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+			break;
+		}
+		break;
+	}
+	if(nfs3WccUnpack(a, ea, &a, &x->dirWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TMknodPrint(Fmt *fmt, Nfs3TMknod *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TMknod");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->name);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "type");
+	fmtprint(fmt, "%s", nfs3FileTypeStr(x->type));
+	fmtprint(fmt, "\n");
+	switch(x->type){
+	case Nfs3FileChar:
+	case Nfs3FileBlock:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3SetAttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "major");
+		fmtprint(fmt, "%ud", x->major);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "minor");
+		fmtprint(fmt, "%ud", x->minor);
+		fmtprint(fmt, "\n");
+		break;
+	case Nfs3FileSocket:
+	case Nfs3FileFifo:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3SetAttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3TMknodSize(Nfs3TMknod *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + sunStringSize(x->name) + 4;
+	switch(x->type){
+	case Nfs3FileChar:
+	case Nfs3FileBlock:
+		a = a + nfs3SetAttrSize(&x->attr) + 4 + 4;
+		break;
+	case Nfs3FileSocket:
+	case Nfs3FileFifo:
+		a = a + nfs3SetAttrSize(&x->attr);
+		break;
+	}
+	return a;
+}
+int
+nfs3TMknodPack(uchar *a, uchar *ea, uchar **pa, Nfs3TMknod *x)
+{
+	int i;
+
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(i=x->type, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	switch(x->type){
+	case Nfs3FileChar:
+	case Nfs3FileBlock:
+		if(nfs3SetAttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->major) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->minor) < 0) goto Err;
+		break;
+	case Nfs3FileSocket:
+	case Nfs3FileFifo:
+		if(nfs3SetAttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TMknodUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TMknod *x)
+{
+	int i;
+
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->type = i;
+	switch(x->type){
+	case Nfs3FileChar:
+	case Nfs3FileBlock:
+		if(nfs3SetAttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->major) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->minor) < 0) goto Err;
+		break;
+	case Nfs3FileSocket:
+	case Nfs3FileFifo:
+		if(nfs3SetAttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RMknodPrint(Fmt *fmt, Nfs3RMknod *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RMknod");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "haveHandle");
+		fmtprint(fmt, "%d", x->haveHandle);
+		fmtprint(fmt, "\n");
+		switch(x->haveHandle){
+		case 1:
+			fmtprint(fmt, "\t%s=", "handle");
+			nfs3HandlePrint(fmt, &x->handle);
+			fmtprint(fmt, "\n");
+			break;
+		}
+		fmtprint(fmt, "\t%s=", "haveAttr");
+		fmtprint(fmt, "%d", x->haveAttr);
+		fmtprint(fmt, "\n");
+		switch(x->haveAttr){
+		case 1:
+			fmtprint(fmt, "\t%s=", "attr");
+			nfs3AttrPrint(fmt, &x->attr);
+			fmtprint(fmt, "\n");
+			break;
+		}
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "dirWcc");
+	nfs3WccPrint(fmt, &x->dirWcc);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3RMknodSize(Nfs3RMknod *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + 4;
+		switch(x->haveHandle){
+		case 1:
+			a = a + nfs3HandleSize(&x->handle);
+			break;
+		}
+		a = a + 4;
+		switch(x->haveAttr){
+		case 1:
+			a = a + nfs3AttrSize(&x->attr);
+			break;
+		}
+			break;
+	}
+	a = a + nfs3WccSize(&x->dirWcc);
+	return a;
+}
+int
+nfs3RMknodPack(uchar *a, uchar *ea, uchar **pa, Nfs3RMknod *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint1Pack(a, ea, &a, &x->haveHandle) < 0) goto Err;
+		switch(x->haveHandle){
+		case 1:
+			if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+			break;
+		}
+		if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+		switch(x->haveAttr){
+		case 1:
+			if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+			break;
+		}
+		break;
+	}
+	if(nfs3WccPack(a, ea, &a, &x->dirWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RMknodUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RMknod *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint1Unpack(a, ea, &a, &x->haveHandle) < 0) goto Err;
+		switch(x->haveHandle){
+		case 1:
+			if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+			break;
+		}
+		if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+		switch(x->haveAttr){
+		case 1:
+			if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+			break;
+		}
+		break;
+	}
+	if(nfs3WccUnpack(a, ea, &a, &x->dirWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TRemovePrint(Fmt *fmt, Nfs3TRemove *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TRemove");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->name);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TRemoveSize(Nfs3TRemove *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + sunStringSize(x->name);
+	return a;
+}
+int
+nfs3TRemovePack(uchar *a, uchar *ea, uchar **pa, Nfs3TRemove *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TRemoveUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TRemove *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RRemovePrint(Fmt *fmt, Nfs3RRemove *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RRemove");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "wcc");
+	nfs3WccPrint(fmt, &x->wcc);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3RRemoveSize(Nfs3RRemove *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + nfs3WccSize(&x->wcc);
+	return a;
+}
+int
+nfs3RRemovePack(uchar *a, uchar *ea, uchar **pa, Nfs3RRemove *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(nfs3WccPack(a, ea, &a, &x->wcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RRemoveUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RRemove *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(nfs3WccUnpack(a, ea, &a, &x->wcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TRmdirPrint(Fmt *fmt, Nfs3TRmdir *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TRmdir");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->name);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TRmdirSize(Nfs3TRmdir *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + sunStringSize(x->name);
+	return a;
+}
+int
+nfs3TRmdirPack(uchar *a, uchar *ea, uchar **pa, Nfs3TRmdir *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TRmdirUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TRmdir *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RRmdirPrint(Fmt *fmt, Nfs3RRmdir *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RRmdir");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "wcc");
+	nfs3WccPrint(fmt, &x->wcc);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3RRmdirSize(Nfs3RRmdir *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + nfs3WccSize(&x->wcc);
+	return a;
+}
+int
+nfs3RRmdirPack(uchar *a, uchar *ea, uchar **pa, Nfs3RRmdir *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(nfs3WccPack(a, ea, &a, &x->wcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RRmdirUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RRmdir *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(nfs3WccUnpack(a, ea, &a, &x->wcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TRenamePrint(Fmt *fmt, Nfs3TRename *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TRename");
+	fmtprint(fmt, "\t%s=", "from");
+	fmtprint(fmt, "{\n");
+	fmtprint(fmt, "\t\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->from.handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->from.name);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t}");
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "to");
+	fmtprint(fmt, "{\n");
+	fmtprint(fmt, "\t\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->to.handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->to.name);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t}");
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TRenameSize(Nfs3TRename *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->from.handle) + sunStringSize(x->from.name) + nfs3HandleSize(&x->to.handle) + sunStringSize(x->to.name);
+	return a;
+}
+int
+nfs3TRenamePack(uchar *a, uchar *ea, uchar **pa, Nfs3TRename *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->from.handle) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->from.name, -1) < 0) goto Err;
+	if(nfs3HandlePack(a, ea, &a, &x->to.handle) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->to.name, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TRenameUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TRename *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->from.handle) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->from.name, -1) < 0) goto Err;
+	if(nfs3HandleUnpack(a, ea, &a, &x->to.handle) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->to.name, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RRenamePrint(Fmt *fmt, Nfs3RRename *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RRename");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "fromWcc");
+	nfs3WccPrint(fmt, &x->fromWcc);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "toWcc");
+	nfs3WccPrint(fmt, &x->toWcc);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3RRenameSize(Nfs3RRename *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + nfs3WccSize(&x->fromWcc) + nfs3WccSize(&x->toWcc);
+	return a;
+}
+int
+nfs3RRenamePack(uchar *a, uchar *ea, uchar **pa, Nfs3RRename *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(nfs3WccPack(a, ea, &a, &x->fromWcc) < 0) goto Err;
+	if(nfs3WccPack(a, ea, &a, &x->toWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RRenameUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RRename *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(nfs3WccUnpack(a, ea, &a, &x->fromWcc) < 0) goto Err;
+	if(nfs3WccUnpack(a, ea, &a, &x->toWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TLinkPrint(Fmt *fmt, Nfs3TLink *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TLink");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "link");
+	fmtprint(fmt, "{\n");
+	fmtprint(fmt, "\t\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->link.handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->link.name);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t}");
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TLinkSize(Nfs3TLink *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + nfs3HandleSize(&x->link.handle) + sunStringSize(x->link.name);
+	return a;
+}
+int
+nfs3TLinkPack(uchar *a, uchar *ea, uchar **pa, Nfs3TLink *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(nfs3HandlePack(a, ea, &a, &x->link.handle) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->link.name, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TLinkUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TLink *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(nfs3HandleUnpack(a, ea, &a, &x->link.handle) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->link.name, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RLinkPrint(Fmt *fmt, Nfs3RLink *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RLink");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "dirWcc");
+	nfs3WccPrint(fmt, &x->dirWcc);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3RLinkSize(Nfs3RLink *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	a = a + nfs3WccSize(&x->dirWcc);
+	return a;
+}
+int
+nfs3RLinkPack(uchar *a, uchar *ea, uchar **pa, Nfs3RLink *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	if(nfs3WccPack(a, ea, &a, &x->dirWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RLinkUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RLink *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	if(nfs3WccUnpack(a, ea, &a, &x->dirWcc) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TReadDirPrint(Fmt *fmt, Nfs3TReadDir *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TReadDir");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "cookie");
+	fmtprint(fmt, "%llud", x->cookie);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "verf");
+	fmtprint(fmt, "%.*H", Nfs3CookieVerfSize, x->verf);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "count");
+	fmtprint(fmt, "%ud", x->count);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TReadDirSize(Nfs3TReadDir *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + 8 + Nfs3CookieVerfSize + 4;
+	return a;
+}
+int
+nfs3TReadDirPack(uchar *a, uchar *ea, uchar **pa, Nfs3TReadDir *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->cookie) < 0) goto Err;
+	if(sunFixedOpaquePack(a, ea, &a, x->verf, Nfs3CookieVerfSize) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->count) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TReadDirUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TReadDir *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->cookie) < 0) goto Err;
+	if(sunFixedOpaqueUnpack(a, ea, &a, x->verf, Nfs3CookieVerfSize) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->count) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3EntryPrint(Fmt *fmt, Nfs3Entry *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3Entry");
+	fmtprint(fmt, "\t%s=", "fileid");
+	fmtprint(fmt, "%llud", x->fileid);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->name);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "cookie");
+	fmtprint(fmt, "%llud", x->cookie);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3EntrySize(Nfs3Entry *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 8 + sunStringSize(x->name) + 8;
+	return a;
+}
+int
+nfs3EntryPack(uchar *a, uchar *ea, uchar **pa, Nfs3Entry *x)
+{
+	u1int one;
+
+	one = 1;
+	if(sunUint1Pack(a, ea, &a, &one) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->fileid) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->cookie) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3EntryUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3Entry *x)
+{
+	u1int one;
+
+	memset(x, 0, sizeof *x);
+	if(sunUint1Unpack(a, ea, &a, &one) < 0 || one != 1) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->fileid) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->cookie) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RReadDirPrint(Fmt *fmt, Nfs3RReadDir *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RReadDir");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "verf");
+		fmtprint(fmt, "%.*H", Nfs3CookieVerfSize, x->verf);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=%ud\n", "count", x->count);
+		fmtprint(fmt, "\t%s=", "eof");
+		fmtprint(fmt, "%d", x->eof);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RReadDirSize(Nfs3RReadDir *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + Nfs3CookieVerfSize;
+		a += x->count;
+		a += 4 + 4;
+		break;
+	}
+	return a;
+}
+int
+nfs3RReadDirPack(uchar *a, uchar *ea, uchar **pa, Nfs3RReadDir *x)
+{
+	int i;
+	u1int zero;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunFixedOpaquePack(a, ea, &a, x->verf, Nfs3CookieVerfSize) < 0) goto Err;
+		if(sunFixedOpaquePack(a, ea, &a, x->data, x->count) < 0) goto Err;
+		zero = 0;
+		if(sunUint1Pack(a, ea, &a, &zero) < 0) goto Err;
+		if(sunUint1Pack(a, ea, &a, &x->eof) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+countEntry(uchar *a, uchar *ea, uchar **pa, u32int *n)
+{
+	uchar *oa;
+	u64int u64;
+	u32int u32;
+	u1int u1;
+
+	oa = a;
+	for(;;){
+		if(sunUint1Unpack(a, ea, &a, &u1) < 0)
+			return -1;
+		if(u1 == 0)
+			break;
+		if(sunUint64Unpack(a, ea, &a, &u64) < 0
+		|| sunUint32Unpack(a, ea, &a, &u32) < 0)
+			return -1;
+		a += (u32+3)&~3;
+		if(a >= ea)
+			return -1;
+		if(sunUint64Unpack(a, ea, &a, &u64) < 0)
+			return -1;
+	}
+	*n = (a-4) - oa;
+	*pa = a;
+	return 0;
+}
+int
+nfs3RReadDirUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RReadDir *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	if(x->status == Nfs3Ok){
+		if(sunFixedOpaqueUnpack(a, ea, &a, x->verf, Nfs3CookieVerfSize) < 0) goto Err;
+		x->data = a;
+		if(countEntry(a, ea, &a, &x->count) < 0) goto Err;
+		if(sunUint1Unpack(a, ea, &a, &x->eof) < 0) goto Err;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TReadDirPlusPrint(Fmt *fmt, Nfs3TReadDirPlus *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TReadDirPlus");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "cookie");
+	fmtprint(fmt, "%llud", x->cookie);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "verf");
+	fmtprint(fmt, "%.*H", Nfs3CookieVerfSize, x->verf);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "dirCount");
+	fmtprint(fmt, "%ud", x->dirCount);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "maxCount");
+	fmtprint(fmt, "%ud", x->maxCount);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TReadDirPlusSize(Nfs3TReadDirPlus *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + 8 + Nfs3CookieVerfSize + 4 + 4;
+	return a;
+}
+int
+nfs3TReadDirPlusPack(uchar *a, uchar *ea, uchar **pa, Nfs3TReadDirPlus *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->cookie) < 0) goto Err;
+	if(sunFixedOpaquePack(a, ea, &a, x->verf, Nfs3CookieVerfSize) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->dirCount) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->maxCount) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TReadDirPlusUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TReadDirPlus *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->cookie) < 0) goto Err;
+	if(sunFixedOpaqueUnpack(a, ea, &a, x->verf, Nfs3CookieVerfSize) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->dirCount) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->maxCount) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3EntryPlusPrint(Fmt *fmt, Nfs3Entry *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3EntryPlus");
+	fmtprint(fmt, "\t%s=", "fileid");
+	fmtprint(fmt, "%llud", x->fileid);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "name");
+	fmtprint(fmt, "\"%s\"", x->name);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "cookie");
+	fmtprint(fmt, "%llud", x->cookie);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	fmtprint(fmt, "\t%s=", "haveHandle");
+	fmtprint(fmt, "%d", x->haveHandle);
+	fmtprint(fmt, "\n");
+	switch(x->haveHandle){
+	case 1:
+		fmtprint(fmt, "\t%s=", "handle");
+		nfs3HandlePrint(fmt, &x->handle);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3EntryPlusSize(Nfs3Entry *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 8 + sunStringSize(x->name) + 8 + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	a = a + 4;
+	switch(x->haveHandle){
+	case 1:
+		a = a + nfs3HandleSize(&x->handle);
+		break;
+	}
+	return a;
+}
+int
+nfs3EntryPlusPack(uchar *a, uchar *ea, uchar **pa, Nfs3Entry *x)
+{
+	u1int u1;
+
+	if(sunUint1Pack(a, ea, &a, &u1) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->fileid) < 0) goto Err;
+	if(sunStringPack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->cookie) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	if(sunUint1Pack(a, ea, &a, &x->haveHandle) < 0) goto Err;
+	switch(x->haveHandle){
+	case 1:
+		if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3EntryPlusUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3Entry *x)
+{
+	u1int u1;
+
+	if(sunUint1Unpack(a, ea, &a, &u1) < 0 || u1 != 1) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->fileid) < 0) goto Err;
+	if(sunStringUnpack(a, ea, &a, &x->name, -1) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->cookie) < 0) goto Err;
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	if(sunUint1Unpack(a, ea, &a, &x->haveHandle) < 0) goto Err;
+	switch(x->haveHandle){
+	case 1:
+		if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RReadDirPlusPrint(Fmt *fmt, Nfs3RReadDirPlus *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RReadDirPlus");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "verf");
+		fmtprint(fmt, "%.*H", Nfs3CookieVerfSize, x->verf);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\tcount=%ud\n", x->count);
+		fmtprint(fmt, "\t%s=", "eof");
+		fmtprint(fmt, "%d", x->eof);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RReadDirPlusSize(Nfs3RReadDirPlus *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + Nfs3CookieVerfSize;
+		a += x->count;
+		a += 4 + 4;
+		break;
+	}
+	return a;
+}
+int
+nfs3RReadDirPlusPack(uchar *a, uchar *ea, uchar **pa, Nfs3RReadDirPlus *x)
+{
+	int i;
+	u1int zero;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunFixedOpaquePack(a, ea, &a, x->verf, Nfs3CookieVerfSize) < 0) goto Err;
+		if(sunFixedOpaquePack(a, ea, &a, x->data, x->count) < 0) goto Err;
+		zero = 0;
+		if(sunUint1Pack(a, ea, &a, &zero) < 0) goto Err;
+		if(sunUint1Pack(a, ea, &a, &x->eof) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+countEntryPlus(uchar *a, uchar *ea, uchar **pa, u32int *n)
+{
+	uchar *oa;
+	u64int u64;
+	u32int u32;
+	u1int u1;
+	Nfs3Handle h;
+	Nfs3Attr attr;
+
+	oa = a;
+	for(;;){
+		if(sunUint1Unpack(a, ea, &a, &u1) < 0)
+			return -1;
+		if(u1 == 0)
+			break;
+		if(sunUint64Unpack(a, ea, &a, &u64) < 0
+		|| sunUint32Unpack(a, ea, &a, &u32) < 0)
+			return -1;
+		a += (u32+3)&~3;
+		if(a >= ea)
+			return -1;
+		if(sunUint64Unpack(a, ea, &a, &u64) < 0
+		|| sunUint1Unpack(a, ea, &a, &u1) < 0
+		|| (u1 && nfs3AttrUnpack(a, ea, &a, &attr) < 0)
+		|| sunUint1Unpack(a, ea, &a, &u1) < 0
+		|| (u1 && nfs3HandleUnpack(a, ea, &a, &h) < 0))
+			return -1;
+	}
+	*n = (a-4) - oa;
+	*pa = a;
+	return 0;
+}
+		
+int
+nfs3RReadDirPlusUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RReadDirPlus *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	if(x->status == Nfs3Ok){
+		if(sunFixedOpaqueUnpack(a, ea, &a, x->verf, Nfs3CookieVerfSize) < 0) goto Err;
+		x->data = a;
+		if(countEntryPlus(a, ea, &a, &x->count) < 0) goto Err;
+		if(sunUint1Unpack(a, ea, &a, &x->eof) < 0) goto Err;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TFsStatPrint(Fmt *fmt, Nfs3TFsStat *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TFsStat");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TFsStatSize(Nfs3TFsStat *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle);
+	return a;
+}
+int
+nfs3TFsStatPack(uchar *a, uchar *ea, uchar **pa, Nfs3TFsStat *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TFsStatUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TFsStat *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RFsStatPrint(Fmt *fmt, Nfs3RFsStat *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RFsStat");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "totalBytes");
+		fmtprint(fmt, "%llud", x->totalBytes);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "freeBytes");
+		fmtprint(fmt, "%llud", x->freeBytes);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "availBytes");
+		fmtprint(fmt, "%llud", x->availBytes);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "totalFiles");
+		fmtprint(fmt, "%llud", x->totalFiles);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "freeFiles");
+		fmtprint(fmt, "%llud", x->freeFiles);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "availFiles");
+		fmtprint(fmt, "%llud", x->availFiles);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "invarSec");
+		fmtprint(fmt, "%ud", x->invarSec);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RFsStatSize(Nfs3RFsStat *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + 8 + 8 + 8 + 8 + 8 + 8 + 4;
+		break;
+	}
+	return a;
+}
+int
+nfs3RFsStatPack(uchar *a, uchar *ea, uchar **pa, Nfs3RFsStat *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint64Pack(a, ea, &a, &x->totalBytes) < 0) goto Err;
+		if(sunUint64Pack(a, ea, &a, &x->freeBytes) < 0) goto Err;
+		if(sunUint64Pack(a, ea, &a, &x->availBytes) < 0) goto Err;
+		if(sunUint64Pack(a, ea, &a, &x->totalFiles) < 0) goto Err;
+		if(sunUint64Pack(a, ea, &a, &x->freeFiles) < 0) goto Err;
+		if(sunUint64Pack(a, ea, &a, &x->availFiles) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->invarSec) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RFsStatUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RFsStat *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint64Unpack(a, ea, &a, &x->totalBytes) < 0) goto Err;
+		if(sunUint64Unpack(a, ea, &a, &x->freeBytes) < 0) goto Err;
+		if(sunUint64Unpack(a, ea, &a, &x->availBytes) < 0) goto Err;
+		if(sunUint64Unpack(a, ea, &a, &x->totalFiles) < 0) goto Err;
+		if(sunUint64Unpack(a, ea, &a, &x->freeFiles) < 0) goto Err;
+		if(sunUint64Unpack(a, ea, &a, &x->availFiles) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->invarSec) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TFsInfoPrint(Fmt *fmt, Nfs3TFsInfo *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TFsInfo");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TFsInfoSize(Nfs3TFsInfo *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle);
+	return a;
+}
+int
+nfs3TFsInfoPack(uchar *a, uchar *ea, uchar **pa, Nfs3TFsInfo *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TFsInfoUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TFsInfo *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RFsInfoPrint(Fmt *fmt, Nfs3RFsInfo *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RFsInfo");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "readMax");
+		fmtprint(fmt, "%ud", x->readMax);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "readPref");
+		fmtprint(fmt, "%ud", x->readPref);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "readMult");
+		fmtprint(fmt, "%ud", x->readMult);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "writeMax");
+		fmtprint(fmt, "%ud", x->writeMax);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "writePref");
+		fmtprint(fmt, "%ud", x->writePref);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "writeMult");
+		fmtprint(fmt, "%ud", x->writeMult);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "readDirPref");
+		fmtprint(fmt, "%ud", x->readDirPref);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "maxFileSize");
+		fmtprint(fmt, "%llud", x->maxFileSize);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "timePrec");
+		nfs3TimePrint(fmt, &x->timePrec);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "flags");
+		fmtprint(fmt, "%ud", x->flags);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RFsInfoSize(Nfs3RFsInfo *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 8 + nfs3TimeSize(&x->timePrec) + 4;
+		break;
+	}
+	return a;
+}
+int
+nfs3RFsInfoPack(uchar *a, uchar *ea, uchar **pa, Nfs3RFsInfo *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint32Pack(a, ea, &a, &x->readMax) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->readPref) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->readMult) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->writeMax) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->writePref) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->writeMult) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->readDirPref) < 0) goto Err;
+		if(sunUint64Pack(a, ea, &a, &x->maxFileSize) < 0) goto Err;
+		if(nfs3TimePack(a, ea, &a, &x->timePrec) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->flags) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RFsInfoUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RFsInfo *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint32Unpack(a, ea, &a, &x->readMax) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->readPref) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->readMult) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->writeMax) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->writePref) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->writeMult) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->readDirPref) < 0) goto Err;
+		if(sunUint64Unpack(a, ea, &a, &x->maxFileSize) < 0) goto Err;
+		if(nfs3TimeUnpack(a, ea, &a, &x->timePrec) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->flags) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TPathconfPrint(Fmt *fmt, Nfs3TPathconf *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TPathconf");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TPathconfSize(Nfs3TPathconf *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle);
+	return a;
+}
+int
+nfs3TPathconfPack(uchar *a, uchar *ea, uchar **pa, Nfs3TPathconf *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TPathconfUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TPathconf *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RPathconfPrint(Fmt *fmt, Nfs3RPathconf *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RPathconf");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "haveAttr");
+	fmtprint(fmt, "%d", x->haveAttr);
+	fmtprint(fmt, "\n");
+	switch(x->haveAttr){
+	case 1:
+		fmtprint(fmt, "\t%s=", "attr");
+		nfs3AttrPrint(fmt, &x->attr);
+		fmtprint(fmt, "\n");
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "maxLink");
+		fmtprint(fmt, "%ud", x->maxLink);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "maxName");
+		fmtprint(fmt, "%ud", x->maxName);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "noTrunc");
+		fmtprint(fmt, "%d", x->noTrunc);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "chownRestricted");
+		fmtprint(fmt, "%d", x->chownRestricted);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "caseInsensitive");
+		fmtprint(fmt, "%d", x->caseInsensitive);
+		fmtprint(fmt, "\n");
+		fmtprint(fmt, "\t%s=", "casePreserving");
+		fmtprint(fmt, "%d", x->casePreserving);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RPathconfSize(Nfs3RPathconf *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4;
+	switch(x->haveAttr){
+	case 1:
+		a = a + nfs3AttrSize(&x->attr);
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + 4 + 4 + 4 + 4 + 4 + 4;
+		break;
+	}
+	return a;
+}
+int
+nfs3RPathconfPack(uchar *a, uchar *ea, uchar **pa, Nfs3RPathconf *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(sunUint1Pack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrPack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint32Pack(a, ea, &a, &x->maxLink) < 0) goto Err;
+		if(sunUint32Pack(a, ea, &a, &x->maxName) < 0) goto Err;
+		if(sunUint1Pack(a, ea, &a, &x->noTrunc) < 0) goto Err;
+		if(sunUint1Pack(a, ea, &a, &x->chownRestricted) < 0) goto Err;
+		if(sunUint1Pack(a, ea, &a, &x->caseInsensitive) < 0) goto Err;
+		if(sunUint1Pack(a, ea, &a, &x->casePreserving) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RPathconfUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RPathconf *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(sunUint1Unpack(a, ea, &a, &x->haveAttr) < 0) goto Err;
+	switch(x->haveAttr){
+	case 1:
+		if(nfs3AttrUnpack(a, ea, &a, &x->attr) < 0) goto Err;
+		break;
+	}
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunUint32Unpack(a, ea, &a, &x->maxLink) < 0) goto Err;
+		if(sunUint32Unpack(a, ea, &a, &x->maxName) < 0) goto Err;
+		if(sunUint1Unpack(a, ea, &a, &x->noTrunc) < 0) goto Err;
+		if(sunUint1Unpack(a, ea, &a, &x->chownRestricted) < 0) goto Err;
+		if(sunUint1Unpack(a, ea, &a, &x->caseInsensitive) < 0) goto Err;
+		if(sunUint1Unpack(a, ea, &a, &x->casePreserving) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3TCommitPrint(Fmt *fmt, Nfs3TCommit *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3TCommit");
+	fmtprint(fmt, "\t%s=", "handle");
+	nfs3HandlePrint(fmt, &x->handle);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "offset");
+	fmtprint(fmt, "%llud", x->offset);
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "count");
+	fmtprint(fmt, "%ud", x->count);
+	fmtprint(fmt, "\n");
+}
+uint
+nfs3TCommitSize(Nfs3TCommit *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + nfs3HandleSize(&x->handle) + 8 + 4;
+	return a;
+}
+int
+nfs3TCommitPack(uchar *a, uchar *ea, uchar **pa, Nfs3TCommit *x)
+{
+	if(nfs3HandlePack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint64Pack(a, ea, &a, &x->offset) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->count) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3TCommitUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3TCommit *x)
+{
+	if(nfs3HandleUnpack(a, ea, &a, &x->handle) < 0) goto Err;
+	if(sunUint64Unpack(a, ea, &a, &x->offset) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->count) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+void
+nfs3RCommitPrint(Fmt *fmt, Nfs3RCommit *x)
+{
+	fmtprint(fmt, "%s\n", "Nfs3RCommit");
+	fmtprint(fmt, "\t%s=", "status");
+	fmtprint(fmt, "%s", nfs3StatusStr(x->status));
+	fmtprint(fmt, "\n");
+	fmtprint(fmt, "\t%s=", "wcc");
+	nfs3WccPrint(fmt, &x->wcc);
+	fmtprint(fmt, "\n");
+	switch(x->status){
+	case Nfs3Ok:
+		fmtprint(fmt, "\t%s=", "verf");
+		fmtprint(fmt, "%.*H", Nfs3WriteVerfSize, x->verf);
+		fmtprint(fmt, "\n");
+		break;
+	}
+}
+uint
+nfs3RCommitSize(Nfs3RCommit *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + nfs3WccSize(&x->wcc);
+	switch(x->status){
+	case Nfs3Ok:
+		a = a + Nfs3WriteVerfSize;
+		break;
+	}
+	return a;
+}
+int
+nfs3RCommitPack(uchar *a, uchar *ea, uchar **pa, Nfs3RCommit *x)
+{
+	int i;
+
+	if(i=x->status, sunEnumPack(a, ea, &a, &i) < 0) goto Err;
+	if(nfs3WccPack(a, ea, &a, &x->wcc) < 0) goto Err;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunFixedOpaquePack(a, ea, &a, x->verf, Nfs3WriteVerfSize) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+int
+nfs3RCommitUnpack(uchar *a, uchar *ea, uchar **pa, Nfs3RCommit *x)
+{
+	int i;
+
+	if(sunEnumUnpack(a, ea, &a, &i) < 0) goto Err; x->status = i;
+	if(nfs3WccUnpack(a, ea, &a, &x->wcc) < 0) goto Err;
+	switch(x->status){
+	case Nfs3Ok:
+		if(sunFixedOpaqueUnpack(a, ea, &a, x->verf, Nfs3WriteVerfSize) < 0) goto Err;
+		break;
+	}
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+
+typedef int (*P)(uchar*, uchar*, uchar**, SunCall*);
+typedef void (*F)(Fmt*, SunCall*);
+typedef uint (*S)(SunCall*);
+
+static SunProc proc[] = {
+	(P)nfs3TNullPack, (P)nfs3TNullUnpack, (S)nfs3TNullSize, (F)nfs3TNullPrint, sizeof(Nfs3TNull),
+	(P)nfs3RNullPack, (P)nfs3RNullUnpack, (S)nfs3RNullSize, (F)nfs3RNullPrint, sizeof(Nfs3RNull),
+	(P)nfs3TGetattrPack, (P)nfs3TGetattrUnpack, (S)nfs3TGetattrSize, (F)nfs3TGetattrPrint, sizeof(Nfs3TGetattr),
+	(P)nfs3RGetattrPack, (P)nfs3RGetattrUnpack, (S)nfs3RGetattrSize, (F)nfs3RGetattrPrint, sizeof(Nfs3RGetattr),
+	(P)nfs3TSetattrPack, (P)nfs3TSetattrUnpack, (S)nfs3TSetattrSize, (F)nfs3TSetattrPrint, sizeof(Nfs3TSetattr),
+	(P)nfs3RSetattrPack, (P)nfs3RSetattrUnpack, (S)nfs3RSetattrSize, (F)nfs3RSetattrPrint, sizeof(Nfs3RSetattr),
+	(P)nfs3TLookupPack, (P)nfs3TLookupUnpack, (S)nfs3TLookupSize, (F)nfs3TLookupPrint, sizeof(Nfs3TLookup),
+	(P)nfs3RLookupPack, (P)nfs3RLookupUnpack, (S)nfs3RLookupSize, (F)nfs3RLookupPrint, sizeof(Nfs3RLookup),
+	(P)nfs3TAccessPack, (P)nfs3TAccessUnpack, (S)nfs3TAccessSize, (F)nfs3TAccessPrint, sizeof(Nfs3TAccess),
+	(P)nfs3RAccessPack, (P)nfs3RAccessUnpack, (S)nfs3RAccessSize, (F)nfs3RAccessPrint, sizeof(Nfs3RAccess),
+	(P)nfs3TReadlinkPack, (P)nfs3TReadlinkUnpack, (S)nfs3TReadlinkSize, (F)nfs3TReadlinkPrint, sizeof(Nfs3TReadlink),
+	(P)nfs3RReadlinkPack, (P)nfs3RReadlinkUnpack, (S)nfs3RReadlinkSize, (F)nfs3RReadlinkPrint, sizeof(Nfs3RReadlink),
+	(P)nfs3TReadPack, (P)nfs3TReadUnpack, (S)nfs3TReadSize, (F)nfs3TReadPrint, sizeof(Nfs3TRead),
+	(P)nfs3RReadPack, (P)nfs3RReadUnpack, (S)nfs3RReadSize, (F)nfs3RReadPrint, sizeof(Nfs3RRead),
+	(P)nfs3TWritePack, (P)nfs3TWriteUnpack, (S)nfs3TWriteSize, (F)nfs3TWritePrint, sizeof(Nfs3TWrite),
+	(P)nfs3RWritePack, (P)nfs3RWriteUnpack, (S)nfs3RWriteSize, (F)nfs3RWritePrint, sizeof(Nfs3RWrite),
+	(P)nfs3TCreatePack, (P)nfs3TCreateUnpack, (S)nfs3TCreateSize, (F)nfs3TCreatePrint, sizeof(Nfs3TCreate),
+	(P)nfs3RCreatePack, (P)nfs3RCreateUnpack, (S)nfs3RCreateSize, (F)nfs3RCreatePrint, sizeof(Nfs3RCreate),
+	(P)nfs3TMkdirPack, (P)nfs3TMkdirUnpack, (S)nfs3TMkdirSize, (F)nfs3TMkdirPrint, sizeof(Nfs3TMkdir),
+	(P)nfs3RMkdirPack, (P)nfs3RMkdirUnpack, (S)nfs3RMkdirSize, (F)nfs3RMkdirPrint, sizeof(Nfs3RMkdir),
+	(P)nfs3TSymlinkPack, (P)nfs3TSymlinkUnpack, (S)nfs3TSymlinkSize, (F)nfs3TSymlinkPrint, sizeof(Nfs3TSymlink),
+	(P)nfs3RSymlinkPack, (P)nfs3RSymlinkUnpack, (S)nfs3RSymlinkSize, (F)nfs3RSymlinkPrint, sizeof(Nfs3RSymlink),
+	(P)nfs3TMknodPack, (P)nfs3TMknodUnpack, (S)nfs3TMknodSize, (F)nfs3TMknodPrint, sizeof(Nfs3TMknod),
+	(P)nfs3RMknodPack, (P)nfs3RMknodUnpack, (S)nfs3RMknodSize, (F)nfs3RMknodPrint, sizeof(Nfs3RMknod),
+	(P)nfs3TRemovePack, (P)nfs3TRemoveUnpack, (S)nfs3TRemoveSize, (F)nfs3TRemovePrint, sizeof(Nfs3TRemove),
+	(P)nfs3RRemovePack, (P)nfs3RRemoveUnpack, (S)nfs3RRemoveSize, (F)nfs3RRemovePrint, sizeof(Nfs3RRemove),
+	(P)nfs3TRmdirPack, (P)nfs3TRmdirUnpack, (S)nfs3TRmdirSize, (F)nfs3TRmdirPrint, sizeof(Nfs3TRmdir),
+	(P)nfs3RRmdirPack, (P)nfs3RRmdirUnpack, (S)nfs3RRmdirSize, (F)nfs3RRmdirPrint, sizeof(Nfs3RRmdir),
+	(P)nfs3TRenamePack, (P)nfs3TRenameUnpack, (S)nfs3TRenameSize, (F)nfs3TRenamePrint, sizeof(Nfs3TRename),
+	(P)nfs3RRenamePack, (P)nfs3RRenameUnpack, (S)nfs3RRenameSize, (F)nfs3RRenamePrint, sizeof(Nfs3RRename),
+	(P)nfs3TLinkPack, (P)nfs3TLinkUnpack, (S)nfs3TLinkSize, (F)nfs3TLinkPrint, sizeof(Nfs3TLink),
+	(P)nfs3RLinkPack, (P)nfs3RLinkUnpack, (S)nfs3RLinkSize, (F)nfs3RLinkPrint, sizeof(Nfs3RLink),
+	(P)nfs3TReadDirPack, (P)nfs3TReadDirUnpack, (S)nfs3TReadDirSize, (F)nfs3TReadDirPrint, sizeof(Nfs3TReadDir),
+	(P)nfs3RReadDirPack, (P)nfs3RReadDirUnpack, (S)nfs3RReadDirSize, (F)nfs3RReadDirPrint, sizeof(Nfs3RReadDir),
+	(P)nfs3TReadDirPlusPack, (P)nfs3TReadDirPlusUnpack, (S)nfs3TReadDirPlusSize, (F)nfs3TReadDirPlusPrint, sizeof(Nfs3TReadDirPlus),
+	(P)nfs3RReadDirPlusPack, (P)nfs3RReadDirPlusUnpack, (S)nfs3RReadDirPlusSize, (F)nfs3RReadDirPlusPrint, sizeof(Nfs3RReadDirPlus),
+	(P)nfs3TFsStatPack, (P)nfs3TFsStatUnpack, (S)nfs3TFsStatSize, (F)nfs3TFsStatPrint, sizeof(Nfs3TFsStat),
+	(P)nfs3RFsStatPack, (P)nfs3RFsStatUnpack, (S)nfs3RFsStatSize, (F)nfs3RFsStatPrint, sizeof(Nfs3RFsStat),
+	(P)nfs3TFsInfoPack, (P)nfs3TFsInfoUnpack, (S)nfs3TFsInfoSize, (F)nfs3TFsInfoPrint, sizeof(Nfs3TFsInfo),
+	(P)nfs3RFsInfoPack, (P)nfs3RFsInfoUnpack, (S)nfs3RFsInfoSize, (F)nfs3RFsInfoPrint, sizeof(Nfs3RFsInfo),
+	(P)nfs3TPathconfPack, (P)nfs3TPathconfUnpack, (S)nfs3TPathconfSize, (F)nfs3TPathconfPrint, sizeof(Nfs3TPathconf),
+	(P)nfs3RPathconfPack, (P)nfs3RPathconfUnpack, (S)nfs3RPathconfSize, (F)nfs3RPathconfPrint, sizeof(Nfs3RPathconf),
+	(P)nfs3TCommitPack, (P)nfs3TCommitUnpack, (S)nfs3TCommitSize, (F)nfs3TCommitPrint, sizeof(Nfs3TCommit),
+	(P)nfs3RCommitPack, (P)nfs3RCommitUnpack, (S)nfs3RCommitSize, (F)nfs3RCommitPrint, sizeof(Nfs3RCommit)
+};
+
+SunProg nfs3Prog = 
+{
+	Nfs3Program,
+	Nfs3Version,
+	proc,
+	nelem(proc),
+};

+ 498 - 0
sys/src/libsunrpc/portmap.c

@@ -0,0 +1,498 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+static void
+portMapPrint(Fmt *fmt, PortMap *x)
+{
+	fmtprint(fmt, "[%ud %ud %ud %ud]", x->prog, x->vers, x->prot, x->port);
+}
+static uint
+portMapSize(PortMap *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4 + 4 + 4;
+	return a;
+}
+static int
+portMapPack(uchar *a, uchar *ea, uchar **pa, PortMap *x)
+{
+	if(sunUint32Pack(a, ea, &a, &x->prog) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->vers) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->prot) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->port) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+portMapUnpack(uchar *a, uchar *ea, uchar **pa, PortMap *x)
+{
+	if(sunUint32Unpack(a, ea, &a, &x->prog) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->vers) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->prot) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->port) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static void
+portTNullPrint(Fmt *fmt, PortTNull *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s", "PortTNull");
+}
+static uint
+portTNullSize(PortTNull *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+static int
+portTNullPack(uchar *a, uchar *ea, uchar **pa, PortTNull *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+static int
+portTNullUnpack(uchar *a, uchar *ea, uchar **pa, PortTNull *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+static void
+portRNullPrint(Fmt *fmt, PortRNull *x)
+{
+	USED(x);
+	fmtprint(fmt, "%s", "PortRNull");
+}
+static uint
+portRNullSize(PortRNull *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+static int
+portRNullPack(uchar *a, uchar *ea, uchar **pa, PortRNull *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+static int
+portRNullUnpack(uchar *a, uchar *ea, uchar **pa, PortRNull *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+static void
+portTSetPrint(Fmt *fmt, PortTSet *x)
+{
+	fmtprint(fmt, "PortTSet ");
+	portMapPrint(fmt, &x->map);
+}
+static uint
+portTSetSize(PortTSet *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + portMapSize(&x->map);
+	return a;
+}
+static int
+portTSetPack(uchar *a, uchar *ea, uchar **pa, PortTSet *x)
+{
+	if(portMapPack(a, ea, &a, &x->map) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+portTSetUnpack(uchar *a, uchar *ea, uchar **pa, PortTSet *x)
+{
+	if(portMapUnpack(a, ea, &a, &x->map) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static void
+portRSetPrint(Fmt *fmt, PortRSet *x)
+{
+	fmtprint(fmt, "PortRSet %ud", x->b);
+}
+static uint
+portRSetSize(PortRSet *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	return a;
+}
+static int
+portRSetPack(uchar *a, uchar *ea, uchar **pa, PortRSet *x)
+{
+	if(sunUint1Pack(a, ea, &a, &x->b) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+portRSetUnpack(uchar *a, uchar *ea, uchar **pa, PortRSet *x)
+{
+	if(sunUint1Unpack(a, ea, &a, &x->b) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static void
+portTUnsetPrint(Fmt *fmt, PortTUnset *x)
+{
+	fmtprint(fmt, "PortTUnset ");
+	portMapPrint(fmt, &x->map);
+}
+static uint
+portTUnsetSize(PortTUnset *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + portMapSize(&x->map);
+	return a;
+}
+static int
+portTUnsetPack(uchar *a, uchar *ea, uchar **pa, PortTUnset *x)
+{
+	if(portMapPack(a, ea, &a, &x->map) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+portTUnsetUnpack(uchar *a, uchar *ea, uchar **pa, PortTUnset *x)
+{
+	if(portMapUnpack(a, ea, &a, &x->map) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static void
+portRUnsetPrint(Fmt *fmt, PortRUnset *x)
+{
+	fmtprint(fmt, "PortRUnset %ud", x->b);
+}
+static uint
+portRUnsetSize(PortRUnset *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	return a;
+}
+static int
+portRUnsetPack(uchar *a, uchar *ea, uchar **pa, PortRUnset *x)
+{
+	if(sunUint1Pack(a, ea, &a, &x->b) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+portRUnsetUnpack(uchar *a, uchar *ea, uchar **pa, PortRUnset *x)
+{
+	if(sunUint1Unpack(a, ea, &a, &x->b) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static void
+portTGetportPrint(Fmt *fmt, PortTGetport *x)
+{
+	fmtprint(fmt, "PortTGetport ");
+	portMapPrint(fmt, &x->map);
+}
+static uint
+portTGetportSize(PortTGetport *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + portMapSize(&x->map);
+	return a;
+}
+static int
+portTGetportPack(uchar *a, uchar *ea, uchar **pa, PortTGetport *x)
+{
+	if(portMapPack(a, ea, &a, &x->map) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+portTGetportUnpack(uchar *a, uchar *ea, uchar **pa, PortTGetport *x)
+{
+	if(portMapUnpack(a, ea, &a, &x->map) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static void
+portRGetportPrint(Fmt *fmt, PortRGetport *x)
+{
+	fmtprint(fmt, "PortRGetport %ud", x->port);
+}
+static uint
+portRGetportSize(PortRGetport *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4;
+	return a;
+}
+static int
+portRGetportPack(uchar *a, uchar *ea, uchar **pa, PortRGetport *x)
+{
+	if(sunUint32Pack(a, ea, &a, &x->port) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+portRGetportUnpack(uchar *a, uchar *ea, uchar **pa, PortRGetport *x)
+{
+	if(sunUint32Unpack(a, ea, &a, &x->port) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static void
+portTDumpPrint(Fmt *fmt, PortTDump *x)
+{
+	USED(x);
+	fmtprint(fmt, "PortTDump");
+}
+static uint
+portTDumpSize(PortTDump *x)
+{
+	uint a;
+	USED(x);
+	a = 0;
+	return a;
+}
+static int
+portTDumpPack(uchar *a, uchar *ea, uchar **pa, PortTDump *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+static int
+portTDumpUnpack(uchar *a, uchar *ea, uchar **pa, PortTDump *x)
+{
+	USED(ea);
+	USED(x);
+	*pa = a;
+	return 0;
+}
+static void
+portRDumpPrint(Fmt *fmt, PortRDump *x)
+{
+	int i;
+
+	fmtprint(fmt, "PortRDump");
+	for(i=0; i<x->nmap; i++){
+		fmtprint(fmt, " ");
+		portMapPrint(fmt, &x->map[i]);
+	}
+}
+static uint
+portRDumpSize(PortRDump *x)
+{
+	return (5*4*x->nmap) + 4;
+}
+static int
+portRDumpPack(uchar *a, uchar *ea, uchar **pa, PortRDump *x)
+{
+	int i;
+	u32int zero, one;
+
+	zero = 0;
+	one = 1;
+	for(i=0; i<x->nmap; i++){
+		if(sunUint32Pack(a, ea, &a, &one) < 0
+		|| portMapPack(a, ea, &a, &x->map[i]) < 0)
+			goto Err;
+	}
+	if(sunUint32Pack(a, ea, &a, &zero) < 0)
+		goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+portRDumpUnpack(uchar *a, uchar *ea, uchar **pa, PortRDump *x)
+{
+	int i;
+	u1int u1;
+	PortMap *m;
+
+	m = (PortMap*)a;
+	for(i=0;; i++){
+		if(sunUint1Unpack(a, ea, &a, &u1) < 0)
+			goto Err;
+		if(u1 == 0)
+			break;
+		if(portMapUnpack(a, ea, &a, &m[i]) < 0)
+			goto Err;
+	}
+	x->nmap = i;
+	x->map = m;
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+static void
+portTCallitPrint(Fmt *fmt, PortTCallit *x)
+{
+	fmtprint(fmt, "PortTCallit [%ud,%ud,%ud] %ud", x->prog, x->vers, x->proc, x->count);
+}
+static uint
+portTCallitSize(PortTCallit *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + 4 + 4 + sunVarOpaqueSize(x->count);
+	return a;
+}
+static int
+portTCallitPack(uchar *a, uchar *ea, uchar **pa, PortTCallit *x)
+{
+	if(sunUint32Pack(a, ea, &a, &x->prog) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->vers) < 0) goto Err;
+	if(sunUint32Pack(a, ea, &a, &x->proc) < 0) goto Err;
+	if(sunVarOpaquePack(a, ea, &a, &x->data, &x->count, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+portTCallitUnpack(uchar *a, uchar *ea, uchar **pa, PortTCallit *x)
+{
+	if(sunUint32Unpack(a, ea, &a, &x->prog) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->vers) < 0) goto Err;
+	if(sunUint32Unpack(a, ea, &a, &x->proc) < 0) goto Err;
+	if(sunVarOpaqueUnpack(a, ea, &a, &x->data, &x->count, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static void
+portRCallitPrint(Fmt *fmt, PortRCallit *x)
+{
+	fmtprint(fmt, "PortRCallit %ud %ud", x->port, x->count);
+}
+static uint
+portRCallitSize(PortRCallit *x)
+{
+	uint a;
+	USED(x);
+	a = 0 + 4 + sunVarOpaqueSize(x->count);
+	return a;
+}
+static int
+portRCallitPack(uchar *a, uchar *ea, uchar **pa, PortRCallit *x)
+{
+	if(sunUint32Pack(a, ea, &a, &x->port) < 0) goto Err;
+	if(sunVarOpaquePack(a, ea, &a, &x->data, &x->count, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+static int
+portRCallitUnpack(uchar *a, uchar *ea, uchar **pa, PortRCallit *x)
+{
+	if(sunUint32Unpack(a, ea, &a, &x->port) < 0) goto Err;
+	if(sunVarOpaqueUnpack(a, ea, &a, &x->data, &x->count, -1) < 0) goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+
+typedef int (*P)(uchar*, uchar*, uchar**, SunCall*);
+typedef void (*F)(Fmt*, SunCall*);
+typedef uint (*S)(SunCall*);
+
+static SunProc proc[] = {
+	(P)portTNullPack, (P)portTNullUnpack, (S)portTNullSize, (F)portTNullPrint, sizeof(PortTNull),
+	(P)portRNullPack, (P)portRNullUnpack, (S)portRNullSize, (F)portRNullPrint, sizeof(PortRNull),
+	(P)portTSetPack, (P)portTSetUnpack, (S)portTSetSize, (F)portTSetPrint, sizeof(PortTSet),
+	(P)portRSetPack, (P)portRSetUnpack, (S)portRSetSize, (F)portRSetPrint, sizeof(PortRSet),
+	(P)portTUnsetPack, (P)portTUnsetUnpack, (S)portTUnsetSize, (F)portTUnsetPrint, sizeof(PortTUnset),
+	(P)portRUnsetPack, (P)portRUnsetUnpack, (S)portRUnsetSize, (F)portRUnsetPrint, sizeof(PortRUnset),
+	(P)portTGetportPack, (P)portTGetportUnpack, (S)portTGetportSize, (F)portTGetportPrint, sizeof(PortTGetport),
+	(P)portRGetportPack, (P)portRGetportUnpack, (S)portRGetportSize, (F)portRGetportPrint, sizeof(PortRGetport),
+	(P)portTDumpPack, (P)portTDumpUnpack, (S)portTDumpSize, (F)portTDumpPrint, sizeof(PortTDump),
+	(P)portRDumpPack, (P)portRDumpUnpack, (S)portRDumpSize, (F)portRDumpPrint, sizeof(PortRDump),
+	(P)portTCallitPack, (P)portTCallitUnpack, (S)portTCallitSize, (F)portTCallitPrint, sizeof(PortTCallit),
+	(P)portRCallitPack, (P)portRCallitUnpack, (S)portRCallitSize, (F)portRCallitPrint, sizeof(PortRCallit),
+};
+
+SunProg portProg = 
+{
+	PortProgram,
+	PortVersion,
+	proc,
+	nelem(proc),
+};

+ 71 - 0
sys/src/libsunrpc/prog.c

@@ -0,0 +1,71 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+SunStatus
+sunCallPack(SunProg *prog, uchar *a, uchar *ea, uchar **pa, SunCall *c)
+{
+	uchar *x;
+	int (*pack)(uchar*, uchar*, uchar**, SunCall*);
+
+	if(pa == nil)
+		pa = &x;
+	if(c->type < 0 || c->type >= prog->nproc || (pack=prog->proc[c->type].pack) == nil)
+		return SunProcUnavail;
+	if((*pack)(a, ea, pa, c) < 0)
+		return SunGarbageArgs;
+	return SunSuccess;
+}
+
+SunStatus
+sunCallUnpack(SunProg *prog, uchar *a, uchar *ea, uchar **pa, SunCall *c)
+{
+	uchar *x;
+	int (*unpack)(uchar*, uchar*, uchar**, SunCall*);
+
+	if(pa == nil)
+		pa = &x;
+	if(c->type < 0 || c->type >= prog->nproc || (unpack=prog->proc[c->type].unpack) == nil)
+		return SunProcUnavail;
+	if((*unpack)(a, ea, pa, c) < 0)
+		return SunGarbageArgs;
+	return SunSuccess;
+}
+
+SunStatus
+sunCallUnpackAlloc(SunProg *prog, SunCallType type, uchar *a, uchar *ea, uchar **pa, SunCall **pc)
+{
+	uchar *x;
+	uint size;
+	int (*unpack)(uchar*, uchar*, uchar**, SunCall*);
+	SunCall *c;
+
+	if(pa == nil)
+		pa = &x;
+	if(type < 0 || type >= prog->nproc || (unpack=prog->proc[type].unpack) == nil)
+		return SunProcUnavail;
+	size = prog->proc[type].sizeoftype;
+	if(size == 0)
+		return SunProcUnavail;
+	c = mallocz(size, 1);
+	if(c == nil)
+		return SunSystemErr;
+	c->type = type;
+	if((*unpack)(a, ea, pa, c) < 0){
+		free(c);
+		return SunGarbageArgs;
+	}
+	*pc = c;
+	return SunSuccess;
+}
+
+uint
+sunCallSize(SunProg *prog, SunCall *c)
+{
+	uint (*size)(SunCall*);
+
+	if(c->type < 0 || c->type >= prog->nproc || (size=prog->proc[c->type].size) == nil)
+		return ~0;
+	return (*size)(c);
+}

+ 528 - 0
sys/src/libsunrpc/rpc.c

@@ -0,0 +1,528 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+/*
+ * RPC protocol constants
+ */
+enum
+{
+	RpcVersion = 2,
+
+	/* msg type */
+	MsgCall = 0,
+	MsgReply = 1,
+
+	/* reply stat */
+	MsgAccepted = 0,
+	MsgDenied = 1,
+
+	/* accept stat */
+	MsgSuccess = 0,
+	MsgProgUnavail = 1,
+	MsgProgMismatch = 2,
+	MsgProcUnavail = 3,
+	MsgGarbageArgs = 4,
+	MsgSystemErr = 5,
+
+	/* reject stat */
+	MsgRpcMismatch = 0,
+	MsgAuthError = 1,
+
+	/* msg auth xxx */
+	MsgAuthOk = 0,
+	MsgAuthBadCred = 1,
+	MsgAuthRejectedCred = 2,
+	MsgAuthBadVerf = 3,
+	MsgAuthRejectedVerf = 4,
+	MsgAuthTooWeak = 5,
+	MsgAuthInvalidResp = 6,
+	MsgAuthFailed = 7,
+};
+
+SunStatus
+sunRpcPack(uchar *a, uchar *ea, uchar **pa, SunRpc *rpc)
+{
+	u32int x;
+
+	if(sunUint32Pack(a, ea, &a, &rpc->xid) < 0)
+		goto Err;
+	if(rpc->iscall){
+		if(sunUint32Pack(a, ea, &a, (x=MsgCall, &x)) < 0
+		|| sunUint32Pack(a, ea, &a, (x=RpcVersion, &x)) < 0
+		|| sunUint32Pack(a, ea, &a, &rpc->prog) < 0
+		|| sunUint32Pack(a, ea, &a, &rpc->vers) < 0
+		|| sunUint32Pack(a, ea, &a, &rpc->proc) < 0
+		|| sunAuthInfoPack(a, ea, &a, &rpc->cred) < 0
+		|| sunAuthInfoPack(a, ea, &a, &rpc->verf) < 0
+		|| sunFixedOpaquePack(a, ea, &a, rpc->data, rpc->ndata) < 0)
+			goto Err;
+	}else{
+		if(sunUint32Pack(a, ea, &a, (x=MsgReply, &x)) < 0)
+			goto Err;
+		switch(rpc->status&0xF0000){
+		case 0:
+		case SunAcceptError:
+			if(sunUint32Pack(a, ea, &a, (x=MsgAccepted, &x)) < 0
+			|| sunAuthInfoPack(a, ea, &a, &rpc->verf) < 0)
+				goto Err;
+			break;
+		default:
+			if(sunUint32Pack(a, ea, &a, (x=MsgDenied, &x)) < 0)
+				goto Err;
+			break;
+		}
+
+		switch(rpc->status){
+		case SunSuccess:
+			if(sunUint32Pack(a, ea, &a, (x=MsgSuccess, &x)) < 0
+			|| sunFixedOpaquePack(a, ea, &a, rpc->data, rpc->ndata) < 0)
+				goto Err;
+			break;
+		case SunRpcMismatch:
+		case SunProgMismatch:
+			if(sunUint32Pack(a, ea, &a, (x=rpc->status&0xFFFF, &x)) < 0
+			|| sunUint32Pack(a, ea, &a, &rpc->low) < 0
+			|| sunUint32Pack(a, ea, &a, &rpc->high) < 0)
+				goto Err;
+			break;
+		default:
+			if(sunUint32Pack(a, ea, &a, (x=rpc->status&0xFFFF, &x)) < 0)
+				goto Err;
+			break;
+		}
+	}
+	*pa = a;
+	return SunSuccess;
+
+Err:
+	*pa = ea;
+	return SunGarbageArgs;
+}
+
+uint
+sunRpcSize(SunRpc *rpc)
+{
+	uint a;
+
+	a = 4;
+	if(rpc->iscall){
+		a += 5*4;
+		a += sunAuthInfoSize(&rpc->cred);
+		a += sunAuthInfoSize(&rpc->verf);
+		a += sunFixedOpaqueSize(rpc->ndata);
+	}else{
+		a += 4;
+		switch(rpc->status&0xF0000){
+		case 0:
+		case SunAcceptError:
+			a += 4+sunAuthInfoSize(&rpc->verf);
+			break;
+		default:
+			a += 4;
+			break;
+		}
+
+		switch(rpc->status){
+		case SunSuccess:
+			a += 4+sunFixedOpaqueSize(rpc->ndata);
+			break;
+		case SunRpcMismatch:
+		case SunProgMismatch:
+			a += 3*4;
+		default:
+			a += 4;
+		}
+	}
+	return a;
+}
+
+SunStatus
+sunRpcUnpack(uchar *a, uchar *ea, uchar **pa, SunRpc *rpc)
+{
+	u32int x;
+
+	memset(rpc, 0, sizeof *rpc);
+	if(sunUint32Unpack(a, ea, &a, &rpc->xid) < 0
+	|| sunUint32Unpack(a, ea, &a, &x) < 0)
+		goto Err;
+
+	switch(x){
+	default:
+		goto Err;
+	case MsgCall:
+		rpc->iscall = 1;
+		if(sunUint32Unpack(a, ea, &a, &x) < 0 || x != RpcVersion
+		|| sunUint32Unpack(a, ea, &a, &rpc->prog) < 0
+		|| sunUint32Unpack(a, ea, &a, &rpc->vers) < 0
+		|| sunUint32Unpack(a, ea, &a, &rpc->proc) < 0
+		|| sunAuthInfoUnpack(a, ea, &a, &rpc->cred) < 0
+		|| sunAuthInfoUnpack(a, ea, &a, &rpc->verf) < 0)
+			goto Err;
+		rpc->ndata = ea-a;
+		rpc->data = a;
+		a = ea;
+		break;
+
+	case MsgReply:
+		rpc->iscall = 0;
+		if(sunUint32Unpack(a, ea, &a, &x) < 0)
+			goto Err;
+		switch(x){
+		default:
+			goto Err;
+		case MsgAccepted:
+			if(sunAuthInfoUnpack(a, ea, &a, &rpc->verf) < 0
+			|| sunUint32Unpack(a, ea, &a, &x) < 0)
+				goto Err;
+			switch(x){
+			case MsgSuccess:
+				rpc->status = SunSuccess;
+				rpc->ndata = ea-a;
+				rpc->data = a;
+				a = ea;
+				break;
+			case MsgProgUnavail:
+			case MsgProcUnavail:
+			case MsgGarbageArgs:
+			case MsgSystemErr:
+				rpc->status = SunAcceptError | x;
+				break;
+			case MsgProgMismatch:
+				rpc->status = SunAcceptError | x;
+				if(sunUint32Unpack(a, ea, &a, &rpc->low) < 0
+				|| sunUint32Unpack(a, ea, &a, &rpc->high) < 0)
+					goto Err;
+				break;
+			}
+			break;
+		case MsgDenied:
+			if(sunUint32Unpack(a, ea, &a, &x) < 0)
+				goto Err;
+			switch(x){
+			default:
+				goto Err;
+			case MsgAuthError:
+				if(sunUint32Unpack(a, ea, &a, &x) < 0)
+					goto Err;
+				rpc->status = SunAuthError | x;
+				break;
+			case MsgRpcMismatch:
+				rpc->status = SunRejectError | x;
+				if(sunUint32Unpack(a, ea, &a, &rpc->low) < 0
+				|| sunUint32Unpack(a, ea, &a, &rpc->high) < 0)
+					goto Err;
+				break;
+			}
+			break;
+		}
+	}
+	*pa = a;
+	return SunSuccess;
+
+Err:
+	*pa = ea;
+	return SunGarbageArgs;
+}
+
+void
+sunRpcPrint(Fmt *fmt, SunRpc *rpc)
+{
+	fmtprint(fmt, "xid=%#ux", rpc->xid);
+	if(rpc->iscall){
+		fmtprint(fmt, " prog %#ux vers %#ux proc %#ux [", rpc->prog, rpc->vers, rpc->proc);
+		sunAuthInfoPrint(fmt, &rpc->cred);
+		fmtprint(fmt, "] [");
+		sunAuthInfoPrint(fmt, &rpc->verf);
+		fmtprint(fmt, "]");
+	}else{
+		fmtprint(fmt, " status %#ux [", rpc->status);
+		sunAuthInfoPrint(fmt, &rpc->verf);
+		fmtprint(fmt, "] low %#ux high %#ux", rpc->low, rpc->high);
+	}
+}
+
+void
+sunAuthInfoPrint(Fmt *fmt, SunAuthInfo *ai)
+{
+	switch(ai->flavor){
+	case SunAuthNone:
+		fmtprint(fmt, "none");
+		break;
+	case SunAuthShort:
+		fmtprint(fmt, "short");
+		break;
+	case SunAuthSys:
+		fmtprint(fmt, "sys");
+		break;
+	default:
+		fmtprint(fmt, "%#ux", ai->flavor);
+		break;
+	}
+//	if(ai->ndata)
+//		fmtprint(fmt, " %.*H", ai->ndata, ai->data);
+}
+
+uint
+sunAuthInfoSize(SunAuthInfo *ai)
+{
+	return 4 + sunVarOpaqueSize(ai->ndata);
+}
+
+int
+sunAuthInfoPack(uchar *a, uchar *ea, uchar **pa, SunAuthInfo *ai)
+{
+	if(sunUint32Pack(a, ea, &a, &ai->flavor) < 0
+	|| sunVarOpaquePack(a, ea, &a, &ai->data, &ai->ndata, 400) < 0)
+		goto Err;
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+
+int
+sunAuthInfoUnpack(uchar *a, uchar *ea, uchar **pa, SunAuthInfo *ai)
+{
+	if(sunUint32Unpack(a, ea, &a, &ai->flavor) < 0
+	|| sunVarOpaqueUnpack(a, ea, &a, &ai->data, &ai->ndata, 400) < 0)
+		goto Err;
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+
+int
+sunEnumPack(uchar *a, uchar *ea, uchar **pa, int *e)
+{
+	u32int x;
+
+	x = *e;
+	return sunUint32Pack(a, ea, pa, &x);
+}
+
+int
+sunUint1Pack(uchar *a, uchar *ea, uchar **pa, u1int *u)
+{
+	u32int x;
+
+	x = *u;
+	return sunUint32Pack(a, ea, pa, &x);
+}
+
+int
+sunUint32Pack(uchar *a, uchar *ea, uchar **pa, u32int *u)
+{
+	u32int x;
+
+	if(ea-a < 4)
+		goto Err;
+
+	x = *u;
+	*a++ = x>>24;
+	*a++ = x>>16;
+	*a++ = x>>8;
+	*a++ = x;
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+
+int
+sunEnumUnpack(uchar *a, uchar *ea, uchar **pa, int *e)
+{
+	u32int x;
+	if(sunUint32Unpack(a, ea, pa, &x) < 0)
+		return -1;
+	*e = x;
+	return 0;
+}
+
+int
+sunUint1Unpack(uchar *a, uchar *ea, uchar **pa, u1int *u)
+{
+	u32int x;
+	if(sunUint32Unpack(a, ea, pa, &x) < 0 || (x!=0 && x!=1)){
+		*pa = ea;
+		return -1;
+	}
+	*u = x;
+	return 0;
+}
+
+int
+sunUint32Unpack(uchar *a, uchar *ea, uchar **pa, u32int *u)
+{
+	u32int x;
+
+	if(ea-a < 4)
+		goto Err;
+	x = *a++ << 24;
+	x |= *a++ << 16;
+	x |= *a++ << 8;
+	x |= *a++;
+	*pa = a;
+	*u = x;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+
+int
+sunUint64Unpack(uchar *a, uchar *ea, uchar **pa, u64int *u)
+{
+	u32int x, y;
+
+	if(sunUint32Unpack(a, ea, &a, &x) < 0
+	|| sunUint32Unpack(a, ea, &a, &y) < 0)
+		goto Err;
+	*u = ((uvlong)x<<32) | y;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+
+int
+sunUint64Pack(uchar *a, uchar *ea, uchar **pa, u64int *u)
+{
+	u32int x, y;
+
+	x = *u >> 32;
+	y = *u;
+	if(sunUint32Pack(a, ea, &a, &x) < 0
+	|| sunUint32Pack(a, ea, &a, &y) < 0)
+		goto Err;
+	*pa = a;
+	return 0;
+Err:
+	*pa = ea;
+	return -1;
+}
+
+uint
+sunStringSize(char *s)
+{
+	return (4+strlen(s)+3) & ~3;
+}
+
+int
+sunStringUnpack(uchar *a, uchar *ea, uchar **pa, char **s, u32int max)
+{
+	uchar *dat;
+	u32int n;
+
+	if(sunVarOpaqueUnpack(a, ea, pa, &dat, &n, max) < 0)
+		goto Err;
+	/* slide string down over length to make room for NUL */
+	memmove(dat-1, dat, n);
+	dat[-1+n] = 0;
+	*s = (char*)(dat-1);
+	return 0;
+Err:
+	return -1;
+}
+
+int
+sunStringPack(uchar *a, uchar *ea, uchar **pa, char **s, u32int max)
+{
+	u32int n;
+
+	n = strlen(*s);
+	return sunVarOpaquePack(a, ea, pa, (uchar**)s, &n, max);
+}
+
+uint
+sunVarOpaqueSize(u32int n)
+{
+	return (4+n+3) & ~3;
+}
+
+int
+sunVarOpaquePack(uchar *a, uchar *ea, uchar **pa, uchar **dat, u32int *ndat, u32int max)
+{
+	if(*ndat > max || sunUint32Pack(a, ea, &a, ndat) < 0
+	|| sunFixedOpaquePack(a, ea, &a, *dat, *ndat) < 0)
+		goto Err;
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+
+int
+sunVarOpaqueUnpack(uchar *a, uchar *ea, uchar **pa, uchar **dat, u32int *ndat, u32int max)
+{
+	if(sunUint32Unpack(a, ea, &a, ndat) < 0
+	|| *ndat > max)
+		goto Err;
+	*dat = a;
+	a += (*ndat+3)&~3;
+	if(a > ea)
+		goto Err;
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+
+uint
+sunFixedOpaqueSize(u32int n)
+{
+	return (n+3) & ~3;
+}
+
+int
+sunFixedOpaquePack(uchar *a, uchar *ea, uchar **pa, uchar *dat, u32int n)
+{
+	uint nn;
+
+	nn = (n+3)&~3;
+	if(a+nn > ea)
+		goto Err;
+	memmove(a, dat, n);
+	if(nn > n)
+		memset(a+n, 0, nn-n);
+	a += nn;
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+
+int
+sunFixedOpaqueUnpack(uchar *a, uchar *ea, uchar **pa, uchar *dat, u32int n)
+{
+	uint nn;
+
+	nn = (n+3)&~3;
+	if(a+nn > ea)
+		goto Err;
+	memmove(dat, a, n);
+	a += nn;
+	*pa = a;
+	return 0;
+
+Err:
+	*pa = ea;
+	return -1;
+}
+

+ 277 - 0
sys/src/libsunrpc/server.c

@@ -0,0 +1,277 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+/*
+ * Sun RPC server; for now, no reply cache
+ */
+
+static void sunRpcProc(void*);
+static void sunRpcRequestThread(void*);
+static void sunRpcReplyThread(void*);
+static void sunRpcForkThread(void*);
+static SunProg *sunFindProg(SunSrv*, SunMsg*, SunRpc*, Channel**);
+
+typedef struct Targ Targ;
+struct Targ
+{
+	void (*fn)(void*);
+	void *arg;
+};
+
+SunSrv*
+sunSrv(void)
+{
+	SunSrv *srv;
+
+	srv = emalloc(sizeof(SunSrv));
+	srv->chatty = 0;
+	srv->crequest = chancreate(sizeof(SunMsg*), 16);
+	srv->creply = chancreate(sizeof(SunMsg*), 16);
+	srv->cthread = chancreate(sizeof(Targ), 4);
+
+	proccreate(sunRpcProc, srv, SunStackSize);
+	return srv;
+}
+
+void
+sunSrvProg(SunSrv *srv, SunProg *prog, Channel *c)
+{
+	if(srv->nprog%16 == 0){
+		srv->prog = erealloc(srv->prog, (srv->nprog+16)*sizeof(srv->prog[0]));
+		srv->cdispatch = erealloc(srv->cdispatch, (srv->nprog+16)*sizeof(srv->cdispatch[0]));
+	}
+	srv->prog[srv->nprog] = prog;
+	srv->cdispatch[srv->nprog] = c;
+	srv->nprog++;
+}
+
+static void
+sunRpcProc(void *v)
+{
+	threadcreate(sunRpcReplyThread, v, SunStackSize);
+	threadcreate(sunRpcRequestThread, v, SunStackSize);
+	threadcreate(sunRpcForkThread, v, SunStackSize);
+
+}
+
+static void
+sunRpcForkThread(void *v)
+{
+	SunSrv *srv = v;
+	Targ t;
+
+	while(recv(srv->cthread, &t) == 1)
+		threadcreate(t.fn, t.arg, SunStackSize);
+}
+
+void
+sunSrvThreadCreate(SunSrv *srv, void (*fn)(void*), void *arg)
+{
+	Targ t;
+
+	t.fn = fn;
+	t.arg = arg;
+	send(srv->cthread, &t);
+}
+
+static void
+sunRpcRequestThread(void *v)
+{
+	uchar *p, *ep;
+	Channel *c;
+	SunSrv *srv = v;
+	SunMsg *m;
+	SunProg *pg;
+	SunStatus ok;
+
+	while((m = recvp(srv->crequest)) != nil){
+		/* could look up in cache here? */
+
+if(srv->chatty) print("sun msg %p count %d\n", m, m->count);
+		m->srv = srv;
+		p = m->data;
+		ep = p+m->count;
+		if(sunRpcUnpack(p, ep, &p, &m->rpc) < 0){
+			print("in: %.*H unpack failed\n", m->count, m->data);
+			sunMsgDrop(m);
+			continue;
+		}
+		if(srv->chatty)
+			print("in: %B\n", &m->rpc);
+
+		if(srv->alwaysReject){
+			if(srv->chatty)
+				print("\trejecting\n");
+			sunMsgReplyError(m, SunAuthTooWeak);
+			continue;
+		}
+
+		if(!m->rpc.iscall){
+			sunMsgReplyError(m, SunGarbageArgs);
+			continue;
+		}
+
+		if((pg = sunFindProg(srv, m, &m->rpc, &c)) == nil){
+			/* sunFindProg sent error */
+			continue;
+		}
+
+		p = m->rpc.data;
+		ep = p+m->rpc.ndata;
+		m->call = nil;
+		if((ok = sunCallUnpackAlloc(pg, m->rpc.proc<<1, p, ep, &p, &m->call)) != SunSuccess){
+			sunMsgReplyError(m, ok);
+			continue;
+		}
+		m->call->rpc = m->rpc;
+
+		if(srv->chatty)
+			print("\t%C\n", m->call);
+
+		m->pg = pg;
+		sendp(c, m);
+	}
+}
+
+static SunProg*
+sunFindProg(SunSrv *srv, SunMsg *m, SunRpc *rpc, Channel **pc)
+{
+	int i, vlo, vhi;
+	SunProg *pg;
+
+	vlo = 0x7fffffff;
+	vhi = -1;
+
+	for(i=0; i<srv->nprog; i++){
+		pg = srv->prog[i];
+		if(pg->prog != rpc->prog)
+			continue;
+		if(pg->vers == rpc->vers){
+			*pc = srv->cdispatch[i];
+			return pg;
+		}
+		/* right program, wrong version: record range */
+		if(pg->vers < vlo)
+			vlo = pg->vers;
+		if(pg->vers > vhi)
+			vhi = pg->vers;
+	}
+	if(vhi == -1){
+		if(srv->chatty)
+			print("\tprogram %ud unavailable\n", rpc->prog);
+		sunMsgReplyError(m, SunProgUnavail);
+	}else{
+		/* putting these in rpc is a botch */
+		rpc->low = vlo;
+		rpc->high = vhi;
+		if(srv->chatty)
+			print("\tversion %ud unavailable; have %d-%d\n", rpc->vers, vlo, vhi);
+		sunMsgReplyError(m, SunProgMismatch);
+	}
+	return nil;
+}
+
+static void
+sunRpcReplyThread(void *v)
+{
+	SunMsg *m;
+	SunSrv *srv = v;
+
+	while((m = recvp(srv->creply)) != nil){
+		/* could record in cache here? */
+		sendp(m->creply, m);
+	}	
+}
+
+int
+sunMsgReplyError(SunMsg *m, SunStatus error)
+{
+	uchar *p, *bp, *ep;
+	int n;
+
+	m->rpc.status = error;
+	m->rpc.iscall = 0;
+	m->rpc.verf.flavor = SunAuthNone;
+	m->rpc.data = nil;
+	m->rpc.ndata = 0;
+
+	if(m->srv->chatty)
+		print("out: %B\n", &m->rpc);
+
+	n = sunRpcSize(&m->rpc);
+	bp = emalloc(n);
+	ep = bp+n;
+	p = bp;
+	if(sunRpcPack(p, ep, &p, &m->rpc) < 0){
+		fprint(2, "sunRpcPack failed\n");
+		sunMsgDrop(m);
+		return 0;
+	}
+	if(p != ep){
+		fprint(2, "sunMsgReplyError: rpc sizes didn't work out\n");
+		sunMsgDrop(m);
+		return 0;
+	}
+	free(m->data);
+	m->data = bp;
+	m->count = n;
+	sendp(m->srv->creply, m);
+	return 0;
+}
+
+int
+sunMsgReply(SunMsg *m, SunCall *c)
+{
+	int n1, n2;
+	uchar *bp, *p, *ep;
+
+	c->type = m->call->type+1;
+	c->rpc.iscall = 0;
+	c->rpc.prog = m->rpc.prog;
+	c->rpc.vers = m->rpc.vers;
+	c->rpc.proc = m->rpc.proc;
+	c->rpc.xid = m->rpc.xid;
+
+	if(m->srv->chatty){
+		print("out: %B\n", &c->rpc);
+		print("\t%C\n", c);
+	}
+
+	n1 = sunRpcSize(&c->rpc);
+	n2 = sunCallSize(m->pg, c);
+
+	bp = emalloc(n1+n2);
+	ep = bp+n1+n2;
+	p = bp;
+	if(sunRpcPack(p, ep, &p, &c->rpc) != SunSuccess){
+		fprint(2, "sunRpcPack failed\n");
+		return sunMsgDrop(m);
+	}
+	if(sunCallPack(m->pg, p, ep, &p, c) != SunSuccess){
+		fprint(2, "pg->pack failed\n");
+		return sunMsgDrop(m);
+	}
+	if(p != ep){
+		fprint(2, "sunMsgReply: sizes didn't work out\n");
+		return sunMsgDrop(m);
+	}
+	free(m->data);
+	m->data = bp;
+	m->count = n1+n2;
+
+	sendp(m->srv->creply, m);
+	return 0;
+}
+
+int
+sunMsgDrop(SunMsg *m)
+{
+	free(m->data);
+	free(m->call);
+	memset(m, 0xFB, sizeof *m);
+	free(m);
+	return 0;
+}
+

+ 112 - 0
sys/src/libsunrpc/udp.c

@@ -0,0 +1,112 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <thread.h>
+#include <sunrpc.h>
+
+typedef struct SunMsgUdp SunMsgUdp;
+struct SunMsgUdp
+{
+	SunMsg msg;
+	Udphdr udp;
+};
+
+typedef struct Arg Arg;
+struct Arg
+{
+	SunSrv *srv;
+	Channel *creply;
+	Channel *csync;
+	int fd;
+};
+
+enum
+{
+	UdpMaxRead = 65536+Udphdrsize
+};
+static void
+sunUdpRead(void *v)
+{
+	int n;
+	uchar *buf;
+	Arg arg = *(Arg*)v;
+	SunMsgUdp *msg;
+
+	sendp(arg.csync, 0);
+
+	buf = emalloc(UdpMaxRead);
+	while((n = read(arg.fd, buf, UdpMaxRead)) > 0){
+		if(arg.srv->chatty)
+			fprint(2, "udp got %d (%d)\n", n, Udphdrsize);
+		msg = emalloc(sizeof(SunMsgUdp));
+		memmove(&msg->udp, buf, Udphdrsize);
+		msg->msg.data = emalloc(n);
+		msg->msg.count = n-Udphdrsize;
+		memmove(msg->msg.data, buf+Udphdrsize, n-Udphdrsize);
+		memmove(&msg->udp, buf, Udphdrsize);
+		msg->msg.creply = arg.creply;
+		if(arg.srv->chatty)
+			fprint(2, "message %p count %d\n", msg, msg->msg.count);
+		sendp(arg.srv->crequest, msg);
+	}
+}
+
+static void
+sunUdpWrite(void *v)
+{
+	uchar *buf;
+	Arg arg = *(Arg*)v;
+	SunMsgUdp *msg;
+
+	sendp(arg.csync, 0);
+
+	buf = emalloc(UdpMaxRead);
+	while((msg = recvp(arg.creply)) != nil){
+		memmove(buf+Udphdrsize, msg->msg.data, msg->msg.count);
+		memmove(buf, &msg->udp, Udphdrsize);
+		msg->msg.count += Udphdrsize;
+		if(write(arg.fd, buf, msg->msg.count) != msg->msg.count)
+			fprint(2, "udpWrite: %r\n");
+		free(msg->msg.data);
+		free(msg);
+	}
+}
+
+int
+sunSrvUdp(SunSrv *srv, char *address)
+{
+	int acfd, fd;
+	char adir[40], data[60];
+	Arg *arg;
+
+	acfd = announce(address, adir);
+	if(acfd < 0)
+		return 0;
+	if(write(acfd, "headers", 7) < 0){
+		werrstr("setting headers: %r");
+		close(acfd);
+		return 0;
+	}
+	snprint(data, sizeof data, "%s/data", adir);
+	if((fd = open(data, ORDWR)) < 0){
+		werrstr("open %s: %r", data);
+		close(acfd);
+		return 0;
+	}
+	close(acfd); 
+	
+	arg = emalloc(sizeof(Arg));
+	arg->fd = fd;
+	arg->srv = srv;
+	arg->creply = chancreate(sizeof(SunMsg*), 10);
+	arg->csync = chancreate(sizeof(void*), 10);
+
+	proccreate(sunUdpRead, arg, SunStackSize);
+	proccreate(sunUdpWrite, arg, SunStackSize);
+	recvp(arg->csync);
+	recvp(arg->csync);
+	chanfree(arg->csync);
+	free(arg);
+
+	return 1;
+}

+ 1 - 0
sys/src/mkfile

@@ -28,6 +28,7 @@ LIBS=\
 	libscribble\
 	libsec\
 	libstdio\
+	libsunrpc\
 	libthread\
 	libventi\