summaryrefslogtreecommitdiff
path: root/appl/cmd
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/cmd')
-rw-r--r--appl/cmd/9660srv.b1504
-rw-r--r--appl/cmd/9export.b180
-rw-r--r--appl/cmd/9srvfs.b99
-rw-r--r--appl/cmd/9win.b453
-rw-r--r--appl/cmd/B.b107
-rw-r--r--appl/cmd/archfs.b630
-rw-r--r--appl/cmd/auplay.b114
-rw-r--r--appl/cmd/auth/aescbc.b254
-rw-r--r--appl/cmd/auth/changelogin.b305
-rw-r--r--appl/cmd/auth/convpasswd.b120
-rw-r--r--appl/cmd/auth/countersigner.b59
-rw-r--r--appl/cmd/auth/createsignerkey.b144
-rw-r--r--appl/cmd/auth/factotum/authio.m80
-rw-r--r--appl/cmd/auth/factotum/factotum.b978
-rw-r--r--appl/cmd/auth/factotum/feedkey.b321
-rw-r--r--appl/cmd/auth/factotum/mkfile27
-rw-r--r--appl/cmd/auth/factotum/proto/infauth.b362
-rw-r--r--appl/cmd/auth/factotum/proto/keyreps.b173
-rw-r--r--appl/cmd/auth/factotum/proto/keyreps.m23
-rw-r--r--appl/cmd/auth/factotum/proto/mkfile22
-rw-r--r--appl/cmd/auth/factotum/proto/p9any.b232
-rw-r--r--appl/cmd/auth/factotum/proto/pass.b29
-rw-r--r--appl/cmd/auth/factotum/rpc.b68
-rw-r--r--appl/cmd/auth/getpk.b83
-rw-r--r--appl/cmd/auth/keyfs.b806
-rw-r--r--appl/cmd/auth/keysrv.b199
-rw-r--r--appl/cmd/auth/logind.b244
-rw-r--r--appl/cmd/auth/mkauthinfo.b125
-rw-r--r--appl/cmd/auth/mkfile38
-rw-r--r--appl/cmd/auth/passwd.b290
-rw-r--r--appl/cmd/auth/secstore.b317
-rw-r--r--appl/cmd/auth/signer.b132
-rw-r--r--appl/cmd/auth/verify.b85
-rw-r--r--appl/cmd/auxi/cpuslave.b79
-rw-r--r--appl/cmd/auxi/digest.b91
-rw-r--r--appl/cmd/auxi/fpgaload.b67
-rw-r--r--appl/cmd/auxi/mangaload.b362
-rw-r--r--appl/cmd/auxi/mkfile24
-rw-r--r--appl/cmd/auxi/pcmcia.b491
-rw-r--r--appl/cmd/auxi/rdbgsrv.b222
-rw-r--r--appl/cmd/auxi/rstyxd.b114
-rw-r--r--appl/cmd/avr/burn.b859
-rw-r--r--appl/cmd/avr/mkfile10
-rw-r--r--appl/cmd/basename.b50
-rw-r--r--appl/cmd/bind.b66
-rw-r--r--appl/cmd/bit2gif.b86
-rw-r--r--appl/cmd/broke.b84
-rw-r--r--appl/cmd/bytes.b212
-rw-r--r--appl/cmd/cal.b295
-rw-r--r--appl/cmd/cat.b57
-rw-r--r--appl/cmd/cd.b48
-rw-r--r--appl/cmd/chgrp.b58
-rw-r--r--appl/cmd/chmod.b125
-rw-r--r--appl/cmd/cleanname.b45
-rw-r--r--appl/cmd/cmp.b151
-rwxr-xr-xappl/cmd/comm.b124
-rw-r--r--appl/cmd/cook.b1924
-rw-r--r--appl/cmd/cp.b237
-rw-r--r--appl/cmd/cprof.b190
-rw-r--r--appl/cmd/cpu.b168
-rw-r--r--appl/cmd/crypt.b234
-rw-r--r--appl/cmd/date.b71
-rw-r--r--appl/cmd/dbfs.b518
-rwxr-xr-xappl/cmd/dbm/delete.b34
-rwxr-xr-xappl/cmd/dbm/fetch.b37
-rwxr-xr-xappl/cmd/dbm/keys.b32
-rwxr-xr-xappl/cmd/dbm/list.b34
-rw-r--r--appl/cmd/dbm/mkfile19
-rwxr-xr-xappl/cmd/dbm/store.b69
-rw-r--r--appl/cmd/dd.b625
-rw-r--r--appl/cmd/dial.b148
-rw-r--r--appl/cmd/diff.b858
-rw-r--r--appl/cmd/disdep.b250
-rw-r--r--appl/cmd/disdump.b52
-rw-r--r--appl/cmd/disk/format.b755
-rw-r--r--appl/cmd/disk/ftl.b911
-rw-r--r--appl/cmd/disk/kfs.b3842
-rw-r--r--appl/cmd/disk/kfscmd.b53
-rw-r--r--appl/cmd/disk/mbr.b134
-rw-r--r--appl/cmd/disk/mkext.b377
-rw-r--r--appl/cmd/disk/mkfile25
-rw-r--r--appl/cmd/disk/mkfs.b778
-rw-r--r--appl/cmd/disk/prep/calc.tab.b454
-rw-r--r--appl/cmd/disk/prep/calc.tab.m7
-rw-r--r--appl/cmd/disk/prep/calc.y174
-rw-r--r--appl/cmd/disk/prep/fdisk.b925
-rw-r--r--appl/cmd/disk/prep/mkfile26
-rw-r--r--appl/cmd/disk/prep/pedit.b504
-rw-r--r--appl/cmd/disk/prep/pedit.m53
-rw-r--r--appl/cmd/disk/prep/prep.b509
-rw-r--r--appl/cmd/dossrv.b3432
-rw-r--r--appl/cmd/du.b163
-rw-r--r--appl/cmd/echo.b36
-rw-r--r--appl/cmd/ed.b1588
-rw-r--r--appl/cmd/emuinit.b110
-rw-r--r--appl/cmd/env.b53
-rw-r--r--appl/cmd/export.b57
-rw-r--r--appl/cmd/fc.b612
-rw-r--r--appl/cmd/fcp.b312
-rwxr-xr-xappl/cmd/fmt.b204
-rw-r--r--appl/cmd/fone.b560
-rwxr-xr-xappl/cmd/fortune.b100
-rwxr-xr-xappl/cmd/freq.b112
-rw-r--r--appl/cmd/fs.b109
-rw-r--r--appl/cmd/fs/and.b65
-rw-r--r--appl/cmd/fs/bundle.b195
-rw-r--r--appl/cmd/fs/chstat.b185
-rw-r--r--appl/cmd/fs/compose.b100
-rw-r--r--appl/cmd/fs/depth.b49
-rw-r--r--appl/cmd/fs/entries.b86
-rw-r--r--appl/cmd/fs/eval.b648
-rw-r--r--appl/cmd/fs/exec.b162
-rw-r--r--appl/cmd/fs/filter.b64
-rw-r--r--appl/cmd/fs/ls.b97
-rw-r--r--appl/cmd/fs/match.b79
-rw-r--r--appl/cmd/fs/merge.b187
-rw-r--r--appl/cmd/fs/mergewrite.b186
-rw-r--r--appl/cmd/fs/mkfile60
-rw-r--r--appl/cmd/fs/mode.b120
-rw-r--r--appl/cmd/fs/not.b48
-rw-r--r--appl/cmd/fs/or.b65
-rw-r--r--appl/cmd/fs/path.b77
-rw-r--r--appl/cmd/fs/pipe.b223
-rw-r--r--appl/cmd/fs/print.b51
-rw-r--r--appl/cmd/fs/proto.b388
-rw-r--r--appl/cmd/fs/query.b130
-rw-r--r--appl/cmd/fs/readfile.b144
-rw-r--r--appl/cmd/fs/run.b60
-rw-r--r--appl/cmd/fs/select.b56
-rw-r--r--appl/cmd/fs/setroot.b104
-rw-r--r--appl/cmd/fs/size.b54
-rw-r--r--appl/cmd/fs/template.b35
-rw-r--r--appl/cmd/fs/unbundle.b259
-rw-r--r--appl/cmd/fs/void.b33
-rw-r--r--appl/cmd/fs/walk.b233
-rw-r--r--appl/cmd/fs/write.b111
-rw-r--r--appl/cmd/ftest.b153
-rw-r--r--appl/cmd/ftpfs.b1959
-rw-r--r--appl/cmd/getauthinfo.b185
-rw-r--r--appl/cmd/getfile.b74
-rw-r--r--appl/cmd/gettar.b248
-rw-r--r--appl/cmd/gif2bit.b101
-rw-r--r--appl/cmd/grep.b155
-rw-r--r--appl/cmd/gunzip.b139
-rw-r--r--appl/cmd/gzip.b228
-rw-r--r--appl/cmd/idea.b116
-rw-r--r--appl/cmd/import.b192
-rw-r--r--appl/cmd/install/NOTICE6
-rw-r--r--appl/cmd/install/applylog.b699
-rw-r--r--appl/cmd/install/arch.b288
-rw-r--r--appl/cmd/install/arch.m36
-rw-r--r--appl/cmd/install/archfs.b579
-rw-r--r--appl/cmd/install/archfs.m7
-rw-r--r--appl/cmd/install/ckproto.b267
-rw-r--r--appl/cmd/install/create.b445
-rw-r--r--appl/cmd/install/eproto.b357
-rw-r--r--appl/cmd/install/info.b73
-rw-r--r--appl/cmd/install/inst.b500
-rw-r--r--appl/cmd/install/install.b430
-rw-r--r--appl/cmd/install/log.b76
-rw-r--r--appl/cmd/install/logs.b287
-rw-r--r--appl/cmd/install/logs.m44
-rw-r--r--appl/cmd/install/mergelog.b239
-rw-r--r--appl/cmd/install/mkfile43
-rw-r--r--appl/cmd/install/mkproto.b99
-rw-r--r--appl/cmd/install/proto.b320
-rw-r--r--appl/cmd/install/proto.m6
-rw-r--r--appl/cmd/install/proto2list.b209
-rw-r--r--appl/cmd/install/protocaller.m8
-rw-r--r--appl/cmd/install/updatelog.b386
-rw-r--r--appl/cmd/install/wdiff.b148
-rw-r--r--appl/cmd/install/wfind.b204
-rw-r--r--appl/cmd/install/wrap.b684
-rw-r--r--appl/cmd/install/wrap.m41
-rw-r--r--appl/cmd/install/wrap2list.b305
-rw-r--r--appl/cmd/iostats.b635
-rw-r--r--appl/cmd/ip/bootpd.b662
-rw-r--r--appl/cmd/ip/dhcp.b162
-rw-r--r--appl/cmd/ip/mkfile30
-rw-r--r--appl/cmd/ip/nppp/mkfile24
-rw-r--r--appl/cmd/ip/nppp/modem.b469
-rw-r--r--appl/cmd/ip/nppp/modem.m47
-rw-r--r--appl/cmd/ip/nppp/pppchat.b322
-rw-r--r--appl/cmd/ip/nppp/ppplink.b782
-rw-r--r--appl/cmd/ip/nppp/ppptest.b90
-rw-r--r--appl/cmd/ip/nppp/script.b171
-rw-r--r--appl/cmd/ip/nppp/script.m15
-rw-r--r--appl/cmd/ip/obootpd.b777
-rw-r--r--appl/cmd/ip/ping.b369
-rw-r--r--appl/cmd/ip/ppp/mkfile27
-rw-r--r--appl/cmd/ip/ppp/modem.b468
-rw-r--r--appl/cmd/ip/ppp/modem.m41
-rw-r--r--appl/cmd/ip/ppp/pppclient.b216
-rw-r--r--appl/cmd/ip/ppp/pppclient.m31
-rw-r--r--appl/cmd/ip/ppp/pppdial.b283
-rw-r--r--appl/cmd/ip/ppp/pppgui.b373
-rw-r--r--appl/cmd/ip/ppp/pppgui.m21
-rw-r--r--appl/cmd/ip/ppp/ppptest.b86
-rw-r--r--appl/cmd/ip/ppp/script.b168
-rw-r--r--appl/cmd/ip/ppp/script.m14
-rw-r--r--appl/cmd/ip/rip.b620
-rw-r--r--appl/cmd/ip/sntp.b313
-rw-r--r--appl/cmd/ip/tftpd.b514
-rw-r--r--appl/cmd/ip/virgild.b127
-rw-r--r--appl/cmd/irtest.b70
-rw-r--r--appl/cmd/itest.b478
-rw-r--r--appl/cmd/itreplay.b230
-rw-r--r--appl/cmd/kill.b146
-rw-r--r--appl/cmd/lc.b156
-rw-r--r--appl/cmd/lego/clock.b214
-rw-r--r--appl/cmd/lego/clockface.b384
-rw-r--r--appl/cmd/lego/firmdl.b294
-rw-r--r--appl/cmd/lego/link.b603
-rw-r--r--appl/cmd/lego/mkfile23
-rw-r--r--appl/cmd/lego/rcxsend.b240
-rw-r--r--appl/cmd/lego/rcxsend.m6
-rw-r--r--appl/cmd/lego/send.b86
-rw-r--r--appl/cmd/lego/timers.b263
-rw-r--r--appl/cmd/lego/timers.m17
-rw-r--r--appl/cmd/limbo/arg.m50
-rw-r--r--appl/cmd/limbo/asm.b263
-rw-r--r--appl/cmd/limbo/com.b1387
-rw-r--r--appl/cmd/limbo/decls.b1177
-rw-r--r--appl/cmd/limbo/dis.b560
-rw-r--r--appl/cmd/limbo/disoptab.m355
-rw-r--r--appl/cmd/limbo/ecom.b2345
-rw-r--r--appl/cmd/limbo/gen.b1012
-rw-r--r--appl/cmd/limbo/isa.m247
-rw-r--r--appl/cmd/limbo/lex.b1146
-rw-r--r--appl/cmd/limbo/limbo.b3099
-rw-r--r--appl/cmd/limbo/limbo.m527
-rw-r--r--appl/cmd/limbo/limbo.y1973
-rw-r--r--appl/cmd/limbo/mkfile35
-rw-r--r--appl/cmd/limbo/nodes.b1402
-rw-r--r--appl/cmd/limbo/opname.m109
-rw-r--r--appl/cmd/limbo/optim.b3
-rw-r--r--appl/cmd/limbo/sbl.b397
-rw-r--r--appl/cmd/limbo/stubs.b575
-rw-r--r--appl/cmd/limbo/typecheck.b3223
-rw-r--r--appl/cmd/limbo/types.b4234
-rw-r--r--appl/cmd/listen.b261
-rw-r--r--appl/cmd/lockfs.b773
-rw-r--r--appl/cmd/logfile.b259
-rwxr-xr-xappl/cmd/look.b393
-rw-r--r--appl/cmd/lookman.b250
-rw-r--r--appl/cmd/ls.b318
-rw-r--r--appl/cmd/lstar.b120
-rw-r--r--appl/cmd/man.b199
-rw-r--r--appl/cmd/man2txt.b79
-rw-r--r--appl/cmd/manufacture.b42
-rw-r--r--appl/cmd/mash/builtins.b347
-rw-r--r--appl/cmd/mash/depends.b228
-rw-r--r--appl/cmd/mash/dump.b199
-rw-r--r--appl/cmd/mash/exec.b401
-rw-r--r--appl/cmd/mash/expr.b158
-rw-r--r--appl/cmd/mash/eyacc.b2785
-rw-r--r--appl/cmd/mash/eyaccpar223
-rw-r--r--appl/cmd/mash/history.b206
-rw-r--r--appl/cmd/mash/lex.b547
-rw-r--r--appl/cmd/mash/make.b723
-rw-r--r--appl/cmd/mash/mash.b154
-rw-r--r--appl/cmd/mash/mash.m372
-rw-r--r--appl/cmd/mash/mash.y269
-rw-r--r--appl/cmd/mash/mashfile36
-rw-r--r--appl/cmd/mash/mashlib.b60
-rw-r--r--appl/cmd/mash/mashparse.b662
-rw-r--r--appl/cmd/mash/mashparse.m56
-rw-r--r--appl/cmd/mash/misc.b313
-rw-r--r--appl/cmd/mash/mkfile78
-rw-r--r--appl/cmd/mash/serve.b154
-rw-r--r--appl/cmd/mash/symb.b265
-rw-r--r--appl/cmd/mash/tk.b603
-rw-r--r--appl/cmd/mash/xeq.b543
-rw-r--r--appl/cmd/mathcalc.b79
-rw-r--r--appl/cmd/mc.b2547
-rw-r--r--appl/cmd/md5sum.b65
-rw-r--r--appl/cmd/mdb.b335
-rw-r--r--appl/cmd/memfs.b648
-rw-r--r--appl/cmd/metamorph.b94
-rw-r--r--appl/cmd/mk/ar.m26
-rw-r--r--appl/cmd/mk/mk.b4211
-rw-r--r--appl/cmd/mk/mkbinds2
-rw-r--r--appl/cmd/mk/mkconfig28
-rw-r--r--appl/cmd/mk/mkfile19
-rw-r--r--appl/cmd/mk/mksubdirs16
-rw-r--r--appl/cmd/mkdir.b75
-rw-r--r--appl/cmd/mkfile219
-rw-r--r--appl/cmd/mntgen.b188
-rw-r--r--appl/cmd/mount.b348
-rw-r--r--appl/cmd/mouse.b394
-rw-r--r--appl/cmd/mpc/mkfile14
-rw-r--r--appl/cmd/mpc/qconfig.b193
-rw-r--r--appl/cmd/mpc/qflash.b188
-rw-r--r--appl/cmd/mprof.b260
-rw-r--r--appl/cmd/mv.b184
-rw-r--r--appl/cmd/ndb/cs.b676
-rw-r--r--appl/cmd/ndb/csquery.b97
-rw-r--r--appl/cmd/ndb/dns.b1860
-rw-r--r--appl/cmd/ndb/dnsquery.b177
-rw-r--r--appl/cmd/ndb/mkfile28
-rw-r--r--appl/cmd/ndb/mkhash.b119
-rw-r--r--appl/cmd/ndb/query.b135
-rw-r--r--appl/cmd/ndb/registry.b671
-rw-r--r--appl/cmd/ndb/regquery.b104
-rw-r--r--appl/cmd/netkey.b166
-rw-r--r--appl/cmd/netstat.b91
-rw-r--r--appl/cmd/newer.b36
-rw-r--r--appl/cmd/ns.b157
-rw-r--r--appl/cmd/nsbuild.b41
-rw-r--r--appl/cmd/os.b155
-rw-r--r--appl/cmd/p.b141
-rw-r--r--appl/cmd/palm/connex.b124
-rw-r--r--appl/cmd/palm/desklink.b843
-rw-r--r--appl/cmd/palm/desklink.m90
-rw-r--r--appl/cmd/palm/mkfile16
-rw-r--r--appl/cmd/palm/palmsrv.b901
-rw-r--r--appl/cmd/pause.b17
-rw-r--r--appl/cmd/plumb.b115
-rw-r--r--appl/cmd/plumber.b766
-rw-r--r--appl/cmd/prof.b243
-rw-r--r--appl/cmd/promptstring.b66
-rw-r--r--appl/cmd/ps.b61
-rw-r--r--appl/cmd/puttar.b183
-rw-r--r--appl/cmd/pwd.b28
-rw-r--r--appl/cmd/ramfile.b97
-rw-r--r--appl/cmd/randpass.b45
-rw-r--r--appl/cmd/raw2iaf.b122
-rw-r--r--appl/cmd/rawdbfs.b813
-rw-r--r--appl/cmd/rcmd.b170
-rw-r--r--appl/cmd/rdp.b1230
-rw-r--r--appl/cmd/read.b62
-rw-r--r--appl/cmd/rioimport.b620
-rw-r--r--appl/cmd/rm.b99
-rw-r--r--appl/cmd/runas.b60
-rw-r--r--appl/cmd/sed.b908
-rw-r--r--appl/cmd/sendmail.b252
-rw-r--r--appl/cmd/sh/arg.b181
-rw-r--r--appl/cmd/sh/csv.b244
-rw-r--r--appl/cmd/sh/doc/History14
-rw-r--r--appl/cmd/sh/echo.b96
-rw-r--r--appl/cmd/sh/expr.b281
-rw-r--r--appl/cmd/sh/file2chan.b459
-rw-r--r--appl/cmd/sh/mkfile60
-rw-r--r--appl/cmd/sh/regex.b220
-rw-r--r--appl/cmd/sh/sexprs.b271
-rw-r--r--appl/cmd/sh/sh.b2843
-rw-r--r--appl/cmd/sh/sh.y2592
-rw-r--r--appl/cmd/sh/std.b812
-rw-r--r--appl/cmd/sh/string.b212
-rw-r--r--appl/cmd/sh/test.b96
-rw-r--r--appl/cmd/sh/tk.b426
-rw-r--r--appl/cmd/sha1sum.b65
-rw-r--r--appl/cmd/shutdown.b72
-rw-r--r--appl/cmd/sleep.b46
-rw-r--r--appl/cmd/sort.b129
-rw-r--r--appl/cmd/spki/mkfile22
-rw-r--r--appl/cmd/spki/verify.b107
-rw-r--r--appl/cmd/src.b28
-rw-r--r--appl/cmd/stack.b184
-rw-r--r--appl/cmd/stackv.b445
-rw-r--r--appl/cmd/stream.b98
-rw-r--r--appl/cmd/strings.b87
-rw-r--r--appl/cmd/styxchat.b557
-rw-r--r--appl/cmd/styxlisten.b262
-rw-r--r--appl/cmd/styxmon.b110
-rw-r--r--appl/cmd/sum.b59
-rw-r--r--appl/cmd/tail.b379
-rw-r--r--appl/cmd/tarfs.b411
-rw-r--r--appl/cmd/tclsh.b48
-rw-r--r--appl/cmd/tcs.b184
-rw-r--r--appl/cmd/tee.b79
-rw-r--r--appl/cmd/telnet.b482
-rw-r--r--appl/cmd/test.b278
-rw-r--r--appl/cmd/time.b97
-rw-r--r--appl/cmd/timestamp.b42
-rw-r--r--appl/cmd/tkcmd.b190
-rw-r--r--appl/cmd/tokenize.b33
-rw-r--r--appl/cmd/touch.b77
-rw-r--r--appl/cmd/touchcal.b278
-rw-r--r--appl/cmd/tr.b319
-rw-r--r--appl/cmd/tsort.b133
-rw-r--r--appl/cmd/unicode.b162
-rw-r--r--appl/cmd/uniq.b79
-rw-r--r--appl/cmd/units.b1061
-rw-r--r--appl/cmd/units.y771
-rw-r--r--appl/cmd/unmount.b44
-rw-r--r--appl/cmd/usb/mkfile11
-rw-r--r--appl/cmd/usb/usbd.b835
-rw-r--r--appl/cmd/uudecode.b132
-rw-r--r--appl/cmd/uuencode.b101
-rw-r--r--appl/cmd/wav2iaf.b171
-rw-r--r--appl/cmd/wc.b303
-rw-r--r--appl/cmd/webgrab.b532
-rw-r--r--appl/cmd/wish.b191
-rw-r--r--appl/cmd/wmexport.b557
-rw-r--r--appl/cmd/wmimport.b64
-rw-r--r--appl/cmd/xargs.b86
-rw-r--r--appl/cmd/xd.b316
-rw-r--r--appl/cmd/xmount.b231
-rw-r--r--appl/cmd/yacc.b2810
-rw-r--r--appl/cmd/zeros.b68
401 files changed, 142101 insertions, 0 deletions
diff --git a/appl/cmd/9660srv.b b/appl/cmd/9660srv.b
new file mode 100644
index 00000000..17fa053b
--- /dev/null
+++ b/appl/cmd/9660srv.b
@@ -0,0 +1,1504 @@
+implement ISO9660;
+
+include "sys.m";
+ sys: Sys;
+ Dir, Qid, QTDIR, QTFILE, DMDIR: import sys;
+
+include "draw.m";
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "string.m";
+ str: String;
+
+include "styx.m";
+ styx: Styx;
+ Rmsg, Tmsg: import styx;
+
+include "arg.m";
+
+ISO9660: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Sectorsize: con 2048;
+Maxname: con 256;
+
+Enonexist: con "file does not exist";
+Eperm: con "permission denied";
+Enofile: con "no file system specified";
+Eauth: con "authentication failed";
+Ebadfid: con "invalid fid";
+Efidinuse: con "fid already in use";
+Enotdir: con "not a directory";
+Esyntax: con "file name syntax";
+
+devname: string;
+
+chatty := 0;
+showstyx := 0;
+progname := "9660srv";
+stderr: ref Sys->FD;
+noplan9 := 0;
+nojoliet := 0;
+norock := 0;
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: %s [-rabc] [-9JR] [-s] cd_device dir\n", progname);
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ stderr = sys->fildes(2);
+
+ if(args != nil)
+ progname = hd args;
+ styx = load Styx Styx->PATH;
+ if(styx == nil)
+ noload(Styx->PATH);
+ styx->init();
+
+ if(args != nil)
+ progname = hd args;
+ mountopt := Sys->MREPL;
+ copt := 0;
+ stdio := 0;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ noload(Arg->PATH);
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'v' or 'D' => chatty = 1; showstyx = 1;
+ 'r' => mountopt = Sys->MREPL;
+ 'a' => mountopt = Sys->MAFTER;
+ 'b' => mountopt = Sys->MBEFORE;
+ 'c' => copt = Sys->MCREATE;
+ 's' => stdio = 1;
+ '9' => noplan9 = 1;
+ 'J' => nojoliet = 1;
+ 'R' => norock = 1;
+ * => usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(args == nil || tl args == nil)
+ usage();
+ what := hd args;
+ mountpt := hd tl args;
+
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ noload(Daytime->PATH);
+
+ iobufinit(Sectorsize);
+
+ pip := array[2] of ref Sys->FD;
+ if(stdio){
+ pip[0] = sys->fildes(0);
+ pip[1] = sys->fildes(1);
+ }else
+ if(sys->pipe(pip) < 0)
+ error(sys->sprint("can't create pipe: %r"));
+
+ devname = what;
+
+ sync := chan of int;
+ spawn fileserve(pip[1], sync);
+ <-sync;
+
+ if(sys->mount(pip[0], nil, mountpt, mountopt|copt, nil) < 0) {
+ sys->fprint(sys->fildes(2), "%s: mount %s %s failed: %r\n", progname, what, mountpt);
+ exit;
+ }
+}
+
+noload(s: string)
+{
+ sys->fprint(sys->fildes(2), "%s: can't load %s: %r\n", progname, s);
+ raise "fail:load";
+}
+
+error(p: string)
+{
+ sys->fprint(sys->fildes(2), "9660srv: %s\n", p);
+ raise "fail:error";
+}
+
+fileserve(rfd: ref Sys->FD, sync: chan of int)
+{
+ sys->pctl(Sys->NEWFD|Sys->FORKNS, list of {2, rfd.fd});
+ rfd = sys->fildes(rfd.fd);
+ stderr = sys->fildes(2);
+ sync <-= 1;
+ while((m := Tmsg.read(rfd, 0)) != nil){
+ if(showstyx)
+ chat(sys->sprint("%s...", m.text()));
+ r: ref Rmsg;
+ pick t := m {
+ Readerror =>
+ error(sys->sprint("mount read error: %s", t.error));
+ Version =>
+ r = rversion(t);
+ Auth =>
+ r = rauth(t);
+ Flush =>
+ r = rflush(t);
+ Attach =>
+ r = rattach(t);
+ Walk =>
+ r = rwalk(t);
+ Open =>
+ r = ropen(t);
+ Create =>
+ r = rcreate(t);
+ Read =>
+ r = rread(t);
+ Write =>
+ r = rwrite(t);
+ Clunk =>
+ r = rclunk(t);
+ Remove =>
+ r = rremove(t);
+ Stat =>
+ r = rstat(t);
+ Wstat =>
+ r = rwstat(t);
+ * =>
+ error(sys->sprint("invalid T-message tag: %d", tagof m));
+ }
+ pick e := r {
+ Error =>
+ r.tag = m.tag;
+ }
+ rbuf := r.pack();
+ if(rbuf == nil)
+ error("bad R-message conversion");
+ if(showstyx)
+ chat(r.text()+"\n");
+ if(styx->write(rfd, rbuf, len rbuf) != len rbuf)
+ error(sys->sprint("connection write error: %r"));
+ }
+
+ if(chatty)
+ chat("server end of file\n");
+}
+
+E(s: string): ref Rmsg.Error
+{
+ return ref Rmsg.Error(0, s);
+}
+
+rversion(t: ref Tmsg.Version): ref Rmsg
+{
+ (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION);
+ return ref Rmsg.Version(t.tag, msize, version);
+}
+
+rauth(t: ref Tmsg.Auth): ref Rmsg
+{
+ return ref Rmsg.Error(t.tag, "authentication not required");
+}
+
+rflush(t: ref Tmsg.Flush): ref Rmsg
+{
+ return ref Rmsg.Flush(t.tag);
+}
+
+rattach(t: ref Tmsg.Attach): ref Rmsg
+{
+ dname := devname;
+ if(t.aname != "")
+ dname = t.aname;
+ (dev, err) := devattach(dname, Sys->OREAD, Sectorsize);
+ if(dev == nil)
+ return E(err);
+
+ xf := Xfs.new(dev);
+ root := cleanfid(t.fid);
+ root.qid = Sys->Qid(big 0, 0, Sys->QTDIR);
+ root.xf = xf;
+ err = root.attach();
+ if(err != nil){
+ clunkfid(t.fid);
+ return E(err);
+ }
+ xf.rootqid = root.qid;
+ return ref Rmsg.Attach(t.tag, root.qid);
+}
+
+walk1(f: ref Xfile, name: string): string
+{
+ if(!(f.qid.qtype & Sys->QTDIR))
+ return Enotdir;
+ case name {
+ "." =>
+ return nil; # nop, but shouldn't happen
+ ".." =>
+ if(f.qid.path==f.xf.rootqid.path)
+ return nil;
+ return f.walkup();
+ * =>
+ return f.walk(name);
+ }
+}
+
+rwalk(t: ref Tmsg.Walk): ref Rmsg
+{
+ f:=findfid(t.fid);
+ if(f == nil)
+ return E(Ebadfid);
+ nf, sf: ref Xfile;
+ if(t.newfid != t.fid){
+ nf = cleanfid(t.newfid);
+ if(nf == nil)
+ return E(Efidinuse);
+ f.clone(nf);
+ f = nf;
+ }else
+ sf = f.save();
+
+ qids: array of Sys->Qid;
+ if(len t.names > 0){
+ qids = array[len t.names] of Sys->Qid;
+ for(i := 0; i < len t.names; i++){
+ e := walk1(f, t.names[i]);
+ if(e != nil){
+ if(nf != nil){
+ nf.clunk();
+ clunkfid(t.newfid);
+ }else
+ f.restore(sf);
+ if(i == 0)
+ return E(e);
+ return ref Rmsg.Walk(t.tag, qids[0:i]);
+ }
+ qids[i] = f.qid;
+ }
+ }
+ return ref Rmsg.Walk(t.tag, qids);
+}
+
+ropen(t: ref Tmsg.Open): ref Rmsg
+{
+ f := findfid(t.fid);
+ if(f == nil)
+ return E(Ebadfid);
+ if(f.flags&Omodes)
+ return E("open on open file");
+ e := f.open(t.mode);
+ if(e != nil)
+ return E(e);
+ f.flags = openflags(t.mode);
+ return ref Rmsg.Open(t.tag, f.qid, Styx->MAXFDATA);
+}
+
+rcreate(t: ref Tmsg.Create): ref Rmsg
+{
+ name := t.name;
+ if(name == "." || name == "..")
+ return E(Esyntax);
+ f := findfid(t.fid);
+ if(f == nil)
+ return E(Ebadfid);
+ if(f.flags&Omodes)
+ return E("create on open file");
+ if(!(f.qid.qtype&Sys->QTDIR))
+ return E("create in non-directory");
+ e := f.create(name, t.perm, t.mode);
+ if(e != nil)
+ return E(e);
+ f.flags = openflags(t.mode);
+ return ref Rmsg.Create(t.tag, f.qid, Styx->MAXFDATA);
+}
+
+rread(t: ref Tmsg.Read): ref Rmsg
+{
+ err: string;
+
+ f := findfid(t.fid);
+ if(f == nil)
+ return E(Ebadfid);
+ if (!(f.flags&Oread))
+ return E("file not opened for reading");
+ if(t.count < 0 || t.offset < big 0)
+ return E("negative offset or count");
+ b := array[Styx->MAXFDATA] of byte;
+ count: int;
+ if(f.qid.qtype & Sys->QTDIR)
+ (count, err) = f.readdir(b, int t.offset, t.count);
+ else
+ (count, err) = f.read(b, int t.offset, t.count);
+ if(err != nil)
+ return E(err);
+ if(count != len b)
+ b = b[0:count];
+ return ref Rmsg.Read(t.tag, b);
+}
+
+rwrite(nil: ref Tmsg.Write): ref Rmsg
+{
+ return E(Eperm);
+}
+
+rclunk(t: ref Tmsg.Clunk): ref Rmsg
+{
+ f := findfid(t.fid);
+ if(f == nil)
+ return E(Ebadfid);
+ f.clunk();
+ clunkfid(t.fid);
+ return ref Rmsg.Clunk(t.tag);
+}
+
+rremove(t: ref Tmsg.Remove): ref Rmsg
+{
+ f := findfid(t.fid);
+ if(f == nil)
+ return E(Ebadfid);
+ f.clunk();
+ clunkfid(t.fid);
+ return E(Eperm);
+}
+
+rstat(t: ref Tmsg.Stat): ref Rmsg
+{
+ f := findfid(t.fid);
+ if(f == nil)
+ return E(Ebadfid);
+ (dir, nil) := f.stat();
+ return ref Rmsg.Stat(t.tag, *dir);
+}
+
+rwstat(nil: ref Tmsg.Wstat): ref Rmsg
+{
+ return E(Eperm);
+}
+
+openflags(mode: int): int
+{
+ flags := 0;
+ case mode & ~(Sys->OTRUNC|Sys->ORCLOSE) {
+ Sys->OREAD =>
+ flags = Oread;
+ Sys->OWRITE =>
+ flags = Owrite;
+ Sys->ORDWR =>
+ flags = Oread|Owrite;
+ }
+ if(mode & Sys->ORCLOSE)
+ flags |= Orclose;
+ return flags;
+}
+
+chat(s: string)
+{
+ if(chatty)
+ sys->fprint(stderr, "%s", s);
+}
+
+Fid: adt {
+ fid: int;
+ file: ref Xfile;
+};
+
+FIDMOD: con 127; # prime
+fids := array[FIDMOD] of list of ref Fid;
+
+hashfid(fid: int): (ref Fid, array of list of ref Fid)
+{
+ nl: list of ref Fid;
+
+ hp := fids[fid%FIDMOD:];
+ nl = nil;
+ for(l := hp[0]; l != nil; l = tl l){
+ f := hd l;
+ if(f.fid == fid){
+ l = tl l; # excluding f
+ for(; nl != nil; nl = tl nl)
+ l = (hd nl) :: l; # put examined ones back, in order
+ hp[0] = l;
+ return (f, hp);
+ } else
+ nl = f :: nl;
+ }
+ return (nil, hp);
+}
+
+findfid(fid: int): ref Xfile
+{
+ (f, hp) := hashfid(fid);
+ if(f == nil){
+ chat("unassigned fid");
+ return nil;
+ }
+ hp[0] = f :: hp[0];
+ return f.file;
+}
+
+cleanfid(fid: int): ref Xfile
+{
+ (f, hp) := hashfid(fid);
+ if(f != nil){
+ chat("fid in use");
+ return nil;
+ }
+ f = ref Fid;
+ f.fid = fid;
+ f.file = Xfile.new();
+ hp[0] = f :: hp[0];
+ return f.file.clean();
+}
+
+clunkfid(fid: int)
+{
+ (f, nil) := hashfid(fid);
+ if(f != nil)
+ f.file.clean();
+}
+
+#
+#
+#
+
+Xfs: adt {
+ d: ref Device;
+ inuse: int;
+ issusp: int; # system use sharing protocol in use?
+ suspoff: int; # LEN_SKP, if so
+ isplan9: int; # has Plan 9-specific directory info
+ isrock: int; # is rock ridge
+ rootqid: Sys->Qid;
+ ptr: int; # tag for private data
+
+ new: fn(nil: ref Device): ref Xfs;
+ incref: fn(nil: self ref Xfs);
+ decref: fn(nil: self ref Xfs);
+};
+
+Xfile: adt {
+ xf: ref Xfs;
+ flags: int;
+ qid: Sys->Qid;
+ ptr: ref Isofile; # tag for private data
+
+ new: fn(): ref Xfile;
+ clean: fn(nil: self ref Xfile): ref Xfile;
+
+ save: fn(nil: self ref Xfile): ref Xfile;
+ restore: fn(nil: self ref Xfile, s: ref Xfile);
+
+ attach: fn(nil: self ref Xfile): string;
+ clone: fn(nil: self ref Xfile, nil: ref Xfile);
+ walkup: fn(nil: self ref Xfile): string;
+ walk: fn(nil: self ref Xfile, nil: string): string;
+ open: fn(nil: self ref Xfile, nil: int): string;
+ create: fn(nil: self ref Xfile, nil: string, nil: int, nil: int): string;
+ readdir: fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string);
+ read: fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string);
+ write: fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string);
+ clunk: fn(nil: self ref Xfile);
+ remove: fn(nil: self ref Xfile): string;
+ stat: fn(nil: self ref Xfile): (ref Sys->Dir, string);
+ wstat: fn(nil: self ref Xfile, nil: ref Sys->Dir): string;
+};
+
+Oread, Owrite, Orclose: con 1<<iota;
+Omodes: con 3; # mask
+
+VOLDESC: con 16; # sector number
+
+Drec: adt {
+ reclen: int;
+ attrlen: int;
+ addr: int; # should be big?
+ size: int; # should be big?
+ date: array of byte;
+ time: int;
+ tzone: int; # not in high sierra
+ flags: int;
+ unitsize: int;
+ gapsize: int;
+ vseqno: int;
+ name: array of byte;
+ data: array of byte; # system extensions
+};
+
+Isofile: adt {
+ fmt: int; # 'z' if iso, 'r' if high sierra
+ blksize: int;
+ offset: int; # true offset when reading directory
+ doffset: int; # styx offset when reading directory
+ d: ref Drec;
+};
+
+Xfile.new(): ref Xfile
+{
+ f := ref Xfile;
+ return f.clean();
+}
+
+Xfile.clean(f: self ref Xfile): ref Xfile
+{
+ if(f.xf != nil){
+ f.xf.decref();
+ f.xf = nil;
+ }
+ f.ptr = nil;
+ f.flags = 0;
+ f.qid = Qid(big 0, 0, 0);
+ return f;
+}
+
+Xfile.save(f: self ref Xfile): ref Xfile
+{
+ s := ref Xfile;
+ *s = *f;
+ s.ptr = ref *f.ptr;
+ s.ptr.d = ref *f.ptr.d;
+ return s;
+}
+
+Xfile.restore(f: self ref Xfile, s: ref Xfile)
+{
+ f.flags = s.flags;
+ f.qid = s.qid;
+ *f.ptr = *s.ptr;
+}
+
+Xfile.attach(root: self ref Xfile): string
+{
+ fmt := 0;
+ blksize := 0;
+ haveplan9 := 0;
+ dirp: ref Block;
+ dp := ref Drec;
+ for(a:=VOLDESC;a<VOLDESC+100;a++){
+ p := Block.get(root.xf.d, a);
+ if(p == nil){
+ if(dirp != nil)
+ dirp.put();
+ return "can't read volume descriptor";
+ }
+ v := p.data; # Voldesc
+ if(eqs(v[0:7], "\u0001CD001\u0001")){ # ISO
+ if(dirp != nil)
+ dirp.put();
+ dirp = p;
+ fmt = 'z';
+ convM2Drec(v[156:], dp, 0); # v.z.desc.rootdir
+ blksize = l16(v[128:]); # v.z.desc.blksize
+ if(chatty)
+ chat(sys->sprint("iso, blksize=%d...", blksize));
+ haveplan9 = eqs(v[8:8+6], "PLAN 9"); # v.z.boot.sysid
+ if(haveplan9){
+ if(noplan9) {
+ chat("ignoring plan9");
+ haveplan9 = 0;
+ }else{
+ fmt = '9';
+ chat("plan9 iso...");
+ }
+ }
+ continue;
+ }
+ if(eqs(v[8:8+7], "\u0001CDROM\u0001")){ # high sierra
+ if(dirp != nil)
+ dirp.put();
+ dirp = p;
+ fmt = 'r';
+ convM2Drec(v[180:], dp, 1); # v.r.desc.rootdir
+ blksize = l16(v[136:]); # v.r.desc.blksize
+ if(chatty)
+ chat(sys->sprint("high sierra, blksize=%d...", blksize));
+ continue;
+ }
+ if(haveplan9==0 && !nojoliet && eqs(v[0:7], "\u0002CD001\u0001")){
+ q := v[88:]; # v.z.desc.escapes
+ if(q[0] == byte 16r25 && q[1] == byte 16r2F &&
+ (q[2] == byte 16r40 || q[2] == byte 16r43 || q[2] == byte 16r45)){ # joliet, it appears
+ if(dirp != nil)
+ dirp.put();
+ dirp = p;
+ fmt = 'J';
+ convM2Drec(v[156:], dp, 0); # v.z.desc.rootdir
+ if(blksize != l16(v[128:])) # v.z.desc.blksize
+ sys->fprint(stderr, "9660srv: warning: suspicious Joliet block size: %d\n", l16(v[128:]));
+ chat("joliet...");
+ continue;
+ }
+ }else{
+ p.put();
+ if(v[0] == byte 16rFF)
+ break;
+ }
+ }
+
+ if(fmt == 0){
+ if(dirp != nil)
+ dirp.put();
+ return "CD format not recognised";
+ }
+
+ if(chatty)
+ showdrec(stderr, fmt, dp);
+ if(blksize > Sectorsize){
+ dirp.put();
+ return "blocksize too big";
+ }
+ fp := iso(root);
+ root.xf.isplan9 = haveplan9;
+ fp.fmt = fmt;
+ fp.blksize = blksize;
+ fp.offset = 0;
+ fp.doffset = 0;
+ fp.d = dp;
+ root.qid.path = big dp.addr;
+ root.qid.qtype = QTDIR;
+ root.qid.vers = 0;
+ dirp.put();
+ dp = ref Drec;
+ if(getdrec(root, dp) >= 0){
+ s := dp.data;
+ n := len s;
+ if(n >= 7 && s[0] == byte 'S' && s[1] == byte 'P' && s[2] == byte 7 &&
+ s[3] == byte 1 && s[4] == byte 16rBE && s[5] == byte 16rEF){
+ root.xf.issusp = 1;
+ root.xf.suspoff = int s[6];
+ n -= root.xf.suspoff;
+ s = s[root.xf.suspoff:];
+ while(n >= 4){
+ l := int s[2];
+ if(s[0] == byte 'E' && s[1] == byte 'R'){
+ if(int s[4] == 10 && eqs(s[8:18], "RRIP_1991A"))
+ root.xf.isrock = 1;
+ break;
+ } else if(s[0] == byte 'C' && s[1] == byte 'E' && int s[2] >= 28){
+ (s, n) = getcontin(root.xf.d, s);
+ continue;
+ } else if(s[0] == byte 'R' && s[1] == byte 'R'){
+ if(!norock)
+ root.xf.isrock = 1;
+ break; # can skip search for ER
+ } else if(s[0] == byte 'S' && s[1] == byte 'T')
+ break;
+ s = s[l:];
+ n -= l;
+ }
+ }
+ }
+ if(root.xf.isrock)
+ chat("Rock Ridge...");
+ fp.offset = 0;
+ fp.doffset = 0;
+ return nil;
+}
+
+Xfile.clone(oldf: self ref Xfile, newf: ref Xfile)
+{
+ *newf = *oldf;
+ newf.ptr = nil;
+ newf.xf.incref();
+ ip := iso(oldf);
+ np := iso(newf);
+ *np = *ip; # might not be right; shares ip.d
+}
+
+Xfile.walkup(f: self ref Xfile): string
+{
+ pf := Xfile.new();
+ ppf := Xfile.new();
+ e := walkup(f, pf, ppf);
+ pf.clunk();
+ ppf.clunk();
+ return e;
+}
+
+walkup(f, pf, ppf: ref Xfile): string
+{
+ e := opendotdot(f, pf);
+ if(e != nil)
+ return sys->sprint("can't open pf: %s", e);
+ paddr := iso(pf).d.addr;
+ if(iso(f).d.addr == paddr)
+ return nil;
+ e = opendotdot(pf, ppf);
+ if(e != nil)
+ return sys->sprint("can't open ppf: %s", e);
+ d := ref Drec;
+ while(getdrec(ppf, d) >= 0){
+ if(d.addr == paddr){
+ newdrec(f, d);
+ f.qid.path = big paddr;
+ f.qid.qtype = QTDIR;
+ f.qid.vers = 0;
+ return nil;
+ }
+ }
+ return "can't find addr of ..";
+}
+
+Xfile.walk(f: self ref Xfile, name: string): string
+{
+ ip := iso(f);
+ if(!f.xf.isplan9){
+ for(i := 0; i < len name; i++)
+ if(name[i] == ';')
+ break;
+ if(i >= Maxname)
+ i = Maxname-1;
+ name = name[0:i];
+ }
+ if(chatty)
+ chat(sys->sprint("%d \"%s\"...", len name, name));
+ ip.offset = 0;
+ dir := ref Dir;
+ d := ref Drec;
+ while(getdrec(f, d) >= 0) {
+ dvers := rzdir(f.xf, dir, ip.fmt, d);
+ if(name != dir.name)
+ continue;
+ newdrec(f, d);
+ f.qid.path = dir.qid.path;
+ f.qid.qtype = dir.qid.qtype;
+ f.qid.vers = dir.qid.vers;
+ if(dvers){
+ # versions ignored
+ }
+ return nil;
+ }
+ return Enonexist;
+}
+
+Xfile.open(f: self ref Xfile, mode: int): string
+{
+ if(mode != Sys->OREAD)
+ return Eperm;
+ ip := iso(f);
+ ip.offset = 0;
+ ip.doffset = 0;
+ return nil;
+}
+
+Xfile.create(nil: self ref Xfile, nil: string, nil: int, nil: int): string
+{
+ return Eperm;
+}
+
+Xfile.readdir(f: self ref Xfile, buf: array of byte, offset: int, count: int): (int, string)
+{
+ ip := iso(f);
+ d := ref Dir;
+ drec := ref Drec;
+ if(offset < ip.doffset){
+ ip.offset = 0;
+ ip.doffset = 0;
+ }
+ rcnt := 0;
+ while(rcnt < count && getdrec(f, drec) >= 0){
+ if(len drec.name == 1){
+ if(drec.name[0] == byte 0)
+ continue;
+ if(drec.name[0] == byte 1)
+ continue;
+ }
+ rzdir(f.xf, d, ip.fmt, drec);
+ d.qid.vers = f.qid.vers;
+ a := styx->packdir(*d);
+ if(ip.doffset < offset){
+ ip.doffset += len a;
+ continue;
+ }
+ if(rcnt+len a > count)
+ break;
+ buf[rcnt:] = a; # BOTCH: copy
+ rcnt += len a;
+ }
+ ip.doffset += rcnt;
+ return (rcnt, nil);
+}
+
+Xfile.read(f: self ref Xfile, buf: array of byte, offset: int, count: int): (int, string)
+{
+ ip := iso(f);
+ if(offset >= ip.d.size)
+ return (0, nil);
+ if(offset+count > ip.d.size)
+ count = ip.d.size - offset;
+ addr := (ip.d.addr+ip.d.attrlen)*ip.blksize + offset;
+ o := addr % Sectorsize;
+ addr /= Sectorsize;
+ if(chatty)
+ chat(sys->sprint("d.addr=0x%x, addr=0x%x, o=0x%x...", ip.d.addr, addr, o));
+ n := Sectorsize - o;
+ rcnt := 0;
+ while(count > 0){
+ if(n > count)
+ n = count;
+ p := Block.get(f.xf.d, addr);
+ if(p == nil)
+ return (-1, "i/o error");
+ buf[rcnt:] = p.data[o:o+n];
+ p.put();
+ count -= n;
+ rcnt += n;
+ addr++;
+ o = 0;
+ n = Sectorsize;
+ }
+ return (rcnt, nil);
+}
+
+Xfile.write(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string)
+{
+ return (-1, Eperm);
+}
+
+Xfile.clunk(f: self ref Xfile)
+{
+ f.ptr = nil;
+}
+
+Xfile.remove(nil: self ref Xfile): string
+{
+ return Eperm;
+}
+
+Xfile.stat(f: self ref Xfile): (ref Dir, string)
+{
+ ip := iso(f);
+ d := ref Dir;
+ rzdir(f.xf, d, ip.fmt, ip.d);
+ d.qid.vers = f.qid.vers;
+ if(d.qid.path==f.xf.rootqid.path){
+ d.qid.path = big 0;
+ d.qid.qtype = QTDIR;
+ }
+ return (d, nil);
+}
+
+Xfile.wstat(nil: self ref Xfile, nil: ref Dir): string
+{
+ return Eperm;
+}
+
+Xfs.new(d: ref Device): ref Xfs
+{
+ xf := ref Xfs;
+ xf.inuse = 1;
+ xf.d = d;
+ xf.isplan9 = 0;
+ xf.issusp = 0;
+ xf.isrock = 0;
+ xf.suspoff = 0;
+ xf.ptr = 0;
+ xf.rootqid = Qid(big 0, 0, QTDIR);
+ return xf;
+}
+
+Xfs.incref(xf: self ref Xfs)
+{
+ xf.inuse++;
+}
+
+Xfs.decref(xf: self ref Xfs)
+{
+ xf.inuse--;
+ if(xf.inuse == 0){
+ if(xf.d != nil)
+ xf.d.detach();
+ }
+}
+
+showdrec(fd: ref Sys->FD, fmt: int, d: ref Drec)
+{
+ if(d.reclen == 0)
+ return;
+ sys->fprint(fd, "%d %d %d %d ",
+ d.reclen, d.attrlen, d.addr, d.size);
+ sys->fprint(fd, "%s 0x%2.2x %d %d %d ",
+ rdate(d.date, fmt), d.flags,
+ d.unitsize, d.gapsize, d.vseqno);
+ sys->fprint(fd, "%d %s", len d.name, nstr(d.name));
+ syslen := len d.data;
+ if(syslen != 0)
+ sys->fprint(fd, " %s", nstr(d.data));
+ sys->fprint(fd, "\n");
+}
+
+newdrec(f: ref Xfile, dp: ref Drec)
+{
+ x := iso(f);
+ n := ref Isofile;
+ n.fmt = x.fmt;
+ n.blksize = x.blksize;
+ n.offset = 0;
+ n.doffset = 0;
+ n.d = dp;
+ f.ptr = n;
+}
+
+getdrec(f: ref Xfile, d: ref Drec): int
+{
+ if(f.ptr == nil)
+ return -1;
+ boff := 0;
+ ip := iso(f);
+ size := ip.d.size;
+ while(ip.offset<size){
+ addr := (ip.d.addr+ip.d.attrlen)*ip.blksize + ip.offset;
+ boff = addr % Sectorsize;
+ if(boff > Sectorsize-34){
+ ip.offset += Sectorsize-boff;
+ continue;
+ }
+ p := Block.get(f.xf.d, addr/Sectorsize);
+ if(p == nil)
+ return -1;
+ nb := int p.data[boff];
+ if(nb >= 34) {
+ convM2Drec(p.data[boff:], d, ip.fmt=='r');
+ #chat(sys->sprint("off %d", ip.offset));
+ #showdrec(stderr, ip.fmt, d);
+ p.put();
+ ip.offset += nb + (nb&1);
+ return 0;
+ }
+ p.put();
+ p = nil;
+ ip.offset += Sectorsize-boff;
+ }
+ return -1;
+}
+
+# getcontin returns a slice of the Iobuf, valid until next i/o call
+getcontin(d: ref Device, a: array of byte): (array of byte, int)
+{
+ bn := l32(a[4:]);
+ off := l32(a[12:]);
+ n := l32(a[20:]);
+ p := Block.get(d, bn);
+ if(p == nil)
+ return (nil, 0);
+ return (p.data[off:off+n], n);
+}
+
+iso(f: ref Xfile): ref Isofile
+{
+ if(f.ptr == nil){
+ f.ptr = ref Isofile;
+ f.ptr.d = ref Drec;
+ }
+ return f.ptr;
+}
+
+opendotdot(f: ref Xfile, pf: ref Xfile): string
+{
+ d := ref Drec;
+ ip := iso(f);
+ ip.offset = 0;
+ if(getdrec(f, d) < 0)
+ return "opendotdot: getdrec(.) failed";
+ if(len d.name != 1 || d.name[0] != byte 0)
+ return "opendotdot: no . entry";
+ if(d.addr != ip.d.addr)
+ return "opendotdot: bad . address";
+ if(getdrec(f, d) < 0)
+ return "opendotdot: getdrec(..) failed";
+ if(len d.name != 1 || d.name[0] != byte 1)
+ return "opendotdot: no .. entry";
+
+ pf.xf = f.xf;
+ pip := iso(pf);
+ pip.fmt = ip.fmt;
+ pip.blksize = ip.blksize;
+ pip.offset = 0;
+ pip.doffset = 0;
+ pip.d = d;
+ return nil;
+}
+
+rzdir(fs: ref Xfs, d: ref Dir, fmt: int, dp: ref Drec): int
+{
+ Hmode, Hname: con 1<<iota;
+ vers := -1;
+ have := 0;
+ d.qid.path = big dp.addr;
+ d.qid.vers = 0;
+ d.qid.qtype = QTFILE;
+ n := len dp.name;
+ if(n == 1) {
+ case int dp.name[0] {
+ 0 => d.name = "."; have |= Hname;
+ 1 => d.name = ".."; have |= Hname;
+ * => d.name = ""; d.name[0] = tolower(int dp.name[0]);
+ }
+ } else {
+ if(fmt == 'J'){ # Joliet, 16-bit Unicode
+ d.name = "";
+ for(i:=0; i<n; i+=2){
+ r := (int dp.name[i]<<8) | int dp.name[i+1];
+ d.name[len d.name] = r;
+ }
+ }else{
+ if(n >= Maxname)
+ n = Maxname-1;
+ d.name = "";
+ for(i:=0; i<n && int dp.name[i] != '\r'; i++)
+ d.name[i] = tolower(int dp.name[i]);
+ }
+ }
+
+ if(fs.isplan9 && dp.reclen>34+len dp.name) {
+ #
+ # get gid, uid, mode and possibly name
+ # from plan9 directory extension
+ #
+ s := dp.data;
+ n = int s[0];
+ if(n)
+ d.name = string s[1:1+n];
+ l := 1+n;
+ n = int s[l++];
+ d.uid = string s[l:l+n];
+ l += n;
+ n = int s[l++];
+ d.gid = string s[l:l+n];
+ l += n;
+ if(l & 1)
+ l++;
+ d.mode = l32(s[l:]);
+ if(d.mode & DMDIR)
+ d.qid.qtype = QTDIR;
+ } else {
+ d.mode = 8r444;
+ case fmt {
+ 'z' =>
+ if(fs.isrock)
+ d.gid = "ridge";
+ else
+ d.gid = "iso";
+ 'r' =>
+ d.gid = "sierra";
+ 'J' =>
+ d.gid = "joliet";
+ * =>
+ d.gid = "???";
+ }
+ flags := dp.flags;
+ if(flags & 2){
+ d.qid.qtype = QTDIR;
+ d.mode |= DMDIR|8r111;
+ }
+ d.uid = "cdrom";
+ for(i := 0; i < len d.name; i++)
+ if(d.name[i] == ';') {
+ vers = int string d.name[i+1:]; # inefficient
+ d.name = d.name[0:i]; # inefficient
+ break;
+ }
+ n = len dp.data - fs.suspoff;
+ if(fs.isrock && n >= 4){
+ s := dp.data[fs.suspoff:];
+ nm := 0;
+ while(n >= 4 && have != (Hname|Hmode)){
+ l := int s[2];
+ if(s[0] == byte 'P' && s[1] == byte 'X' && s[3] == byte 1){
+ # posix file attributes
+ mode := l32(s[4:12]);
+ d.mode = mode & 8r777;
+ if((mode & 8r170000) == 8r0040000){
+ d.mode |= DMDIR;
+ d.qid.qtype = QTDIR;
+ }
+ have |= Hmode;
+ } else if(s[0] == byte 'N' && s[1] == byte 'M' && s[3] == byte 1){
+ # alternative name
+ flags = int s[4];
+ if((flags & ~1) == 0){
+ if(nm == 0){
+ d.name = string s[5:l];
+ nm = 1;
+ } else
+ d.name += string s[5:l];
+ if(flags == 0)
+ have |= Hname; # no more
+ }
+ } else if(s[0] == byte 'C' && s[1] == byte 'E' && int s[2] >= 28){
+ (s, n) = getcontin(fs.d, s);
+ continue;
+ } else if(s[0] == byte 'S' && s[1] == byte 'T')
+ break;
+ n -= l;
+ s = s[l:];
+ }
+ }
+ }
+ d.length = big 0;
+ if((d.mode & DMDIR) == 0)
+ d.length = big dp.size;
+ d.dtype = 0;
+ d.dev = 0;
+ d.atime = dp.time;
+ d.mtime = d.atime;
+ return vers;
+}
+
+convM2Drec(a: array of byte, d: ref Drec, highsierra: int)
+{
+ d.reclen = int a[0];
+ d.attrlen = int a[1];
+ d.addr = int l32(a[2:10]);
+ d.size = int l32(a[10:18]);
+ d.time = gtime(a[18:24]);
+ d.date = array[7] of byte;
+ d.date[0:] = a[18:25];
+ if(highsierra){
+ d.tzone = 0;
+ d.flags = int a[24];
+ d.unitsize = 0;
+ d.gapsize = 0;
+ d.vseqno = 0;
+ } else {
+ d.tzone = int a[24];
+ d.flags = int a[25];
+ d.unitsize = int a[26];
+ d.gapsize = int a[27];
+ d.vseqno = l32(a[28:32]);
+ }
+ n := int a[32];
+ d.name = array[n] of byte;
+ d.name[0:] = a[33:33+n];
+ n += 33;
+ if(n & 1)
+ n++; # check this
+ syslen := d.reclen - n;
+ if(syslen > 0){
+ d.data = array[syslen] of byte;
+ d.data[0:] = a[n:n+syslen];
+ } else
+ d.data = nil;
+}
+
+nstr(p: array of byte): string
+{
+ q := "";
+ n := len p;
+ for(i := 0; i < n; i++){
+ if(int p[i] == '\\')
+ q[len q] = '\\';
+ if(' ' <= int p[i] && int p[i] <= '~')
+ q[len q] = int p[i];
+ else
+ q += sys->sprint("\\%2.2ux", int p[i]);
+ }
+ return q;
+}
+
+rdate(p: array of byte, fmt: int): string
+{
+ c: int;
+
+ s := sys->sprint("%2.2d.%2.2d.%2.2d %2.2d:%2.2d:%2.2d",
+ int p[0], int p[1], int p[2], int p[3], int p[4], int p[5]);
+ if(fmt == 'z'){
+ htz := int p[6];
+ if(htz >= 128){
+ htz = 256-htz;
+ c = '-';
+ }else
+ c = '+';
+ s += sys->sprint(" (%c%.1f)", c, real htz/2.0);
+ }
+ return s;
+}
+
+dmsize := array[] of {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
+};
+
+dysize(y: int): int
+{
+ if((y%4) == 0)
+ return 366;
+ return 365;
+}
+
+gtime(p: array of byte): int # yMdhms
+{
+ y:=int p[0]; M:=int p[1]; d:=int p[2];
+ h:=int p[3]; m:=int p[4]; s:=int p[5];;
+ if(y < 70)
+ return 0;
+ if(M < 1 || M > 12)
+ return 0;
+ if(d < 1 || d > dmsize[M-1])
+ return 0;
+ if(h > 23)
+ return 0;
+ if(m > 59)
+ return 0;
+ if(s > 59)
+ return 0;
+ y += 1900;
+ t := 0;
+ for(i:=1970; i<y; i++)
+ t += dysize(i);
+ if(dysize(y)==366 && M >= 3)
+ t++;
+ M--;
+ while(M-- > 0)
+ t += dmsize[M];
+ t += d-1;
+ t = 24*t + h;
+ t = 60*t + m;
+ t = 60*t + s;
+ return t;
+}
+
+l16(p: array of byte): int
+{
+ v := (int p[1]<<8)| int p[0];
+ if (v >= 16r8000)
+ v -= 16r10000;
+ return v;
+}
+
+l32(p: array of byte): int
+{
+ return (((((int p[3]<<8)| int p[2])<<8)| int p[1])<<8)| int p[0];
+}
+
+eqs(a: array of byte, b: string): int
+{
+ if(len a != len b)
+ return 0;
+ for(i := 0; i < len a; i++)
+ if(int a[i] != b[i])
+ return 0;
+ return 1;
+}
+
+tolower(c: int): int
+{
+ if(c >= 'A' && c <= 'Z')
+ return c-'A' + 'a';
+ return c;
+}
+
+#
+# I/O buffers
+#
+
+Device: adt {
+ inuse: int; # attach count
+ name: string; # of underlying file
+ fd: ref Sys->FD;
+ sectorsize: int;
+ qid: Sys->Qid; # (qid,dtype,dev) identify uniquely
+ dtype: int;
+ dev: int;
+
+ detach: fn(nil: self ref Device);
+};
+
+Block: adt {
+ dev: ref Device;
+ addr: int;
+ data: array of byte;
+
+ # internal
+ next: cyclic ref Block;
+ prev: cyclic ref Block;
+ busy: int;
+
+ get: fn(nil: ref Device, addr: int): ref Block;
+ put: fn(nil: self ref Block);
+};
+
+devices: list of ref Device;
+
+NIOB: con 100; # for starters
+HIOB: con 127; # prime
+
+hiob := array[HIOB] of list of ref Block; # hash buckets
+iohead: ref Block;
+iotail: ref Block;
+bufsize := 0;
+
+iobufinit(bsize: int)
+{
+ bufsize = bsize;
+ for(i:=0; i<NIOB; i++)
+ newblock();
+}
+
+newblock(): ref Block
+{
+ p := ref Block;
+ p.busy = 0;
+ p.addr = -1;
+ p.dev = nil;
+ p.data = array[bufsize] of byte;
+ p.next = iohead;
+ if(iohead != nil)
+ iohead.prev = p;
+ iohead = p;
+ if(iotail == nil)
+ iotail = p;
+ return p;
+}
+
+Block.get(dev: ref Device, addr: int): ref Block
+{
+ p: ref Block;
+
+ dh := hiob[addr%HIOB:];
+ for(l := dh[0]; l != nil; l = tl l) {
+ p = hd l;
+ if(p.addr == addr && p.dev == dev) {
+ p.busy++;
+ return p;
+ }
+ }
+ # Find a non-busy buffer from the tail
+ for(p = iotail; p != nil && p.busy; p = p.prev)
+ ;
+ if(p == nil)
+ p = newblock();
+
+ # Delete from hash chain
+ if(p.addr >= 0) {
+ hp := hiob[p.addr%HIOB:];
+ l = nil;
+ for(f := hp[0]; f != nil; f = tl f)
+ if(hd f != p)
+ l = (hd f) :: l;
+ hp[0] = l;
+ }
+
+ # Hash and fill
+ p.addr = addr;
+ p.dev = dev;
+ p.busy++;
+ sys->seek(dev.fd, big addr*big dev.sectorsize, 0);
+ if(sys->read(dev.fd, p.data, dev.sectorsize) != dev.sectorsize){
+ p.addr = -1; # stop caching
+ p.put();
+ purge(dev);
+ return nil;
+ }
+ dh[0] = p :: dh[0];
+ return p;
+}
+
+Block.put(p: self ref Block)
+{
+ p.busy--;
+ if(p.busy < 0)
+ panic("Block.put");
+
+ if(p == iohead)
+ return;
+
+ # Link onto head for lru
+ if(p.prev != nil)
+ p.prev.next = p.next;
+ else
+ iohead = p.next;
+
+ if(p.next != nil)
+ p.next.prev = p.prev;
+ else
+ iotail = p.prev;
+
+ p.prev = nil;
+ p.next = iohead;
+ iohead.prev = p;
+ iohead = p;
+}
+
+purge(dev: ref Device)
+{
+ for(i := 0; i < HIOB; i++){
+ l := hiob[i];
+ hiob[i] = nil;
+ for(; l != nil; l = tl l){ # reverses bucket's list, but never mind
+ p := hd l;
+ if(p.dev == dev)
+ p.busy = 0;
+ else
+ hiob[i] = p :: hiob[i];
+ }
+ }
+}
+
+devattach(name: string, mode: int, sectorsize: int): (ref Device, string)
+{
+ if(sectorsize > bufsize)
+ return (nil, "sector size too big");
+ fd := sys->open(name, mode);
+ if(fd == nil)
+ return(nil, sys->sprint("%s: can't open: %r", name));
+ (rc, dir) := sys->fstat(fd);
+ if(rc < 0)
+ return (nil, sys->sprint("%r"));
+ for(dl := devices; dl != nil; dl = tl dl){
+ d := hd dl;
+ if(d.qid.path != dir.qid.path || d.qid.vers != dir.qid.vers)
+ continue;
+ if(d.dtype != dir.dtype || d.dev != dir.dev)
+ continue;
+ d.inuse++;
+ if(chatty)
+ sys->print("inuse=%d, \"%s\", dev=%H...\n", d.inuse, d.name, d.fd);
+ return (d, nil);
+ }
+ if(chatty)
+ sys->print("alloc \"%s\", dev=%H...\n", name, fd);
+ d := ref Device;
+ d.inuse = 1;
+ d.name = name;
+ d.qid = dir.qid;
+ d.dtype = dir.dtype;
+ d.dev = dir.dev;
+ d.fd = fd;
+ d.sectorsize = sectorsize;
+ devices = d :: devices;
+ return (d, nil);
+}
+
+Device.detach(d: self ref Device)
+{
+ d.inuse--;
+ if(d.inuse < 0)
+ panic("putxdata");
+ if(chatty)
+ sys->print("decref=%d, \"%s\", dev=%H...\n", d.inuse, d.name, d.fd);
+ if(d.inuse == 0){
+ if(chatty)
+ sys->print("purge...\n");
+ purge(d);
+ dl := devices;
+ devices = nil;
+ for(; dl != nil; dl = tl dl)
+ if((hd dl) != d)
+ devices = (hd dl) :: devices;
+ }
+}
+
+panic(s: string)
+{
+ sys->print("panic: %s\n", s);
+ a: array of byte;
+ a[5] = byte 0; # trap
+}
diff --git a/appl/cmd/9export.b b/appl/cmd/9export.b
new file mode 100644
index 00000000..5df1c8cf
--- /dev/null
+++ b/appl/cmd/9export.b
@@ -0,0 +1,180 @@
+implement P9export;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+include "keyring.m";
+include "security.m";
+include "factotum.m";
+include "encoding.m";
+include "arg.m";
+
+P9export: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+factotumfile := "/mnt/factotum/rpc";
+
+fail(status, msg: string)
+{
+ sys->fprint(sys->fildes(2), "9export: %s\n", msg);
+ raise "fail:"+status;
+}
+
+nomod(mod: string)
+{
+ fail("load", sys->sprint("can't load %s: %r", mod));
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+
+ arg->init(args);
+ arg->setusage("9export [-aA9] [-k keyspec] [-e enc digest]");
+ flags := 0;
+ cryptalg := ""; # will be rc4_256 sha1
+ keyspec := "";
+ noauth := 0;
+ xflag := Sys->EXPWAIT;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' =>
+ xflag = Sys->EXPASYNC;
+ 'A' =>
+ noauth = 1;
+ 'e' =>
+ cryptalg = arg->earg();
+ if(cryptalg == "clear")
+ cryptalg = nil;
+ 'k' =>
+ keyspec = arg->earg();
+ '9' =>
+ ;
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ sys->pctl(Sys->FORKFD|Sys->FORKNS, nil);
+
+ fd := sys->fildes(0);
+
+ secret: array of byte;
+ if(noauth == 0){
+ factotum := load Factotum Factotum->PATH;
+ if(factotum == nil)
+ nomod(Factotum->PATH);
+ factotum->init();
+ facfd := sys->open(factotumfile, Sys->ORDWR);
+ if(facfd == nil)
+ fail("factotum", sys->sprint("can't open %s: %r", factotumfile));
+ ai := factotum->proxy(fd, facfd, "proto=p9any role=server "+keyspec);
+ if(ai == nil)
+ fail("auth", sys->sprint("can't authenticate 9export: %r"));
+ secret = ai.secret;
+ }
+
+ # read tree; it's a Plan 9 bug that there's no reliable delimiter
+ btree := array[2048] of byte;
+ n := sys->read(fd, btree, len btree);
+ if(n <= 0)
+ fail("tree", sys->sprint("can't read tree: %r"));
+ tree := string btree[0:n];
+ if(sys->chdir(tree) < 0){
+ sys->fprint(fd, "chdir(%d:\"%s\"): %r", n, tree);
+ fail("tree", sys->sprint("bad tree: %s", tree));
+ }
+ if(sys->write(fd, array of byte "OK", 2) != 2)
+ fail("tree", sys->sprint("can't OK tree: %r"));
+ impo := array[2048] of byte;
+ for(n = 0; n < len impo; n++)
+ if(sys->read(fd, impo[n:], 1) != 1)
+ fail("impo", sys->sprint("can't read impo: %r"));
+ else if(impo[n] == byte 0 || impo[n] == byte '\n')
+ break;
+ if(n < 4 || string impo[0:4] != "impo")
+ fail("impo", "wasn't impo: possibly old import/cpu");
+ if(noauth == 0 && cryptalg != nil){
+ if(secret == nil)
+ fail("import", "didn't establish shared secret");
+ random := load Random Random->PATH;
+ if(random == nil)
+ nomod(Random->PATH);
+ kr := load Keyring Keyring->PATH;
+ if(kr == nil)
+ nomod(Keyring->PATH);
+ ssl := load SSL SSL->PATH;
+ if(ssl == nil)
+ nomod(SSL->PATH);
+ base64 := load Encoding Encoding->BASE64PATH;
+ if(base64 == nil)
+ nomod(Encoding->BASE64PATH);
+ key := array[16] of byte; # myrand[4] secret[8] hisrand[4]
+ key[0:] = random->randombuf(Random->ReallyRandom, 4);
+ ns := len secret;
+ if(ns > 8)
+ ns = 8;
+ key[12:] = secret[0:ns];
+ if(sys->write(fd, key[12:], 4) != 4)
+ fail("import", sys->sprint("can't write key to remote: %r"));
+ if(readn(fd, key, 4) != 4)
+ fail("import", sys->sprint("can't read remote key: %r"));
+ digest := array[Keyring->SHA1dlen] of byte;
+ kr->sha1(key, len key, digest, nil);
+ err: string;
+ (fd, err) = pushssl(fd, base64->dec(S(digest[10:20])), base64->dec(S(digest[0:10])), cryptalg);
+ if(err != nil)
+ fail("import", sys->sprint("can't push security layer: %s", err));
+ }
+ if(sys->export(fd, ".", xflag) < 0)
+ fail("export", sys->sprint("can't export %s: %r", tree));
+}
+
+readn(fd: ref Sys->FD, buf: array of byte, nb: int): int
+{
+ for(nr := 0; nr < nb;){
+ n := sys->read(fd, buf[nr:], nb-nr);
+ if(n <= 0){
+ if(nr == 0)
+ return n;
+ break;
+ }
+ nr += n;
+ }
+ return nr;
+}
+
+S(a: array of byte): string
+{
+ s := "";
+ for(i:=0; i<len a; i++)
+ s += sys->sprint("%.2ux", int a[i]);
+ return s;
+}
+
+pushssl(fd: ref Sys->FD, secretin, secretout: array of byte, alg: string): (ref Sys->FD, string)
+{
+ ssl := load SSL SSL->PATH;
+ if(ssl == nil)
+ nomod(SSL->PATH);
+
+ (err, c) := ssl->connect(fd);
+ if(err != nil)
+ return (nil, "can't connect ssl: " + err);
+
+ err = ssl->secret(c, secretin, secretout);
+ if(err != nil)
+ return (nil, "can't write secret: " + err);
+ if(sys->fprint(c.cfd, "alg %s", alg) < 0)
+ return (nil, sys->sprint("can't push algorithm %s: %r", alg));
+
+ return (c.dfd, nil);
+}
diff --git a/appl/cmd/9srvfs.b b/appl/cmd/9srvfs.b
new file mode 100644
index 00000000..d152d1bb
--- /dev/null
+++ b/appl/cmd/9srvfs.b
@@ -0,0 +1,99 @@
+implement P9srvfs;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "sh.m";
+ sh: Sh;
+
+include "arg.m";
+
+P9srvfs: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ if(str == nil)
+ nomod(String->PATH);
+
+ perm := 8r600;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ arg->init(args);
+ arg->setusage("9srvfs [-p perm] name path|{command}");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'p' =>
+ s := arg->earg();
+ if(s == nil)
+ arg->usage();
+ (perm, s) = str->toint(s, 8);
+ if(s != nil)
+ arg->usage();
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(len args != 2)
+ arg->usage();
+ arg = nil;
+
+ srvname := hd args;
+ args = tl args;
+ dest := hd args;
+ if(dest == nil)
+ dest = ".";
+ iscmd := dest[0] == '{' && dest[len dest-1] == '}';
+ if(!iscmd){ # quick check before creating service file
+ (ok, d) := sys->stat(dest);
+ if(ok < 0)
+ error(sys->sprint("can't stat %s: %r", dest));
+ if((d.mode & Sys->DMDIR) == 0)
+ error(sys->sprint("%s: not a directory", dest));
+ }else{
+ sh = load Sh Sh->PATH;
+ if(sh == nil)
+ nomod(Sh->PATH);
+ }
+ srvfd := sys->create("/srv/"+srvname, Sys->ORDWR, perm);
+ if(srvfd == nil)
+ error(sys->sprint("can't create /srv/%s: %r", srvname));
+ if(iscmd){
+ sync := chan of int;
+ spawn runcmd(sh, ctxt, dest :: nil, srvfd, sync);
+ <-sync;
+ }else{
+ if(sys->export(srvfd, dest, Sys->EXPWAIT) < 0)
+ error(sys->sprint("export failed: %r"));
+ }
+}
+
+error(msg: string)
+{
+ sys->fprint(sys->fildes(2), "9srvfs: %s\n", msg);
+ raise "fail:error";
+}
+
+nomod(mod: string)
+{
+ error(sys->sprint("can't load %s: %r", mod));
+}
+
+runcmd(sh: Sh, ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD, sync: chan of int)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ sys->dup(stdin.fd, 0);
+ stdin = nil;
+ sync <-= 0;
+ sh->run(ctxt, argv);
+}
diff --git a/appl/cmd/9win.b b/appl/cmd/9win.b
new file mode 100644
index 00000000..b2d2bd47
--- /dev/null
+++ b/appl/cmd/9win.b
@@ -0,0 +1,453 @@
+implement Ninewin;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Image, Display, Pointer: import draw;
+include "arg.m";
+include "keyboard.m";
+include "tk.m";
+include "wmclient.m";
+ wmclient: Wmclient;
+ Window: import wmclient;
+include "sh.m";
+ sh: Sh;
+
+# run a p9 graphics program (default rio) under inferno wm,
+# making available to it:
+# /dev/winname - naming the current inferno window (changing on resize)
+# /dev/mouse - pointer file + resize events; write to change position
+# /dev/cursor - change appearance of cursor.
+# /dev/draw - inferno draw device
+# /dev/cons - read keyboard events, write to 9win stdout.
+
+Ninewin: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+winname: string;
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ size := Draw->Point(500, 500);
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ wmclient = load Wmclient Wmclient->PATH;
+ wmclient->init();
+ sh = load Sh Sh->PATH;
+
+ buts := Wmclient->Resize;
+ if(ctxt == nil){
+ ctxt = wmclient->makedrawcontext();
+ buts = Wmclient->Plain;
+ }
+ arg := load Arg Arg->PATH;
+ arg->init(argv);
+ arg->setusage("9win [-s] [-x width] [-y height]");
+ exportonly := 0;
+ while(((opt := arg->opt())) != 0){
+ case opt {
+ 's' =>
+ exportonly = 1;
+ 'x' =>
+ size.x = int arg->earg();
+ 'y' =>
+ size.y = int arg->earg();
+ * =>
+ arg->usage();
+ }
+ }
+ if(size.x < 1 || size.y < 1)
+ arg->usage();
+ argv = arg->argv();
+ if(argv != nil && hd argv == "-s"){
+ exportonly = 1;
+ argv = tl argv;
+ }
+ if(argv == nil && !exportonly)
+ argv = "rio" :: nil;
+ if(argv != nil && exportonly){
+ sys->fprint(sys->fildes(2), "9win: no command allowed with -s flag\n");
+ raise "fail:usage";
+ }
+ title := "9win";
+ if(!exportonly)
+ title += " " + hd argv;
+ w := wmclient->window(ctxt, title, buts);
+ w.reshape(((0, 0), size));
+ w.onscreen(nil);
+ if(w.image == nil){
+ sys->fprint(sys->fildes(2), "9win: cannot get image to draw on\n");
+ raise "fail:no window";
+ }
+
+ sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil);
+ ld := "/n/9win";
+ if(sys->bind("#s", ld, Sys->MREPL) == -1 &&
+ sys->bind("#s", ld = "/n/local", Sys->MREPL) == -1){
+ sys->fprint(sys->fildes(2), "9win: cannot bind files: %r\n");
+ raise "fail:error";
+ }
+ w.startinput("kbd" :: "ptr" :: nil);
+ spawn ptrproc(rq := chan of Sys->Rread, ptr := chan[10] of ref Pointer, reshape := chan[1] of int);
+
+
+ fwinname := sys->file2chan(ld, "winname");
+ fconsctl := sys->file2chan(ld, "consctl");
+ fcons := sys->file2chan(ld, "cons");
+ fmouse := sys->file2chan(ld, "mouse");
+ fcursor := sys->file2chan(ld, "cursor");
+ if(!exportonly){
+ spawn run(sync := chan of string, w.ctl, ld, argv);
+ if((e := <-sync) != nil){
+ sys->fprint(sys->fildes(2), "9win: %s", e);
+ raise "fail:error";
+ }
+ }
+ spawn serveproc(w, rq, fwinname, fconsctl, fcons, fmouse, fcursor);
+ if(!exportonly){
+ # handle events synchronously so that we don't get a "killed" message
+ # from the shell.
+ handleevents(w, ptr, reshape);
+ }else{
+ spawn handleevents(w, ptr, reshape);
+ sys->bind(ld, "/dev", Sys->MBEFORE);
+ export(sys->fildes(0), w.ctl);
+ }
+}
+
+handleevents(w: ref Window, ptr: chan of ref Pointer, reshape: chan of int)
+{
+ for(;;)alt{
+ c := <-w.ctxt.ctl or
+ c = <-w.ctl =>
+ e := w.wmctl(c);
+ if(e != nil)
+ sys->fprint(sys->fildes(2), "9win: ctl error: %s\n", e);
+ if(e == nil && c != nil && c[0] == '!'){
+ alt{
+ reshape <-= 1 =>
+ ;
+ * =>
+ ;
+ }
+ winname = nil;
+ }
+ p := <-w.ctxt.ptr =>
+ if(w.pointer(*p) == 0){
+ # XXX would block here if client isn't reading mouse... but we do want to
+ # extert back-pressure, which conflicts.
+ alt{
+ ptr <-= p =>
+ ;
+ * =>
+ ; # sys->fprint(sys->fildes(2), "9win: discarding mouse event\n");
+ }
+ }
+ }
+}
+
+serveproc(w: ref Window, mouserq: chan of Sys->Rread, fwinname, fconsctl, fcons, fmouse, fcursor: ref Sys->FileIO)
+{
+ winid := 0;
+ krc: list of Sys->Rread;
+ ks: string;
+
+ for(;;)alt {
+ c := <-w.ctxt.kbd =>
+ ks[len ks] = inf2p9key(c);
+ if(krc != nil){
+ hd krc <-= (array of byte ks, nil);
+ ks = nil;
+ krc = tl krc;
+ }
+ (nil, d, nil, wc) := <-fcons.write =>
+ if(wc != nil){
+ sys->write(sys->fildes(1), d, len d);
+ wc <-= (len d, nil);
+ }
+ (nil, nil, nil, rc) := <-fcons.read =>
+ if(rc != nil){
+ if(ks != nil){
+ rc <-= (array of byte ks, nil);
+ ks = nil;
+ }else
+ krc = rc :: krc;
+ }
+ (offset, nil, nil, rc) := <-fwinname.read =>
+ if(rc != nil){
+ if(winname == nil){
+ winname = sys->sprint("noborder.9win.%d", winid++);
+ if(w.image.name(winname, 1) == -1){
+ sys->fprint(sys->fildes(2), "9win: namewin %q failed: %r", winname);
+ rc <-= (nil, "namewin failure");
+ break;
+ }
+ }
+ d := array of byte winname;
+ if(offset < len d)
+ d = d[offset:];
+ else
+ d = nil;
+ rc <-= (d, nil);
+ }
+ (nil, nil, nil, wc) := <-fwinname.write =>
+ if(wc != nil)
+ wc <-= (-1, "permission denied");
+ (nil, nil, nil, rc) := <-fconsctl.read =>
+ if(rc != nil)
+ rc <-= (nil, "permission denied");
+ (nil, d, nil, wc) := <-fconsctl.write =>
+ if(wc != nil){
+ if(string d != "rawon")
+ wc <-= (-1, "cannot change console mode");
+ else
+ wc <-= (len d, nil);
+ }
+ (nil, nil, nil, rc) := <-fmouse.read =>
+ if(rc != nil)
+ mouserq <-= rc;
+ (nil, d, nil, wc) := <-fmouse.write =>
+ if(wc != nil){
+ e := cursorset(w, string d);
+ if(e == nil)
+ wc <-= (len d, nil);
+ else
+ wc <-= (-1, e);
+ }
+ (nil, nil, nil, rc) := <-fcursor.read =>
+ if(rc != nil)
+ rc <-= (nil, "permission denied");
+ (nil, d, nil, wc) := <-fcursor.write =>
+ if(wc != nil){
+ e := cursorswitch(w, d);
+ if(e == nil)
+ wc <-= (len d, nil);
+ else
+ wc <-= (-1, e);
+ }
+ }
+}
+
+ptrproc(rq: chan of Sys->Rread, ptr: chan of ref Pointer, reshape: chan of int)
+{
+ rl: list of Sys->Rread;
+ c := ref Pointer(0, (0, 0), 0);
+ for(;;){
+ ch: int;
+ alt{
+ p := <-ptr =>
+ ch = 'm';
+ c = p;
+ <-reshape =>
+ ch = 'r';
+ rc := <-rq =>
+ rl = rc :: rl;
+ continue;
+ }
+ if(rl == nil)
+ rl = <-rq :: rl;
+ hd rl <-= (sys->aprint("%c%11d %11d %11d %11d ", ch, c.xy.x, c.xy.y, c.buttons, c.msec), nil);
+ rl = tl rl;
+ }
+}
+
+cursorset(w: ref Window, m: string): string
+{
+ if(m == nil || m[0] != 'm')
+ return "invalid mouse message";
+ x := int m[1:];
+ for(i := 1; i < len m; i++)
+ if(m[i] == ' '){
+ while(m[i] == ' ')
+ i++;
+ break;
+ }
+ if(i == len m)
+ return "invalid mouse message";
+ y := int m[i:];
+ return w.wmctl(sys->sprint("ptr %d %d", x, y));
+}
+
+cursorswitch(w: ref Window, d: array of byte): string
+{
+ Hex: con "0123456789abcdef";
+ if(len d != 2*4+64)
+ return w.wmctl("cursor");
+ hot := Draw->Point(bglong(d, 0*4), bglong(d, 1*4));
+ s := sys->sprint("cursor %d %d 16 32 ", hot.x, hot.y);
+ for(i := 2*4; i < len d; i++){
+ c := int d[i];
+ s[len s] = Hex[c >> 4];
+ s[len s] = Hex[c & 16rf];
+ }
+ return w.wmctl(s);
+}
+
+run(sync, ctl: chan of string, ld: string, argv: list of string)
+{
+ Rcmeta: con "|<>&^*[]?();";
+ sys->pctl(Sys->FORKNS, nil);
+ if(sys->bind("#₪", "/srv", Sys->MCREATE) == -1){
+ sync <-= sys->sprint("cannot bind srv device: %r");
+ exit;
+ }
+ srvname := "/srv/9win."+string sys->pctl(0, nil); # XXX do better.
+ fd := sys->create(srvname, Sys->ORDWR, 8r600);
+ if(fd == nil){
+ sync <-= sys->sprint("cannot create %s: %r", srvname);
+ exit;
+ }
+ sync <-= nil;
+ spawn export(fd, ctl);
+ sh->run(nil, "os" ::
+ "rc" :: "-c" ::
+ "mount "+srvname+" /mnt/term;"+
+ "rm "+srvname+";"+
+ "bind -b /mnt/term"+ld+" /dev;"+
+ "bind /mnt/term/dev/draw /dev/draw ||"+
+ "bind -a /mnt/term/dev /dev;"+
+ quotedc("cd"::"/mnt/term"+cwd()::nil, Rcmeta)+";"+
+ quotedc(argv, Rcmeta)+";"::
+ nil
+ );
+}
+
+export(fd: ref Sys->FD, ctl: chan of string)
+{
+ sys->export(fd, "/", Sys->EXPWAIT);
+ ctl <-= "exit";
+}
+
+inf2p9key(c: int): int
+{
+ KF: import Keyboard;
+
+ P9KF: con 16rF000;
+ Spec: con 16rF800;
+ Khome: con P9KF|16r0D;
+ Kup: con P9KF|16r0E;
+ Kpgup: con P9KF|16r0F;
+ Kprint: con P9KF|16r10;
+ Kleft: con P9KF|16r11;
+ Kright: con P9KF|16r12;
+ Kdown: con Spec|16r00;
+ Kview: con Spec|16r00;
+ Kpgdown: con P9KF|16r13;
+ Kins: con P9KF|16r14;
+ Kend: con P9KF|16r18;
+ Kalt: con P9KF|16r15;
+ Kshift: con P9KF|16r16;
+ Kctl: con P9KF|16r17;
+
+ case c {
+ Keyboard->LShift =>
+ return Kshift;
+ Keyboard->LCtrl =>
+ return Kctl;
+ Keyboard->LAlt =>
+ return Kalt;
+ Keyboard->Home =>
+ return Khome;
+ Keyboard->End =>
+ return Kend;
+ Keyboard->Up =>
+ return Kup;
+ Keyboard->Down =>
+ return Kdown;
+ Keyboard->Left =>
+ return Kleft;
+ Keyboard->Right =>
+ return Kright;
+ Keyboard->Pgup =>
+ return Kpgup;
+ Keyboard->Pgdown =>
+ return Kpgdown;
+ Keyboard->Ins =>
+ return Kins;
+
+ # function keys
+ KF|1 or
+ KF|2 or
+ KF|3 or
+ KF|4 or
+ KF|5 or
+ KF|6 or
+ KF|7 or
+ KF|8 or
+ KF|9 or
+ KF|10 or
+ KF|11 or
+ KF|12 =>
+ return (c - KF) + P9KF;
+ }
+ return c;
+}
+
+cwd(): string
+{
+ return sys->fd2path(sys->open(".", Sys->OREAD));
+}
+
+# from string.b, waiting for declaration to be uncommented.
+quotedc(argv: list of string, cl: string): string
+{
+ s := "";
+ while (argv != nil) {
+ arg := hd argv;
+ for (i := 0; i < len arg; i++) {
+ c := arg[i];
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\'' || in(c, cl))
+ break;
+ }
+ if (i < len arg || arg == nil) {
+ s += "'" + arg[0:i];
+ for (; i < len arg; i++) {
+ if (arg[i] == '\'')
+ s[len s] = '\'';
+ s[len s] = arg[i];
+ }
+ s[len s] = '\'';
+ } else
+ s += arg;
+ if (tl argv != nil)
+ s[len s] = ' ';
+ argv = tl argv;
+ }
+ return s;
+}
+
+in(c: int, s: string): int
+{
+ n := len s;
+ if(n == 0)
+ return 0;
+ ans := 0;
+ negate := 0;
+ if(s[0] == '^') {
+ negate = 1;
+ s = s[1:];
+ n--;
+ }
+ for(i := 0; i < n; i++) {
+ if(s[i] == '-' && i > 0 && i < n-1) {
+ if(c >= s[i-1] && c <= s[i+1]) {
+ ans = 1;
+ break;
+ }
+ i++;
+ }
+ else
+ if(c == s[i]) {
+ ans = 1;
+ break;
+ }
+ }
+ if(negate)
+ ans = !ans;
+ return ans;
+}
+
+bglong(d: array of byte, i: int): int
+{
+ return int d[i] | (int d[i+1]<<8) | (int d[i+2]<<16) | (int d[i+3]<<24);
+}
diff --git a/appl/cmd/B.b b/appl/cmd/B.b
new file mode 100644
index 00000000..910e3d06
--- /dev/null
+++ b/appl/cmd/B.b
@@ -0,0 +1,107 @@
+implement B;
+
+include "sys.m";
+include "draw.m";
+include "workdir.m";
+
+FD: import Sys;
+Context: import Draw;
+
+B: module
+{
+ init: fn(nil: ref Context, argv: list of string);
+};
+
+sys: Sys;
+stderr: ref FD;
+wkdir: string;
+
+init(nil: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ if(len argv < 2) {
+ sys->fprint(stderr, "Usage: B file ...\n");
+ return;
+ }
+ argv = tl argv;
+
+ cmd := "exec B ";
+ while(argv != nil) {
+ f := hd argv;
+ if(len f > 0 && f[0] != '/' && f[0] != '-')
+ f = wd() + f;
+ cmd += "/usr/inferno"+f;
+ argv = tl argv;
+ if(argv != nil)
+ cmd += " ";
+ }
+ cfd := sys->open("/cmd/clone", sys->ORDWR);
+ if(cfd == nil) {
+ sys->fprint(stderr, "B: open /cmd/clone: %r\n");
+ return;
+ }
+
+ buf := array[32] of byte;
+ n := sys->read(cfd, buf, len buf);
+ if(n <= 0) {
+ sys->fprint(stderr, "B: read /cmd/#/ctl: %r\n");
+ return;
+ }
+ dir := "/cmd/"+string buf[0:n];
+
+ # Start the Command
+ n = sys->fprint(cfd, "%s", cmd);
+ if(n <= 0) {
+ sys->fprint(stderr, "B: exec: %r\n");
+ return;
+ }
+
+ io := sys->open(dir+"/data", sys->ORDWR);
+ if(io == nil) {
+ sys->fprint(stderr, "B: open /cmd/#/data: %r\n");
+ return;
+ }
+
+ sys->pctl(sys->NEWPGRP, nil);
+ copy(io, sys->fildes(1), nil);
+}
+
+wd(): string
+{
+ if(wkdir != nil)
+ return wkdir;
+
+ gwd := load Workdir Workdir->PATH;
+
+ wkdir = gwd->init();
+ if(wkdir == nil) {
+ sys->fprint(stderr, "B: can't get working dir: %r");
+ exit;
+ }
+ wkdir = wkdir+"/";
+ return wkdir;
+}
+
+copy(f, t: ref FD, c: chan of int)
+{
+ if(c != nil)
+ c <-= sys->pctl(0, nil);
+
+ buf := array[8192] of byte;
+ for(;;) {
+ r := sys->read(f, buf, len buf);
+ if(r <= 0)
+ break;
+ w := sys->write(t, buf, r);
+ if(w != r)
+ break;
+ }
+}
+
+kill(pid: int)
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", sys->OWRITE);
+ sys->fprint(fd, "kill");
+}
diff --git a/appl/cmd/archfs.b b/appl/cmd/archfs.b
new file mode 100644
index 00000000..11567731
--- /dev/null
+++ b/appl/cmd/archfs.b
@@ -0,0 +1,630 @@
+implement Archfs;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+
+include "string.m";
+ str: String;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "styx.m";
+ styx: Styx;
+ NOFID: import Styx;
+
+include "arg.m";
+
+Archfs: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Ahdr: adt {
+ name: string;
+ modestr: string;
+ d: ref Sys->Dir;
+};
+
+Archive: adt {
+ b: ref Bufio->Iobuf;
+ nexthdr: big;
+ canseek: int;
+ hdr: ref Ahdr;
+ err: string;
+};
+
+Iobuf: import bufio;
+Tmsg, Rmsg: import styx;
+
+Einuse : con "fid already in use";
+Ebadfid : con "bad fid";
+Eopen : con "fid already opened";
+Enotfound : con "file does not exist";
+Enotdir : con "not a directory";
+Eperm : con "permission denied";
+
+UID: con "inferno";
+GID: con "inferno";
+
+debug := 0;
+
+Dir: adt {
+ dir: Sys->Dir;
+ offset: big;
+ parent: cyclic ref Dir;
+ child: cyclic ref Dir;
+ sibling: cyclic ref Dir;
+};
+
+Fid: adt {
+ fid: int;
+ open: int;
+ dir: ref Dir;
+};
+
+HTSZ: con 32;
+fidtab := array[HTSZ] of list of ref Fid;
+
+root: ref Dir;
+qid: int;
+mtpt := "/mnt/arch";
+bio: ref Iobuf;
+buf: array of byte;
+skip := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ str = load String String->PATH;
+ daytime = load Daytime Daytime->PATH;
+ styx = load Styx Styx->PATH;
+ if(bufio == nil || styx == nil || daytime == nil || str == nil)
+ fatal("failed to load modules");
+ styx->init();
+
+ flags := Sys->MREPL;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ fatal("failed to load "+Arg->PATH);
+ arg->init(args);
+ arg->setusage("archfs [-ab] [-m mntpt] archive [prefix ...]");
+ while((c := arg->opt()) != 0){
+ case c {
+ 'D' =>
+ debug = 1;
+ 'a' =>
+ flags = Sys->MAFTER;
+ 'b' =>
+ flags = Sys->MBEFORE;
+ 'm' =>
+ mtpt = arg->earg();
+ 's' =>
+ skip = 1;
+ * =>
+ arg->usage();
+ }
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ buf = array[Sys->ATOMICIO] of byte;
+ # root = newdir("/", UID, GID, 8r755|Sys->DMDIR, daytime->now());
+ root = newdir(basename(mtpt), UID, GID, 8r555|Sys->DMDIR, daytime->now());
+ root.parent = root;
+ readarch(hd args, tl args);
+ p := array[2] of ref Sys->FD;
+ if(sys->pipe(p) < 0)
+ fatal("can't create pipe");
+ pidch := chan of int;
+ spawn serve(p[1], pidch);
+ pid := <- pidch;
+ if(sys->mount(p[0], nil, mtpt, flags, nil) < 0)
+ fatal(sys->sprint("cannot mount archive on %s: %r", mtpt));
+}
+
+reply(fd: ref Sys->FD, m: ref Rmsg): int
+{
+ if(debug)
+ sys->fprint(sys->fildes(2), "-> %s\n", m.text());
+ s := m.pack();
+ if(s == nil)
+ return -1;
+ return sys->write(fd, s, len s);
+}
+
+error(fd: ref Sys->FD, m: ref Tmsg, e: string)
+{
+ reply(fd, ref Rmsg.Error(m.tag, e));
+}
+
+serve(fd: ref Sys->FD, pidch: chan of int)
+{
+ e: string;
+ f: ref Fid;
+
+ pidch <-= sys->pctl(Sys->NEWNS|Sys->NEWFD, 1 :: 2 :: fd.fd :: bio.fd.fd :: nil);
+ bio.fd = sys->fildes(bio.fd.fd);
+ fd = sys->fildes(fd.fd);
+Work:
+ while((m0 := Tmsg.read(fd, Styx->MAXRPC)) != nil){
+ if(debug)
+ sys->fprint(sys->fildes(2), "<- %s\n", m0.text());
+ pick m := m0 {
+ Readerror =>
+ fatal("read error on styx server");
+ Version =>
+ (s, v) := styx->compatible(m, Styx->MAXRPC, Styx->VERSION);
+ reply(fd, ref Rmsg.Version(m.tag, s, v));
+ Auth =>
+ error(fd, m, "authentication not required");
+ Flush =>
+ reply(fd, ref Rmsg.Flush(m.tag));
+ Walk =>
+ (f, e) = mapfid(m.fid);
+ if(e != nil){
+ error(fd, m, e);
+ continue;
+ }
+ if(f.open){
+ error(fd, m, Eopen);
+ continue;
+ }
+ dir := f.dir;
+ nq := 0;
+ nn := len m.names;
+ qids := array[nn] of Sys->Qid;
+ if(nn > 0){
+ for(k := 0; k < nn; k++){
+ if((dir.dir.mode & Sys->DMDIR) == 0){
+ if(k == 0){
+ error(fd, m, Enotdir);
+ continue Work;
+ }
+ break;
+ }
+ dir = lookup(dir, m.names[k]);
+ if(dir == nil){
+ if(k == 0){
+ error(fd, m, Enotfound);
+ continue Work;
+ }
+ break;
+ }
+ qids[nq++] = dir.dir.qid;
+ }
+ }
+ if(nq < nn)
+ qids = qids[0: nq];
+ if(nq == nn){
+ if(m.newfid != m.fid){
+ f = newfid(m.newfid);
+ if(f == nil){
+ error(fd, m, Einuse);
+ continue Work;
+ }
+ }
+ f.dir = dir;
+ }
+ reply(fd, ref Rmsg.Walk(m.tag, qids));
+ Open =>
+ (f, e) = mapfid(m.fid);
+ if(e != nil){
+ error(fd, m, e);
+ continue;
+ }
+ if(m.mode != Sys->OREAD){
+ error(fd, m, Eperm);
+ continue;
+ }
+ f.open = 1;
+ reply(fd, ref Rmsg.Open(m.tag, f.dir.dir.qid, Styx->MAXFDATA));
+ Create =>
+ error(fd, m, Eperm);
+ Read =>
+ (f, e) = mapfid(m.fid);
+ if(e != nil){
+ error(fd, m, e);
+ continue;
+ }
+ data := read(f.dir, m.offset, m.count);
+ reply(fd, ref Rmsg.Read(m.tag, data));
+ Write =>
+ error(fd, m, Eperm);
+ Clunk =>
+ (f, e) = mapfid(m.fid);
+ if(e != nil){
+ error(fd, m, e);
+ continue;
+ }
+ freefid(f);
+ reply(fd, ref Rmsg.Clunk(m.tag));
+ Stat =>
+ (f, e) = mapfid(m.fid);
+ if(e != nil){
+ error(fd, m, e);
+ continue;
+ }
+ reply(fd, ref Rmsg.Stat(m.tag, f.dir.dir));
+ Remove =>
+ error(fd, m, Eperm);
+ Wstat =>
+ error(fd, m, Eperm);
+ Attach =>
+ f = newfid(m.fid);
+ if(f == nil){
+ error(fd, m, Einuse);
+ continue;
+ }
+ f.dir = root;
+ reply(fd, ref Rmsg.Attach(m.tag, f.dir.dir.qid));
+ * =>
+ fatal("unknown styx message");
+ }
+ }
+}
+
+newfid(fid: int): ref Fid
+{
+ if(fid == NOFID)
+ return nil;
+ hv := hashval(fid);
+ ff: ref Fid;
+ for(l := fidtab[hv]; l != nil; l = tl l){
+ f := hd l;
+ if(f.fid == fid)
+ return nil;
+ if(ff == nil && f.fid == NOFID)
+ ff = f;
+ }
+ if((f := ff) == nil){
+ f = ref Fid;
+ fidtab[hv] = f :: fidtab[hv];
+ }
+ f.fid = fid;
+ f.open = 0;
+ return f;
+}
+
+freefid(f: ref Fid)
+{
+ hv := hashval(f.fid);
+ for(l := fidtab[hv]; l != nil; l = tl l)
+ if(hd l == f){
+ f.fid = NOFID;
+ f.dir = nil;
+ f.open = 0;
+ return;
+ }
+ fatal("cannot find fid");
+}
+
+mapfid(fid: int): (ref Fid, string)
+{
+ if(fid == NOFID)
+ return (nil, Ebadfid);
+ hv := hashval(fid);
+ for(l := fidtab[hv]; l != nil; l = tl l){
+ f := hd l;
+ if(f.fid == fid){
+ if(f.dir == nil)
+ return (nil, Enotfound);
+ return (f, nil);
+ }
+ }
+ return (nil, Ebadfid);
+}
+
+hashval(n: int): int
+{
+ n %= HTSZ;
+ if(n < 0)
+ n += HTSZ;
+ return n;
+}
+
+readarch(f: string, args: list of string)
+{
+ ar := openarch(f);
+ if(ar == nil || ar.b == nil)
+ fatal(sys->sprint("cannot open %s: %r", f));
+ bio = ar.b;
+ while((a := gethdr(ar)) != nil){
+ if(args != nil){
+ if(!selected(a.name, args)){
+ if(skip)
+ return;
+ #drain(ar, int a.d.length);
+ continue;
+ }
+ mkdirs("/", a.name);
+ }
+ d := mkdir(a.name, a.d.mode, a.d.mtime, a.d.uid, a.d.gid, 0);
+ if((a.d.mode & Sys->DMDIR) == 0){
+ d.dir.length = a.d.length;
+ d.offset = bio.offset();
+ }
+ #drain(ar, int a.d.length);
+ }
+ if(ar.err != nil)
+ fatal(ar.err);
+}
+
+selected(s: string, args: list of string): int
+{
+ for(; args != nil; args = tl args)
+ if(fileprefix(hd args, s))
+ return 1;
+ return 0;
+}
+
+fileprefix(prefix, s: string): int
+{
+ n := len prefix;
+ m := len s;
+ if(n > m || !str->prefix(prefix, s))
+ return 0;
+ if(m > n && s[n] != '/')
+ return 0;
+ return 1;
+}
+
+basename(f: string): string
+{
+ for(i := len f; i > 0; )
+ if(f[--i] == '/')
+ return f[i+1:];
+ return f;
+}
+
+split(p: string): (string, string)
+{
+ if(p == nil)
+ fatal("nil string in split");
+ if(p[0] != '/')
+ fatal("p0 not / in split");
+ while(p[0] == '/')
+ p = p[1:];
+ i := 0;
+ while(i < len p && p[i] != '/')
+ i++;
+ if(i == len p)
+ return (p, nil);
+ else
+ return (p[0:i], p[i:]);
+}
+
+mkdirs(basedir, name: string)
+{
+ (nil, names) := sys->tokenize(name, "/");
+ while(names != nil){
+ # sys->print("mkdir %s\n", basedir);
+ mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1);
+ if(tl names == nil)
+ break;
+ basedir = basedir + "/" + hd names;
+ names = tl names;
+ }
+}
+
+read(d: ref Dir, offset: big, n: int): array of byte
+{
+ if(d.dir.mode & Sys->DMDIR)
+ return readdir(d, int offset, n);
+ return readfile(d, offset, n);
+}
+
+readdir(d: ref Dir, o: int, n: int): array of byte
+{
+ k := 0;
+ m := 0;
+ b := array[n] of byte;
+ for(s := d.child; s != nil; s = s.sibling){
+ l := styx->packdirsize(s.dir);
+ if(k < o){
+ k += l;
+ continue;
+ }
+ if(m+l > n)
+ break;
+ b[m: ] = styx->packdir(s.dir);
+ m += l;
+ }
+ return b[0: m];
+}
+
+readfile(d: ref Dir, offset: big, n: int): array of byte
+{
+ if(offset+big n > d.dir.length)
+ n = int(d.dir.length-offset);
+ if(n <= 0 || offset < big 0)
+ return nil;
+ bio.seek(d.offset+offset, Bufio->SEEKSTART);
+ a := array[n] of byte;
+ p := 0;
+ m := 0;
+ for( ; n != 0; n -= m){
+ l := len buf;
+ if(n < l)
+ l = n;
+ m = bio.read(buf, l);
+ if(m <= 0 || m != l)
+ fatal("premature eof");
+ a[p:] = buf[0:m];
+ p += m;
+ }
+ return a;
+}
+
+mkdir(f: string, mode: int, mtime: int, uid: string, gid: string, existsok: int): ref Dir
+{
+ if(f == "/")
+ return nil;
+ d := newdir(basename(f), uid, gid, mode, mtime);
+ addfile(d, f, existsok);
+ return d;
+}
+
+addfile(d: ref Dir, path: string, existsok: int)
+{
+ elem: string;
+
+ opath := path;
+ p := prev := root;
+ basedir := "";
+# sys->print("addfile %s: %s\n", d.dir.name, path);
+ while(path != nil){
+ (elem, path) = split(path);
+ basedir += "/" + elem;
+ op := p;
+ p = lookup(p, elem);
+ if(path == nil){
+ if(p != nil){
+ if(!existsok && (p.dir.mode&Sys->DMDIR) == 0)
+ sys->fprint(sys->fildes(2), "addfile: %s already there", opath);
+ # fatal(sys->sprint("addfile: %s already there", opath));
+ return;
+ }
+ if(prev.child == nil)
+ prev.child = d;
+ else {
+ for(s := prev.child; s.sibling != nil; s = s.sibling)
+ ;
+ s.sibling = d;
+ }
+ d.parent = prev;
+ }
+ else {
+ if(p == nil){
+ mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1);
+ p = lookup(op, elem);
+ if(p == nil)
+ fatal("bad file system");
+ }
+ }
+ prev = p;
+ }
+}
+
+lookup(p: ref Dir, f: string): ref Dir
+{
+ if((p.dir.mode&Sys->DMDIR) == 0)
+ fatal("not a directory in lookup");
+ if(f == ".")
+ return p;
+ if(f == "..")
+ return p.parent;
+ for(d := p.child; d != nil; d = d.sibling)
+ if(d.dir.name == f)
+ return d;
+ return nil;
+}
+
+newdir(name, uid, gid: string, mode, mtime: int): ref Dir
+{
+ dir := sys->zerodir;
+ dir.name = name;
+ dir.uid = uid;
+ dir.gid = gid;
+ dir.mode = mode;
+ dir.qid.path = big (qid++);
+ dir.qid.qtype = mode>>24;
+ dir.qid.vers = 0;
+ dir.atime = dir.mtime = mtime;
+ dir.length = big 0;
+
+ d := ref Dir;
+ d.dir = dir;
+ d.offset = big 0;
+ return d;
+}
+
+prd(d: ref Dir)
+{
+ dir := d.dir;
+ sys->print("%q %q %q %bx %x %x %d %d %bd %d %d %bd\n",
+ dir.name, dir.uid, dir.gid, dir.qid.path, dir.qid.vers, dir.mode, dir.atime, dir.mtime, dir.length, dir.dtype, dir.dev, d.offset);
+}
+
+fatal(e: string)
+{
+ sys->fprint(sys->fildes(2), "archfs: %s\n", e);
+ raise "fail:error";
+}
+
+openarch(file: string): ref Archive
+{
+ b := bufio->open(file, Bufio->OREAD);
+ if(b == nil)
+ return nil;
+ ar := ref Archive;
+ ar.b = b;
+ ar.nexthdr = big 0;
+ ar.canseek = 1;
+ ar.hdr = ref Ahdr;
+ ar.hdr.d = ref Sys->Dir;
+ return ar;
+}
+
+NFLDS: con 6;
+
+gethdr(ar: ref Archive): ref Ahdr
+{
+ a := ar.hdr;
+ b := ar.b;
+ m := b.offset();
+ n := ar.nexthdr;
+ if(m != n){
+ if(ar.canseek)
+ b.seek(n, Bufio->SEEKSTART);
+ else {
+ if(m > n)
+ fatal(sys->sprint("bad offset in gethdr: m=%bd n=%bd", m, n));
+ if(drain(ar, int(n-m)) < 0)
+ return nil;
+ }
+ }
+ if((s := b.gets('\n')) == nil){
+ ar.err = "premature end of archive";
+ return nil;
+ }
+ if(s == "end of archive\n")
+ return nil;
+ (nf, fs) := sys->tokenize(s, " \t\n");
+ if(nf != NFLDS){
+ ar.err = "too few fields in file header";
+ return nil;
+ }
+ a.name = hd fs; fs = tl fs;
+ (a.d.mode, nil) = str->toint(hd fs, 8); fs = tl fs;
+ a.d.uid = hd fs; fs = tl fs;
+ a.d.gid = hd fs; fs = tl fs;
+ (a.d.mtime, nil) = str->toint(hd fs, 10); fs = tl fs;
+ (tmp, nil) := str->toint(hd fs, 10); fs = tl fs;
+ a.d.length = big tmp;
+ ar.nexthdr = b.offset()+a.d.length;
+ return a;
+}
+
+drain(ar: ref Archive, n: int): int
+{
+ while(n > 0){
+ m := n;
+ if(m > len buf)
+ m = len buf;
+ p := ar.b.read(buf, m);
+ if(p != m){
+ ar.err = "unexpectedly short read";
+ return -1;
+ }
+ n -= m;
+ }
+ return 0;
+}
diff --git a/appl/cmd/auplay.b b/appl/cmd/auplay.b
new file mode 100644
index 00000000..0be6f556
--- /dev/null
+++ b/appl/cmd/auplay.b
@@ -0,0 +1,114 @@
+implement AuPlay;
+
+include "sys.m";
+include "draw.m";
+
+sys: Sys;
+FD: import sys;
+stderr: ref FD;
+
+include "string.m";
+
+str: String;
+
+prog: string;
+play: int;
+Magic: con "rate";
+data: con "/dev/audio";
+ctl: con "/dev/audioctl";
+buffz: con Sys->ATOMICIO;
+
+AuPlay: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+process(f: string)
+{
+ buff := array[buffz] of byte;
+ inf := sys->open(f, Sys->OREAD);
+ if (inf == nil) {
+ sys->fprint(stderr, "%s: could not open %s: %r\n", prog, f);
+ return;
+ }
+ n := sys->read(inf, buff, buffz);
+ if (n < 0) {
+ sys->fprint(stderr, "%s: could not read %s: %r\n", prog, f);
+ return;
+ }
+ if (n < 10 || string buff[0:4] != Magic) {
+ sys->fprint(stderr, "%s: %s: not an audio file\n", prog, f);
+ return;
+ }
+ i := 0;
+ for (;;) {
+ if (i == n) {
+ sys->fprint(stderr, "%s: %s: bad header\n", prog, f);
+ return;
+ }
+ if (buff[i] == byte '\n') {
+ i++;
+ if (i == n) {
+ sys->fprint(stderr, "%s: %s: bad header\n", prog, f);
+ return;
+ }
+ if (buff[i] == byte '\n') {
+ i++;
+ if ((i % 4) != 0) {
+ sys->fprint(stderr, "%s: %s: unpadded header\n", prog, f);
+ return;
+ }
+ break;
+ }
+ }
+ else
+ i++;
+ }
+ if (!play) {
+ sys->write(stderr, buff, i - 1);
+ return;
+ }
+ df := sys->open(data, Sys->OWRITE);
+ if (df == nil) {
+ sys->fprint(stderr, "%s: could not open %s: %r\n", prog, data);
+ return;
+ }
+ cf := sys->open(ctl, Sys->OWRITE);
+ if (cf == nil) {
+ sys->fprint(stderr, "%s: could not open %s: %r\n", prog, ctl);
+ return;
+ }
+ if (sys->write(cf, buff, i - 1) < 0) {
+ sys->fprint(stderr, "%s: could not write %s: %r\n", prog, ctl);
+ return;
+ }
+ if (n > i && sys->write(df, buff[i:n], n - i) < 0) {
+ sys->fprint(stderr, "%s: could not write %s: %r\n", prog, data);
+ return;
+ }
+ if (sys->stream(inf, df, Sys->ATOMICIO) < 0) {
+ sys->fprint(stderr, "%s: could not stream %s: %r\n", prog, data);
+ return;
+ }
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ stderr = sys->fildes(2);
+ p := hd argv;
+ v := tl argv;
+ (nil, b) := str->splitr(p, "/");
+ if (b != nil)
+ p = b;
+ (b, nil) = str->splitr(p, ".");
+ if (b != nil)
+ p = b[0:len b - 1];
+ prog = p;
+ play = prog == "auplay";
+ while (v != nil) {
+ process(hd v);
+ v = tl v;
+ }
+}
diff --git a/appl/cmd/auth/aescbc.b b/appl/cmd/auth/aescbc.b
new file mode 100644
index 00000000..c5b6e301
--- /dev/null
+++ b/appl/cmd/auth/aescbc.b
@@ -0,0 +1,254 @@
+implement Aescbc;
+
+#
+# broadly transliterated from the Plan 9 command
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "keyring.m";
+ kr: Keyring;
+ AESbsize, MD5dlen, SHA1dlen: import Keyring;
+
+include "arg.m";
+
+Aescbc: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+#
+# encrypted file: v2hdr, 16 byte IV, AES-CBC(key, random || file), HMAC_SHA1(md5(key), AES-CBC(random || file))
+#
+
+Checkpat: con "XXXXXXXXXXXXXXXX";
+Checklen: con len Checkpat;
+Bufsize: con 4096;
+AESmaxkey: con 32;
+
+V2hdr: con "AES CBC SHA1 2\n";
+
+bin: ref Iobuf;
+bout: ref Iobuf;
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ sys->pctl(Sys->FORKFD, nil);
+ stderr = sys->fildes(2);
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("auth/aescbc -d [-k key] [-f keyfile] <file.aes >clear.txt\n or: auth/aescbc -e [-k key] [-f keyfile] <clear.txt >file.aes");
+ encrypt := -1;
+ keyfile: string;
+ pass: string;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' or 'e' =>
+ if(encrypt >= 0)
+ arg->usage();
+ encrypt = o == 'e';
+ 'f' =>
+ keyfile = arg->earg();
+ 'k' =>
+ pass = arg->earg();
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil || encrypt < 0)
+ arg->usage();
+ arg = nil;
+
+ bin = bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+
+ buf := array[Bufsize+SHA1dlen] of byte; # Checklen <= SHA1dlen
+
+ pwd: array of byte;
+ if(keyfile != nil){
+ fd := sys->open(keyfile, Sys->OREAD);
+ if(fd == nil)
+ error(sys->sprint("can't open %q: %r", keyfile), "keyfile");
+ n := readn(fd, buf, len buf);
+ while(n > 0 && buf[n-1] == byte '\n')
+ n--;
+ if(n <= 0)
+ error("no key", "no key");
+ pwd = buf[0:n];
+ }else{
+ if(pass == nil)
+ pass = readpassword("password");
+ if(pass == nil)
+ error("no key", "no key");
+ pwd = array of byte pass;
+ for(i := 0; i < len pass; i++)
+ pass[i] = 0;
+ }
+ key := array[AESmaxkey] of byte;
+ key2 := array[SHA1dlen] of byte;
+ dstate := kr->sha1(array of byte "aescbc file", 11, nil, nil);
+ kr->sha1(pwd, len pwd, key2, dstate);
+ for(i := 0; i < len pwd; i++)
+ pwd[i] = byte 0;
+ key[0:] = key2[0:MD5dlen];
+ nkey := MD5dlen;
+ kr->md5(key, nkey, key2, nil); # protect key even if HMAC_SHA1 is broken
+ key2 = key2[0:MD5dlen];
+
+ if(encrypt){
+ Write(array of byte V2hdr, AESbsize);
+ genrandom(buf, 2*AESbsize); # CBC is semantically secure if IV is unpredictable.
+ aes := kr->aessetup(key[0:nkey], buf); # use first AESbsize bytes as IV
+ kr->aescbc(aes, buf[AESbsize:], AESbsize, Keyring->Encrypt); # use second AESbsize bytes as initial plaintext
+ Write(buf, 2*AESbsize);
+ dstate = kr->hmac_sha1(buf[AESbsize:], AESbsize, key2, nil, nil);
+ while((n := bin.read(buf, Bufsize)) > 0){
+ kr->aescbc(aes, buf, n, Keyring->Encrypt);
+ Write(buf, n);
+ dstate = kr->hmac_sha1(buf, n, key2, nil, dstate);
+ if(n < Bufsize)
+ break;
+ }
+ if(n < 0)
+ error(sys->sprint("read error: %r"), "read error");
+ kr->hmac_sha1(nil, 0, key2, buf, dstate);
+ Write(buf, SHA1dlen);
+ }else{ # decrypt
+ Read(buf, AESbsize);
+ if(string buf[0:AESbsize] == V2hdr){
+ Read(buf, 2*AESbsize); # read IV and random initial plaintext
+ aes := kr->aessetup(key[0:nkey], buf);
+ dstate = kr->hmac_sha1(buf[AESbsize:], AESbsize, key2, nil, nil);
+ kr->aescbc(aes, buf[AESbsize:], AESbsize, Keyring->Decrypt);
+ Read(buf, SHA1dlen);
+ while((n := bin.read(buf[SHA1dlen:], Bufsize)) > 0){
+ dstate = kr->hmac_sha1(buf, n, key2, nil, dstate);
+ kr->aescbc(aes, buf, n, Keyring->Decrypt);
+ Write(buf, n);
+ buf[0:] = buf[n:n+SHA1dlen]; # these bytes are not yet decrypted
+ }
+ kr->hmac_sha1(nil, 0, key2, buf[SHA1dlen:], dstate);
+ if(!eqbytes(buf, buf[SHA1dlen:], SHA1dlen))
+ error("decrypted file failed to authenticate", "failed to authenticate");
+ }else{ # compatibility with past mistake; assume we're decrypting secstore files
+ aes := kr->aessetup(key[0:AESbsize], buf);
+ Read(buf, Checklen);
+ kr->aescbc(aes, buf, Checklen, Keyring->Decrypt);
+ while((n := bin.read(buf[Checklen:], Bufsize)) > 0){
+ kr->aescbc(aes, buf[Checklen:], n, Keyring->Decrypt);
+ Write(buf, n);
+ buf[0:] = buf[n:n+Checklen];
+ }
+ if(string buf[0:Checklen] != Checkpat)
+ error("decrypted file failed to authenticate", "failed to authenticate");
+ }
+ }
+ bout.flush();
+}
+
+error(s: string, why: string)
+{
+ bout.flush();
+ sys->fprint(stderr, "aescbc: %s\n", s);
+ raise "fail:"+why;
+}
+
+eqbytes(a: array of byte, b: array of byte, n: int): int
+{
+ if(len a < n || len b < n)
+ return 0;
+ for(i := 0; i < n; i++)
+ if(a[i] != b[i])
+ return 0;
+ return 1;
+}
+
+readn(fd: ref Sys->FD, buf: array of byte, nb: int): int
+{
+ for(nr := 0; nr < nb;){
+ n := sys->read(fd, buf[nr:], nb-nr);
+ if(n <= 0){
+ if(nr == 0)
+ return n;
+ break;
+ }
+ nr += n;
+ }
+ return nr;
+}
+
+Read(buf: array of byte, n: int)
+{
+ if(bin.read(buf, n) != n){
+ sys->fprint(sys->fildes(2), "aescbc: unexpectedly short read\n");
+ raise "fail:read error";
+ }
+}
+
+Write(buf: array of byte, n: int)
+{
+ if(bout.write(buf, n) != n){
+ sys->fprint(sys->fildes(2), "aescbc: write error: %r\n");
+ raise "fail:write error";
+ }
+}
+
+readpassword(prompt: string): string
+{
+ cons := sys->open("/dev/cons", Sys->ORDWR);
+ if(cons == nil)
+ return nil;
+ stdin := bufio->fopen(cons, Sys->OREAD);
+ if(stdin == nil)
+ return nil;
+ cfd := sys->open("/dev/consctl", Sys->OWRITE);
+ if (cfd == nil || sys->fprint(cfd, "rawon") <= 0)
+ sys->fprint(stderr, "aescbc: warning: cannot hide typed password\n");
+ s: string;
+L:
+ for(;;){
+ sys->fprint(cons, "%s: ", prompt);
+ s = "";
+ while ((c := stdin.getc()) >= 0){
+ case c {
+ '\n' =>
+ break L;
+ '\b' or 8r177 =>
+ if(len s > 0)
+ s = s[0:len s - 1];
+ 'u' & 8r037 =>
+ sys->fprint(cons, "\n");
+ continue L;
+ * =>
+ s[len s] = c;
+ }
+ }
+ }
+ sys->fprint(cons, "\n");
+ return s;
+}
+
+genrandom(b: array of byte, n: int)
+{
+ fd := sys->open("/dev/notquiterandom", Sys->OREAD);
+ if(fd == nil){
+ sys->fprint(stderr, "aescbc: can't open /dev/notquiterandom: %r\n");
+ raise "fail:random";
+ }
+ if(sys->read(fd, b, n) != n){
+ sys->fprint(stderr, "aescbc: can't read random numbers: %r\n");
+ raise "fail:read random";
+ }
+}
diff --git a/appl/cmd/auth/changelogin.b b/appl/cmd/auth/changelogin.b
new file mode 100644
index 00000000..97141408
--- /dev/null
+++ b/appl/cmd/auth/changelogin.b
@@ -0,0 +1,305 @@
+implement Changelogin;
+
+include "sys.m";
+ sys: Sys;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+
+Changelogin: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr, stdin, stdout: ref Sys->FD;
+keydb := "/mnt/keys";
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ ok: int;
+ word: string;
+
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+
+ stdin = sys->fildes(0);
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+
+ argv0 := hd args;
+ args = tl args;
+
+ if(args == nil){
+ sys->fprint(stderr, "usage: %s userid\n", argv0);
+ raise "fail:usage";
+ }
+
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil) {
+ sys->fprint(stderr, "%s: can't load Daytime: %r\n", argv0);
+ raise "fail:load";
+ }
+
+ # get password
+ id := hd args;
+ (dbdir, secret, expiry, err) := getuser(id);
+ if(dbdir == nil){
+ if(err != nil){
+ sys->fprint(stderr, "%s: can't get auth info for %s in %s: %s\n", argv0, id, keydb, err);
+ raise "fail:no key";
+ }
+ sys->print("new account\n");
+ }
+ for(;;){
+ if(secret != nil)
+ sys->print("secret [default = don't change]: ");
+ else
+ sys->print("secret: ");
+ (ok, word) = readline(stdin, "rawon");
+ if(!ok)
+ exit;
+ if(word == "" && secret != nil)
+ break;
+ if(len word >= 8)
+ break;
+ sys->print("!secret must be at least 8 characters\n");
+ }
+ newsecret: array of byte;
+ if(word != ""){
+ # confirm password change
+ word1 := word;
+ sys->print("confirm: ");
+ (ok, word) = readline(stdin, "rawon");
+ if(!ok || word != word1) {
+ sys->print("Entries do not match. Authinfo record unchanged.\n");
+ raise "fail:mismatch";
+ }
+
+ pwbuf := array of byte word;
+ newsecret = array[Keyring->SHA1dlen] of byte;
+ kr->sha1(pwbuf, len pwbuf, newsecret, nil);
+ }
+
+ # get expiration time (midnight of date specified)
+ maxdate := "17012038"; # largest date possible without incurring integer overflow
+ now := daytime->now();
+ tm := daytime->local(now);
+ tm.sec = 59;
+ tm.min = 59;
+ tm.hour = 23;
+ tm.year += 1;
+ if(dbdir == nil)
+ expsecs := daytime->tm2epoch(tm); # set expiration date to 23:59:59 one year from today
+ else
+ expsecs = expiry;
+ for(;;){
+ defexpdate := "permanent";
+ if(expsecs != 0) {
+ otm := daytime->local(expsecs);
+ defexpdate = sys->sprint("%2.2d%2.2d%4.4d", otm.mday, otm.mon+1, otm.year+1900);
+ }
+ sys->print("expires [DDMMYYYY/permanent, return = %s]: ", defexpdate);
+ (ok, word) = readline(stdin, "rawoff");
+ if(!ok)
+ exit;
+ if(word == "")
+ word = defexpdate;
+ if(word == "permanent"){
+ expsecs = 0;
+ break;
+ }
+ if(len word != 8){
+ sys->print("!bad date format %s\n", word);
+ continue;
+ }
+ tm.mday = int word[0:2];
+ if(tm.mday > 31 || tm.mday < 1){
+ sys->print("!bad day of month %d\n", tm.mday);
+ continue;
+ }
+ tm.mon = int word[2:4] - 1;
+ if(tm.mon > 11 || tm.mday < 0){
+ sys->print("!bad month %d\n", tm.mon + 1);
+ continue;
+ }
+ tm.year = int word[4:8] - 1900;
+ if(tm.year < 70){
+ sys->print("!bad year %d (year may be no earlier than 1970)\n", tm.year + 1900);
+ continue;
+ }
+ expsecs = daytime->tm2epoch(tm);
+ if(expsecs > now)
+ break;
+ else {
+ newexpdate := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900);
+ tm = daytime->local(daytime->now());
+ today := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900);
+ sys->print("!bad expiration date %s (must be between %s and %s)\n", newexpdate, today, maxdate);
+ expsecs = now;
+ }
+ }
+ newexpiry := expsecs;
+
+# # get the free form field
+# if(pw != nil)
+# npw.other = pw.other;
+# else
+# npw.other = "";
+# sys->print("free form info [return = %s]: ", npw.other);
+# (ok, word) = readline(stdin,"rawoff");
+# if(!ok)
+# exit;
+# if(word != "")
+# npw.other = word;
+
+ if(dbdir == nil){
+ dbdir = keydb+"/"+id;
+ fd := sys->create(dbdir, Sys->OREAD, Sys->DMDIR|8r700);
+ if(fd == nil){
+ sys->fprint(stderr, "%s: can't create account %s: %r\n", argv0, id);
+ raise "fail:create user";
+ }
+ }
+ changed := 0;
+ if(!eq(newsecret, secret)){
+ if(putsecret(dbdir, newsecret) < 0){
+ sys->fprint(stderr, "%s: can't update secret for %s: %r\n", argv0, id);
+ raise "fail:update";
+ }
+ changed = 1;
+ }
+ if(newexpiry != expiry){
+ if(putexpiry(dbdir, newexpiry) < 0){
+ sys->fprint(stderr, "%s: can't update expiry time for %s: %r\n", argv0, id);
+ raise "fail:update";
+ }
+ changed = 1;
+ }
+ sys->print("change written\n");
+}
+
+getuser(id: string): (string, array of byte, int, string)
+{
+ (ok, nil) := sys->stat(keydb);
+ if(ok < 0)
+ return (nil, nil, 0, sys->sprint("can't stat %s: %r", id));
+ dbdir := keydb+"/"+id;
+ (ok, nil) = sys->stat(dbdir);
+ if(ok < 0)
+ return (nil, nil, 0, nil);
+ fd := sys->open(dbdir+"/secret", Sys->OREAD);
+ if(fd == nil)
+ return (nil, nil, 0, sys->sprint("can't open %s/secret: %r", id));
+ d: Sys->Dir;
+ (ok, d) = sys->fstat(fd);
+ if(ok < 0)
+ return (nil, nil, 0, sys->sprint("can't stat %s/secret: %r", id));
+ l := int d.length;
+ secret: array of byte;
+ if(l > 0){
+ secret = array[l] of byte;
+ if(sys->read(fd, secret, len secret) != len secret)
+ return (nil, nil, 0, sys->sprint("error reading %s/secret: %r", id));
+ }
+ expiry := 0;
+ fd = sys->open(dbdir+"/expire", Sys->OREAD);
+ if(fd == nil)
+ return (nil, nil, 0, sys->sprint("can't open %s/expiry: %r", id));
+ b := array[32] of byte;
+ n := sys->read(fd, b, len b);
+ if(n <= 0)
+ return (nil, nil, 0, sys->sprint("error reading %s/expiry: %r", id));
+ return (dbdir, secret, int string b[0:n], nil);
+}
+
+eq(a, b: array of byte): int
+{
+ if(len a != len b)
+ return 0;
+ for(i := 0; i < len a; i++)
+ if(a[i] != b[i])
+ return 0;
+ return 1;
+}
+
+putsecret(dir: string, secret: array of byte): int
+{
+ fd := sys->create(dir+"/secret", Sys->OWRITE, 8r600);
+ if(fd == nil)
+ return -1;
+ return sys->write(fd, secret, len secret);
+}
+
+putexpiry(dir: string, expiry: int): int
+{
+ fd := sys->open(dir+"/expire", Sys->OWRITE);
+ if(fd == nil)
+ return -1;
+ return sys->fprint(fd, "%d", expiry);
+}
+
+readline(io: ref Sys->FD, mode: string): (int, string)
+{
+ r : int;
+ line : string;
+ buf := array[8192] of byte;
+ fdctl : ref Sys->FD;
+ rawoff := array of byte "rawoff";
+
+ #
+ # Change console mode to rawon
+ #
+ if(mode == "rawon"){
+ fdctl = sys->open("/dev/consctl", sys->OWRITE);
+ if(fdctl == nil || sys->write(fdctl,array of byte mode,len mode) != len mode){
+ sys->fprint(stderr, "unable to change console mode");
+ return (0,nil);
+ }
+ }
+
+ #
+ # Read up to the CRLF
+ #
+ line = "";
+ for(;;) {
+ r = sys->read(io, buf, len buf);
+ if(r <= 0){
+ sys->fprint(stderr, "error read from console mode");
+ if(mode == "rawon")
+ sys->write(fdctl,rawoff,6);
+ return (0, nil);
+ }
+
+ line += string buf[0:r];
+ if ((len line >= 1) && (line[(len line)-1] == '\n')){
+ if(mode == "rawon"){
+ r = sys->write(stdout,array of byte "\n",1);
+ if(r <= 0) {
+ sys->write(fdctl,rawoff,6);
+ return (0, nil);
+ }
+ }
+ break;
+ }
+ else {
+ if(mode == "rawon"){
+ #r = sys->write(stdout, array of byte "*",1);
+ if(r <= 0) {
+ sys->write(fdctl,rawoff,6);
+ return (0, nil);
+ }
+ }
+ }
+ }
+
+ if(mode == "rawon")
+ sys->write(fdctl,rawoff,6);
+
+ # Total success!
+ return (1, line[0:len line - 1]);
+}
diff --git a/appl/cmd/auth/convpasswd.b b/appl/cmd/auth/convpasswd.b
new file mode 100644
index 00000000..8463b0fb
--- /dev/null
+++ b/appl/cmd/auth/convpasswd.b
@@ -0,0 +1,120 @@
+implement Convpasswd;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "keyring.m";
+ keyring: Keyring;
+ IPint: import keyring;
+
+include "security.m";
+
+include "arg.m";
+
+Convpasswd: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+PW: adt {
+ id: string; # user id
+ pw: array of byte; # password hashed by SHA
+ expire: int; # expiration time (epoch seconds)
+ other: string; # about the account
+};
+
+mntpt := "/mnt/keys";
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ keyring = load Keyring Keyring->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ noload(Arg->PATH);
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ noload(Arg->PATH);
+ force := 0;
+ verbose := 0;
+ arg->init(args);
+ arg->setusage("convpasswd [-f] [-v] [-m /mnt/keys] [passwordfile]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'f' => force = 1;
+ 'm' => mntpt = arg->earg();
+ 'v' => verbose = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ f := "/keydb/password";
+ if(args != nil)
+ f = hd args;
+ iob := bufio->open(f, Bufio->OREAD);
+ if(iob == nil)
+ error(sys->sprint("%s: %r", f));
+ for(line := 1; (s := iob.gets('\n')) != nil; line++) {
+ (n, tokl) := sys->tokenize(s, ":\n");
+ if (n < 3){
+ sys->fprint(sys->fildes(2), "convpasswd: %s:%d: invalid format\n", f, line);
+ continue;
+ }
+ pw := ref PW;
+ pw.id = hd tokl;
+ pw.pw = IPint.b64toip(hd tl tokl).iptobytes();
+ pw.expire = int hd tl tl tokl;
+ if (n==3)
+ pw.other = nil;
+ else
+ pw.other = hd tl tl tl tokl;
+ err := writekey(pw, force);
+ if(err != nil)
+ error(sys->sprint("error writing /mnt/keys entry for %s: %s", pw.id, err));
+ if(verbose)
+ sys->print("%s\n", pw.id);
+ }
+}
+
+noload(p: string)
+{
+ error(sys->sprint("can't load %s: %r", p));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "convpasswd: %s\n", s);
+ raise "fail:error";
+}
+
+writekey(pw: ref PW, force: int): string
+{
+ dir := mntpt+"/"+pw.id;
+ if(sys->open(dir, Sys->OREAD) == nil){
+ # make it
+ d := sys->create(dir, Sys->OREAD, Sys->DMDIR|8r600);
+ if(d == nil)
+ return sys->sprint("can't create %s: %r", dir);
+ }else if(!force)
+ return nil; # leave existing entry alone
+ secret := dir+"/secret";
+ fd := sys->open(secret, Sys->OWRITE);
+ if(fd == nil)
+ return sys->sprint("can't open %s: %r", secret);
+ if(sys->write(fd, pw.pw, len pw.pw) != len pw.pw)
+ return sys->sprint("error writing %s: %r", secret);
+ expire := dir+"/expire";
+ fd = sys->open(expire, Sys->OWRITE);
+ if(fd == nil)
+ return sys->sprint("can't open %s: %r", expire);
+ if(sys->fprint(fd, "%d", pw.expire) < 0)
+ return sys->sprint("error writing %s: %r", expire);
+ # no equivalent of `other'
+ return nil;
+}
diff --git a/appl/cmd/auth/countersigner.b b/appl/cmd/auth/countersigner.b
new file mode 100644
index 00000000..a444f807
--- /dev/null
+++ b/appl/cmd/auth/countersigner.b
@@ -0,0 +1,59 @@
+implement Countersigner;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "keyring.m";
+ kr: Keyring;
+
+include "security.m";
+
+Countersigner: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr, stdin, stdout: ref Sys->FD;
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+
+ stdin = sys->fildes(0);
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+
+ sys->pctl(Sys->FORKNS, nil);
+ if(sys->chdir("/keydb") < 0){
+ sys->fprint(stderr, "countersigner: no key database\n");
+ raise "fail:no keydb";
+ }
+
+ # get boxid
+ buf := kr->getmsg(stdin);
+ if(buf == nil){
+ sys->fprint(stderr, "countersigner: client hung up\n");
+ raise "fail:hungup";
+ }
+ boxid := string buf;
+
+ # read file
+ file := "countersigned/"+boxid;
+ fd := sys->open(file, Sys->OREAD);
+ if(fd == nil){
+ sys->fprint(stderr, "countersigner: can't open %s: %r\n", file);
+ raise "fail:bad boxid";
+ }
+ blind := kr->getmsg(fd);
+ if(blind == nil){
+ sys->fprint(stderr, "countersigner: can't read %s\n", file);
+ raise "fail:no blind";
+ }
+
+ # answer client
+ kr->sendmsg(stdout, blind, len blind);
+}
diff --git a/appl/cmd/auth/createsignerkey.b b/appl/cmd/auth/createsignerkey.b
new file mode 100644
index 00000000..90a54b6f
--- /dev/null
+++ b/appl/cmd/auth/createsignerkey.b
@@ -0,0 +1,144 @@
+implement Createsignerkey;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "daytime.m";
+
+include "keyring.m";
+ kr: Keyring;
+
+include "arg.m";
+
+# signer key never expires
+SKexpire: con 0;
+
+# size in bits of modulus for public keys
+PKmodlen: con 512;
+
+# size in bits of modulus for diffie hellman
+DHmodlen: con 512;
+
+algs := array[] of {"rsa", "elgamal"}; # first entry is default
+
+Createsignerkey: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ err: string;
+
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ if(kr == nil)
+ loaderr(Keyring->PATH);
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ loaderr(Arg->PATH);
+
+ arg->init(argv);
+ arg->setusage("createsignerkey [-a algorithm] [-f keyfile] [-e ddmmyyyy] [-b size-in-bits] name-of-owner");
+ alg := algs[0];
+ filename := "/keydb/signerkey";
+ expire := SKexpire;
+ bits := PKmodlen;
+ while((c := arg->opt()) != 0){
+ case c {
+ 'a' =>
+ alg = arg->arg();
+ if(alg == nil)
+ arg->usage();
+ for(i:=0;; i++){
+ if(i >= len algs)
+ error(sys->sprint("unknown algorithm: %s", alg));
+ else if(alg == algs[i])
+ break;
+ }
+ 'f' or 'k' =>
+ filename = arg->earg();
+ 'e' =>
+ s := arg->earg();
+ (err, expire) = checkdate(s);
+ if(err != nil)
+ error(err);
+ 'b' =>
+ s := arg->earg();
+ bits = int s;
+ if(bits < 32 || bits > 4096)
+ error("modulus must be in the range of 32 to 4096 bits");
+ * =>
+ arg->usage();
+ }
+ }
+ argv = arg->argv();
+ if(argv == nil)
+ arg->usage();
+ arg = nil;
+
+ owner := hd argv;
+
+ # generate a local key, self-signed
+ info := ref Keyring->Authinfo;
+ info.mysk = kr->genSK(alg, owner, bits);
+ if(info.mysk == nil)
+ error(sys->sprint("algorithm %s not configured in system", alg));
+ info.mypk = kr->sktopk(info.mysk);
+ info.spk = kr->sktopk(info.mysk);
+ myPKbuf := array of byte kr->pktostr(info.mypk);
+ state := kr->sha1(myPKbuf, len myPKbuf, nil, nil);
+ info.cert = kr->sign(info.mysk, expire, state, "sha1");
+ (info.alpha, info.p) = kr->dhparams(DHmodlen);
+
+ if(kr->writeauthinfo(filename, info) < 0)
+ error(sys->sprint("can't write signerkey file %s: %r", filename));
+}
+
+loaderr(s: string)
+{
+ error(sys->sprint("can't load %s: %r", s));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "createsignerkey: %s\n", s);
+ raise "fail:error";
+}
+
+checkdate(word: string): (string, int)
+{
+ if(len word != 8)
+ return ("!date must be in form ddmmyyyy", 0);
+
+ daytime := load Daytime Daytime->PATH;
+ if(daytime == nil)
+ loaderr(Daytime->PATH);
+
+ now := daytime->now();
+
+ tm := daytime->local(now);
+ tm.sec = 59;
+ tm.min = 59;
+ tm.hour = 24;
+
+ tm.mday = int word[0:2];
+ if(tm.mday > 31 || tm.mday < 1)
+ return ("!bad day of month", 0);
+
+ tm.mon = int word[2:4] - 1;
+ if(tm.mon > 11 || tm.mday < 0)
+ return ("!bad month", 0);
+
+ tm.year = int word[4:8] - 1900;
+ if(tm.year < 70)
+ return ("!bad year", 0);
+
+ newdate := daytime->tm2epoch(tm);
+ if(newdate < now)
+ return ("!expiration date must be in the future", 0);
+
+ return (nil, newdate);
+}
diff --git a/appl/cmd/auth/factotum/authio.m b/appl/cmd/auth/factotum/authio.m
new file mode 100644
index 00000000..7c0565b5
--- /dev/null
+++ b/appl/cmd/auth/factotum/authio.m
@@ -0,0 +1,80 @@
+Authio: module
+{
+
+ Aattr, Aval, Aquery: con iota;
+
+ Attr: adt {
+ tag: int;
+ name: string;
+ val: string;
+
+ text: fn(a: self ref Attr): string;
+ };
+
+ Key: adt {
+ attrs: list of ref Attr;
+ secrets: list of ref Attr;
+ # proto: Authproto;
+
+ mk: fn(attrs: list of ref Attr): ref Key;
+ text: fn(k: self ref Key): string;
+ safetext: fn(k: self ref Key): string;
+ };
+
+ Fid: adt
+ {
+ fid: int;
+ pid: int;
+ err: string;
+ attrs: list of ref Attr;
+ write: chan of (array of byte, Sys->Rwrite);
+ read: chan of (int, Sys->Rread);
+ # proto: Authproto;
+ done: int;
+ ai: ref Authinfo;
+ };
+
+ Rpc: adt {
+ r: ref Fid;
+ cmd: int;
+ arg: array of byte;
+ nbytes: int;
+ rc: chan of (array of byte, string);
+ };
+
+ IO: adt {
+ f: ref Fid;
+ rpc: ref Rpc;
+
+ findkey: fn(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string);
+ needkey: fn(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string);
+ read: fn(io: self ref IO): array of byte;
+ readn: fn(io: self ref IO, n: int): array of byte;
+ write: fn(io: self ref IO, buf: array of byte, n: int): int;
+ toosmall: fn(io: self ref IO, n: int);
+ error: fn(io: self ref IO, s: string);
+ ok: fn(io: self ref IO);
+ done: fn(io: self ref IO, ai: ref Authinfo);
+ };
+
+ # need more ... ?
+ Authinfo: adt {
+ cuid: string; # caller id
+ suid: string; # server id
+ cap: string; # capability (only valid on server side)
+ secret: array of byte;
+ };
+
+ memrandom: fn(a: array of byte, n: int);
+ eqbytes: fn(a, b: array of byte): int;
+ netmkaddr: fn(addr, net, svc: string): string;
+ user: fn(): string;
+ lookattrval: fn(a: list of ref Attr, n: string): string;
+ parseline: fn(s: string): list of ref Attr;
+};
+
+Authproto: module
+{
+ init: fn(f: Authio): string;
+ interaction: fn(attrs: list of ref Authio->Attr, io: ref Authio->IO): string;
+};
diff --git a/appl/cmd/auth/factotum/factotum.b b/appl/cmd/auth/factotum/factotum.b
new file mode 100644
index 00000000..5f5b02a3
--- /dev/null
+++ b/appl/cmd/auth/factotum/factotum.b
@@ -0,0 +1,978 @@
+implement Factotum, Authio;
+
+#
+# Copyright © 2003-2004 Vita Nuova Holdings Limited
+#
+
+include "sys.m";
+ sys: Sys;
+ Rread, Rwrite: import Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "keyring.m";
+
+include "authio.m";
+
+include "arg.m";
+
+Factotum: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+#confirm, log
+
+Files: adt {
+ ctl: ref Sys->FileIO;
+ rpc: ref Sys->FileIO;
+ proto: ref Sys->FileIO;
+ needkey: ref Sys->FileIO;
+};
+
+Debug: con 0;
+debug := Debug;
+
+files: Files;
+authio: Authio;
+
+keymanc: chan of (list of ref Attr, int, chan of (ref Key, string));
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ authio = load Authio "$self";
+
+ svcname := "#sfactotum";
+ mntpt := "/mnt/factotum";
+ arg := load Arg Arg->PATH;
+ if(arg != nil){
+ arg->init(args);
+ arg->setusage("auth/factotum [-d] [-m /mnt/factotum] [-s factotum]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => debug = 1;
+ 'm' => mntpt = arg->earg();
+ 's' => svcname = "#s"+arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ arg->usage();
+ arg = nil;
+ }
+ sys->unmount(nil, mntpt);
+ if(sys->bind(svcname, mntpt, Sys->MREPL) < 0)
+ err(sys->sprint("can't bind %s on %s: %r", svcname, mntpt));
+ files.ctl = sys->file2chan(mntpt, "ctl");
+ files.rpc = sys->file2chan(mntpt, "rpc");
+ files.proto = sys->file2chan(mntpt, "proto");
+ files.needkey = sys->file2chan(mntpt, "needkey");
+ if(files.ctl == nil || files.rpc == nil || files.proto == nil || files.needkey == nil)
+ err(sys->sprint("can't create %s/*: %r", mntpt));
+ keymanc = chan of (list of ref Attr, int, chan of (ref Key, string));
+ spawn factotumsrv();
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ b := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, b, len b);
+ if(n <= 0)
+ return nil;
+ return string b[0:n];
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "factotum: %s\n", s);
+ raise "fail:error";
+}
+
+rlist: list of ref Fid;
+
+factotumsrv()
+{
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKENV, nil);
+ if(!Debug)
+ privacy();
+ allkeys := array[0] of ref Key;
+ pidc := chan of int;
+ donec := chan of ref Fid;
+# keyc := chan of (list of ref Attr, chan of (ref Key, string));
+ needfid := -1;
+ needed, needy: list of (int, list of ref Attr, chan of (ref Key, string));
+ needread: Sys->Rread;
+ needtag := 0;
+ for(;;) X: alt{
+ r := <-donec =>
+ r.pid = 0;
+ cleanfid(r.fid);
+
+ (off, nbytes, nil, rc) := <-files.ctl.read =>
+ if(rc == nil)
+ break;
+ s := "";
+ for(i := 0; i < len allkeys; i++)
+ if((k := allkeys[i]) != nil)
+ s += k.safetext()+"\n";
+ rc <-= reads(s, off, nbytes);
+ (nil, data, nil, wc) := <-files.ctl.write =>
+ if(wc == nil)
+ break;
+ (nf, flds) := sys->tokenize(string data, "\n\r");
+ if(nf > 1){
+ # compatibility with plan 9; has the advantage you can tell which key is wrong
+ wc <-= (0, "multiline write not allowed");
+ break;
+ }
+ s := hd flds;
+ if(s == nil || s[0] == '#'){
+ wc <-= (len data, nil);
+ break;
+ }
+ for(i := 0; i < len s && s[i] != ' '; i++){
+ # skip
+ }
+ verb := s[0:i];
+ if(i < len s)
+ i++;
+ s = s[i:];
+ case verb {
+ "key" =>
+ k := Key.mk(parseline(s));
+ if(k == nil){
+ wc <-= (len data, nil); # ignore it
+ break;
+ }
+ if(lookattrval(k.attrs, "proto") == nil){
+ wc <-= (0, "key without proto");
+ break;
+ }
+ allkeys = addkey(allkeys, k);
+ wc <-= (len data, nil);
+ "delkey" =>
+ attrs := parseline(s);
+ for(al := attrs; al != nil; al = tl al){
+ a := hd al;
+ if(a.name[0] == '!' && (a.val != nil || a.tag != Aquery)){
+ wc <-= (0, "cannot specify values for private fields");
+ break X;
+ }
+ }
+ if(delkey(allkeys, attrs) == 0)
+ wc <-= (0, "no matching keys");
+ else
+ wc <-= (len data, nil);
+ "debug" =>
+ wc <-= (len data, nil);
+ * =>
+ wc <-= (0, "unknown ctl request");
+ }
+
+ (nil, nbytes, fid, rc) := <-files.rpc.read =>
+ if(rc == nil)
+ break;
+ r := findfid(fid);
+ if(r == nil){
+ rc <-= (nil, "unknown request");
+ break;
+ }
+ alt{
+ r.read <-= (nbytes, rc) =>
+ ;
+ * =>
+ rc <-= (nil, "concurrent rpc read not allowed");
+ }
+ (nil, data, fid, wc) := <-files.rpc.write =>
+ if(wc == nil){
+ cleanfid(fid);
+ break;
+ }
+ r := findfid(fid);
+ if(r == nil){
+ r = ref Fid(fid, 0, nil, nil, chan[1] of (array of byte, Rwrite), chan[1] of (int, Rread), 0, nil);
+ spawn request(r, pidc, donec);
+ r.pid = <-pidc;
+ rlist = r :: rlist;
+ }
+ # this non-blocking write avoids a potential deadlock situation that
+ # can happen when a proto module calls findkey at the same time
+ # a client tries to write to the rpc file. this might not be the correct fix!
+ alt{
+ r.write <-= (data, wc) =>
+ ;
+ * =>
+ wc <-= (-1, "concurrent rpc write not allowed");
+ }
+
+ (off, nbytes, nil, rc) := <-files.proto.read =>
+ if(rc == nil)
+ break;
+ rc <-= reads("pass\np9any\n", off, nbytes); # TO DO
+ (nil, nil, nil, wc) := <-files.proto.write =>
+ if(wc != nil)
+ wc <-= (0, "illegal operation");
+
+ (nil, nil, fid, rc) := <-files.needkey.read =>
+ if(rc == nil)
+ break;
+ if(needfid >= 0 && fid != needfid){
+ rc <-= (nil, "file in use");
+ break;
+ }
+ needfid = fid;
+ if(needy != nil){
+ (tag, attr, kc) := hd needy;
+ needy = tl needy;
+ needed = (tag, attr, kc) :: needed;
+ rc <-= (sys->aprint("needkey tag=%ud %s", tag, attrtext(attr)), nil);
+ break;
+ }
+ if(needread != nil){
+ rc <-= (nil, "already reading");
+ break;
+ }
+ needread = rc;
+ (nil, data, fid, wc) := <-files.needkey.write =>
+ if(wc == nil){
+ if(needfid == fid){
+ needfid = -1; # TO DO? give needkey errors back to request
+ needread = nil;
+ }
+ break;
+ }
+ if(needfid >= 0 && fid != needfid){
+ wc <-= (0, "file in use");
+ break;
+ }
+ needfid = fid;
+ tagline := parseline(string data);
+ if(len tagline != 1 || (t := lookattrval(tagline, "tag")) == nil){
+ wc <-= (0, "no tag");
+ break;
+ }
+ tag := int t;
+ nl: list of (int, list of ref Attr, chan of (ref Key, string));
+ found := 0;
+ for(l := needed; l != nil; l = tl l){
+ (ntag, attrs, kc) := hd l;
+ if(tag == ntag){
+ found = 1;
+ k := findkey(allkeys, attrs);
+ if(k != nil)
+ kc <-= (k, nil);
+ else
+ kc <-= (nil, "needkey "+attrtext(attrs));
+ while((l = tl l) != nil)
+ nl = hd l :: nl;
+ break;
+ }
+ nl = hd l :: nl;
+ }
+ if(found)
+ wc <-= (len data, nil);
+ else
+ wc <-= (0, "tag not found");
+
+ (attrs, required, kc) := <-keymanc =>
+ # look for key and reply
+ k := findkey(allkeys, attrs);
+ if(k != nil){
+ kc <-= (k, nil);
+ break;
+ }else if(!required || needfid == -1){
+ kc <-= (nil, "needkey "+attrtext(attrs));
+ break;
+ }
+ # query surrounding environment using needkey
+ if(needread != nil){
+ needed = (needtag, attrs, kc) :: needed;
+ needread <-= (sys->aprint("needkey tag=%ud %s", needtag, attrtext(attrs)), nil);
+ needread = nil;
+ needtag++;
+ }else
+ needy = (needtag++, attrs, kc) :: needy;
+ }
+}
+
+findfid(fid: int): ref Fid
+{
+ for(rl := rlist; rl != nil; rl = tl rl){
+ r := hd rl;
+ if(r.fid == fid)
+ return r;
+ }
+ return nil;
+}
+
+cleanfid(fid: int)
+{
+ rl := rlist;
+ rlist = nil;
+ for(; rl != nil; rl = tl rl){
+ r := hd rl;
+ if(r.fid != fid)
+ rlist = r :: rlist;
+ else if(r.pid)
+ kill(r.pid);
+ }
+}
+
+kill(pid: int)
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+}
+
+privacy()
+{
+ fd := sys->open("#p/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "private") < 0)
+ sys->fprint(sys->fildes(2), "factotum: warning: unable to make memory private: %r\n");
+}
+
+reads(str: string, off, nbytes: int): (array of byte, string)
+{
+ bstr := array of byte str;
+ slen := len bstr;
+ if(off < 0 || off >= slen)
+ return (nil, nil);
+ if(off + nbytes > slen)
+ nbytes = slen - off;
+ if(nbytes <= 0)
+ return (nil, nil);
+ return (bstr[off:off+nbytes], nil);
+}
+
+Ogok, Ostart, Oread, Owrite, Oauthinfo, Oattr: con iota;
+
+ops := array[] of {
+ (Ostart, "start"),
+ (Oread, "read"),
+ (Owrite, "write"),
+ (Oauthinfo, "authinfo"),
+ (Oattr, "attr"),
+};
+
+request(r: ref Fid, pidc: chan of int, donec: chan of ref Fid)
+{
+ pidc <-= sys->pctl(0, nil);
+ rpc := rio(r);
+ while(rpc != nil){
+ if(rpc.cmd == Ostart){
+ (proto, attrs, e) := startproto(string rpc.arg);
+ if(e != nil){
+ reply(rpc, "error "+e);
+ rpc = rio(r);
+ continue;
+ }
+ r.attrs = attrs; # saved for attr request
+ ok(rpc);
+ io := ref IO(r, nil);
+ {
+ err := proto->interaction(attrs, io);
+ if(debug && err != nil)
+ sys->fprint(sys->fildes(2), "factotum: failure: %s\n", err);
+ if(r.err == nil)
+ r.err = err;
+ r.done = 1;
+ }exception ex{
+ "*" =>
+ r.done = 0;
+ r.err = "exception "+ex;
+ }
+ if(r.err != nil)
+ io.error(r.err);
+ rpc = finish(r);
+ r.attrs = nil;
+ r.err = nil;
+ r.done = 0;
+ r.ai = nil;
+ }else
+ reply(rpc, "no current protocol");
+ }
+ flushreq(r, donec);
+}
+
+startproto(request: string): (Authproto, list of ref Attr, string)
+{
+ attrs := parseline(request);
+ if(Debug)
+ sys->print("-> %s <-\n", attrtext(attrs));
+ p := lookattrval(attrs, "proto");
+ if(p == nil)
+ return (nil, nil, "did not specify protocol");
+ if(Debug)
+ sys->print("proto=%s\n", p);
+ if(any(p, "./")) # avoid unpleasantness
+ return (nil, nil, "illegal protocol: "+p);
+ proto := load Authproto "/dis/auth/proto/"+p+".dis";
+ if(proto == nil)
+ return (nil, nil, sys->sprint("protocol %s: %r", p));
+ if(Debug)
+ sys->print("start %s\n", p);
+ e: string;
+ {
+ e = proto->init(authio);
+ }exception ex{
+ "*" =>
+ e = "exception "+ex;
+ }
+ if(e != nil)
+ return (nil, nil, e);
+ return (proto, attrs, nil);
+}
+
+finish(r: ref Fid): ref Rpc
+{
+ while((rpc := rio(r)) != nil)
+ case rpc.cmd {
+ Owrite =>
+ phase(rpc, "protocol phase error");
+ Oread =>
+ if(r.err != nil)
+ reply(rpc, "error "+r.err);
+ else
+ done(rpc, r.ai);
+ Oauthinfo =>
+ if(r.done){
+ if(r.ai == nil)
+ reply(rpc, "error no authinfo available");
+ else{
+ a := packai(r.ai);
+ if(rpc.nbytes-3 < len a)
+ reply(rpc, sys->sprint("toosmall %d", len a + 3));
+ else
+ okdata(rpc, a);
+ }
+ }else
+ reply(rpc, "error authentication unfinished");
+ Ostart =>
+ return rpc;
+ * =>
+ reply(rpc, "error unexpected request");
+ }
+ return nil;
+}
+
+flushreq(r: ref Fid, donec: chan of ref Fid)
+{
+ for(;;) alt{
+ donec <-= r =>
+ exit;
+ (nil, wc) := <-r.write =>
+ wc <-= (0, "write rpc protocol error");
+ (nil, rc) := <-r.read =>
+ rc <-= (nil, "read rpc protocol error");
+ }
+}
+
+rio(r: ref Fid): ref Rpc
+{
+ req: array of byte;
+ for(;;) alt{
+ (data, wc) := <-r.write =>
+ if(req != nil){
+ wc <-= (0, "rpc pending; read to clear");
+ break;
+ }
+ req = data;
+ wc <-= (len data, nil);
+
+ (nbytes, rc) := <-r.read =>
+ if(req == nil){
+ rc <-= (nil, "no rpc pending");
+ break;
+ }
+ (cmd, arg) := op(req, ops);
+ req = nil;
+ rpc := ref Rpc(r, cmd, arg, nbytes, rc);
+ case cmd {
+ Ogok =>
+ reply(rpc, "error unknown rpc");
+ break;
+ Oattr =>
+ if(r.attrs == nil)
+ reply(rpc, "error no attributes");
+ else
+ reply(rpc, "ok "+attrtext(r.attrs));
+ break;
+ * =>
+ return rpc;
+ }
+ }
+}
+
+ok(rpc: ref Rpc)
+{
+ reply(rpc, "ok");
+}
+
+okdata(rpc: ref Rpc, a: array of byte)
+{
+ b := array[len a + 3] of byte;
+ b[0] = byte 'o';
+ b[1] = byte 'k';
+ b[2] = byte ' ';
+ b[3:] = a;
+ rpc.rc <-= (b, nil);
+}
+
+done(rpc: ref Rpc, ai: ref Authinfo)
+{
+ rpc.r.ai = ai;
+ rpc.r.done = 1;
+ if(ai != nil)
+ reply(rpc, "done haveai");
+ else
+ reply(rpc, "done");
+}
+
+phase(rpc: ref Rpc, s: string)
+{
+ reply(rpc, "phase "+s);
+}
+
+needkey(rpc: ref Rpc, attrs: list of ref Attr)
+{
+ reply(rpc, "needkey "+attrtext(attrs));
+}
+
+reply(rpc: ref Rpc, s: string)
+{
+ rpc.rc <-= reads(s, 0, rpc.nbytes);
+}
+
+puta(a: array of byte, n: int, v: array of byte): int
+{
+ if(n < 0)
+ return -1;
+ c := len v;
+ if(n+2+c > len a)
+ return -1;
+ a[n++] = byte c;
+ a[n++] = byte (c>>8);
+ a[n:] = v;
+ return n + len v;
+}
+
+packai(ai: ref Authinfo): array of byte
+{
+ a := array[1024] of byte;
+ i := puta(a, 0, array of byte ai.cuid);
+ i = puta(a, i, array of byte ai.suid);
+ i = puta(a, i, array of byte ai.cap);
+ i = puta(a, i, ai.secret);
+ if(i < 0)
+ return nil;
+ return a[0:i];
+}
+
+op(a: array of byte, ops: array of (int, string)): (int, array of byte)
+{
+ arg: array of byte;
+ for(i := 0; i < len a; i++)
+ if(a[i] == byte ' '){
+ if(i+1 < len a)
+ arg = a[i+1:];
+ break;
+ }
+ s := string a[0:i];
+ for(i = 0; i < len ops; i++){
+ (cmd, name) := ops[i];
+ if(s == name)
+ return (cmd, arg);
+ }
+ return (Ogok, arg);
+}
+
+parseline(s: string): list of ref Attr
+{
+ fld := str->unquoted(s);
+ rfld := fld;
+ for(fld = nil; rfld != nil; rfld = tl rfld)
+ fld = (hd rfld) :: fld;
+ attrs: list of ref Attr;
+ for(; fld != nil; fld = tl fld){
+ n := hd fld;
+ a := "";
+ tag := Aattr;
+ for(i:=0; i<len n; i++)
+ if(n[i] == '='){
+ a = n[i+1:];
+ n = n[0:i];
+ tag = Aval;
+ }
+ if(len n == 0)
+ continue;
+ if(tag == Aattr && len n > 1 && n[len n-1] == '?'){
+ tag = Aquery;
+ n = n[0:len n-1];
+ }
+ attrs = ref Attr(tag, n, a) :: attrs;
+ }
+ return attrs;
+}
+
+Attr.text(a: self ref Attr): string
+{
+ case a.tag {
+ Aattr =>
+ return a.name;
+ Aval =>
+ return a.name+"="+a.val;
+ Aquery =>
+ return a.name+"?";
+ * =>
+ return "??";
+ }
+}
+
+attrtext(attrs: list of ref Attr): string
+{
+ s := "";
+ sp := 0;
+ for(; attrs != nil; attrs = tl attrs){
+ if(sp)
+ s[len s] = ' ';
+ sp = 1;
+ s += (hd attrs).text();
+ }
+ return s;
+}
+
+lookattr(attrs: list of ref Attr, n: string): ref Attr
+{
+ for(; attrs != nil; attrs = tl attrs)
+ if((a := hd attrs).tag != Aquery && a.name == n)
+ return a;
+ return nil;
+}
+
+lookattrval(attrs: list of ref Attr, n: string): string
+{
+ if((a := lookattr(attrs, n)) != nil)
+ return a.val;
+ return nil;
+}
+
+anyattr(attrs: list of ref Attr, n: string): ref Attr
+{
+ for(; attrs != nil; attrs = tl attrs)
+ if((a := hd attrs).name == n)
+ return a;
+ return nil;
+}
+
+reverse[T](l: list of T): list of T
+{
+ r: list of T;
+ for(; l != nil; l = tl l)
+ r = hd l :: r;
+ return r;
+}
+
+setattrs(lv: list of ref Attr, rv: list of ref Attr): list of ref Attr
+{
+ # new attributes
+ nl: list of ref Attr;
+ for(rl := rv; rl != nil; rl = tl rl)
+ if(anyattr(lv, (hd rl).name) == nil)
+ nl = ref(*hd rl) :: nl;
+
+ # new values
+ for(; lv != nil; lv = tl lv){
+ a := lookattr(rv, (hd lv).name); # won't take queries
+ if(a != nil)
+ nl = ref *a :: nl;
+ }
+
+ return reverse(nl);
+}
+
+delattrs(lv: list of ref Attr, rv: list of ref Attr): list of ref Attr
+{
+ nl: list of ref Attr;
+ for(; lv != nil; lv = tl lv)
+ if(anyattr(rv, (hd lv).name) == nil)
+ nl = hd lv :: nl;
+ return reverse(nl);
+}
+
+matchattr(attrs: list of ref Attr, pat: ref Attr): int
+{
+ return (b := lookattr(attrs, pat.name)) != nil && (pat.tag == Aquery || b.val == pat.val);
+}
+
+matchattrs(pub: list of ref Attr, secret: list of ref Attr, pats: list of ref Attr): int
+{
+ for(pl := pats; pl != nil; pl = tl pl)
+ if(!matchattr(pub, hd pl) && !matchattr(secret, hd pl))
+ return 0;
+ return 1;
+}
+
+sortattrs(attrs: list of ref Attr): list of ref Attr
+{
+ a := array[len attrs] of ref Attr;
+ i := 0;
+ for(l := attrs; l != nil; l = tl l)
+ a[i++] = hd l;
+ shellsort(a);
+ for(i = 0; i < len a; i++)
+ l = a[i] :: l;
+ return l;
+}
+
+# sort into decreasing order (we'll reverse the list)
+shellsort(a: array of ref Attr)
+{
+ n := len a;
+ for(gap := n; gap > 0; ) {
+ gap /= 2;
+ max := n-gap;
+ ex: int;
+ do{
+ ex = 0;
+ for(i := 0; i < max; i++) {
+ j := i+gap;
+ if(a[i].name > a[j].name || a[i].name == nil) {
+ t := a[i]; a[i] = a[j]; a[j] = t;
+ ex = 1;
+ }
+ }
+ }while(ex);
+ }
+}
+
+findkey(keys: array of ref Key, attrs: list of ref Attr): ref Key
+{
+ if(Debug)
+ sys->print("findkey %q\n", attrtext(attrs));
+ for(i := 0; i < len keys; i++)
+ if((k := keys[i]) != nil && matchattrs(k.attrs, k.secrets, attrs))
+ return k;
+ return nil;
+}
+
+delkey(keys: array of ref Key, attrs: list of ref Attr): int
+{
+ nk := 0;
+ for(i := 0; i < len keys; i++)
+ if((k := keys[i]) != nil)
+ if(matchattrs(k.attrs, k.secrets, attrs)){
+ nk++;
+ keys[i] = nil;
+ }
+ return nk;
+}
+
+Key.mk(attrs: list of ref Attr): ref Key
+{
+ k := ref Key;
+ for(; attrs != nil; attrs = tl attrs){
+ a := hd attrs;
+ if(a.name != nil){
+ if(a.name[0] == '!')
+ k.secrets = a :: k.secrets;
+ else
+ k.attrs = a :: k.attrs;
+ }
+ }
+ if(k.attrs != nil || k.secrets != nil)
+ return k;
+ return nil;
+}
+
+addkey(keys: array of ref Key, k: ref Key): array of ref Key
+{
+ for(i := 0; i < len keys; i++)
+ if(keys[i] == nil){
+ keys[i] = k;
+ return keys;
+ }
+ n := array[len keys+1] of ref Key;
+ n[0:] = keys;
+ n[len keys] = k;
+ return n;
+}
+
+Key.text(k: self ref Key): string
+{
+ s := attrtext(k.attrs);
+ if(s != nil && k.secrets != nil)
+ s[len s] = ' ';
+ return s + attrtext(k.secrets);
+}
+
+Key.safetext(k: self ref Key): string
+{
+ s := attrtext(sortattrs(k.attrs));
+ sp := s != nil;
+ for(sl := k.secrets; sl != nil; sl = tl sl){
+ if(sp)
+ s[len s] = ' ';
+ s += sys->sprint("%s?", (hd sl).name);
+ }
+ return s;
+}
+
+any(s: string, t: string): int
+{
+ for(i := 0; i < len s; i++)
+ for(j := 0; j < len t; j++)
+ if(s[i] == t[j])
+ return 1;
+ return 0;
+}
+
+IO.findkey(nil: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string)
+{
+ ea := parseline(extra);
+ for(; ea != nil; ea = tl ea)
+ attrs = hd ea :: attrs;
+ kc := chan of (ref Key, string);
+ keymanc <-= (attrs, 1, kc); # TO DO: 1 => 0 for not needed
+ return <-kc;
+}
+
+IO.needkey(nil: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string)
+{
+ ea := parseline(extra);
+ for(; ea != nil; ea = tl ea)
+ attrs = hd ea :: attrs;
+ kc := chan of (ref Key, string);
+ keymanc <-= (attrs, 1, kc);
+ return <-kc;
+}
+
+IO.read(io: self ref IO): array of byte
+{
+ io.ok();
+ while((rpc := rio(io.f)) != nil)
+ case rpc.cmd {
+ * =>
+ phase(rpc, "protocol phase error");
+ Oauthinfo =>
+ reply(rpc, "error authentication unfinished");
+ Owrite =>
+ io.rpc = rpc;
+ if(rpc.arg == nil)
+ rpc.arg = array[0] of byte;
+ return rpc.arg;
+ }
+ exit;
+}
+
+IO.readn(io: self ref IO, n: int): array of byte
+{
+ while((buf := io.read()) != nil && len buf < n)
+ io.toosmall(n);
+ return buf;
+}
+
+IO.write(io: self ref IO, buf: array of byte, n: int): int
+{
+ io.ok();
+ while((rpc := rio(io.f)) != nil)
+ case rpc.cmd {
+ Oread =>
+ if(rpc.nbytes-3 >= n){
+ okdata(rpc, buf[0:n]);
+ return n;
+ }
+ io.toosmall(n+3);
+ Oauthinfo =>
+ reply(rpc, "error authentication unfinished");
+ * =>
+ phase(rpc, "protocol phase error");
+ }
+ exit;
+}
+
+IO.ok(io: self ref IO)
+{
+ if(io.rpc != nil){
+ reply(io.rpc, "ok");
+ io.rpc = nil;
+ }
+}
+
+IO.toosmall(io: self ref IO, n: int)
+{
+ if(io.rpc != nil){
+ reply(io.rpc, sys->sprint("toosmall %d", n));
+ io.rpc = nil;
+ }
+}
+
+IO.error(io: self ref IO, s: string)
+{
+ if(io.rpc != nil){
+ io.rpc.rc <-= (nil, "error "+s);
+ io.rpc = nil;
+ }
+}
+
+IO.done(io: self ref IO, ai: ref Authinfo)
+{
+ io.f.ai = ai;
+ io.ok();
+ while((rpc := rio(io.f)) != nil)
+ case rpc.cmd {
+ Oread or Owrite =>
+ done(rpc, ai);
+ return;
+ * =>
+ phase(rpc, "protocol phase error");
+ }
+}
+
+memrandom(a: array of byte, n: int)
+{
+ if(0){
+ # speed up testing
+ for(i := 0; i < len a; i++)
+ a[i] = byte i;
+ return;
+ }
+ fd := sys->open("/dev/notquiterandom", Sys->OREAD);
+ if(fd == nil)
+ err("can't open /dev/notquiterandom");
+ if(sys->read(fd, a, n) != n)
+ err("can't read /dev/notquiterandom");
+}
+
+eqbytes(a, b: array of byte): int
+{
+ if(len a != len b)
+ return 0;
+ for(i := 0; i < len a; i++)
+ if(a[i] != b[i])
+ return 0;
+ return 1;
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, nil) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/cmd/auth/factotum/feedkey.b b/appl/cmd/auth/factotum/feedkey.b
new file mode 100644
index 00000000..606f065a
--- /dev/null
+++ b/appl/cmd/auth/factotum/feedkey.b
@@ -0,0 +1,321 @@
+implement Feedkey;
+
+#
+# Copyright © 2004 Vita Nuova Holdings Limited
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "string.m";
+ str: String;
+
+Feedkey: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+config := array[] of {
+ "frame .f",
+ "button .f.done -command {send cmd done} -text {Done}",
+ "frame .f.key -bg white",
+ "pack .f.key .f.done .f",
+ "update"
+};
+
+Debug: con 0;
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ str = load String String->PATH;
+
+ needfile := "/mnt/factotum/needkey";
+ if(Debug)
+ needfile = "/dev/null";
+
+ needs := chan of list of ref Attr;
+ acks := chan of int;
+
+ sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2});
+
+ fd := sys->open(needfile, Sys->ORDWR);
+ if(fd == nil)
+ err(sys->sprint("can't open %s: %r", needfile));
+ spawn needy(fd, needs, acks);
+ fd = nil;
+
+ ctlfile := "/mnt/factotum/ctl";
+ keyfd := sys->open(ctlfile, Sys->ORDWR);
+ if(keyfd == nil)
+ err(sys->sprint("can't open %s: %r", ctlfile));
+
+ tkclient->init();
+
+ spawn feedkey(ctxt, keyfd, needs, acks);
+}
+
+feedkey(ctxt: ref Draw->Context, keyfd: ref Sys->FD, needs: chan of list of ref Attr, acks: chan of int)
+{
+ (top, tkctl) := tkclient->toplevel(ctxt, nil, "Need key", Tkclient->Appl);
+
+ cmd := chan of string;
+ tk->namechan(top, cmd, "cmd");
+
+ for(i := 0; i < len config; i++)
+ tkcmd(top, config[i]);
+ tkclient->startinput(top, "ptr" :: nil);
+ tkclient->onscreen(top, nil);
+ if(!Debug)
+ tkclient->wmctl(top, "task");
+
+ attrs: list of ref Attr;
+ for(;;) alt{
+ s :=<-tkctl or
+ s = <-top.ctxt.ctl or
+ s = <-top.wreq =>
+ tkclient->wmctl(top, s);
+ p := <-top.ctxt.ptr =>
+ tk->pointer(top, *p);
+ c := <-top.ctxt.kbd =>
+ tk->keyboard(top, c);
+
+ s := <-cmd =>
+ case s {
+ "done" =>
+ result := extract(top, ".f.key", attrs);
+ if(Debug)
+ sys->print("result: %s\n", attrtext(result));
+ if(sys->fprint(keyfd, "key %s", attrtext(result)) < 0)
+ sys->fprint(sys->fildes(2), "feedkey: can't install key %q: %r\n", attrtext(result));
+ acks <-= 0;
+ tkclient->wmctl(top, "task");
+ tk->cmd(top, "pack forget .f.key");
+ * =>
+ sys->fprint(sys->fildes(2), "feedkey: odd command: %q\n", s);
+ }
+
+ attrs = <-needs =>
+ if(attrs == nil)
+ exit;
+ tkclient->startinput(top, "kbd" :: nil);
+ tkcmd(top, "destroy .f.key");
+ tkcmd(top, "frame .f.key -bg white");
+ populate(top, ".f.key", attrs);
+ tkcmd(top, "pack forget .f.done");
+ tkcmd(top, "pack .f.key .f.done .f");
+ tkcmd(top, "update");
+ tkclient->wmctl(top, "unhide");
+ }
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "feedkey: %s\n", s);
+ raise "fail:error";
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ b := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, b, len b);
+ if(n <= 0)
+ return nil;
+ return string b[0:n];
+}
+
+tkcmd(top: ref Tk->Toplevel, cmd: string): string
+{
+ if(0)
+ sys->print("tk: %q\n", cmd);
+ r := tk->cmd(top, cmd);
+ if(r != nil && r[0] == '!')
+ sys->fprint(sys->fildes(2), "feedkey: tk: %q on %q\n", r, cmd);
+ return r;
+}
+
+populate(top: ref Tk->Toplevel, tag: string, attrs: list of ref Attr)
+{
+ c := 0;
+ for(al := attrs; al != nil; al = tl al){
+ a := hd al;
+ if(a.name == nil)
+ tkcmd(top, sys->sprint("entry %s.n%d -bg yellow", tag, c));
+ else
+ tkcmd(top, sys->sprint("label %s.n%d -bg white -text '%s", tag, c, a.name));
+ tkcmd(top, sys->sprint("label %s.e%d -bg white -text ' = ", tag, c));
+ case a.tag {
+ Aquery =>
+ show := "";
+ if(a.name != nil && a.name[0] == '!')
+ show = " -show {•}";
+ tkcmd(top, sys->sprint("entry %s.v%d%s -bg yellow", tag, c, show));
+ if(a.val == nil && a.name == "user")
+ a.val = user();
+ tkcmd(top, sys->sprint("%s.v%d insert 0 '%s", tag, c, a.val));
+ tkcmd(top, sys->sprint("grid %s.n%d %s.e%d %s.v%d -in %s -sticky w -pady 1", tag, c, tag, c, tag, c, tag));
+ Aval =>
+ if(a.name != nil){
+ val := a.val;
+ if(a.name[0] == '!')
+ val = "..."; # just in case
+ tkcmd(top, sys->sprint("label %s.v%d -bg white -text %s", tag, c, val));
+ }else
+ tkcmd(top, sys->sprint("entry %s.v%d -bg yellow", tag, c));
+ tkcmd(top, sys->sprint("grid %s.n%d %s.e%d %s.v%d -in %s -sticky w -pady 1", tag, c, tag, c, tag, c, tag));
+ Aattr =>
+ tkcmd(top, sys->sprint("grid %s.n%d x x -in %s -sticky w -pady 1", tag, c, tag));
+ }
+ c++;
+ }
+}
+
+extract(top: ref Tk->Toplevel, tag: string, attrs: list of ref Attr): list of ref Attr
+{
+ c := 0;
+ nl: list of ref Attr;
+ for(al := attrs; al != nil; al = tl al){
+ a := ref *hd al;
+ if(a.tag == Aquery){
+ a.val = tkcmd(top, sys->sprint("%s.v%d get", tag, c));
+ if(a.name == nil)
+ a.name = tk->cmd(top, sys->sprint("%s.n%d get", tag, c)); # name might start with `!'
+ if(a.name != nil){
+ a.tag = Aval;
+ nl = a :: nl;
+ }
+ }else
+ nl = a :: nl;
+ c++;
+ }
+ return nl;
+}
+
+reverse[T](l: list of T): list of T
+{
+ rl: list of T;
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+needy(fd: ref Sys->FD, needs: chan of list of ref Attr, acks: chan of int)
+{
+ if(Debug){
+ for(;;){
+ needs <-= parseline("proto=pass user? server=fred.com service=ftp confirm !password?");
+ <-acks;
+ }
+ }
+
+ buf := array[512] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0){
+ s := string buf[0:n];
+ for(i := 0; i < len s; i++)
+ if(s[i] == ' ')
+ break;
+ if(i >= len s)
+ continue;
+ attrs := parseline(s[i+1:]);
+ nl: list of ref Attr;
+ tag: ref Attr;
+ for(; attrs != nil; attrs = tl attrs){
+ a := hd attrs;
+ if(a.name == "tag")
+ tag = a;
+ else
+ nl = a :: nl;
+ }
+ if(nl == nil)
+ continue;
+ attrs = reverse(ref Attr(Aquery, nil, nil) :: ref Attr(Aquery, nil, nil) :: nl); # add a few blank
+ if(attrs != nil && tag != nil && tag.val != nil){
+ needs <-= attrs;
+ <-acks;
+ sys->fprint(fd, "tag=%d", int tag.val);
+ }
+ }
+ if(n < 0)
+ sys->fprint(sys->fildes(2), "feedkey: error reading needkey: %r\n");
+ needs <-= nil;
+}
+
+# need a library module
+
+Aattr, Aval, Aquery: con iota;
+
+Attr: adt {
+ tag: int;
+ name: string;
+ val: string;
+
+ text: fn(a: self ref Attr): string;
+};
+
+parseline(s: string): list of ref Attr
+{
+ fld := str->unquoted(s);
+ rfld := fld;
+ for(fld = nil; rfld != nil; rfld = tl rfld)
+ fld = (hd rfld) :: fld;
+ attrs: list of ref Attr;
+ for(; fld != nil; fld = tl fld){
+ n := hd fld;
+ a := "";
+ tag := Aattr;
+ for(i:=0; i<len n; i++)
+ if(n[i] == '='){
+ a = n[i+1:];
+ n = n[0:i];
+ tag = Aval;
+ }
+ if(len n == 0)
+ continue;
+ if(tag == Aattr && len n > 1 && n[len n-1] == '?'){
+ tag = Aquery;
+ n = n[0:len n-1];
+ }
+ attrs = ref Attr(tag, n, a) :: attrs;
+ }
+ return attrs;
+}
+
+Attr.text(a: self ref Attr): string
+{
+ case a.tag {
+ Aattr =>
+ return a.name;
+ Aval =>
+ return sys->sprint("%q=%q", a.name, a.val);
+ Aquery =>
+ return a.name+"?";
+ * =>
+ return "??";
+ }
+}
+
+attrtext(attrs: list of ref Attr): string
+{
+ s := "";
+ sp := 0;
+ for(; attrs != nil; attrs = tl attrs){
+ if(sp)
+ s[len s] = ' ';
+ sp = 1;
+ s += (hd attrs).text();
+ }
+ return s;
+}
diff --git a/appl/cmd/auth/factotum/mkfile b/appl/cmd/auth/factotum/mkfile
new file mode 100644
index 00000000..1979a14c
--- /dev/null
+++ b/appl/cmd/auth/factotum/mkfile
@@ -0,0 +1,27 @@
+<../../../../mkconfig
+
+DIRS=\
+ proto\
+
+TARG=\
+ factotum.dis\
+ feedkey.dis\
+ rpc.dis\
+
+SYSMODULES=\
+ arg.m\
+ keyring.m\
+ security.m\
+ rand.m\
+ sys.m\
+ draw.m\
+ bufio.m\
+ string.m\
+
+MODULES=\
+ authio.m\
+
+DISBIN=$ROOT/dis/auth
+
+<$ROOT/mkfiles/mkdis
+<$ROOT/mkfiles/mksubdirs
diff --git a/appl/cmd/auth/factotum/proto/infauth.b b/appl/cmd/auth/factotum/proto/infauth.b
new file mode 100644
index 00000000..244979bc
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/infauth.b
@@ -0,0 +1,362 @@
+implement Authproto;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "keyring.m";
+ keyring: Keyring;
+ IPint: import keyring;
+ SK, PK, Certificate, DigestState: import Keyring;
+include "security.m";
+include "bufio.m";
+include "sexprs.m";
+ sexprs: Sexprs;
+ Sexp: import sexprs;
+include "spki.m";
+ spki: SPKI;
+include "daytime.m";
+ daytime: Daytime;
+include "keyreps.m";
+ keyreps: Keyreps;
+ Keyrep: import keyreps;
+include "../authio.m";
+ authio: Authio;
+ Aattr, Aval, Aquery: import Authio;
+ Attr, IO, Key, Authinfo: import authio;
+
+# at end of authentication, sign a hash of the authenticated username and
+# a secret known only to factotum. that certificate can act as
+# a later proof that this factotum has authenticated that user,
+# and hence factotum will disclose certificates that allow disclosure
+# only to that username.
+
+Debug: con 0;
+
+Maxmsg: con 4000;
+
+Error0, Error1: exception(string);
+
+init(f: Authio): string
+{
+ authio = f;
+ sys = load Sys Sys->PATH;
+ spki = load SPKI SPKI->PATH;
+ spki->init();
+ sexprs = load Sexprs Sexprs->PATH;
+ sexprs->init();
+ keyring = load Keyring Keyring->PATH;
+ daytime = load Daytime Daytime->PATH;
+ keyreps = load Keyreps Keyreps->PATH;
+ keyreps->init();
+ return nil;
+}
+
+interaction(attrs: list of ref Attr, io: ref IO): string
+{
+ ai: ref Authinfo;
+ (key, err) := io.findkey(attrs, "proto=infauth");
+ if(key == nil)
+ return err;
+ info: ref Keyring->Authinfo;
+ (info, err) = keytoauthinfo(key);
+ if(info == nil)
+ return err;
+ anysigner := int authio->lookattrval(key.attrs, "anysigner");
+ rattrs: list of ref Sexp;
+ {
+ # send auth protocol version number
+ sendmsg(io, array of byte "1");
+
+ # get auth protocol version number
+ if(int string getmsg(io) != 1)
+ raise Error0("incompatible authentication protocol");
+
+ # generate alpha**r0
+ p := info.p;
+ low := p.shr(p.bits()/4);
+ r0 := rand(low, p, Random->NotQuiteRandom);
+ αr0 := info.alpha.expmod(r0, p);
+ # trim(αr0); the IPint library should do this for us, i think.
+
+ # send alpha**r0 mod p, mycert, and mypk
+ sendmsg(io, array of byte αr0.iptob64());
+ sendmsg(io, array of byte keyring->certtostr(info.cert));
+ sendmsg(io, array of byte keyring->pktostr(info.mypk));
+
+ # get alpha**r1 mod p, hiscert, hispk
+ αr1 := IPint.b64toip(string getmsg(io));
+
+ # trying a fast one
+ if(p.cmp(αr1) <= 0)
+ raise Error0("implausible parameter value");
+
+ # if alpha**r1 == alpha**r0, someone may be trying a replay
+ if(αr0.eq(αr1))
+ raise Error0("possible replay attack");
+
+ hiscert := keyring->strtocert(string getmsg(io));
+ if(hiscert == nil && !anysigner)
+ raise Error0(sys->sprint("bad certificate: %r"));
+
+ buf := getmsg(io);
+ hispk := keyring->strtopk(string buf);
+ if(!anysigner){
+ # verify their public key
+ if(verify(info.spk, hiscert, buf) == 0)
+ raise Error0("pk doesn't match certificate"); # likely the signers don't match.
+
+ # check expiration date - in seconds of epoch
+ if(hiscert.exp != 0 && hiscert.exp <= now())
+ raise Error0("certificate expired");
+ }
+ buf = nil;
+
+ # sign alpha**r0 and alpha**r1 and send
+ αcert := sign(info.mysk, "sha", 0, array of byte (αr0.iptob64() + αr1.iptob64()));
+ sendmsg(io, array of byte keyring->certtostr(αcert));
+
+ # get signature of alpha**r1 and alpha**r0 and verify
+ αcert = keyring->strtocert(string getmsg(io));
+ if(αcert == nil)
+ raise Error0("alpha**r1 doesn't match certificate");
+
+ if(verify(hispk, αcert, array of byte (αr1.iptob64() + αr0.iptob64())) == 0)
+ raise Error0(sys->sprint("bad certificate: %r"));
+
+ ai = ref Authinfo;
+ # we are now authenticated and have a common secret, alpha**(r0*r1)
+ if(!anysigner)
+ rattrs = sl(ss("signer") :: principal(info.spk) :: nil) :: rattrs;
+ rattrs = sl(ss("remote-pk") :: principal(hispk) :: nil) :: rattrs;
+ rattrs = sl(ss("local-pk") :: principal(info.mypk) :: nil) :: rattrs;
+ rattrs = sl(ss("secret") :: sb(αr1.expmod(r0, p).iptobytes()) :: nil) :: rattrs;
+ ai.suid = hispk.owner;
+ ai.cuid = info.mypk.owner;
+ sendmsg(io, array of byte "OK");
+ }exception e{
+ Error0 =>
+ err = e;
+ senderr(io, e);
+ break;
+ Error1 =>
+ senderr(io, "missing your authentication data");
+ x: string = e;
+ return "remote: "+x;
+ }
+
+ {
+ while(string getmsg(io) != "OK")
+ ;
+ }exception e{
+ Error0 =>
+ return e;
+ Error1 =>
+ x: string = e;
+ return "remote: "+x;
+ }
+ if(err != nil)
+ return err;
+
+ return negotiatecrypto(io, key, ai, rattrs);
+}
+
+negotiatecrypto(io: ref IO, key: ref Key, ai: ref Authinfo, attrs: list of ref Sexp): string
+{
+ role := authio->lookattrval(key.attrs, "role");
+ alg: string;
+ {
+ if(role == "client"){
+ alg = authio->lookattrval(key.attrs, "alg");
+ if(alg == nil)
+ alg = "md5/rc4_256";
+ sendmsg(io, array of byte alg);
+ }else if(role == "server"){
+ alg = string getmsg(io);
+ if(!algcompatible(alg, sys->tokenize(authio->lookattrval(key.attrs, "algs"), " ").t1))
+ raise Error0("unsupported client algorithm");
+ }
+ }exception e{
+ Error0 or
+ Error1 =>
+ return e;
+ }
+
+ if(alg != nil)
+ attrs = sl(ss("alg") :: ss(alg) :: nil) :: attrs;
+ ai.secret = sl(attrs).pack();
+
+ io.done(ai);
+ return nil;
+}
+
+algcompatible(nil: string, nil: list of string): int
+{
+ return 1; # XXX
+}
+
+principal(pk: ref Keyring->PK): ref Sexp
+{
+ return spki->(Keyrep.pk(pk).mkkey()).sexp();
+}
+
+ipint(i: int): ref IPint
+{
+ return IPint.inttoip(i);
+}
+
+rand(p, q: ref IPint, nil: int): ref IPint
+{
+ if(p.cmp(q) > 0)
+ (p, q) = (q, p);
+ diff := q.sub(p);
+ q = nil;
+ if(diff.cmp(ipint(2)) < 0){
+ sys->print("rand range must be at least 2");
+ return IPint.inttoip(0);
+ }
+ l := diff.bits();
+ T := ipint(1).shl(l);
+ l = ((l + 7) / 8) * 8;
+ slop := T.div(diff).t1;
+ r: ref IPint;
+ do{
+ r = IPint.random(0, l);
+ }while(r.cmp(slop) < 0);
+ r = r.div(diff).t1.add(p);
+ return r;
+}
+
+now(): int
+{
+ return daytime->now();
+}
+
+Hashfn: type ref fn(a: array of byte, alen: int, digest: array of byte, state: ref DigestState): ref DigestState;
+
+hashalg(ha: string): Hashfn
+{
+ case ha {
+ "sha" or
+ "sha1" =>
+ return keyring->sha1;
+ "md4" =>
+ return keyring->md4;
+ "md5" =>
+ return keyring->md5;
+ }
+ return nil;
+}
+
+sign(sk: ref SK, ha: string, exp: int, buf: array of byte): ref Certificate
+{
+ state := hashalg(ha)(buf, len buf, nil, nil);
+ return keyring->sign(sk, exp, state, ha);
+}
+
+verify(pk: ref PK, cert: ref Certificate, buf: array of byte): int
+{
+ state := hashalg(cert.ha)(buf, len buf, nil, nil);
+ return keyring->verify(pk, cert, state);
+}
+
+getmsg(io: ref IO): array of byte raises (Error0, Error1)
+{
+ while((buf := io.read()) == nil || (n := len buf) < 5)
+ io.toosmall(5);
+ if(len buf != 5)
+ raise Error0("io error: (impossible?) msg length " + string n);
+ h := string buf;
+ if(h[0] == '!')
+ m := int h[1:];
+ else
+ m = int h;
+ while((buf = io.read()) == nil || (n = len buf) < m)
+ io.toosmall(m);
+ if(len buf != m)
+ raise Error0("io error: (impossible?) msg length " + string m);
+ if(h[0] == '!'){
+sys->print("got remote error: %s, len %d\n", string buf, len string buf);
+ raise Error1(string buf);
+ }
+ return buf;
+}
+
+sendmsg(io: ref IO, buf: array of byte)
+{
+ h := sys->aprint("%4.4d\n", len buf);
+ io.write(h, len h);
+ io.write(buf, len buf);
+}
+
+senderr(io: ref IO, e: string)
+{
+ buf := array of byte e;
+ h := sys->aprint("!%3.3d\n", len buf);
+ io.write(h, len h);
+ io.write(buf, len buf);
+}
+
+keytoauthinfo(key:ref Key): (ref Keyring->Authinfo, string)
+{
+ if((s := authio->lookattrval(key.secrets, "!authinfo")) == nil){
+ # XXX could look up authinfo by hash at this point
+ return (nil, "no authinfo attribute");
+ }
+
+ return strtoauthinfo(s);
+}
+
+strtoauthinfo(s: string): (ref Keyring->Authinfo, string)
+{
+ (se, err, nil) := Sexp.parse(s);
+ if(se == nil)
+ return (nil, err);
+ els := se.els();
+ if(len els != 5)
+ return (nil, "bad authinfo contents");
+ ai := ref Keyring->Authinfo;
+ if((ai.spk = keyring->strtopk((hd els).astext())) == nil)
+ return (nil, "bad signer public key");
+ els = tl els;
+ if((ai.cert = keyring->strtocert((hd els).astext())) == nil)
+ return (nil, "bad certificate");
+ els = tl els;
+ if((ai.mysk = keyring->strtosk((hd els).astext())) == nil)
+ return (nil, "bad secret/public key");
+ if((ai.mypk = keyring->sktopk(ai.mysk)) == nil)
+ return (nil, "cannot make pk from sk");
+ els = tl els;
+ if((ai.alpha = IPint.bytestoip((hd els).asdata())) == nil)
+ return (nil, "bad value for alpha");
+ els = tl els;
+ if((ai.p = IPint.bytestoip((hd els).asdata())) == nil)
+ return (nil, "bad value for p");
+ return (ai, nil);
+}
+
+authinfotostr(ai: ref Keyring->Authinfo): string
+{
+ return (ref Sexp.List(
+ ss(keyring->pktostr(ai.spk)) ::
+ ss(keyring->certtostr(ai.cert)) ::
+ ss(keyring->sktostr(ai.mysk)) ::
+ sb(ai.alpha.iptobytes()) ::
+ sb(ai.p.iptobytes()) ::
+ nil
+ )).b64text();
+}
+
+ss(s: string): ref Sexp.String
+{
+ return ref Sexp.String(s, nil);
+}
+
+sb(d: array of byte): ref Sexp.Binary
+{
+ return ref Sexp.Binary(d, nil);
+}
+
+sl(l: list of ref Sexp): ref Sexp
+{
+ return ref Sexp.List(l);
+}
diff --git a/appl/cmd/auth/factotum/proto/keyreps.b b/appl/cmd/auth/factotum/proto/keyreps.b
new file mode 100644
index 00000000..5fdac2c0
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/keyreps.b
@@ -0,0 +1,173 @@
+implement Keyreps;
+include "sys.m";
+ sys: Sys;
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+include "sexprs.m";
+include "spki.m";
+include "encoding.m";
+ base64: Encoding;
+include "keyreps.m";
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ base64 = load Encoding Encoding->BASE64PATH;
+}
+
+keyextract(flds: list of string, names: list of (string, int)): list of (string, ref IPint)
+{
+ a := array[len flds] of ref IPint;
+ for(i := 0; i < len a; i++){
+ a[i] = IPint.b64toip(hd flds);
+ flds = tl flds;
+ }
+ rl: list of (string, ref IPint);
+ for(; names != nil; names = tl names){
+ (n, p) := hd names;
+ if(p < len a)
+ rl = (n, a[p]) :: rl;
+ }
+ return revt(rl);
+}
+
+Keyrep.pk(pk: ref Keyring->PK): ref Keyrep.PK
+{
+ s := kr->pktostr(pk);
+ (nf, flds) := sys->tokenize(s, "\n");
+ if((nf -= 2) < 0)
+ return nil;
+ case hd flds {
+ "rsa" =>
+ return ref Keyrep.PK(hd flds, hd tl flds,
+ keyextract(tl tl flds, list of {("e",1), ("n",0)}));
+ "elgamal" or "dsa" =>
+ return ref Keyrep.PK(hd flds, hd tl flds,
+ keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2)}));
+ * =>
+ return nil;
+ }
+}
+
+Keyrep.sk(pk: ref Keyring->SK): ref Keyrep.SK
+{
+ s := kr->pktostr(pk);
+ (nf, flds) := sys->tokenize(s, "\n");
+ if((nf -= 2) < 0)
+ return nil;
+ case hd flds {
+ "rsa" =>
+ return ref Keyrep.SK(hd flds, hd tl flds,
+ keyextract(tl tl flds,list of {("e",1), ("n",0), ("!dk",2), ("!p",3), ("!q",4), ("!kp",5), ("!kq",6), ("!c2",7)}));
+ "elgamal" or "dsa" =>
+ return ref Keyrep.SK(hd flds, hd tl flds,
+ keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2), ("!secret",3)}));
+ * =>
+ return nil;
+ }
+}
+
+Keyrep.get(k: self ref Keyrep, n: string): ref IPint
+{
+ for(el := k.els; el != nil; el = tl el)
+ if((hd el).t0 == n)
+ return (hd el).t1;
+ return nil;
+}
+
+Keyrep.getb(k: self ref Keyrep, n: string): array of byte
+{
+ v := k.get(n);
+ if(v == nil)
+ return nil;
+ return pre0(v.iptobebytes());
+}
+
+pre0(a: array of byte): array of byte
+{
+ for(i:=0; i<len a-1; i++)
+ if(a[i] != a[i+1] && (a[i] != byte 0 || (int a[i+1] & 16r80) != 0))
+ break;
+ if(i > 0)
+ a = a[i:];
+ if(len a < 1 || (int a[0] & 16r80) == 0)
+ return a;
+ b := array[len a + 1] of byte;
+ b[0] = byte 0;
+ b[1:] = a;
+ return b;
+}
+
+Keyrep.mkpk(k: self ref Keyrep): (ref Keyring->PK, int)
+{
+ case k.alg {
+ "rsa" =>
+ e := k.get("e");
+ n := k.get("n");
+ return (kr->strtopk(sys->sprint("rsa\n%s\n%s\n%s\n", k.owner, n.iptob64(), e.iptob64())), n.bits());
+ * =>
+ raise "Keyrep: unknown algorithm" + k.alg;
+ }
+}
+
+Keyrep.mksk(k: self ref Keyrep): ref Keyring->SK
+{
+ case k.alg {
+ "rsa" =>
+ e := k.get("e");
+ n := k.get("n");
+ dk := k.get("!dk");
+ p := k.get("!p");
+ q := k.get("!q");
+ kp := k.get("!kp");
+ kq := k.get("!kq");
+ c12 := k.get("!c2");
+ return kr->strtosk(sys->sprint("rsa\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+ k.owner, n.iptob64(), e.iptob64(), dk.iptob64(), p.iptob64(), q.iptob64(),
+ kp.iptob64(), kq.iptob64(), c12.iptob64()));
+ * =>
+ raise "Keyrep: unknown algorithm";
+ }
+}
+
+Keyrep.eq(k1: self ref Keyrep, k2: ref Keyrep): int
+{
+ # n⁲ but n is small
+ for(l1 := k1.els; l1 != nil; l1 = tl l1){
+ (n, v1) := hd l1;
+ v2 := k2.get(n);
+ if(v2 == nil || !v1.eq(v2))
+ return 0;
+ }
+ for(l2 := k2.els; l2 != nil; l2 = tl l2)
+ if(k1.get((hd l2).t0) == nil)
+ return 0;
+ return 1;
+}
+
+Keyrep.mkkey(kr: self ref Keyrep): ref SPKI->Key
+{
+ k := ref SPKI->Key;
+ (k.pk, k.nbits) = kr.mkpk();
+ k.sk = kr.mksk();
+ return k;
+}
+
+sig2icert(sig: ref SPKI->Signature, signer: string, exp: int): ref Keyring->Certificate
+{
+ if(sig.sig == nil)
+ return nil;
+ s := sys->sprint("%s\n%s\n%s\n%d\n%s\n", "rsa", sig.hash.alg, signer, exp, base64->enc((hd sig.sig).t1));
+#sys->print("alg %s *** %s\n", sig.sa, base64->enc((hd sig.sig).t1));
+ return kr->strtocert(s);
+}
+
+revt[S,T](l: list of (S,T)): list of (S,T)
+{
+ rl: list of (S,T);
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
diff --git a/appl/cmd/auth/factotum/proto/keyreps.m b/appl/cmd/auth/factotum/proto/keyreps.m
new file mode 100644
index 00000000..ddfd7f0d
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/keyreps.m
@@ -0,0 +1,23 @@
+Keyreps: module
+{
+ PATH: con "/dis/lib/spki/keyreps.dis";
+ init: fn();
+ Keyrep: adt {
+ alg: string;
+ owner: string;
+ els: list of (string, ref Keyring->IPint);
+ pick{ # keeps a type distance between public and private keys
+ PK =>
+ SK =>
+ }
+
+ pk: fn(pk: ref Keyring->PK): ref Keyrep.PK;
+ sk: fn(sk: ref Keyring->SK): ref Keyrep.SK;
+ mkpk: fn(k: self ref Keyrep): (ref Keyring->PK, int);
+ mksk: fn(k: self ref Keyrep): ref Keyring->SK;
+ get: fn(k: self ref Keyrep, n: string): ref Keyring->IPint;
+ getb: fn(k: self ref Keyrep, n: string): array of byte;
+ eq: fn(k1: self ref Keyrep, k2: ref Keyrep): int;
+ mkkey: fn(k: self ref Keyrep): ref SPKI->Key;
+ };
+};
diff --git a/appl/cmd/auth/factotum/proto/mkfile b/appl/cmd/auth/factotum/proto/mkfile
new file mode 100644
index 00000000..efdd73da
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/mkfile
@@ -0,0 +1,22 @@
+<../../../../../mkconfig
+
+TARG=\
+ p9any.dis\
+ pass.dis\
+
+SYSMODULES=\
+ factotum.m\
+ keyring.m\
+ security.m\
+ rand.m\
+ sys.m\
+ draw.m\
+ bufio.m\
+ string.m\
+
+MODULES=\
+ ../authio.m\
+
+DISBIN=$ROOT/dis/auth/proto
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/auth/factotum/proto/p9any.b b/appl/cmd/auth/factotum/proto/p9any.b
new file mode 100644
index 00000000..1668a701
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/p9any.b
@@ -0,0 +1,232 @@
+implement Authproto;
+
+# currently includes p9sk1
+
+include "sys.m";
+ sys: Sys;
+ Rread, Rwrite: import Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+
+include "auth9.m";
+ auth9: Auth9;
+ ANAMELEN, AERRLEN, DOMLEN, DESKEYLEN, CHALLEN, SECRETLEN: import Auth9;
+ TICKREQLEN, TICKETLEN, AUTHENTLEN: import Auth9;
+ Ticketreq, Ticket, Authenticator: import auth9;
+
+include "../authio.m";
+ authio: Authio;
+ Aattr, Aval, Aquery: import Authio;
+ Attr, IO, Key, Authinfo: import authio;
+ netmkaddr, eqbytes, memrandom: import authio;
+
+include "encoding.m";
+ base16: Encoding;
+
+Debug: con 0;
+
+# init, addkey, closekey, write, read, close, keyprompt
+
+init(f: Authio): string
+{
+ authio = f;
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ auth9 = load Auth9 Auth9->PATH;
+ auth9->init();
+ base16 = load Encoding Encoding->BASE16PATH;
+ return nil;
+}
+
+version := 1;
+
+interaction(attrs: list of ref Attr, io: ref IO): string
+{
+ return p9any(io);
+}
+
+p9any(io: ref IO): string
+{
+ while((buf := io.read()) == nil || (n := len buf) == 0 || buf[n-1] != byte 0)
+ io.toosmall(2048);
+ s := string buf[0:n-1];
+ if(Debug)
+ sys->print("s: %q\n", s);
+ (nil, flds) := sys->tokenize(s, " \t");
+ if(flds != nil && len hd flds >= 2 && (hd flds)[0:2] == "v."){
+ if(hd flds == "v.2"){
+ version = 2;
+ flds = tl flds;
+ if(Debug)
+ sys->print("version 2\n");
+ }else
+ return "p9any: unknown version";
+ }
+ doms: list of string;
+ for(; flds != nil; flds = tl flds){
+ (nf, subf) := sys->tokenize(hd flds, "@");
+ if(nf == 2 && hd subf == "p9sk1")
+ doms = hd tl subf :: doms;
+ }
+ if(doms == nil)
+ return "p9any: unsupported protocol";
+ if(Debug){
+ for(l := doms; l != nil; l = tl l)
+ sys->print("dom: %q\n", hd l);
+ }
+ r := array of byte ("p9sk1 "+hd doms);
+ buf[0:] = r;
+ buf[len r] = byte 0;
+ io.write(buf, len r + 1);
+ if(version == 2){
+ b := io.readn(3);
+ if(b == nil || b[0] != byte 'O' || b[1] != byte 'K' || b[2] != byte 0)
+ return "p9any: AS protocol botch: not OK";
+ if(Debug)
+ sys->print("OK\n");
+ }
+ return p9sk1client(io, hd doms);
+}
+
+#p9sk1:
+# C->S: nonce-C
+# S->C: nonce-S, uid-S, domain-S
+# C->A: nonce-S, uid-S, domain-S, uid-C, factotum-C
+# A->C: Kc{nonce-S, uid-C, uid-S, Kn}, Ks{nonce-S, uid-C, uid-S, K-n}
+# C->S: Ks{nonce-S, uid-C, uid-S, K-n}, Kn{nonce-S, counter}
+# S->C: Kn{nonce-C, counter}
+
+#asserts that uid-S and uid-C share new secret Kn
+#increment the counter to reuse the ticket.
+
+p9sk1client(io: ref IO, udom: string): string
+{
+
+ # C->S: nonce-C
+ cchal := array[CHALLEN] of byte;
+ memrandom(cchal, CHALLEN);
+ if(io.write(cchal, len cchal) != len cchal)
+ return sys->sprint("p9sk1: can't write cchal: %r");
+
+ # S->C: nonce-S, uid-S, domain-S
+ trbuf := io.readn(TICKREQLEN);
+ if(trbuf == nil)
+ return sys->sprint("p9sk1: can't read ticketreq: %r");
+
+ (nil, tr) := Ticketreq.unpack(trbuf);
+ if(tr == nil)
+ return "p9sk1: can't unpack ticket request";
+ if(Debug)
+ sys->print("ticketreq: type=%d authid=%q authdom=%q chal= hostid=%q uid=%q\n",
+ tr.rtype, tr.authid, tr.authdom, tr.hostid, tr.uid);
+
+ (mykey, diag) := io.findkey(nil, sys->sprint("dom=%q proto=p9sk1 user? !password?", udom));
+ if(mykey == nil)
+ return "can't find key: "+diag;
+ ukey: array of byte;
+ if((a := authio->lookattrval(mykey.secrets, "!hex")) != nil){
+ ukey = base16->dec(a);
+ if(len ukey != DESKEYLEN)
+ return "p9sk1: invalid !hex key";
+ }else if((a = authio->lookattrval(mykey.secrets, "!password")) != nil)
+ ukey = auth9->passtokey(a);
+ else
+ return "no !password (or !hex) in key";
+
+ # A->C: Kc{nonce-S, uid-C, uid-S, Kn}, Ks{nonce-S, uid-C, uid-S, K-n}
+ user := authio->lookattrval(mykey.attrs, "user");
+ if(user == nil)
+ user = authio->user(); # shouldn't happen
+ tr.rtype = Auth9->AuthTreq;
+ tr.hostid = user;
+ tr.uid = tr.hostid; # not speaking for anyone else
+ (tick, serverbits) := getastickets(tr, ukey);
+ if(tick == nil)
+ return sys->sprint("p9sk1: getasticket failed: %r");
+ if(tick.num != Auth9->AuthTc)
+ return "p9sk1: getasticket: failed: wrong key?";
+ if(Debug)
+ sys->print("ticket: num=%d chal= cuid=%q suid=%q key=\n", tick.num, tick.cuid, tick.suid);
+
+ # C->S: Ks{nonce-S, uid-C, uid-S, K-n}, Kn{nonce-S, counter}
+ ar := ref Authenticator;
+ ar.num = Auth9->AuthAc;
+ ar.chal = tick.chal;
+ ar.id = 0;
+ obuf := array[TICKETLEN+AUTHENTLEN] of byte;
+ obuf[0:] = serverbits;
+ obuf[TICKETLEN:] = ar.pack(tick.key);
+ if(io.write(obuf, len obuf) != len obuf)
+ return "p9sk1: error writing authenticator: %r";
+
+ # S->C: Kn{nonce-C, counter}
+ sbuf := io.readn(AUTHENTLEN);
+ if(sbuf == nil)
+ return sys->sprint("p9sk1: can't read server's authenticator: %r");
+ (nil, ar) = Authenticator.unpack(sbuf, tick.key);
+ if(ar.num != Auth9->AuthAs || !eqbytes(ar.chal, cchal) || ar.id != 0)
+ return "invalid authenticator from server";
+
+ ai := ref Authinfo(tick.cuid, tick.suid, nil, auth9->des56to64(tick.key));
+ io.done(ai);
+
+ return nil;
+}
+
+getastickets(tr: ref Ticketreq, key: array of byte): (ref Ticket, array of byte)
+{
+ afd := authdial(nil, tr.authdom);
+ if(afd == nil)
+ return (nil, nil);
+ return auth9->_asgetticket(afd, tr, key);
+}
+
+#
+# where to put the following functions?
+#
+
+csgetvalue(netroot: string, keytag: string, keyval: string, needtag: string): string
+{
+ cs := "/net/cs";
+ if(netroot != nil)
+ cs = netroot+"/cs";
+ fd := sys->open(cs, Sys->ORDWR); # TO DO: choice of root
+ if(fd == nil)
+ return nil;
+ if(sys->fprint(fd, "!%s=%s %s=*", keytag, keyval, needtag) < 0)
+ return nil;
+ sys->seek(fd, big 0, 0);
+ buf := array[1024] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0){
+ al := authio->parseline(string buf[0:n]); # assume the conventions match factotum's
+ for(; al != nil; al = tl al)
+ if((hd al).name == needtag)
+ return (hd al).val;
+ }
+ return nil;
+}
+
+authdial(netroot: string, dom: string): ref Sys->FD
+{
+ p: string;
+ if(dom != nil){
+ # look up an auth server in an authentication domain
+ p = csgetvalue(netroot, "authdom", dom, "auth");
+
+ # if that didn't work, just try the IP domain
+ if(p == nil)
+ p = csgetvalue(netroot, "dom", dom, "auth");
+ if(p == nil)
+ p = "$auth"; # temporary ...
+ if(p == nil){
+ sys->werrstr("no auth server found for "+dom);
+ return nil;
+ }
+ }else
+ p = "$auth"; # look for one relative to my machine
+ (nil, conn) := sys->dial(netmkaddr(p, netroot, "ticket"), nil);
+ return conn.dfd;
+}
diff --git a/appl/cmd/auth/factotum/proto/pass.b b/appl/cmd/auth/factotum/proto/pass.b
new file mode 100644
index 00000000..9c4462b3
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/pass.b
@@ -0,0 +1,29 @@
+implement Authproto;
+
+include "sys.m";
+ sys: Sys;
+
+include "../authio.m";
+ authio: Authio;
+ Attr, IO: import authio;
+
+init(f: Authio): string
+{
+ sys = load Sys Sys->PATH;
+ authio = f;
+ return nil;
+}
+
+interaction(attrs: list of ref Attr, io: ref Authio->IO): string
+{
+ (key, err) := io.findkey(attrs, "user? !password?");
+ if(key == nil)
+ return err;
+ user := authio->lookattrval(key.attrs, "user");
+ if(user == nil)
+ return "unknown user";
+ pass := authio->lookattrval(key.secrets, "!password");
+ a := sys->aprint("%q %q", user, pass);
+ io.write(a, len a);
+ return nil;
+}
diff --git a/appl/cmd/auth/factotum/rpc.b b/appl/cmd/auth/factotum/rpc.b
new file mode 100644
index 00000000..220980a8
--- /dev/null
+++ b/appl/cmd/auth/factotum/rpc.b
@@ -0,0 +1,68 @@
+implement Rpcio;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+
+Rpcio: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: rpc\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ cantload(Bufio->PATH);
+
+ file := "/mnt/factotum/rpc";
+ if(len args > 1)
+ file = hd tl args;
+ rfd := sys->open(file, Sys->ORDWR);
+ if(rfd == nil){
+ sys->fprint(sys->fildes(2), "rpc: can't open %s: %r\n", file);
+ raise "fail:load";
+ }
+ f := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ for(;;){
+ sys->print("> ");
+ s := f.gets('\n');
+ if(s == nil)
+ break;
+ rpc(rfd, s[0:len s-1]);
+ }
+}
+
+cantload(s: string)
+{
+ sys->fprint(sys->fildes(2), "csquery: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+rpc(f: ref Sys->FD, addr: string)
+{
+ b := array of byte addr;
+ if(sys->write(f, b, len b) > 0){
+ sys->seek(f, big 0, Sys->SEEKSTART);
+ buf := array[256] of byte;
+ if((n := sys->read(f, buf, len buf)) > 0)
+ sys->print("%s\n", string buf[0:n]);
+ if(n >= 0)
+ return;
+ }
+ sys->print("!%r\n");
+}
diff --git a/appl/cmd/auth/getpk.b b/appl/cmd/auth/getpk.b
new file mode 100644
index 00000000..24283340
--- /dev/null
+++ b/appl/cmd/auth/getpk.b
@@ -0,0 +1,83 @@
+implement Getpk;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+include "keyring.m";
+ keyring: Keyring;
+
+Getpk: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+badmodule(p: string)
+{
+ sys->fprint(sys->fildes(2), "getpk: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ keyring = load Keyring Keyring->PATH;
+ if(keyring == nil)
+ badmodule(Keyring->PATH);
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ badmodule(Arg->PATH);
+ arg->init(argv);
+ arg->setusage("usage: getpk [-asu] file...");
+ aflag := 0;
+ sflag := 0;
+ uflag := 0;
+ while((opt := arg->opt()) != 0){
+ case opt {
+ 's' =>
+ sflag++;
+ 'a' =>
+ aflag++;
+ 'u' =>
+ uflag++;
+ * =>
+ arg->usage();
+ }
+ }
+ argv = arg->argv();
+ if(argv == nil)
+ arg->usage();
+ multi := len argv > 1;
+ for(; argv != nil; argv = tl argv){
+ info := keyring->readauthinfo(hd argv);
+ if(info == nil){
+ sys->fprint(sys->fildes(2), "getpk: cannot read %s: %r\n", hd argv);
+ continue;
+ }
+ pk := info.mypk;
+ if(sflag)
+ pk = info.spk;
+ s := keyring->pktostr(pk);
+ if(!aflag)
+ s = hex(hash(s));
+ if(multi)
+ s = hd argv + ": " + s;
+ if(uflag)
+ s += " " + pk.owner;
+ sys->print("%s\n", s);
+ }
+}
+
+hash(s: string): array of byte
+{
+ d := array of byte s;
+ digest := array[Keyring->SHA1dlen] of byte;
+ keyring->sha1(d, len d, digest, nil);
+ return digest;
+}
+
+hex(a: array of byte): string
+{
+ s := "";
+ for(i := 0; i < len a; i++)
+ s += sys->sprint("%2.2ux", int a[i]);
+ return s;
+}
diff --git a/appl/cmd/auth/keyfs.b b/appl/cmd/auth/keyfs.b
new file mode 100644
index 00000000..f81c3ee7
--- /dev/null
+++ b/appl/cmd/auth/keyfs.b
@@ -0,0 +1,806 @@
+implement Keyfs;
+
+#
+# Copyright © 2002,2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+ Qid: import Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+ AESbsize, AESstate: import kr;
+
+include "rand.m";
+ rand: Rand;
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+
+include "styxservers.m";
+ styxservers: Styxservers;
+ Fid, Styxserver, Navigator, Navop: import styxservers;
+ Enotfound, Eperm, Ebadarg, Edot: import styxservers;
+
+include "arg.m";
+
+Keyfs: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+User: adt
+{
+ x: int; # table index
+ name: string;
+ secret: array of byte; # eg, password hashed by SHA1
+ expire: int; # expiration time (epoch seconds)
+ status: int;
+ failed: int; # count of failed attempts
+ path: big;
+};
+
+Qroot, Quser, Qsecret, Qlog, Qstatus, Qexpire: con iota;
+files := array[] of {
+ (Qsecret, "secret"),
+ (Qlog, "log"),
+ (Qstatus, "status"),
+ (Qexpire, "expire")
+};
+
+Maxsecret: con 255;
+Maxname: con 255;
+Maxfail: con 50;
+users: array of ref User;
+Sok, Sdisabled: con iota;
+status := array[] of {Sok => "ok", Sdisabled => "disabled" };
+Never: con 0; # expiry time
+
+Eremoved: con "user has been removed";
+
+pathgen := 0;
+keyversion := 0;
+user: string;
+now: int;
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "Usage: keyfs [-D] [-m mountpoint] [keyfile]\n");
+ raise "fail:usage";
+}
+
+nomod(s: string)
+{
+ sys->fprint(sys->fildes(2), "keyfs: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->NEWPGRP, nil);
+ kr = load Keyring Keyring->PATH;
+ if(kr == nil)
+ nomod(Keyring->PATH);
+ styx = load Styx Styx->PATH;
+ if(styx == nil)
+ nomod(Styx->PATH);
+ styxservers = load Styxservers Styxservers->PATH;
+ if(styxservers == nil)
+ nomod(Styxservers->PATH);
+ rand = load Rand Rand->PATH;
+ if(rand == nil)
+ nomod(Rand->PATH);
+
+ styx->init();
+ styxservers->init(styx);
+ rand->init(sys->millisec());
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ arg->init(args);
+ arg->setusage("keyfs [-m mntpt] [-D] [-n nvramfile] [keyfile]");
+ mountpt := "/mnt/keys";
+ keyfile := "/keydb/keys";
+ nvram: string;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'm' =>
+ mountpt = arg->earg();
+ 'D' =>
+ styxservers->traceset(1);
+ 'n' =>
+ nvram = arg->earg();
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(args != nil)
+ keyfile = hd args;
+
+ pwd, err: string;
+ if(nvram != nil){
+ pwd = rf(nvram);
+ if(pwd == nil)
+ error(sys->sprint("can't read %s: %r", nvram));
+ }
+ if(pwd == nil){
+ (pwd, err) = readconsline("Key: ", 1);
+ if(pwd == nil || err == "exit")
+ exit;
+ if(err != nil)
+ error(sys->sprint("couldn't get key: %s", err));
+ (rc, d) := sys->stat(keyfile);
+ if(rc == -1 || d.length == big 0){
+ pwd0 := pwd;
+ (pwd, err) = readconsline("Confirm key: ", 1);
+ if(pwd == nil || err == "exit")
+ exit;
+ if(pwd != pwd0)
+ error("key mismatch");
+ for(i := 0; i < len pwd0; i++)
+ pwd0[i] = ' '; # clear it out
+ }
+ }
+
+ thekey = hashkey(pwd);
+ for(i:=0; i<len pwd; i++)
+ pwd[i] = ' '; # clear it out
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil); # immediately avoid sharing keyfd
+
+ readkeys(keyfile);
+
+ user = rf("/dev/user");
+ if(user == nil)
+ user = "keyfs";
+
+ fds := array[2] of ref Sys->FD;
+ if(sys->pipe(fds) < 0)
+ error(sys->sprint("can't create pipe: %r"));
+
+ navops := chan of ref Navop;
+ spawn navigator(navops);
+
+ (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qroot);
+ fds[0] = nil;
+
+ pidc := chan of int;
+ spawn serveloop(tchan, srv, pidc, navops, keyfile);
+ <-pidc;
+
+ if(sys->mount(fds[1], nil, mountpt, Sys->MREPL|Sys->MCREATE, nil) < 0)
+ error(sys->sprint("mount on %s failed: %r", mountpt));
+}
+
+rf(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ b := array[256] of byte;
+ n := sys->read(fd, b, len b);
+ if(n < 0)
+ return nil;
+ return string b[0:n];
+}
+
+quit(err: string)
+{
+ fd := sys->open("/prog/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+ if(err != nil)
+ raise "fail:"+err;
+ exit;
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "keyfs: %s\n", s);
+ quit("error");
+}
+
+thekey: array of byte;
+
+hashkey(s: string): array of byte
+{
+ key := array of byte s;
+ skey := array[Keyring->SHA1dlen] of byte;
+ sha := kr->sha1(array of byte "aescbc file", 11, nil, nil);
+ kr->sha1(key, len key, skey, sha);
+ for(i:=0; i<len key; i++)
+ key[i] = byte 0; # clear it out
+#{sys->print("HEX="); for(i:=0;i<len skey&&i<AESbsize; i++)sys->print("%.2ux", int skey[i]);sys->print("\n");}
+ return skey[0:AESbsize];
+}
+
+readconsline(prompt: string, raw: int): (string, string)
+{
+ fd := sys->open("/dev/cons", Sys->ORDWR);
+ if(fd == nil)
+ return (nil, sys->sprint("can't open cons: %r"));
+ sys->fprint(fd, "%s", prompt);
+ fdctl: ref Sys->FD;
+ if(raw){
+ fdctl = sys->open("/dev/consctl", sys->OWRITE);
+ if(fdctl == nil || sys->fprint(fdctl, "rawon") < 0)
+ return (nil, sys->sprint("can't open consctl: %r"));
+ }
+ line := array[256] of byte;
+ o := 0;
+ err: string;
+ buf := array[1] of byte;
+ Read:
+ while((r := sys->read(fd, buf, len buf)) > 0){
+ c := int buf[0];
+ case c {
+ 16r7F =>
+ err = "interrupt";
+ break Read;
+ '\b' =>
+ if(o > 0)
+ o--;
+ '\n' or '\r' or 16r4 =>
+ break Read;
+ * =>
+ if(o > len line){
+ err = "line too long";
+ break Read;
+ }
+ line[o++] = byte c;
+ }
+ }
+ sys->fprint(fd, "\n");
+ if(r < 0)
+ err = sys->sprint("can't read cons: %r");
+ if(raw)
+ sys->fprint(fdctl, "rawoff");
+ if(err != nil)
+ return (nil, err);
+ return (string line[0:o], err);
+}
+
+serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop, keyfile: string)
+{
+ pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::nil);
+ while((gm := <-tchan) != nil){
+ now = time();
+ pick m := gm {
+ Readerror =>
+ error(sys->sprint("mount read error: %s", m.error));
+ Create =>
+ (c, mode, nil, err) := srv.cancreate(m);
+ if(c == nil){
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ case TYPE(c.path) { # parent
+ Qroot =>
+ if((m.perm & Sys->DMDIR) == 0){
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ break;
+ }
+ u := findusername(m.name);
+ if(u != nil){
+ srv.reply(ref Rmsg.Error(m.tag, "user already exists"));
+ continue;
+ }
+ if(len m.name > Maxname){
+ srv.reply(ref Rmsg.Error(m.tag, "user name too long"));
+ continue;
+ }
+ u = newuser(m.name, nil);
+ qid := Qid((u.path | big Quser), 0, Sys->QTDIR);
+ c.open(mode, qid);
+ writekeys(keyfile);
+ srv.reply(ref Rmsg.Create(m.tag, qid, srv.iounit()));
+ * =>
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ break;
+ }
+ Read =>
+ (c, err) := srv.canread(m);
+ if(c == nil){
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ if(c.qtype & Sys->QTDIR){
+ srv.read(m); # does readdir
+ break;
+ }
+ u := finduserpath(c.path);
+ if(u == nil){
+ srv.reply(ref Rmsg.Error(m.tag, Eremoved));
+ break;
+ }
+ case TYPE(c.path) {
+ Qsecret =>
+ if(u.status != Sok){
+ srv.reply(ref Rmsg.Error(m.tag, "user disabled"));
+ break;
+ }
+ if(u.expire < now && u.expire != Never){
+ srv.reply(ref Rmsg.Error(m.tag, "user expired"));
+ break;
+ }
+ srv.reply(styxservers->readbytes(m, u.secret));
+ Qlog =>
+ srv.reply(styxservers->readstr(m, sys->sprint("%d", u.failed)));
+ Qstatus =>
+ s := status[u.status];
+ if(u.status == Sok && u.expire != Never && u.expire < now)
+ s = "expired";
+ srv.reply(styxservers->readstr(m, s));
+ Qexpire =>
+ s: string;
+ if(u.expire != Never)
+ s = sys->sprint("%ud", u.expire);
+ else
+ s = "never";
+ srv.reply(styxservers->readstr(m, s));
+ * =>
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ }
+ Write =>
+ (c, merr) := srv.canwrite(m);
+ if(c == nil){
+ srv.reply(ref Rmsg.Error(m.tag, merr));
+ break;
+ }
+ u := finduserpath(c.path);
+ if(u == nil){
+ srv.reply(ref Rmsg.Error(m.tag, Eremoved));
+ break;
+ }
+ Case:
+ case TYPE(c.path) {
+ Qsecret =>
+ if(m.offset != big 0 || len m.data > Maxsecret){
+ srv.reply(ref Rmsg.Error(m.tag, "illegal write"));
+ break;
+ }
+ u.secret = m.data;
+ writekeys(keyfile);
+ srv.reply(ref Rmsg.Write(m.tag, len m.data));
+ Qexpire =>
+ s := trim(string m.data);
+ if(s != "never"){
+ if(!isnumeric(s)){
+ srv.reply(ref Rmsg.Error(m.tag, "illegal expiry time"));
+ break;
+ }
+ u.expire = int s;
+ }else
+ u.expire = Never;
+ u.failed = 0;
+ writekeys(keyfile);
+ srv.reply(ref Rmsg.Write(m.tag, len m.data));
+ Qstatus =>
+ s := trim(string m.data);
+ for(i := 0; i < len status; i++)
+ if(s == status[i]){
+ u.status = i;
+ if(i == Sok)
+ u.failed = 0;
+ writekeys(keyfile);
+ srv.reply(ref Rmsg.Write(m.tag, len m.data));
+ break Case;
+ }
+ srv.reply(ref Rmsg.Error(m.tag, "unknown status"));
+ Qlog =>
+ s := trim(string m.data);
+ if(s != "good" && s != "ok"){
+ if(++u.failed >= Maxfail)
+ u.status = Sdisabled;
+ }else
+ u.failed = 0;
+ writekeys(keyfile);
+ srv.reply(ref Rmsg.Write(m.tag, len m.data));
+ * =>
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ }
+ Remove =>
+ c := srv.getfid(m.fid);
+ if(c == nil){
+ srv.remove(m); # let it diagnose the errors
+ break;
+ }
+ case TYPE(c.path) {
+ Quser =>
+ u := finduserpath(c.path);
+ if(u == nil){
+ srv.reply(ref Rmsg.Error(m.tag, Eremoved));
+ break;
+ }
+ removeuser(u);
+ writekeys(keyfile);
+ srv.delfid(c);
+ srv.reply(ref Rmsg.Remove(m.tag));
+ Qsecret =>
+ u := finduserpath(c.path);
+ if(u == nil){
+ srv.reply(ref Rmsg.Error(m.tag, Eremoved));
+ break;
+ }
+ u.secret = nil;
+ writekeys(keyfile);
+ srv.delfid(c);
+ srv.reply(ref Rmsg.Remove(m.tag));
+ * =>
+ srv.remove(m); # let it reject it
+ }
+ Wstat =>
+ # rename user
+ c := srv.getfid(m.fid);
+ if(c == nil || TYPE(c.path) != Quser){
+ srv.default(gm); # let it reject it
+ break;
+ }
+ u := finduserpath(c.path);
+ if(u == nil){
+ srv.reply(ref Rmsg.Error(m.tag, Eremoved));
+ break;
+ }
+ if((new := m.stat.name) == nil){
+ srv.default(gm);
+ break;
+ }
+ if(new == "." || new == ".."){
+ srv.reply(ref Rmsg.Error(m.tag, Edot));
+ break;
+ }
+ if(findusername(new) != nil){
+ srv.reply(ref Rmsg.Error(m.tag, "user already exists"));
+ break;
+ }
+ # unhashuser(u);
+ u.name = new;
+ # hashuser(u);
+ writekeys(keyfile);
+ srv.reply(ref Rmsg.Wstat(m.tag));
+ * =>
+ srv.default(gm);
+ }
+ }
+ navops <-= nil; # shut down navigator
+}
+
+trim(s: string): string
+{
+ (nf, flds) := sys->tokenize(s, " \t\n");
+ if(nf == 0)
+ return nil;
+ return hd flds;
+}
+
+isnumeric(s: string): int
+{
+ for(i:=0; i<len s; i++)
+ if(!(s[i]>='0' && s[i]<='9'))
+ return 0;
+ return i>0;
+}
+
+TYPE(path: big): int
+{
+ return int path & 16rF;
+}
+
+INDEX(path: big): int
+{
+ return (int path & 16rFFFF) >> 4;
+}
+
+finduserpath(path: big): ref User
+{
+ i := INDEX(path);
+ if(i >= len users || (u := users[i]) == nil || u.path != (path & ~big 16rF))
+ return nil;
+ return u;
+}
+
+findusername(name: string): ref User
+{
+ for(i := 0; i < len users; i++)
+ if((u := users[i]) != nil && u.name == name)
+ return u;
+ return nil;
+}
+
+newuser(name: string, u: ref User): ref User
+{
+ for(i := 0; i < len users; i++)
+ if(users[i] == nil)
+ break;
+ if(i >= len users)
+ users = (array[i+16] of ref User)[0:] = users;
+ path := big ((pathgen++ << 16) | (i<<4));
+ if(u == nil)
+ u = ref User(i, name, nil, Never, Sok, 0, path);
+ else{
+ u.x = i;
+ u.path = path;
+ }
+ users[i] = u;
+ return u;
+}
+
+removeuser(u: ref User)
+{
+ if(u != nil)
+ users[u.x] = nil;
+}
+
+dirslot(n: int): int
+{
+ for(i := 0; i < len users; i++){
+ u := users[i];
+ if(u != nil){
+ if(n == 0)
+ break;
+ n--;
+ }
+ }
+ return i;
+}
+
+dir(qid: Sys->Qid, name: string, length: big, perm: int): ref Sys->Dir
+{
+ d := ref sys->zerodir;
+ d.qid = qid;
+ if(qid.qtype & Sys->QTDIR)
+ perm |= Sys->DMDIR;
+ d.mode = perm;
+ d.name = name;
+ d.uid = user;
+ d.gid = user;
+ d.length = length;
+ d.atime = now;
+ d.mtime = now;
+ return d;
+}
+
+dirgen(p: big, name: string, u: ref User): (ref Sys->Dir, string)
+{
+ case t := TYPE(p) {
+ Qroot =>
+ return (dir(Qid(big Qroot, keyversion,Sys->QTDIR), "/", big 0, 8r755), nil);
+ Quser =>
+ if(name == nil){
+ if(u == nil){
+ u = finduserpath(p);
+ if(u == nil)
+ return (nil, Enotfound);
+ }
+ name = u.name;
+ }
+ return (dir(Qid(p,0,Sys->QTDIR), name, big 0, 8r500), nil); # note: unwritable
+ * =>
+ l := 0;
+ if(t == Qsecret){
+ if(u == nil)
+ u = finduserpath(p);
+ if(u != nil)
+ l = len u.secret;
+ }
+ return (dir(Qid(p,0,Sys->QTFILE), name, big l, 8r600), nil);
+ }
+}
+
+navigator(navops: chan of ref Navop)
+{
+ while((m := <-navops) != nil){
+ Pick:
+ pick n := m {
+ Stat =>
+ n.reply <-= dirgen(n.path, nil, nil);
+ Walk =>
+ case TYPE(n.path) {
+ Qroot =>
+ if(n.name == ".."){
+ n.reply <-= dirgen(n.path, nil, nil);
+ break;
+ }
+ u := findusername(n.name);
+ if(u == nil){
+ n.reply <-= (nil, Enotfound);
+ break;
+ }
+ n.reply <-= dirgen(u.path | big Quser, u.name, u);
+ Quser =>
+ if(n.name == ".."){
+ n.reply <-= dirgen(big Qroot, nil, nil);
+ break;
+ }
+ for(j := 0; j < len files; j++){
+ (ftype, name) := files[j];
+ if(n.name == name){
+ n.reply <-= dirgen((n.path & ~big 16rF) | big ftype, name, nil);
+ break Pick;
+ }
+ }
+ n.reply <-= (nil, Enotfound);
+ * =>
+ if(n.name != ".."){
+ n.reply <-= (nil, Enotfound);
+ break;
+ }
+ n.reply <-= dirgen((n.path & ~big 16rF) | big Quser, nil, nil); # parent directory
+ }
+ Readdir =>
+ case TYPE(n.path) {
+ Qroot =>
+ for(j := dirslot(n.offset); --n.count >= 0 && j < len users; j++)
+ if((u := users[j]) != nil)
+ n.reply <-= dirgen(u.path | big Quser, u.name, u);
+ n.reply <-= (nil, nil);
+ Quser =>
+ u := finduserpath(n.path);
+ if(u == nil){
+ n.reply <-= (nil, Eremoved);
+ break;
+ }
+ for(j := n.offset; --n.count >= 0 && j < len files; j++){
+ (ftype, name) := files[j];
+ n.reply <-= dirgen((n.path & ~big 16rF)|big ftype, name, u);
+ }
+ n.reply <-= (nil, nil);
+ }
+ }
+ }
+}
+
+timefd: ref Sys->FD;
+
+time(): int
+{
+ if(timefd == nil){
+ timefd = sys->open("/dev/time", Sys->OREAD);
+ if(timefd == nil)
+ return 0;
+ }
+ buf := array[128] of byte;
+ sys->seek(timefd, big 0, 0);
+ n := sys->read(timefd, buf, len buf);
+ if(n < 0)
+ return 0;
+ t := (big string buf[0:n]) / big 1000000;
+ return int t;
+}
+
+Checkpat: con "XXXXXXXXXXXXXXXX"; # it's what Plan 9's aescbc uses
+Checklen: con len Checkpat;
+
+Hdrlen: con 1+1+4;
+
+packedsize(u: ref User): int
+{
+ return Hdrlen+(1+len array of byte u.name)+(1+len u.secret);
+}
+
+pack(u: ref User): array of byte
+{
+ a := array[packedsize(u)] of byte;
+ a[0] = byte u.status;
+ a[1] = byte u.failed;
+ a[2] = byte u.expire;
+ a[3] = byte (u.expire>>8);
+ a[4] = byte (u.expire>>16);
+ a[5] = byte (u.expire>>24);
+ bn := array of byte u.name;
+ n := len bn;
+ if(n > 255)
+ error(sys->sprint("overlong user name: %s", u.name)); # shouldn't happen
+ a[6] = byte n;
+ a[7:] = bn;
+ n += 7;
+ a[n] = byte len u.secret;
+ a[n+1:] = u.secret;
+ return a;
+}
+
+unpack(a: array of byte): (ref User, int)
+{
+ if(len a < Hdrlen+2)
+ return (nil, 0);
+ u := ref User;
+ u.status = int a[0];
+ u.failed = int a[1];
+ u.expire = (int a[5] << 24) | (int a[4] << 16) | (int a[3] << 8) | int a[2];
+ n := int a[6];
+ j := 7+n;
+ if(j > len a)
+ return (nil, 0);
+ u.name = string a[7:j];
+ if(j >= len a)
+ return (nil, 0);
+ n = int a[j++];
+ if(j+n > len a)
+ return (nil, 0);
+ if(n > 0){
+ u.secret = array[n] of byte;
+ u.secret[0:] = a[j:j+n];
+ }
+ return (u, j+n);
+}
+
+corrupt(keyfile: string)
+{
+ error(sys->sprint("%s: incorrect key or corrupt/damaged keyfile", keyfile));
+}
+
+readkeys(keyfile: string)
+{
+ fd := sys->open(keyfile, Sys->OREAD);
+ if(fd == nil)
+ error(sys->sprint("can't open %s: %r", keyfile));
+ (rc, d) := sys->fstat(fd);
+ if(rc < 0)
+ error(sys->sprint("can't get status of %s: %r", keyfile));
+ length := int d.length;
+ if(length == 0)
+ return;
+ if(length < AESbsize+Checklen)
+ corrupt(keyfile);
+ buf := array[length] of byte;
+ if(sys->read(fd, buf, len buf) != len buf)
+ error(sys->sprint("can't read %s: %r", keyfile));
+ state := kr->aessetup(thekey, buf[0:AESbsize]);
+ if(state == nil)
+ error("can't initialise AES");
+ kr->aescbc(state, buf[AESbsize:], length-AESbsize, Keyring->Decrypt);
+ if(string buf[length-Checklen:] != Checkpat)
+ corrupt(keyfile);
+ length -= Checklen;
+ for(i := AESbsize; i < length;){
+ (u, n) := unpack(buf[i:]);
+ if(u == nil)
+ corrupt(keyfile);
+ newuser(u.name, u);
+ i += n;
+ }
+}
+
+writekeys(keyfile: string)
+{
+ length := 0;
+ for(i := 0; i < len users; i++)
+ if((u := users[i]) != nil)
+ length += packedsize(u);
+ if(length == 0){
+ # leave it empty for clarity
+ fd := sys->create(keyfile, Sys->OWRITE, 8r600);
+ if(fd == nil)
+ error(sys->sprint("can't create %s: %r", keyfile));
+ return;
+ }
+ length += AESbsize+Checklen;
+ buf := array[length] of byte;
+ for(i=0; i<AESbsize; i++)
+ buf[i] = byte rand->rand(256);
+ j := AESbsize;
+ for(i = 0; i < len users; i++)
+ if((u = users[i]) != nil){
+ a := pack(u);
+ buf[j:] = a;
+ j += len a;
+ }
+ buf[length-Checklen:] = array of byte Checkpat;
+ state := kr->aessetup(thekey, buf[0:AESbsize]);
+ if(state == nil)
+ error("can't initialise AES");
+ kr->aescbc(state, buf[AESbsize:], length-AESbsize, Keyring->Encrypt);
+ fd := sys->create(keyfile, Sys->OWRITE, 8r600);
+ if(fd == nil)
+ error(sys->sprint("can't create %s: %r", keyfile));
+ if(sys->write(fd, buf, len buf) != len buf)
+ error(sys->sprint("error writing to %s: %r", keyfile));
+}
diff --git a/appl/cmd/auth/keysrv.b b/appl/cmd/auth/keysrv.b
new file mode 100644
index 00000000..c7144256
--- /dev/null
+++ b/appl/cmd/auth/keysrv.b
@@ -0,0 +1,199 @@
+implement Keysrv;
+
+#
+# remote access to keys (currently only to change secret)
+#
+# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+
+include "security.m";
+ auth: Auth;
+
+include "arg.m";
+
+keydb := "/mnt/keys";
+
+Keysrv: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: keysrv\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if(sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil) < 0)
+ err(sys->sprint("can't fork name space: %r"));
+
+ keyfile := "/usr/"+user()+"/keyring/default";
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ err("can't load Arg");
+ arg->init(args);
+ while((o := arg->opt()) != 0)
+ case o {
+ 'k' =>
+ keyfile = arg->arg();
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ kr = load Keyring Keyring->PATH;
+ if(kr == nil)
+ err("can't load Keyring");
+
+ auth = load Auth Auth->PATH;
+ if(auth == nil)
+ err("can't load Auth");
+ auth->init();
+
+ ai := kr->readauthinfo(keyfile);
+ if(ai == nil)
+ err(sys->sprint("can't read server key file %s: %r", keyfile));
+
+ (fd, id_or_err) := auth->server("sha1" :: "rc4_256" :: nil, ai, sys->fildes(0), 0);
+ if(fd == nil)
+ err(sys->sprint("can't authenticate: %s", id_or_err));
+
+ if(sys->bind("#s", "/mnt/keysrv", Sys->MREPL) < 0)
+ err(sys->sprint("can't bind #s on /mnt/keysrv: %r"));
+ srv := sys->file2chan("/mnt/keysrv", "secret");
+ if(srv == nil)
+ err(sys->sprint("can't create file2chan on /mnt/keysrv: %r"));
+ exitc := chan of int;
+ spawn worker(srv, id_or_err, exitc);
+ if(sys->export(fd, "/mnt/keysrv", Sys->EXPWAIT) < 0){
+ exitc <-= 1;
+ err(sys->sprint("can't export %s: %r", "/mnt/keysrv"));
+ }
+ exitc <-= 1;
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "keysrv: %s\n", s);
+ raise "fail:error";
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", Sys->OREAD);
+ if(fd == nil)
+ err(sys->sprint("can't open /dev/user: %r"));
+
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ err(sys->sprint("error reading /dev/user: %r"));
+
+ return string buf[0:n];
+}
+
+worker(file: ref Sys->FileIO, user: string, exitc: chan of int)
+{
+ (keydir, secret, err) := getuser(user);
+ if(keydir == nil || secret == nil){
+ if(err == nil)
+ err = "no existing secret"; # can't change it remotely until set
+ }
+ (nil, hash) := hashkey(secret);
+ for(;;)alt{
+ <-exitc =>
+ exit;
+ (nil, nbytes, fid, rc) := <-file.read =>
+ if(rc == nil)
+ break;
+ if(err != nil){
+ rc <-= (nil, err);
+ break;
+ }
+ rc <-= (nil, nil);
+ (nil, data, fid, wc) := <-file.write =>
+ if(wc == nil)
+ break;
+ if(err != nil){
+ wc <-= (0, err);
+ break;
+ }
+ for(i := 0; i < len data; i++)
+ if(data[i] == byte ' ')
+ break;
+ if(string data[0:i] != hash){
+ wc <-= (0, "wrong secret");
+ break;
+ }
+ if(++i >= len data){
+ wc <-= (0, nil);
+ break;
+ }
+ if(len data - i < 8){
+ wc <-= (0, "unacceptable secret");
+ break;
+ }
+ if(putsecret(keydir, data[i:]) < 0){
+ wc <-= (0, sys->sprint("can't update secret: %r"));
+ break;
+ }
+ wc <-= (len data, nil);
+ }
+}
+
+hashkey(a: array of byte): (array of byte, string)
+{
+ hash := array[Keyring->SHA1dlen] of byte;
+ kr->sha1(a, len a, hash, nil);
+ s := "";
+ for(i := 0; i < len hash; i++)
+ s += sys->sprint("%2.2ux", int hash[i]);
+ return (hash, s);
+}
+
+getuser(id: string): (string, array of byte, string)
+{
+ (ok, nil) := sys->stat(keydb);
+ if(ok < 0)
+ return (nil, nil, sys->sprint("can't stat %s: %r", id));
+ dbdir := keydb+"/"+id;
+ (ok, nil) = sys->stat(dbdir);
+ if(ok < 0)
+ return (nil, nil, sys->sprint("user not registered: %s", id));
+ fd := sys->open(dbdir+"/secret", Sys->OREAD);
+ if(fd == nil)
+ return (nil, nil, sys->sprint("can't open %s/secret: %r", id));
+ d: Sys->Dir;
+ (ok, d) = sys->fstat(fd);
+ if(ok < 0)
+ return (nil, nil, sys->sprint("can't stat %s/secret: %r", id));
+ l := int d.length;
+ secret: array of byte;
+ if(l > 0){
+ secret = array[l] of byte;
+ if(sys->read(fd, secret, len secret) != len secret)
+ return (nil, nil, sys->sprint("error reading %s/secret: %r", id));
+ }
+ return (dbdir, secret, nil);
+}
+
+putsecret(dir: string, secret: array of byte): int
+{
+ fd := sys->create(dir+"/secret", Sys->OWRITE, 8r600);
+ if(fd == nil)
+ return -1;
+ return sys->write(fd, secret, len secret);
+}
diff --git a/appl/cmd/auth/logind.b b/appl/cmd/auth/logind.b
new file mode 100644
index 00000000..f9d14616
--- /dev/null
+++ b/appl/cmd/auth/logind.b
@@ -0,0 +1,244 @@
+implement Logind;
+
+#
+# certification service (signer)
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+
+include "security.m";
+ ssl: SSL;
+
+include "daytime.m";
+ daytime: Daytime;
+
+Logind: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+TimeLimit: con 5*60*1000; # five minutes
+keydb := "/mnt/keys";
+
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->open("/dev/cons", sys->OWRITE);
+
+ kr = load Keyring Keyring->PATH;
+
+ ssl = load SSL SSL->PATH;
+ if(ssl == nil)
+ nomod(SSL->PATH);
+
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ nomod(Daytime->PATH);
+
+ (err, c) := ssl->connect(sys->fildes(0));
+ if(c == nil)
+ fatal("pushing ssl: " + err);
+
+ # impose time out to ensure dead network connections recovered well before TCP/IP's long time out
+
+ grpid := sys->pctl(Sys->NEWPGRP,nil);
+ pidc := chan of int;
+ spawn stalker(pidc, grpid);
+ tpid := <-pidc;
+ err = dologin(c);
+ if(err != nil){
+ sys->fprint(stderr, "logind: %s\n", err);
+ kr->puterror(c.dfd, err);
+ }
+ kill(tpid, "kill");
+}
+
+dologin(c: ref Sys->Connection): string
+{
+ ivec: array of byte;
+
+ (info, err) := signerkey("/keydb/signerkey");
+ if(info == nil)
+ return "can't read signer's own key: "+err;
+
+ # get user name; ack
+ s: string;
+ (s, err) = kr->getstring(c.dfd);
+ if(err != nil)
+ return err;
+ name := s;
+ kr->putstring(c.dfd, name);
+
+ # get initialization vector
+ (ivec, err) = kr->getbytearray(c.dfd);
+ if(err != nil)
+ return "can't get initialization vector: "+err;
+
+ # lookup password
+ pw := getsecret(s);
+ if(pw == nil)
+ return sys->sprint("no password entry for %s: %r", s);
+ if(len pw < Keyring->SHA1dlen)
+ return "bad password for "+s+": not SHA1 hashed?";
+ userexp := getexpiry(s);
+ if(userexp < 0)
+ return sys->sprint("expiry time for %s: %r", s);
+
+ # generate our random diffie hellman part
+ bits := info.p.bits();
+ r0 := kr->IPint.random(bits/4, bits);
+
+ # generate alpha0 = alpha**r0 mod p
+ alphar0 := info.alpha.expmod(r0, info.p);
+
+ # start encrypting
+ pwbuf := array[8] of byte;
+ for(i := 0; i < 8; i++)
+ pwbuf[i] = pw[i] ^ pw[8+i];
+ for(i = 0; i < 4; i++)
+ pwbuf[i] ^= pw[16+i];
+ for(i = 0; i < 8; i++)
+ pwbuf[i] ^= ivec[i];
+ err = ssl->secret(c, pwbuf, pwbuf);
+ if(err != nil)
+ return "can't set ssl secret: "+err;
+
+ if(sys->fprint(c.cfd, "alg rc4") < 0)
+ return sys->sprint("can't push alg rc4: %r");
+
+ # send P(alpha**r0 mod p)
+ if(kr->putstring(c.dfd, alphar0.iptob64()) < 0)
+ return sys->sprint("can't send (alpha**r0 mod p): %r");
+
+ # stop encrypting
+ if(sys->fprint(c.cfd, "alg clear") < 0)
+ return sys->sprint("can't clear alg: %r");
+
+ # send alpha, p
+ if(kr->putstring(c.dfd, info.alpha.iptob64()) < 0 ||
+ kr->putstring(c.dfd, info.p.iptob64()) < 0)
+ return sys->sprint("can't send alpha, p: %r");
+
+ # get alpha**r1 mod p
+ (s, err) = kr->getstring(c.dfd);
+ if(err != nil)
+ return "can't get alpha**r1 mod p:"+err;
+ alphar1 := kr->IPint.b64toip(s);
+
+ # compute alpha**(r0*r1) mod p
+ alphar0r1 := alphar1.expmod(r0, info.p);
+
+ # turn on digesting
+ secret := alphar0r1.iptobytes();
+ err = ssl->secret(c, secret, secret);
+ if(err != nil)
+ return "can't set digest secret: "+err;
+ if(sys->fprint(c.cfd, "alg sha1") < 0)
+ return sys->sprint("can't push alg sha1: %r");
+
+ # send our public key
+ if(kr->putstring(c.dfd, kr->pktostr(kr->sktopk(info.mysk))) < 0)
+ return sys->sprint("can't send signer's public key: %r");
+
+ # get his public key
+ (s, err) = kr->getstring(c.dfd);
+ if(err != nil)
+ return "client public key: "+err;
+ hisPKbuf := array of byte s;
+ hisPK := kr->strtopk(s);
+ if(hisPK.owner != name)
+ return "pk name doesn't match user name";
+
+ # sign and return
+ state := kr->sha1(hisPKbuf, len hisPKbuf, nil, nil);
+ cert := kr->sign(info.mysk, userexp, state, "sha1");
+
+ if(kr->putstring(c.dfd, kr->certtostr(cert)) < 0)
+ return sys->sprint("can't send certificate: %r");
+
+ return nil;
+}
+
+nomod(mod: string)
+{
+ fatal(sys->sprint("can't load %s: %r",mod));
+}
+
+fatal(msg: string)
+{
+ sys->fprint(stderr, "logind: %s\n", msg);
+ exit;
+}
+
+signerkey(filename: string): (ref Keyring->Authinfo, string)
+{
+
+ info := kr->readauthinfo(filename);
+ if(info == nil)
+ return (nil, sys->sprint("readauthinfo %r"));
+
+ # validate signer key
+ now := daytime->now();
+ if(info.cert.exp != 0 && info.cert.exp < now)
+ return (nil, sys->sprint("signer key expired"));
+
+ return (info, nil);
+}
+
+getsecret(id: string): array of byte
+{
+ fd := sys->open(sys->sprint("%s/%s/secret", keydb, id), Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0)
+ return nil;
+ a := array[int d.length] of byte;
+ n := sys->read(fd, a, len a);
+ if(n < 0)
+ return nil;
+ return a[0:n];
+}
+
+getexpiry(id: string): int
+{
+ fd := sys->open(sys->sprint("%s/%s/expire", keydb, id), Sys->OREAD);
+ if(fd == nil)
+ return -1;
+ a := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, a, len a);
+ if(n < 0)
+ return -1;
+ s := string a[0:n];
+ if(s == "never")
+ return 0;
+ if(s == "expired"){
+ sys->werrstr(sys->sprint("entry for %s expired", id));
+ return -1;
+ }
+ return int s;
+}
+
+stalker(pidc: chan of int, killpid: int)
+{
+ pidc <-= sys->pctl(0, nil);
+ sys->sleep(TimeLimit);
+ sys->fprint(stderr, "logind: login timed out\n");
+ kill(killpid, "killgrp");
+}
+
+kill(pid: int, how: string)
+{
+ fd := sys->open("#p/" + string pid + "/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "%s", how) < 0)
+ sys->fprint(stderr, "logind: can't %s %d: %r\n", how, pid);
+}
diff --git a/appl/cmd/auth/mkauthinfo.b b/appl/cmd/auth/mkauthinfo.b
new file mode 100644
index 00000000..33feffbb
--- /dev/null
+++ b/appl/cmd/auth/mkauthinfo.b
@@ -0,0 +1,125 @@
+implement Mkauthinfo;
+
+#
+# sign a new key to produce a certificate
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+
+include "security.m";
+ auth: Auth;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "arg.m";
+
+Mkauthinfo: module{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->open("/dev/cons", sys->OWRITE);
+
+ kr = load Keyring Keyring->PATH;
+
+ auth = load Auth Auth->PATH;
+ if(auth == nil)
+ nomod(Auth->PATH);
+
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ nomod(Daytime->PATH);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ arg->init(args);
+ arg->setusage("auth/mkauthinfo [-k keyspec] [-e ddmmyyyy] user [keyfile]");
+ keyspec := "key=default";
+ expiry := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'k' =>
+ keyspec = arg->earg();
+ 'e' =>
+ expiry = parsedate(arg->earg());
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ user := hd args;
+ args = tl args;
+ dstfile := "/fd/1";
+ if(args != nil)
+ dstfile = hd args;
+ arg = nil;
+
+ sai := auth->key(keyspec);
+ if(sai == nil){
+ sys->fprint(stderr, "sign: can't find key matching %q: %r\n", keyspec);
+ raise "fail:no key";
+ }
+
+ info := ref Keyring->Authinfo;
+ info.alpha = sai.alpha;
+ info.p = sai.p;
+ info.mysk = kr->genSKfromPK(sai.spk, user);
+ info.mypk = kr->sktopk(info.mysk);
+ info.spk = sai.mypk;
+ pkbuf := array of byte kr->pktostr(info.mypk);
+ state := kr->sha1(pkbuf, len pkbuf, nil, nil);
+ info.cert = kr->sign(sai.mysk, expiry, state, "sha1");
+ if(kr->writeauthinfo("/fd/1", info) < 0){
+ sys->fprint(stderr, "sign: error writing certificate: %r\n");
+ raise "fail:write error";
+ }
+}
+
+parsedate(s: string): int
+{
+ now := daytime->now();
+ tm := daytime->local(now);
+ if(s == "permanent")
+ return 0;
+ if(len s != 8)
+ fatal("bad date format "+s+" (expected DDMMYYYY)");
+ tm.mday = int s[0:2];
+ if(tm.mday > 31 || tm.mday < 1)
+ fatal(sys->sprint("bad day of month %d", tm.mday));
+ tm.mon = int s[2:4] - 1;
+ if(tm.mon > 11 || tm.mday < 0)
+ fatal(sys->sprint("bad month %d\n", tm.mon + 1));
+ tm.year = int s[4:8] - 1900;
+ if(tm.year < 70)
+ fatal(sys->sprint("bad year %d (year may be no earlier than 1970)", tm.year + 1900));
+ expiry := daytime->tm2epoch(tm);
+ expiry += 60;
+ if(expiry <= now)
+ fatal("expiry date has already passed");
+ return expiry;
+}
+
+nomod(mod: string)
+{
+ fatal(sys->sprint("can't load %s: %r",mod));
+}
+
+fatal(msg: string)
+{
+ sys->fprint(stderr, "mkauthinfo: %s\n", msg);
+ raise "fail:error";
+}
diff --git a/appl/cmd/auth/mkfile b/appl/cmd/auth/mkfile
new file mode 100644
index 00000000..112ba66a
--- /dev/null
+++ b/appl/cmd/auth/mkfile
@@ -0,0 +1,38 @@
+<../../../mkconfig
+
+DIRS=\
+ factotum\
+
+TARG=\
+ aescbc.dis\
+ changelogin.dis\
+ countersigner.dis\
+ convpasswd.dis\
+ createsignerkey.dis\
+ keyfs.dis\
+ keysrv.dis\
+ getpk.dis\
+ logind.dis\
+ mkauthinfo.dis\
+ passwd.dis\
+ secstore.dis\
+ signer.dis\
+ verify.dis\
+
+SYSMODULES=\
+ arg.m\
+ keyring.m\
+ security.m\
+ rand.m\
+ sys.m\
+ draw.m\
+ bufio.m\
+ secstore.m\
+ string.m\
+ styx.m\
+ styxservers.m\
+
+DISBIN=$ROOT/dis/auth
+
+<$ROOT/mkfiles/mkdis
+<$ROOT/mkfiles/mksubdirs
diff --git a/appl/cmd/auth/passwd.b b/appl/cmd/auth/passwd.b
new file mode 100644
index 00000000..d10b5c95
--- /dev/null
+++ b/appl/cmd/auth/passwd.b
@@ -0,0 +1,290 @@
+implement Passwd;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+
+include "security.m";
+ auth: Auth;
+
+include "arg.m";
+
+Passwd: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr, stdin, stdout: ref Sys->FD;
+keysrv := "/mnt/keysrv";
+signer := "$SIGNER";
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: passwd [-u user] [-s signer] [keyfile]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ stdin = sys->fildes(0);
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+
+ kr = load Keyring Keyring->PATH;
+ if(kr == nil)
+ noload(Keyring->PATH);
+ auth = load Auth Auth->PATH;
+ if(auth == nil)
+ noload(Auth->PATH);
+ auth->init();
+
+ keyfile, id: string;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ noload(Arg->PATH);
+ arg->init(args);
+ while((o := arg->opt()) != 0)
+ case o {
+ 's' =>
+ signer = arg->arg();
+ 'u' =>
+ id = arg->arg();
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(args == nil)
+ args = "default" :: nil;
+
+ if(id == nil)
+ id= user();
+
+ if(args != nil)
+ keyfile = hd args;
+ else
+ keyfile = "default";
+ if(len keyfile > 0 && keyfile[0] != '/')
+ keyfile = "/usr/" + id + "/keyring/" + keyfile;
+
+ ai := kr->readauthinfo(keyfile);
+ if(ai == nil)
+ err(sys->sprint("can't read certificate from %s: %r", keyfile));
+sys->print("key owner: %s\n", ai.mypk.owner);
+
+ sys->pctl(Sys->FORKNS|Sys->FORKFD, nil);
+ remid := mountsrv(ai);
+
+ # get password
+ ok: int;
+ secret: array of byte;
+ oldhash: array of byte;
+ word: string;
+ for(;;){
+ sys->print("Inferno secret: ");
+ (ok, word) = readline(stdin, "rawon");
+ if(!ok || word == nil)
+ exit;
+ secret = array of byte word;
+ (nil, s) := hashkey(secret);
+ for(i := 0; i < len word; i++)
+ word[i] = ' ';
+ oldhash = array of byte s;
+ e := putsecret(oldhash, nil);
+ if(e != "wrong secret"){
+ if(e == nil)
+ break;
+ err(e);
+ }
+ sys->fprint(stderr, "!wrong secret\n");
+ }
+ newsecret: array of byte;
+ for(;;){
+ for(;;){
+ sys->print("new secret [default = don't change]: ");
+ (ok, word) = readline(stdin, "rawon");
+ if(!ok)
+ exit;
+ if(word == "" && secret != nil)
+ break;
+ if(len word >= 8)
+ break;
+ sys->print("!secret must be at least 8 characters\n");
+ }
+ if(word != ""){
+ # confirm password change
+ word1 := word;
+ sys->print("confirm: ");
+ (ok, word) = readline(stdin, "rawon");
+ if(!ok || word != word1){
+ sys->fprint(stderr, "!entries didn't match\n");
+ continue;
+ }
+ # TO DO...
+ #pwbuf := array of byte word;
+ #newsecret = array[Keyring->SHA1dlen] of byte;
+ #kr->sha1(pwbuf, len pwbuf, newsecret, nil);
+ newsecret = array of byte word;
+ }
+ if(!eq(newsecret, secret)){
+ if((e := putsecret(oldhash, newsecret)) != nil){
+ sys->fprint(stderr, "passwd: can't update secret for %s: %s\n", id, e);
+ continue;
+ }
+ }
+ break;
+ }
+}
+
+noload(s: string)
+{
+ err(sys->sprint("can't load %s: %r", s));
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "passwd: %s\n", s);
+ raise "fail:error";
+}
+
+mountsrv(ai: ref Keyring->Authinfo): string
+{
+ (rc, c) := sys->dial(netmkaddr(signer, "net", "infkey"), nil);
+ if(rc < 0)
+ err(sys->sprint("can't dial %s: %r", signer));
+ (fd, id_or_err) := auth->client("sha1/rc4_256", ai, c.dfd);
+ if(fd == nil)
+ err(sys->sprint("can't authenticate with %s: %r", signer));
+ if(sys->mount(fd, nil, keysrv, Sys->MREPL, nil) < 0)
+ err(sys->sprint("can't mount %s on %s: %r", signer, keysrv));
+ return id_or_err;
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", Sys->OREAD);
+ if(fd == nil)
+ err(sys->sprint("can't open /dev/user: %r"));
+
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ err(sys->sprint("error reading /dev/user: %r"));
+
+ return string buf[0:n];
+}
+
+eq(a, b: array of byte): int
+{
+ if(len a != len b)
+ return 0;
+ for(i := 0; i < len a; i++)
+ if(a[i] != b[i])
+ return 0;
+ return 1;
+}
+
+hashkey(a: array of byte): (array of byte, string)
+{
+ hash := array[Keyring->SHA1dlen] of byte;
+ kr->sha1(a, len a, hash, nil);
+ s := "";
+ for(i := 0; i < len hash; i++)
+ s += sys->sprint("%2.2ux", int hash[i]);
+ return (hash, s);
+}
+
+putsecret(oldhash: array of byte, secret: array of byte): string
+{
+ fd := sys->create(keysrv+"/secret", Sys->OWRITE, 8r600);
+ if(fd == nil)
+ return sys->sprint("%r");
+ n := len oldhash;
+ if(secret != nil)
+ n += 1 + len secret;
+ buf := array[n] of byte;
+ buf[0:] = oldhash;
+ if(secret != nil){
+ buf[len oldhash] = byte ' ';
+ buf[len oldhash+1:] = secret;
+ }
+ if(sys->write(fd, buf, len buf) < 0)
+ return sys->sprint("%r");
+ return nil;
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
+
+readline(io: ref Sys->FD, mode: string): (int, string)
+{
+ r : int;
+ line : string;
+ buf := array[8192] of byte;
+ fdctl : ref Sys->FD;
+ rawoff := array of byte "rawoff";
+
+ if(mode == "rawon"){
+ fdctl = sys->open("/dev/consctl", sys->OWRITE);
+ if(fdctl == nil || sys->write(fdctl,array of byte mode,len mode) != len mode){
+ sys->fprint(stderr, "unable to change console mode");
+ return (0,nil);
+ }
+ }
+
+ line = "";
+ for(;;) {
+ r = sys->read(io, buf, len buf);
+ if(r <= 0){
+ sys->fprint(stderr, "error read from console mode");
+ if(mode == "rawon")
+ sys->write(fdctl,rawoff,6);
+ return (0, nil);
+ }
+
+ line += string buf[0:r];
+ if ((len line >= 1) && (line[(len line)-1] == '\n')){
+ if(mode == "rawon"){
+ r = sys->write(stdout,array of byte "\n",1);
+ if(r <= 0) {
+ sys->write(fdctl,rawoff,6);
+ return (0, nil);
+ }
+ }
+ break;
+ }
+ else {
+ if(mode == "rawon"){
+ #r = sys->write(stdout, array of byte "*",1);
+ if(r <= 0) {
+ sys->write(fdctl,rawoff,6);
+ return (0, nil);
+ }
+ }
+ }
+ }
+
+ if(mode == "rawon")
+ sys->write(fdctl,rawoff,6);
+
+ return (1, line[0:len line - 1]);
+}
diff --git a/appl/cmd/auth/secstore.b b/appl/cmd/auth/secstore.b
new file mode 100644
index 00000000..5a63b78d
--- /dev/null
+++ b/appl/cmd/auth/secstore.b
@@ -0,0 +1,317 @@
+implement Secstorec;
+
+#
+# interact with the Plan 9 secstore
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "secstore.m";
+ secstore: Secstore;
+
+include "arg.m";
+
+Secstorec: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Maxfilesize: con 128*1024;
+
+stderr: ref Sys->FD;
+conn: ref Sys->Connection;
+seckey: array of byte;
+filekey: array of byte;
+file: array of byte;
+verbose := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ secstore = load Secstore Secstore->PATH;
+
+ sys->pctl(Sys->FORKFD, nil);
+ stderr = sys->fildes(2);
+ secstore->init();
+ secstore->privacy();
+
+ addr := "net!$auth!secstore";
+ user := readfile("/dev/user");
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("auth/secstore [-iv] [-k key] [-p pin] [-s net!server!secstore] [-u user] [{drptx} file ...]");
+ iflag := 0;
+ pass, pin: string;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'i' => iflag = 1;
+ 'k' => pass = arg->earg();
+ 'v' => verbose = 1;
+ 's' => addr = arg->earg();
+ 'u' => user = arg->earg();
+ 'p' => pin = arg->earg();
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ op := -1;
+ if(args != nil){
+ if(len hd args != 1)
+ arg->usage();
+ op = (hd args)[0];
+ args = tl args;
+ case op {
+ 'd' or 'r' or 'p' or 'x' =>
+ if(args == nil)
+ arg->usage();
+ 't' =>
+ ;
+ * =>
+ arg->usage();
+ }
+ }
+ arg = nil;
+
+ if(iflag){
+ buf := array[Secstore->Maxmsg] of byte;
+ stdin := sys->fildes(0);
+ for(nr := 0; nr < len buf && (n := sys->read(stdin, buf, len buf-nr)) > 0;)
+ nr += n;
+ s := string buf[0:nr];
+ secstore->erasekey(buf[0:nr]);
+ (nf, flds) := sys->tokenize(s, "\n");
+ for(i := 0; i < len s; i++)
+ s[i] = 0;
+ if(nf < 1)
+ error("no password on standard input");
+ pass = hd flds;
+ if(nf > 1)
+ pin = hd tl flds;
+ }
+ conn: ref Sys->Connection;
+Auth:
+ for(;;){
+ if(!iflag)
+ pass = readpassword("secstore password");
+ if(pass == nil)
+ exit;
+ erase();
+ seckey = secstore->mkseckey(pass);
+ filekey = secstore->mkfilekey(pass);
+ for(i := 0; i < len pass; i++)
+ pass[i] = 0; # clear it
+ conn = secstore->dial(netmkaddr(addr, "net", "secstore"));
+ if(conn == nil)
+ error(sys->sprint("can't connect to secstore: %r"));
+ (srvname, diag) := secstore->auth(conn, user, seckey);
+ if(srvname == nil){
+ secstore->bye(conn);
+ sys->fprint(stderr, "secstore: authentication failed: %s\n", diag);
+ if(iflag)
+ raise "fail:auth";
+ continue;
+ }
+ case diag {
+ "" =>
+ if(verbose)
+ sys->fprint(stderr, "server: %s\n", srvname);
+ secstore->erasekey(seckey);
+ seckey = nil;
+ break Auth;
+ "need pin" =>
+ if(!iflag){
+ pin = readpassword("STA PIN+SecureID");
+ if(len pin == 0){
+ sys->fprint(stderr, "cancelled");
+ exit;
+ }
+ }else if(pin == nil)
+ raise "fail:no pin";
+ if(secstore->sendpin(conn, pin) < 0){
+ sys->fprint(stderr, "secstore: pin rejected: %r\n");
+ if(iflag)
+ raise "fail:bad pin";
+ continue;
+ }
+ }
+ }
+ if(op == 't'){
+ erase(); # no longer need the keys
+ entries := secstore->files(conn);
+ for(; entries != nil; entries = tl entries){
+ (name, size, date, hash, nil) := hd entries;
+ if(args != nil){
+ for(l := args; l != nil; l = tl l)
+ if((hd args) == name)
+ break;
+ if(args == nil)
+ continue;
+ }
+ if(verbose)
+ sys->print("%-14q %10d %s %s\n", name, size, date, hash);
+ else
+ sys->print("%q\n", name);
+ }
+ exit;
+ }
+ for(; args != nil; args = tl args){
+ fname := hd args;
+ case op {
+ 'd' =>
+ checkname(fname, 1);
+ if(secstore->remove(conn, fname) < 0)
+ error(sys->sprint("can't remove %q: %r", fname));
+ verb('d', fname);
+ 'p' =>
+ checkname(fname, 1);
+ file = getfile(conn, fname, filekey);
+ lines := secstore->lines(file);
+ lno := 1;
+ for(; lines != nil; lines = tl lines){
+ l := hd lines;
+ if(sys->write(sys->fildes(1), l, len l) != len l)
+ sys->fprint(sys->fildes(2), "secstore (%s:%d): %r\n", fname, lno);
+ lno++;
+ }
+ secstore->erasekey(file);
+ file = nil;
+ verb('p', fname);
+ 'x' =>
+ checkname(fname, 1);
+ file = getfile(conn, fname, filekey);
+ ofd := sys->create(fname, Sys->OWRITE, 8r600);
+ if(ofd == nil)
+ error(sys->sprint("can't create %q: %r", fname));
+ if(sys->write(ofd, file, len file) != len file)
+ error(sys->sprint("error writing to %q: %r", fname));
+ secstore->erasekey(file);
+ file = nil;
+ verb('x', fname);
+ 'r' or * =>
+ error(sys->sprint("op %c not implemented", op));
+ }
+ }
+ erase();
+}
+
+checkname(s: string, noslash: int): string
+{
+ tail := s;
+ for(i := 0; i < len s; i++){
+ if(s[i] == '/'){
+ if(noslash)
+ break;
+ tail = s[i+1:];
+ }
+ if(s[i] == '\n' || s[i] <= ' ')
+ break;
+ }
+ if(s == nil || tail == nil || i < len s || s == "..")
+ error(sys->sprint("can't use %q as a secstore file name", s)); # server checks as well, of course
+ return tail;
+}
+
+verb(op: int, n: string)
+{
+ if(verbose)
+ sys->fprint(stderr, "%c %q\n", op, n);
+}
+
+getfile(conn: ref Sys->Connection, fname: string, key: array of byte): array of byte
+{
+ f := secstore->getfile(conn, fname, 0);
+ if(f == nil)
+ error(sys->sprint("can't fetch %q: %r", fname));
+ if(fname != "."){
+ f = secstore->decrypt(f, key);
+ if(f == nil)
+ error(sys->sprint("can't decrypt %q: %r", fname));
+ }
+ return f;
+}
+
+erase()
+{
+ if(secstore != nil){
+ secstore->erasekey(seckey);
+ secstore->erasekey(filekey);
+ secstore->erasekey(file);
+ }
+}
+
+error(s: string)
+{
+ erase();
+ sys->fprint(stderr, "secstore: %s\n", s);
+ raise "fail:error";
+}
+
+readpassword(prompt: string): string
+{
+ cons := sys->open("/dev/cons", Sys->ORDWR);
+ if(cons == nil)
+ return nil;
+ stdin := bufio->fopen(cons, Sys->OREAD);
+ if(stdin == nil)
+ return nil;
+ cfd := sys->open("/dev/consctl", Sys->OWRITE);
+ if (cfd == nil || sys->fprint(cfd, "rawon") <= 0)
+ sys->fprint(stderr, "secstore: warning: cannot hide typed password\n");
+L:
+ for(;;){
+ sys->fprint(cons, "%s: ", prompt);
+ s := "";
+ while ((c := stdin.getc()) >= 0){
+ case c {
+ '\n' or ('d'&8r037) =>
+ sys->fprint(cons, "\n");
+ return s;
+ '\b' or 8r177 =>
+ if(len s > 0)
+ s = s[0:len s - 1];
+ 'u' & 8r037 =>
+ sys->fprint(cons, "\n");
+ continue L;
+ * =>
+ s[len s] = c;
+ }
+ }
+ break;
+ }
+ return nil;
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if(fd == nil)
+ return "";
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+ return string buf[0:n];
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, nil) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/cmd/auth/signer.b b/appl/cmd/auth/signer.b
new file mode 100644
index 00000000..b3f4669d
--- /dev/null
+++ b/appl/cmd/auth/signer.b
@@ -0,0 +1,132 @@
+implement Signer;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+
+include "security.m";
+ random: Random;
+
+Signer: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+# size in bits of modulus for public keys
+PKmodlen: con 512;
+
+# size in bits of modulus for diffie hellman
+DHmodlen: con 512;
+
+stderr, stdin, stdout: ref Sys->FD;
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ random = load Random Random->PATH;
+ kr = load Keyring Keyring->PATH;
+
+ stdin = sys->fildes(0);
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+
+ sys->pctl(Sys->FORKNS, nil);
+ if(sys->chdir("/keydb") < 0){
+ sys->fprint(stderr, "signer: no key database\n");
+ raise "fail:no keydb";
+ }
+
+ err := sign();
+ if(err != nil){
+ sys->fprint(stderr, "signer: %s\n", err);
+ raise "fail:error";
+ }
+}
+
+sign(): string
+{
+ info := signerkey("signerkey");
+ if(info == nil)
+ return "can't read key";
+
+ # send public part to client
+ mypkbuf := array of byte kr->pktostr(kr->sktopk(info.mysk));
+ kr->sendmsg(stdout, mypkbuf, len mypkbuf);
+ alphabuf := array of byte info.alpha.iptob64();
+ kr->sendmsg(stdout, alphabuf, len alphabuf);
+ pbuf := array of byte info.p.iptob64();
+ kr->sendmsg(stdout, pbuf, len pbuf);
+
+ # get client's public key
+ hisPKbuf := kr->getmsg(stdin);
+ if(hisPKbuf == nil)
+ return "caller hung up";
+ hisPK := kr->strtopk(string hisPKbuf);
+ if(hisPK == nil)
+ return "illegal caller PK";
+
+ # hash, sign, and blind
+ state := kr->sha1(hisPKbuf, len hisPKbuf, nil, nil);
+ cert := kr->sign(info.mysk, 0, state, "sha1");
+
+ # sanity clause
+ state = kr->sha1(hisPKbuf, len hisPKbuf, nil, nil);
+ if(kr->verify(info.mypk, cert, state) == 0)
+ return "bad signer certificate";
+
+ certbuf := array of byte kr->certtostr(cert);
+ blind := random->randombuf(random->ReallyRandom, len certbuf);
+ for(i := 0; i < len blind; i++)
+ certbuf[i] = certbuf[i] ^ blind[i];
+
+ # sum PKs and blinded certificate
+ state = kr->md5(mypkbuf, len mypkbuf, nil, nil);
+ kr->md5(hisPKbuf, len hisPKbuf, nil, state);
+ digest := array[Keyring->MD5dlen] of byte;
+ kr->md5(certbuf, len certbuf, digest, state);
+
+ # save sum and blinded cert in a file
+ file := "signed/"+hisPK.owner;
+ fd := sys->create(file, Sys->OWRITE, 8r600);
+ if(fd == nil)
+ return "can't create "+file+sys->sprint(": %r");
+ if(kr->sendmsg(fd, blind, len blind) < 0 ||
+ kr->sendmsg(fd, digest, len digest) < 0){
+ sys->remove(file);
+ return "can't write "+file+sys->sprint(": %r");
+ }
+
+ # send blinded cert to client
+ kr->sendmsg(stdout, certbuf, len certbuf);
+
+ return nil;
+}
+
+signerkey(filename: string): ref Keyring->Authinfo
+{
+ info := kr->readauthinfo(filename);
+ if(info != nil)
+ return info;
+
+ # generate a local key
+ info = ref Keyring->Authinfo;
+ info.mysk = kr->genSK("elgamal", "*", PKmodlen);
+ info.mypk = kr->sktopk(info.mysk);
+ info.spk = kr->sktopk(info.mysk);
+ myPKbuf := array of byte kr->pktostr(info.mypk);
+ state := kr->sha1(myPKbuf, len myPKbuf, nil, nil);
+ info.cert = kr->sign(info.mysk, 0, state, "sha1");
+ (info.alpha, info.p) = kr->dhparams(DHmodlen);
+
+ if(kr->writeauthinfo(filename, info) < 0){
+ sys->fprint(stderr, "can't write signerkey file: %r\n");
+ return nil;
+ }
+
+ return info;
+}
diff --git a/appl/cmd/auth/verify.b b/appl/cmd/auth/verify.b
new file mode 100644
index 00000000..d829a76c
--- /dev/null
+++ b/appl/cmd/auth/verify.b
@@ -0,0 +1,85 @@
+implement Verify;
+
+include "sys.m";
+ sys: Sys;
+
+include "keyring.m";
+ kr: Keyring;
+
+include "draw.m";
+
+Verify: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr, stdin: ref Sys->FD;
+
+pro := array[] of {
+ "alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf",
+ "hotel", "india", "juliet", "kilo", "lima", "mike", "nancy", "oscar",
+ "papa", "quebec", "romeo", "sierra", "tango", "uniform",
+ "victor", "whisky", "xray", "yankee", "zulu"
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+
+ stdin = sys->fildes(0);
+ stderr = sys->fildes(2);
+
+ if(args != nil)
+ args = tl args;
+ if(args == nil){
+ sys->fprint(stderr, "usage: verify boxid\n");
+ raise "fail:usage";
+ }
+
+ sys->pctl(Sys->FORKNS, nil);
+ if(sys->chdir("/keydb") < 0){
+ sys->fprint(stderr, "signer: no key database\n");
+ raise "fail:no keydb";
+ }
+
+ boxid := hd args;
+ file := "signed/"+boxid;
+ fd := sys->open(file, Sys->OREAD);
+ if(fd == nil){
+ sys->fprint(stderr, "signer: can't open %s: %r\n", file);
+ raise "fail:no certificate";
+ }
+ certbuf := kr->getmsg(fd);
+ digest := kr->getmsg(fd);
+ if(digest == nil || certbuf == nil){
+ sys->fprint(stderr, "signer: can't read %s: %r\n", file);
+ raise "fail:bad certificate";
+ }
+
+ s: string;
+ for(i := 0; i < len digest; i++){
+ s = s + (string (2*i)) + ": " + pro[((int digest[i])>>4)%len pro] + "\t";
+ s = s + (string (2*i+1)) + ": " + pro[(int digest[i])%len pro] + "\n";
+ }
+
+ sys->print("%s\naccept (y or n)? ", s);
+ buf := array[5] of byte;
+ n := sys->read(stdin, buf, len buf);
+ if(n < 1 || buf[0] != byte 'y'){
+ sys->print("\nrejected\n");
+ raise "fail:rejected";
+ }
+ sys->print("\naccepted\n");
+
+ nfile := "countersigned/"+boxid;
+ fd = sys->create(nfile, Sys->OWRITE, 8r600);
+ if(fd == nil){
+ sys->fprint(stderr, "signer: can't create %s: %r\n", nfile);
+ raise "fail:create";
+ }
+ if(kr->sendmsg(fd, certbuf, len certbuf) < 0){
+ sys->fprint(stderr, "signer: can't write %s: %r\n", nfile);
+ raise "fail:write";
+ }
+}
diff --git a/appl/cmd/auxi/cpuslave.b b/appl/cmd/auxi/cpuslave.b
new file mode 100644
index 00000000..66b409ac
--- /dev/null
+++ b/appl/cmd/auxi/cpuslave.b
@@ -0,0 +1,79 @@
+implement CPUslave;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Context, Display, Screen: import draw;
+include "arg.m";
+
+include "sh.m";
+
+stderr: ref Sys->FD;
+
+CPUslave: module
+{
+ init: fn(ctxt: ref Context, args: list of string);
+};
+
+usage()
+{
+ sys->fprint(stderr, "usage: cpuslave [-s screenid] command args\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ draw = load Draw Draw->PATH;
+
+ arg := load Arg Arg->PATH;
+ if (arg == nil) {
+ sys->fprint(stderr, "cpuslave: cannot load %s: %r\n", Arg->PATH);
+ raise "fail:bad module";
+ }
+ screenid := -1;
+ arg->init(args);
+ while ((opt := arg->opt()) != 0) {
+ if (opt != 's' || (a := arg->arg()) == nil)
+ usage();
+ screenid = int a;
+ }
+ args = arg->argv();
+ if(args == nil)
+ usage();
+
+ file := hd args + ".dis";
+ cmd := load Command file;
+ if(cmd == nil)
+ cmd = load Command "/dis/"+file;
+ if(cmd == nil){
+ sys->fprint(stderr, "cpuslave: can't load %s: %r\n", hd args);
+ raise "fail:bad command";
+ }
+
+ ctxt: ref Context;
+ if (screenid >= 0) {
+ display := Display.allocate(nil);
+ if(display == nil){
+ sys->fprint(stderr, "cpuslave: can't initialize display: %r\n");
+ raise "fail:no display";
+ }
+
+ screen: ref Screen;
+ if(screenid >= 0){
+ screen = display.publicscreen(screenid);
+ if(screen == nil){
+ sys->fprint(stderr, "cpuslave: cannot access screen %d: %r\n", screenid);
+ raise "fail:bad screen";
+ }
+ }
+
+ ctxt = ref Context;
+ ctxt.screen = screen;
+ ctxt.display = display;
+ }
+
+ spawn cmd->init(ctxt, args);
+}
diff --git a/appl/cmd/auxi/digest.b b/appl/cmd/auxi/digest.b
new file mode 100644
index 00000000..108de205
--- /dev/null
+++ b/appl/cmd/auxi/digest.b
@@ -0,0 +1,91 @@
+implement Digest;
+
+#
+# read a classifier example file and write its digest
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "strokes.m";
+ strokes: Strokes;
+ Classifier, Penpoint, Stroke: import strokes;
+ readstrokes: Readstrokes;
+ writestrokes: Writestrokes;
+
+include "arg.m";
+
+Digest: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "Usage: digest [file.cl ...]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ strokes = load Strokes Strokes->PATH;
+ if(strokes == nil)
+ nomod(Strokes->PATH);
+ strokes->init();
+ readstrokes = load Readstrokes Readstrokes->PATH;
+ if(readstrokes == nil)
+ nomod(Readstrokes->PATH);
+ readstrokes->init(strokes);
+ writestrokes = load Writestrokes Writestrokes->PATH;
+ if(writestrokes == nil)
+ nomod(Writestrokes->PATH);
+ writestrokes->init(strokes);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ arg->init(args);
+ while((opt := arg->opt()) != 0)
+ case opt {
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ for(; args != nil; args = tl args){
+ ofile := file := hd args;
+ n := len file;
+ if(n >= 3 && ofile[n-3:] == ".cl")
+ ofile = ofile[0:n-3];
+ ofile += ".clx";
+ (err, rec) := readstrokes->read_classifier(hd args, 1, 0);
+ if(err != nil)
+ error(sys->sprint("error reading classifier from %s: %s", file, err));
+ fd := sys->create(ofile, Sys->OWRITE, 8r666);
+ if(fd == nil)
+ error(sys->sprint("can't create %s: %r", file));
+ err = writestrokes->write_digest(fd, rec.cnames, rec.dompts);
+ if(err != nil)
+ error(sys->sprint("error writing digest to %s: %s", file, err));
+ }
+}
+
+nomod(s: string)
+{
+ error(sys->sprint("can't load %s: %r", s));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "digest: %s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/auxi/fpgaload.b b/appl/cmd/auxi/fpgaload.b
new file mode 100644
index 00000000..5c37b80b
--- /dev/null
+++ b/appl/cmd/auxi/fpgaload.b
@@ -0,0 +1,67 @@
+implement Fpgaload;
+
+include"sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+Fpgaload: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ error(sys->sprint("can't load %s: %r", Arg->PATH));
+ arg->init(args);
+ arg->setusage("fpgaload [-c clock] file.rbf");
+ clock := -1;
+ while((c := arg->opt()) != 0)
+ case c {
+ 'c' =>
+ clock = int arg->earg();
+ if(clock <= 0)
+ error("invalid clock value");
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ fd := sys->open(hd args, Sys->OREAD);
+ if(fd == nil)
+ error(sys->sprint("can't open %s: %r", hd args));
+ ofd := sys->open("#G/fpgaprog", Sys->OWRITE);
+ if(ofd == nil)
+ error(sys->sprint("can't open %s: %r", "#G/fpgaprog"));
+ a := array[128*1024] of byte;
+ while((n := sys->read(fd, a, len a)) > 0)
+ if(sys->write(ofd, a, n) != n)
+ error(sys->sprint("write error: %r"));
+ if(n < 0)
+ error(sys->sprint("read error: %r"));
+ if(clock >= 0)
+ setclock(clock);
+}
+
+setclock(n: int)
+{
+ fd := sys->open("#G/fpgactl", Sys->OWRITE);
+ if(fd == nil)
+ error(sys->sprint("can't open %s: %r", "#G/fpgactl"));
+ if(sys->fprint(fd, "bclk %d", n) < 0)
+ error(sys->sprint("can't set clock to %d: %r", n));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "fpgaload: %s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/auxi/mangaload.b b/appl/cmd/auxi/mangaload.b
new file mode 100644
index 00000000..380dd22e
--- /dev/null
+++ b/appl/cmd/auxi/mangaload.b
@@ -0,0 +1,362 @@
+implement Mangaload;
+
+# to do:
+# - set arp entry based on /lib/ndb if necessary
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "ip.m";
+ ip: IP;
+ IPaddr: import ip;
+
+include "timers.m";
+ timers: Timers;
+ Timer: import timers;
+
+include "arg.m";
+
+Mangaload: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# manga parameters
+FlashBlocksize: con 16r10000;
+FlashSize: con 16r400000; # 4meg for now
+FlashUserArea: con 16r3C0000;
+
+# magic values
+FooterOffset: con 16rFFEC;
+FooterSig: con 16rA0FFFF9F; # ARM flash library
+FileInfosize: con 64;
+FileNamesize: con FileInfosize - 3*4; # x, y, z
+Packetdatasize: con 1500-28; # ether data less IP + ICMP header
+RequestTimeout: con 500;
+Probecount: con 10; # query unit every so many packets
+
+# manga uses extended TFTP ops in ICMP InfoRequest packets
+Tftp_Req: con 0;
+Tftp_Read: con 1;
+Tftp_Write: con 2;
+Tftp_Data: con 3;
+Tftp_Ack: con 4;
+Tftp_Error: con 5;
+Tftp_Last: con 6;
+
+Icmp: adt
+{
+ ttl: int; # time to live
+ src: IPaddr;
+ dst: IPaddr;
+ ptype: int;
+ code: int;
+ id: int;
+ seq: int;
+ data: array of byte;
+ munged: int; # packet received but corrupt
+
+ unpack: fn(b: array of byte): ref Icmp;
+};
+
+# ICMP packet types
+EchoReply: con 0;
+Unreachable: con 3;
+SrcQuench: con 4;
+EchoRequest: con 8;
+TimeExceed: con 11;
+Timestamp: con 13;
+TimestampReply: con 14;
+InfoRequest: con 15;
+InfoReply: con 16;
+
+Nmsg: con 32;
+Interval: con 1000; # ms
+
+debug := 0;
+flashblock := 1; # never 0, that's the boot firmware
+maxfilesize := 8*FlashBlocksize;
+flashlim := FlashSize/FlashBlocksize;
+loadinitrd := 0;
+maxlen := 512*1024;
+mypid := 0;
+Datablocksize: con 4096;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ timers = load Timers Timers->PATH;
+ ip = load IP IP->PATH;
+ ip->init();
+
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("mangaload [-48dr] destination file");
+ while((o := arg->opt()) != 0)
+ case o {
+ '4' =>
+ flashlim = 4*1024*1024/FlashBlocksize;
+ '8' =>
+ flashlim = 8*1024*1024/FlashBlocksize;
+ 'r' =>
+ loadinitrd = 1;
+ flashblock = 9;
+ if(flashlim > 4*1024*1024/FlashBlocksize)
+ maxfilesize = 113*FlashBlocksize;
+ else
+ maxfilesize = 50*FlashBlocksize;
+ 'd' =>
+ debug++;
+ }
+ args = arg->argv();
+ if(len args != 2)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+
+ filename := hd tl args;
+ fd := sys->open(filename, Sys->OREAD);
+ if(fd == nil){
+ sys->fprint(sys->fildes(2), "mangaload: can't open %s: %r\n", filename);
+ raise "fail:open";
+ }
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0){
+ sys->fprint(sys->fildes(2), "mangaload: can't stat %s: %r\n", filename);
+ raise "fail:stat";
+ }
+ if(d.length > big maxfilesize){
+ sys->fprint(sys->fildes(2), "mangaload: file %s too long (must not exceed %d bytes)\n",
+ filename, maxfilesize);
+ raise "fail:size";
+ }
+ filesize := int d.length;
+
+ port := sys->sprint("%d", 16r8695);
+ addr := netmkaddr(hd args, "icmp", port);
+ (rok, c) := sys->dial(addr, port);
+ if(rok < 0){
+ sys->fprint(sys->fildes(2), "mangaload: can't dial %s: %r\n", addr);
+ raise "fail:dial";
+ }
+
+ tpid := timers->init(20);
+
+ pids := chan of int;
+ replies := chan [2] of ref Icmp;
+ spawn reader(c.dfd, replies, pids);
+ rpid := <-pids;
+
+ flashoffset := flashblock * FlashBlocksize;
+
+ # file name first
+ bname := array of byte filename;
+ l := len bname;
+ buf := array[Packetdatasize] of byte;
+ ip->put4(buf, 0, filesize);
+ ip->put4(buf, 4, l);
+ buf[8:] = bname;
+ l += 2*4;
+ buf[l++] = byte 0;
+ ip->put4(buf, l, flashoffset);
+ l += 4;
+ {
+ if(send(c.dfd, buf[0:l], Tftp_Write, 0) < 0)
+ senderr();
+ (op, iseq, data) := recv(replies, 400);
+ sys->print("initial reply: %d %d\n", op, iseq);
+ if(op != Tftp_Ack){
+ why := "no response";
+ if(op == Tftp_Error)
+ why = "manga cannot receive file";
+ sys->fprint(sys->fildes(2), "mangaload: %s\n", why);
+ raise "fail:error";
+ }
+ sys->print("sending %s size %d at address %d (0x%x)\n", filename, filesize, flashoffset, flashoffset);
+ seq := 1;
+ nsent := 0;
+ last := 0;
+ while((n := sys->read(fd, buf, len buf)) >= 0 && !last){
+ last = n != len buf;
+ nretry := 0;
+ Retry:
+ for(;;){
+ if(++nsent%10 == 0){ # probe
+ o = Tftp_Req;
+ send(c.dfd, array[0] of byte, Tftp_Req, seq);
+ (op, iseq, data) = recv(replies, 500);
+ if(debug || op != Tftp_Ack)
+ sys->print("ack reply: %d %d\n", op, iseq);
+ if(op == Tftp_Last || op == Tftp_Error){
+ if(op == Tftp_Last)
+ sys->print("timed out\n");
+ else
+ sys->print("error reply\n");
+ raise "disaster";
+ }
+ if(debug)
+ sys->print("ok\n");
+ continue Retry;
+ }
+ send(c.dfd, buf[0:n], Tftp_Data, seq);
+ (op, iseq, data) = recv(replies, 40);
+ case op {
+ Tftp_Error =>
+ sys->fprint(sys->fildes(2), "mangaload: manga refused data\n");
+ raise "disaster";
+ Tftp_Ack =>
+ if(seq == iseq){
+ seq++;
+ break Retry;
+ }
+ sys->print("sequence error: rcvd %d expected %d\n", iseq, seq);
+ if(iseq > seq){
+ sys->print("unrecoverable sequence error\n");
+ send(c.dfd, array[0] of byte, Tftp_Data, ++seq); # stop manga
+ raise "disaster";
+ }
+ # resend
+ sys->seek(fd, -big ((seq-iseq)*len buf), 1);
+ seq = iseq;
+ Tftp_Last =>
+ seq++;
+ break Retry; # timeout ok: manga doesn't usually reply unless packet lost
+ }
+ }
+ }
+ }exception{
+ * =>
+ ;
+ }
+ kill(rpid);
+ kill(tpid);
+ sys->print("ok?\n");
+}
+
+kill(pid: int)
+{
+ if(pid)
+ sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill");
+}
+
+senderr()
+{
+ sys->fprint(sys->fildes(2), "mangaload: icmp write failed: %r\n");
+ raise "disaster";
+}
+
+send(fd: ref Sys->FD, data: array of byte, op: int, seq: int): int
+{
+ buf := array[64*1024+512] of {* => byte 0};
+ buf[Odata:] = data;
+ ip->put2(buf, Oseq, seq);
+ buf[Otype] = byte InfoRequest;
+ buf[Ocode] = byte op;
+ if(sys->write(fd, buf, Odata+len data) < Odata+len data)
+ return -1;
+ if(debug)
+ sys->print("sent op=%d seq=%d ld=%d\n", op, seq, len data);
+ return 0;
+}
+
+flush(input: chan of ref Icmp)
+{
+ for(;;)alt{
+ <-input =>
+ ;
+ * =>
+ return;
+ }
+}
+
+recv(input: chan of ref Icmp, msec: int): (int, int, array of byte)
+{
+ t := Timer.start(msec);
+ alt{
+ <-t.timeout =>
+ return (Tftp_Last, 0, nil);
+ ic := <-input =>
+ t.stop();
+ if(ic.ptype == InfoReply)
+ return (ic.code, ic.seq, ic.data);
+ return (Tftp_Last, 0, nil);
+ }
+}
+
+reader(fd: ref Sys->FD, out: chan of ref Icmp, pid: chan of int)
+{
+ pid <-= sys->pctl(0, nil);
+ for(;;){
+ buf := array[64*1024+512] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0){
+ if(n == 0)
+ sys->werrstr("unexpected eof");
+ break;
+ }
+ ic := Icmp.unpack(buf[0:n]);
+ if(ic != nil){
+ if(debug)
+ sys->print("recv type=%d op=%d seq=%d id=%d\n", ic.ptype, ic.code, ic.seq, ic.id);
+ out <-= ic;
+ }else
+ sys->fprint(sys->fildes(2), "mangaload: corrupt icmp packet rcvd\n");
+ }
+ sys->print("read: %r\n");
+ out <-= nil;
+}
+
+# IP and ICMP packet header
+Ovihl: con 0;
+Otos: con 1;
+Olength: con 2;
+Oid: con Olength+2;
+Ofrag: con Oid+2;
+Ottl: con Ofrag+2;
+Oproto: con Ottl+1;
+Oipcksum: con Oproto+1;
+Osrc: con Oipcksum+2;
+Odst: con Osrc+4;
+Otype: con Odst+4;
+Ocode: con Otype+1;
+Ocksum: con Ocode+1;
+Oicmpid: con Ocksum+2;
+Oseq: con Oicmpid+2;
+Odata: con Oseq+2;
+
+Icmp.unpack(b: array of byte): ref Icmp
+{
+ if(len b < Odata)
+ return nil;
+ ic := ref Icmp;
+ ic.ttl = int b[Ottl];
+ ic.src = IPaddr.newv4(b[Osrc:]);
+ ic.dst = IPaddr.newv4(b[Odst:]);
+ ic.ptype = int b[Otype];
+ ic.code = int b[Ocode];
+ ic.seq = ip->get2(b, Oseq);
+ ic.id = ip->get2(b, Oicmpid);
+ ic.munged = 0;
+ if(len b > Odata)
+ ic.data = b[Odata:];
+ return ic;
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, nil) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/cmd/auxi/mkfile b/appl/cmd/auxi/mkfile
new file mode 100644
index 00000000..6d8dfc88
--- /dev/null
+++ b/appl/cmd/auxi/mkfile
@@ -0,0 +1,24 @@
+<../../../mkconfig
+
+TARG=\
+ cpuslave.dis\
+ digest.dis\
+ fpgaload.dis\
+ mangaload.dis\
+ pcmcia.dis\
+ rdbgsrv.dis\
+ rstyxd.dis\
+
+SYSMODULES=\
+ arg.m\
+ bufio.m\
+ draw.m\
+ sh.m\
+ string.m\
+ strokes.m\
+ styx.m\
+ sys.m\
+
+DISBIN=$ROOT/dis/auxi
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/auxi/pcmcia.b b/appl/cmd/auxi/pcmcia.b
new file mode 100644
index 00000000..d5d998b0
--- /dev/null
+++ b/appl/cmd/auxi/pcmcia.b
@@ -0,0 +1,491 @@
+implement Pcmcia;
+
+#
+# Copyright © 1995-2001 Lucent Technologies Inc. All rights reserved.
+# Revisions Copyright © 2001-2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+ print, fprint: import sys;
+
+include "draw.m";
+
+Pcmcia: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+End: con 16rFF;
+
+fd: ref Sys->FD;
+stderr: ref Sys->FD;
+pos := 0;
+
+hex := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ if(args != nil)
+ args = tl args;
+ if(args != nil && hd args == "-x"){
+ hex = 1;
+ args = tl args;
+ }
+
+ file := "#y/pcm0attr";
+ if(args != nil)
+ file = hd args;
+
+ fd = sys->open(file, Sys->OREAD);
+ if(fd == nil)
+ fatal(sys->sprint("can't open %s: %r", file));
+
+ for(next := 0; next >= 0;)
+ next = dtuple(next);
+}
+
+fatal(s: string)
+{
+ fprint(stderr, "pcmcia: %s\n", s);
+ raise "fail:error";
+}
+
+readc(): int
+{
+ x := array[1] of byte;
+ sys->seek(fd, big(2*pos), 0);
+ pos++;
+ rv := sys->read(fd, x, 1);
+ if(rv != 1){
+ if(rv < 0)
+ sys->print("readc err: %r\n");
+ return -1;
+ }
+ v := int x[0];
+ if(hex)
+ print("%2.2ux ", v);
+ return v;
+}
+
+dtuple(next: int): int
+{
+ pos = next;
+ if((ttype := readc()) < 0)
+ return -1;
+ if(ttype == End)
+ return -1;
+ if((link := readc()) < 0)
+ return -1;
+ case ttype {
+ * => print("unknown tuple type #%2.2ux\n", ttype);
+ 16r01 => tdevice(ttype, link);
+ 16r15 => tvers1(ttype, link);
+ 16r17 => tdevice(ttype, link);
+ 16r1A => tcfig(ttype, link);
+ 16r1B => tentry(ttype, link);
+ }
+ if(link == End)
+ next = -1;
+ else
+ next = next+2+link;
+ return next;
+}
+
+speedtab := array[16] of {
+0 => 0,
+1 => 250,
+2 => 200,
+3 => 150,
+4 => 100,
+};
+
+mantissa := array[16] of {
+1 => 10,
+2 => 12,
+3 => 13,
+4 => 15,
+5 => 20,
+6 => 25,
+7 => 30,
+8 => 35,
+9 => 40,
+10=> 45,
+11=> 50,
+12=> 55,
+13=> 60,
+14=> 70,
+15=> 80,
+};
+
+exponent := array[] of {
+ 1,
+ 10,
+ 100,
+ 1000,
+ 10000,
+ 100000,
+ 1000000,
+ 10000000,
+};
+
+typetab := array [256] of {
+1=> "Masked ROM",
+2=> "PROM",
+3=> "EPROM",
+4=> "EEPROM",
+5=> "FLASH",
+6=> "SRAM",
+7=> "DRAM",
+16rD=> "IO+MEM",
+* => "Unknown",
+};
+
+getlong(size: int): int
+{
+ x := 0;
+ for(i := 0; i < size; i++){
+ if((c := readc()) < 0)
+ break;
+ x |= c<<(i*8);
+ }
+ return x;
+}
+
+tdevice(dtype: int, tlen: int)
+{
+ while(tlen > 0){
+ if((id := readc()) < 0)
+ return;
+ tlen--;
+ if(id == End)
+ return;
+
+ speed := id & 16r7;
+ ns := 0;
+ if(speed == 16r7){
+ if((speed = readc()) < 0)
+ return;
+ tlen--;
+ if(speed & 16r80){
+ if((aespeed := readc()) < 0)
+ return;
+ ns = 0;
+ } else
+ ns = (mantissa[(speed>>3)&16rF]*exponent[speed&7])/10;
+ } else
+ ns = speedtab[speed];
+
+ ttype := id>>4;
+ if(ttype == 16rE){
+ if((ttype = readc()) < 0)
+ return;
+ tlen--;
+ }
+ tname := typetab[ttype];
+ if(tname == nil)
+ tname = "unknown";
+
+ if((size := readc()) < 0)
+ return;
+ tlen--;
+ bytes := ((size>>3)+1) * 512 * (1<<(2*(size&16r7)));
+
+ ttname := "attr device";
+ if(dtype == 1)
+ ttname = "device";
+ print("%s %d bytes of %dns %s\n", ttname, bytes, ns, tname);
+ }
+}
+
+tvers1(nil: int, tlen: int)
+{
+ if((major := readc()) < 0)
+ return;
+ tlen--;
+ if((minor := readc()) < 0)
+ return;
+ tlen--;
+ print("version %d.%d\n", major, minor);
+ while(tlen > 0){
+ s := "";
+ while(tlen > 0){
+ if((c := readc()) < 0)
+ return;
+ tlen--;
+ if(c == 0)
+ break;
+ if(c == End){
+ if(s != "")
+ print("\t%s<missing null>\n", s);
+ return;
+ }
+ s[len s] = c;
+ }
+ print("\t%s\n", s);
+ }
+}
+
+tcfig(nil: int, nil: int)
+{
+ if((size := readc()) < 0)
+ return;
+ rasize := (size&16r3) + 1;
+ rmsize := ((size>>2)&16rf) + 1;
+ if((last := readc()) < 0)
+ return;
+ caddr := getlong(rasize);
+ cregs := getlong(rmsize);
+
+ print("configuration registers at");
+ for(i := 0; i < 16; i++)
+ if((1<<i) & cregs)
+ print(" (%d) #%ux", i, caddr + i*2);
+ print("\n");
+}
+
+intrname := array[16] of {
+0 => "memory",
+1 => "I/O",
+4 => "Custom 0",
+5 => "Custom 1",
+6 => "Custom 2",
+7 => "Custom 3",
+* => "unknown"
+};
+
+vexp := array[8] of {
+ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000
+};
+vmant := array[16] of {
+ 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, 90,
+};
+
+volt(name: string)
+{
+ if((c := readc()) < 0)
+ return;
+ exp := vexp[c&16r7];
+ microv := vmant[(c>>3)&16rf]*exp;
+ while(c & 16r80){
+ if((c = readc()) < 0)
+ return;
+ case c {
+ 16r7d =>
+ break; # high impedence when sleeping
+ 16r7e or 16r7f =>
+ microv = 0; # no connection
+ * =>
+ exp /= 10;
+ microv += exp*(c&16r7f);
+ }
+ }
+ print(" V%s %duV", name, microv);
+}
+
+amps(name: string)
+{
+ if((c := readc()) < 0)
+ return;
+ amps := vexp[c&16r7]*vmant[(c>>3)&16rf];
+ while(c & 16r80){
+ if((c = readc()) < 0)
+ return;
+ if(c == 16r7d || c == 16r7e || c == 16r7f)
+ amps = 0;
+ }
+ if(amps >= 1000000)
+ print(" I%s %dmA", name, amps/100000);
+ else if(amps >= 1000)
+ print(" I%s %duA", name, amps/100);
+ else
+ print(" I%s %dnA", name, amps*10);
+}
+
+power(name: string)
+{
+ print("\t%s: ", name);
+ if((feature := readc()) < 0)
+ return;
+ if(feature & 1)
+ volt("nominal");
+ if(feature & 2)
+ volt("min");
+ if(feature & 4)
+ volt("max");
+ if(feature & 8)
+ amps("static");
+ if(feature & 16r10)
+ amps("avg");
+ if(feature & 16r20)
+ amps("peak");
+ if(feature & 16r40)
+ amps("powerdown");
+ print("\n");
+}
+
+ttiming(name: string, scale: int)
+{
+ if((unscaled := readc()) < 0)
+ return;
+ scaled := (mantissa[(unscaled>>3)&16rf]*exponent[unscaled&7])/10;
+ scaled = scaled * vexp[scale];
+ print("\t%s %dns\n", name, scaled);
+}
+
+timing()
+{
+ if((c := readc()) < 0)
+ return;
+ i := c&16r3;
+ if(i != 3)
+ ttiming("max wait", i);
+ i = (c>>2)&16r7;
+ if(i != 7)
+ ttiming("max ready/busy wait", i);
+ i = (c>>5)&16r7;
+ if(i != 7)
+ ttiming("reserved wait", i);
+}
+
+range(asize: int, lsize: int)
+{
+ address := getlong(asize);
+ alen := getlong(lsize);
+ print("\t\t%ux - %ux\n", address, address+alen);
+}
+
+ioaccess := array[] of {
+ 0 => " no access",
+ 1 => " 8bit access only",
+ 2 => " 8bit or 16bit access",
+ 3 => " selectable 8bit or 8&16bit access",
+};
+
+iospace(c: int): int
+{
+ print("\tIO space %d address lines%s\n", c&16r1f, ioaccess[(c>>5)&3]);
+ if((c & 16r80) == 0)
+ return -1;
+
+ if((c = readc()) < 0)
+ return -1;
+
+ for(i := (c&16rf)+1; i; i--)
+ range((c>>4)&16r3, (c>>6)&16r3);
+ return 0;
+}
+
+iospaces()
+{
+ if((c := readc()) < 0)
+ return;
+ iospace(c);
+}
+
+irq()
+{
+ if((c := readc()) < 0)
+ return;
+ irqs: int;
+ if(c & 16r10){
+ if((irq1 := readc()) < 0)
+ return;
+ if((irq2 := readc()) < 0)
+ return;
+ irqs = irq1|(irq2<<8);
+ } else
+ irqs = 1<<(c&16rf);
+ level := "";
+ if(c & 16r20)
+ level = " level";
+ pulse := "";
+ if(c & 16r40)
+ pulse = " pulse";
+ shared := "";
+ if(c & 16r80)
+ shared = " shared";
+ print("\tinterrupts%s%s%s", level, pulse, shared);
+ for(i := 0; i < 16; i++)
+ if(irqs & (1<<i))
+ print(", %d", i);
+ print("\n");
+}
+
+memspace(asize: int, lsize: int, host: int)
+{
+ alen := getlong(lsize)*256;
+ address := getlong(asize)*256;
+ if(host){
+ haddress := getlong(asize)*256;
+ print("\tmemory address range #%ux - #%ux hostaddr #%ux\n",
+ address, address+alen, haddress);
+ } else
+ print("\tmemory address range #%ux - #%ux\n", address, address+alen);
+}
+
+misc()
+{
+}
+
+tentry(nil: int, nil: int)
+{
+ if((c := readc()) < 0)
+ return;
+ def := "";
+ if(c & 16r40)
+ def = " (default)";
+ print("configuration %d%s\n", c&16r3f, def);
+ if(c & 16r80){
+ if((i := readc()) < 0)
+ return;
+ tname := intrname[i & 16rf];
+ if(tname == "")
+ tname = sys->sprint("type %d", i & 16rf);
+ attrib := "";
+ if(i & 16r10)
+ attrib += " Battery status active";
+ if(i & 16r20)
+ attrib += " Write Protect active";
+ if(i & 16r40)
+ attrib += " Ready/Busy active";
+ if(i & 16r80)
+ attrib += " Memory Wait required";
+ print("\t%s device, %s\n", tname, attrib);
+ }
+ if((feature := readc()) < 0)
+ return;
+ case feature&16r3 {
+ 1 =>
+ power("Vcc");
+ 2 =>
+ power("Vcc");
+ power("Vpp");
+ 3 =>
+ power("Vcc");
+ power("Vpp1");
+ power("Vpp2");
+ }
+ if(feature&16r4)
+ timing();
+ if(feature&16r8)
+ iospaces();
+ if(feature&16r10)
+ irq();
+ case (feature>>5)&16r3 {
+ 1 =>
+ memspace(0, 2, 0);
+ 2 =>
+ memspace(2, 2, 0);
+ 3 =>
+ if((c = readc()) < 0)
+ return;
+ for(i := 0; i <= (c&16r7); i++)
+ memspace((c>>5)&16r3, (c>>3)&16r3, c&16r80);
+ break;
+ }
+ if(feature&16r80)
+ misc();
+}
diff --git a/appl/cmd/auxi/rdbgsrv.b b/appl/cmd/auxi/rdbgsrv.b
new file mode 100644
index 00000000..2a958eee
--- /dev/null
+++ b/appl/cmd/auxi/rdbgsrv.b
@@ -0,0 +1,222 @@
+implement RDbgSrv;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+include "styx.m";
+ styx: Styx;
+ Rmsg, Tmsg: import styx;
+
+include "arg.m";
+ arg: Arg;
+
+RDbgSrv: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+debug:= 0;
+dev:= "/dev/eia0";
+speed:= 38400;
+progname: string;
+rpid := 0;
+wpid := 0;
+
+usage()
+{
+ sys->fprint(stderr(), "Usage: rdbgsrv [-d n] [-s speed] [-f dev] mountpoint\n");
+ raise "fail: usage";
+}
+
+init(nil: ref Draw->Context, av: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if(sys == nil)
+ return;
+ styx = load Styx Styx->PATH;
+ if(styx == nil){
+ sys->fprint(stderr(), "rdbgsrv: can't load %s; %r\n", Styx->PATH);
+ raise "fail:load";
+ }
+ arg = load Arg Arg->PATH;
+ if(arg == nil){
+ sys->fprint(stderr(), "rdbgsrv: can't load %s: %r\n", Arg->PATH);
+ raise "fail:load";
+ }
+
+ arg->init(av);
+ progname = arg->progname();
+ while(o := arg->opt())
+ case o {
+ 'd' =>
+ d := arg->arg();
+ if(d == nil)
+ usage();
+ debug = int d;
+ 's' =>
+ s := arg->arg();
+ if(s == nil)
+ usage();
+ speed = int s;
+ 'f' =>
+ s := arg->arg();
+ if(s == nil)
+ usage();
+ dev = s;
+ 'h' =>
+ usage();
+ }
+
+ mtpt := arg->arg();
+ if(mtpt == nil)
+ usage();
+
+ ctl := dev + "ctl";
+ cfd := sys->open(ctl, Sys->OWRITE);
+ if(cfd == nil){
+ sys->fprint(stderr(), "%s: can't open %s: %r\n", progname, ctl);
+ raise "fail: open eia\n";
+ }
+
+ sys->fprint(cfd, "b%d", speed);
+ sys->fprint(cfd, "l8");
+ sys->fprint(cfd, "pn");
+ sys->fprint(cfd, "s1");
+
+ (rfd, wfd) := start(dev);
+ if(rfd == nil){
+ sys->fprint(stderr(), "%s: failed to start protocol\n", progname);
+ raise "fail:proto start";
+ }
+
+ fds := array[2] of ref Sys->FD;
+
+ if(sys->pipe(fds) == -1){
+ sys->fprint(stderr(), "%s: pipe: %r\n", progname);
+ raise "fail:no pipe";
+ }
+
+ if(debug)
+ sys->fprint(stderr(), "%s: starting server\n", progname);
+
+ rc := chan of int;
+ spawn copymsg(fds[1], wfd, "->", rc);
+ rpid = <-rc;
+ spawn copymsg(rfd, fds[1], "<-", rc);
+ wpid = <-rc;
+
+ if(sys->mount(fds[0], nil, mtpt, Sys->MREPL, nil) == -1) {
+ fds[1] = nil;
+ sys->fprint(stderr(), "%s: can't mount on %s: %r\n", progname, mtpt);
+ quit("mount");
+ }
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+killpid(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+}
+
+quit(err: string)
+{
+ killpid(rpid);
+ killpid(wpid);
+ if(err != nil)
+ raise "fail:"+err;
+ exit;
+}
+
+start(name:string): (ref Sys->FD, ref Sys->FD)
+{
+ rfd := sys->open(name, Sys->OREAD);
+ wfd := sys->open(name, Sys->OWRITE);
+ if(rfd == nil || wfd == nil)
+ return (nil, nil);
+ if(sys->fprint(wfd, "go") < 0)
+ return (nil, nil);
+ c := array[1] of byte;
+ state := 0;
+ for(;;) {
+ if(sys->read(rfd, c, 1) != 1)
+ return (nil, nil);
+ if(state == 0 && c[0] == byte 'o')
+ state = 1;
+ else if(state == 1 && c[0] == byte 'k')
+ break;
+ else
+ state = 0;
+ }
+ return (rfd, wfd);
+}
+
+copymsg(f: ref Sys->FD, t: ref Sys->FD, dir: string, pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil);
+
+ {
+ for(;;) {
+ (msg, err) := styx->readmsg(f, 0);
+ if(msg == nil){
+ sys->fprint(stderr(), "%s: %s: read error: %s\n", progname, dir, err);
+ quit("error");
+ }
+ if(debug &1)
+ trace(dir, msg);
+ if(debug & 2)
+ dump(dir, msg, len msg);
+ if(sys->write(t, msg, len msg) != len msg){
+ sys->fprint(stderr(), "%s: %s: write error: %r\n", progname, dir);
+ quit("error");
+ }
+ }
+ }exception e{
+ "*" =>
+ sys->print("%s: %s: %s: exiting\n", progname, dir, e);
+ quit("exception");
+ }
+}
+
+trace(sourcept: string, op: array of byte )
+{
+ if(styx->istmsg(op)){
+ (nil, m) := Tmsg.unpack(op);
+ if(m != nil)
+ sys->print("%s: %s\n", sourcept, m.text());
+ else
+ sys->print("%s: unknown\n", sourcept);
+ }else{
+ (nil, m) := Rmsg.unpack(op);
+ if(m != nil)
+ sys->print("%s: %s\n", sourcept, m.text());
+ else
+ sys->print("%s: unknown\n", sourcept);
+ }
+}
+
+dump(msg: string, buf: array of byte, n: int)
+{
+ sys->print("%s: [%d bytes]: ", msg, n);
+ s := "";
+ for(i:=0;i<n;i++) {
+ if((i % 20) == 0) {
+ sys->print(" %s\n", s);
+ s = "";
+ }
+ sys->print("%2.2x ", int buf[i]);
+ if(int buf[i] >= 32 && int buf[i] < 127)
+ s[len s] = int buf[i];
+ else
+ s += ".";
+ }
+ for(i %= 20; i < 20; i++)
+ sys->print(" ");
+ sys->print(" %s\n\n", s);
+}
diff --git a/appl/cmd/auxi/rstyxd.b b/appl/cmd/auxi/rstyxd.b
new file mode 100644
index 00000000..2f853ad5
--- /dev/null
+++ b/appl/cmd/auxi/rstyxd.b
@@ -0,0 +1,114 @@
+implement Rstyxd;
+
+include "sys.m";
+include "draw.m";
+include "sh.m";
+include "string.m";
+
+sys: Sys;
+str: String;
+stderr: ref Sys->FD;
+
+Rstyxd: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+#
+# argv is a list of Inferno supported algorithms from Security->Auth
+#
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ if (str == nil)
+ badmod(String->PATH);
+
+ fd := sys->fildes(0);
+ stderr = sys->fildes(2);
+ sys->pctl(sys->FORKFD, fd.fd :: nil);
+
+ args := readargs(fd);
+ if(args == nil)
+ err(sys->sprint("error reading arguments: %r"));
+
+ cmd := hd args;
+ s := "";
+ for (a := args; a != nil; a = tl a)
+ s += hd a + " ";
+ sys->fprint(stderr, "rstyxd: cmd: %s\n", s);
+ s = nil;
+ file: string;
+ if(cmd == "sh")
+ file = "/dis/sh.dis";
+ else
+ file = cmd + ".dis";
+ mod := load Command file;
+ if(mod == nil){
+ mod = load Command "/dis/"+file;
+ if(mod == nil)
+ badmod("/dis/"+file);
+ }
+
+ sys->pctl(Sys->FORKNS|Sys->FORKENV, nil);
+
+ if(sys->mount(fd, nil, "/n/client", Sys->MREPL, "") < 0)
+ err(sys->sprint("cannot mount connection on /n/client: %r"));
+
+ if(sys->bind("/n/client/dev", "/dev", Sys->MBEFORE) < 0)
+ err(sys->sprint("cannot bind /n/client/dev to /dev: %r"));
+
+ fd = sys->open("/dev/cons", sys->OREAD);
+ sys->dup(fd.fd, 0);
+ fd = sys->open("/dev/cons", sys->OWRITE);
+ sys->dup(fd.fd, 1);
+ sys->dup(fd.fd, 2);
+ fd = nil;
+
+ mod->init(nil, args);
+}
+
+readargs(fd: ref Sys->FD): list of string
+{
+ buf := array[1024] of byte;
+ c := array[1] of byte;
+ for(i:=0; ; i++){
+ if(i>=len buf || sys->read(fd, c, 1)!=1)
+ return nil;
+ buf[i] = c[0];
+ if(c[0] == byte '\n')
+ break;
+ }
+ nb := int string buf[0:i];
+ if(nb <= 0)
+ return nil;
+ args := readn(fd, nb);
+ if (args == nil)
+ return nil;
+ return str->unquoted(string args[0:nb]);
+}
+
+readn(fd: ref Sys->FD, nb: int): array of byte
+{
+ buf:= array[nb] of byte;
+ for(n:=0; n<nb;){
+ m := sys->read(fd, buf[n:], nb-n);
+ if(m <= 0)
+ return nil;
+ n += m;
+ }
+ return buf;
+}
+
+
+err(s: string)
+{
+ sys->fprint(stderr, "rstyxd: %s\n", s);
+ raise "fail:error";
+}
+
+badmod(s: string)
+{
+ sys->fprint(stderr, "rstyxd: can't load %s: %r\n", s);
+ raise "fail:load";
+}
diff --git a/appl/cmd/avr/burn.b b/appl/cmd/avr/burn.b
new file mode 100644
index 00000000..d1004cd1
--- /dev/null
+++ b/appl/cmd/avr/burn.b
@@ -0,0 +1,859 @@
+implement Burn;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "timers.m";
+ timers: Timers;
+ Timer: import timers;
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+
+Burn: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Avr: adt {
+ id: int;
+ rev: int;
+ flashsize: int;
+ eepromsize: int;
+ fusebytes: int;
+ lockbytes: int;
+ serfprog: int; # serial fuse programming support
+ serlprog: int; # serial lockbit programming support
+ serflread: int; # serial fuse/lockbit reading support
+ commonlfr: int; # lockbits and fuses are combined
+ sermemprog: int; # serial memory programming support
+ pagesize: int;
+ eeprompagesize: int;
+ selftimed: int; # all instructions are self-timed
+ fullpar: int; # part has full parallel interface
+ polling: int; # polling can be used during SPI access
+ fpoll: int; # flash poll value
+ epoll1: int; # eeprom poll value 1
+ epoll2: int; # eeprom poll value 2
+ name: string;
+ signalpagel: int; # posn of PAGEL signal (16rD7 by default)
+ signalbs2: int; # posn of BS2 signal (16rA0 by default)
+};
+
+F, T: con iota;
+ATMEGA128: con 16rB2; # 128k devices
+
+avrs: array of Avr = array[] of {
+ (ATMEGA128, 1, 131072, 4096, 3, 1, T, T, T, F, T, 256, 8, T, T, T, 16rFF, 16rFF, 16rFF, "ATmega128", 16rD7, 16rA0),
+};
+
+sfd: ref Sys->FD;
+cfd: ref Sys->FD;
+rd: ref Rd;
+mib510 := 1;
+
+Rd: adt {
+ c: chan of array of byte;
+ pid: int;
+ fd: ref Sys->FD;
+ buf: array of byte;
+ new: fn(fd: ref Sys->FD): ref Rd;
+ read: fn(r: self ref Rd, ms: int): array of byte;
+ readn: fn(r: self ref Rd, n: int, ms: int): array of byte;
+ flush: fn(r: self ref Rd);
+ stop: fn(r: self ref Rd);
+ reader: fn(r: self ref Rd, c: chan of int);
+};
+
+debug := 0;
+verify := 0;
+erase := 1;
+ignore := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = ckl(load Bufio Bufio->PATH, Bufio->PATH);
+ str = ckl(load String String->PATH, String->PATH);
+ timers = ckl(load Timers Timers->PATH, Timers->PATH);
+
+ serial := "/dev/eia0";
+ fuseext := -1;
+ fuselow := -1;
+ fusehigh := -1;
+ arg := ckl(load Arg Arg->PATH, Arg->PATH);
+ arg->init(args);
+ arg->setusage("burn [-rD] [-d serialdev] file.out");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'D' => debug++;
+ 'e' => erase = 0;
+ 'r' => verify = 1;
+ 'd' => serial = arg->earg();
+ 'i' => ignore = 1;
+ 'E' => fuseext = fuseval(arg->earg());
+ 'L' => fuselow = fuseval(arg->earg());
+ 'H' => fusehigh = fuseval(arg->earg());
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(len args != 1)
+ arg->usage();
+ arg = nil;
+
+ sfile := hd args;
+ fd := bufio->open(sfile, Sys->OREAD);
+ if(fd == nil)
+ err(sys->sprint("can't open %s: %r", sfile));
+
+ timers->init(2);
+ sfd = sys->open(serial, Sys->ORDWR);
+ if(sfd == nil)
+ err(sys->sprint("can't open %s: %r", "/dev/eia0"));
+ cfd = sys->open(serial+"ctl", Sys->ORDWR);
+ sys->fprint(cfd, "f");
+ sys->fprint(cfd, "b115200");
+ sys->fprint(cfd, "i8");
+# sys->fprint(cfd, "f\nb115200\ni8");
+ rd = Rd.new(sfd);
+
+ initialise();
+ if(fuseext >= 0 || fuselow >= 0 || fusehigh >= 0){
+ if(fuselow >= 0 && (fuselow & 16rF) == 0)
+ err("don't program external clock");
+ if(fuseext >= 0 && (fuseext & (1<<0)) == 0)
+ err("don't program ATmega103 compatibility");
+ if(fusehigh >= 0 && (fusehigh & (1<<7)) == 0)
+ err("don't program OCDEN=0");
+ if(fusehigh >= 0 && writefusehigh(fusehigh) >= 0)
+ sys->print("set fuse high=%.2ux\n", fusehigh);
+ if(fuselow >= 0 && writefuselow(fuselow) >= 0)
+ sys->print("set fuse low=%.2ux\n", fuselow);
+ if(fuseext >= 0 && writefuseext(fuseext) >= 0)
+ sys->print("set fuse ext=%.2ux\n", fuseext);
+ shutdown();
+ exit;
+ }
+
+ if(!verify && erase){
+ chiperase();
+ sys->print("Erased flash\n");
+ }
+
+ totbytes := 0;
+ while((l := fd.gets('\n')) != nil){
+ (c, addr, data) := sdecode(l);
+ if(c >= '1' && c <= '3'){
+ if(verify){
+ fdata := readflashdata(addr, len data);
+ if(!eq(fdata, data))
+ sys->print("mismatch: %d::%d at %4.4ux\n", len data, len fdata, addr);
+ }else if(writeflashdata(addr, data) != len data)
+ err("failed to program device");
+ totbytes += len data;
+ } else if(c == '0')
+ sys->print("title: %q\n", string data);
+ }
+ if(!verify){
+ flushpage();
+ sys->print("Programmed %ud (0x%4.4ux) bytes\n", totbytes, totbytes);
+ }
+
+ shutdown();
+}
+
+ckl[T](m: T, s: string): T
+{
+ if(m == nil)
+ err(sys->sprint("can't load %s: %r", s));
+ return m;
+}
+
+fuseval(s: string): int
+{
+ (n, t) := str->toint(s, 16);
+ if(t != nil || n < 0 || n > 255)
+ err("illegal fuse value");
+ return n;
+}
+
+cache: (int, array of byte);
+
+readflashdata(addr: int, nbytes: int): array of byte
+{
+ data := array[nbytes] of byte;
+ ia := addr;
+ ea := addr+nbytes;
+ while(addr < ea){
+ (ca, cd) := cache;
+ if(addr >= ca && addr < ca+len cd){
+ n := nbytes;
+ o := addr-ca;
+ if(o+n > len cd)
+ n = len cd - o;
+ if(addr-ia+n > len data)
+ n = len data - (addr-ia);
+ data[addr-ia:] = cd[o:o+n];
+ addr += n;
+ }else{
+ ca = addr & ~16rFF;
+ cd = readflashpage(ca, 16r100);
+ cache = (ca, cd);
+ }
+ }
+ return data;
+}
+
+writeflashdata(addr: int, data: array of byte): int
+{
+ pagesize := avrs[0].pagesize;
+ ia := addr;
+ ea := addr+len data;
+ while(addr < ea){
+ (ca, cd) := cache;
+ if(addr >= ca && addr < ca+len cd){
+ n := len data;
+ o := addr-ca;
+ if(o+n > len cd)
+ n = len cd - o;
+ cd[o:] = data[0:n];
+ addr += n;
+ data = data[n:];
+ }else{
+ if(flushpage() < 0)
+ break;
+ cache = (addr & ~16rFF, array[pagesize] of {* => byte 16rFF});
+ }
+ }
+ return addr-ia;
+}
+
+flushpage(): int
+{
+ (ca, cd) := cache;
+ if(len cd == 0)
+ return 0;
+ cache = (0, nil);
+ if(writeflashpage(ca, cd) != len cd)
+ return -1;
+ return len cd;
+}
+
+shutdown()
+{
+# setisp(0);
+ if(rd != nil){
+ rd.stop();
+ rd = nil;
+ }
+ if(timers != nil)
+ timers->shutdown();
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "burn: %s\n", s);
+ shutdown();
+ raise "fail:error";
+}
+
+dump(a: array of byte): string
+{
+ s := sys->sprint("[%d]", len a);
+ for(i := 0; i < len a; i++)
+ s += sys->sprint(" %.2ux", int a[i]);
+ return s;
+}
+
+initialise()
+{
+ if(mib510){
+ # MIB510-specific: switch rs232 to STK500
+ for(i:=0; i<8; i++){
+ setisp0(1);
+ sys->sleep(10);
+ rd.flush();
+ if(setisp(1))
+ break;
+ }
+ if(!setisp(1))
+ err("no response from programmer");
+ }
+ resync();
+ resync();
+ if(!mib510){
+ r := rpc(array[] of {Cmd_STK_GET_SIGN_ON}, 7);
+ if(r != nil)
+ sys->print("got: %q\n", string r);
+ }
+ r := readsig();
+ if(len r > 0 && r[0] != byte 16rFF)
+ sys->print("sig: %s\n", dump(r));
+ (min, maj) := version();
+ sys->print("Firmware version: %s.%s\n", min, maj);
+ setdevice(avrs[0]);
+ pgmon();
+ r = readsig();
+ sys->print("sig: %s\n", dump(r));
+ pgmoff();
+ if(len r < 3 || r[0] != byte 16r1e || r[1] != byte 16r97 || r[2] != byte 16r02)
+ if(!ignore)
+ err("unlikely response: check connections");
+
+ # could set voltages here...
+ sys->print("fuses: h=%.2ux l=%.2ux e=%.2ux\n", readfusehigh(), readfuselow(), readfuseext());
+}
+
+resync()
+{
+ for(i := 0; i < 8; i++){
+ rd.flush();
+ r := rpc(array[] of {Cmd_STK_GET_SYNC}, 0);
+ if(r != nil)
+ return;
+ }
+ err("lost sync with programmer");
+}
+
+getparam(p: byte): int
+{
+ r := rpc(array[] of {Cmd_STK_GET_PARAMETER, p}, 1);
+ if(len r > 0)
+ return int r[0];
+ return -1;
+}
+
+version(): (string, string)
+{
+ maj := getparam(Parm_STK_SW_MAJOR);
+ min := getparam(Parm_STK_SW_MINOR);
+ if(mib510)
+ return (sys->sprint("%c", maj), sys->sprint("%c", min));
+ return (sys->sprint("%d", maj), sys->sprint("%d", min));
+}
+
+eq(a, b: array of byte): int
+{
+ if(len a != len b)
+ return 0;
+ for(i := 0; i < len a; i++)
+ if(a[i] != b[i])
+ return 0;
+ return 1;
+}
+
+#
+# Motorola S records
+#
+
+badsrec(s: string)
+{
+ err("bad S record: "+s);
+}
+
+hexc(c: int): int
+{
+ if(c >= '0' && c <= '9')
+ return c-'0';
+ if(c >= 'a' && c <= 'f')
+ return c-'a'+10;
+ if(c >= 'A' && c <= 'F')
+ return c-'A'+10;
+ return -1;
+}
+
+g8(s: string): int
+{
+ if(len s >= 2){
+ c0 := hexc(s[0]);
+ c1 := hexc(s[1]);
+ if(c0 >= 0 && c1 >= 0)
+ return (c0<<4) | c1;
+ }
+ return -1;
+}
+
+# S d len
+sdecode(s: string): (int, int, array of byte)
+{
+ while(len s > 0 && (s[len s-1] == '\r' || s[len s-1] == '\n'))
+ s = s[0:len s-1];
+ if(len s < 4 || s[0] != 'S')
+ badsrec(s);
+ l := g8(s[2:4]);
+ if(l < 0)
+ badsrec("length: "+s);
+ if(2*l != len s - 4)
+ badsrec("length: "+s);
+ csum := l;
+ na := 2;
+ if(s[1] >= '1' && s[1] <= '3')
+ na = s[1]-'1'+2;
+ addr := 0;
+ for(i:=0; i<na; i++){
+ b := g8(s[4+i*2:]);
+ if(b < 0)
+ badsrec(s);
+ csum += b;
+ addr = (addr << 8) | b;
+ }
+ case s[1] {
+ '0' or # used as segment name (seems to be srec file name with TinyOS)
+ '1' to '3' or # data
+ '5' or # plot so far
+ '7' to '9' => # end/start address
+ ;
+ * =>
+ badsrec("type: "+s);
+ }
+ data := array[l-na-1] of byte;
+ for(i = 0; i < len data; i++){
+ c := g8(s[4+(na+i)*2:]);
+ csum += c;
+ data[i] = byte c;
+ }
+ v := g8(s[4+l*2-2:]);
+ csum += v;
+ if((csum & 16rFF) != 16rFF)
+ badsrec("checksum: "+s);
+ return (s[1], addr, data);
+}
+
+#
+# serial port
+#
+
+Rd.new(fd: ref Sys->FD): ref Rd
+{
+ r := ref Rd(chan[4] of array of byte, 0, fd, nil);
+ c := chan of int;
+ spawn r.reader(c);
+ <-c;
+ return r;
+}
+
+Rd.reader(r: self ref Rd, c: chan of int)
+{
+ r.pid = sys->pctl(0, nil);
+ c <-= 1;
+ for(;;){
+ buf := array[258] of byte;
+ n := sys->read(r.fd, buf, len buf);
+ if(n <= 0){
+ r.pid = 0;
+ err(sys->sprint("read error: %r"));
+ }
+ if(debug)
+ sys->print("<- %s\n", dump(buf[0:n]));
+ r.c <-= buf[0:n];
+ }
+}
+
+Rd.read(r: self ref Rd, ms: int): array of byte
+{
+ if((a := r.buf) != nil){
+ r.buf = nil;
+ return a;
+ }
+ t := Timer.start(ms);
+ alt{
+ a = <-r.c =>
+ t.stop();
+ Acc:
+ for(;;){
+ sys->sleep(5);
+ alt{
+ b := <-r.c =>
+ if(b == nil)
+ break Acc;
+ a = cat(a, b);
+ * =>
+ break Acc;
+ }
+ }
+ return a;
+ <-t.timeout =>
+ return nil;
+ }
+}
+
+Rd.readn(r: self ref Rd, n: int, ms: int): array of byte
+{
+ a: array of byte;
+
+ while((need := n - len a) > 0){
+ b := r.read(ms);
+ if(b == nil)
+ break;
+ if(len b > need){
+ r.buf = b[need:];
+ b = b[0:need];
+ }
+ a = cat(a, b);
+ }
+ return a;
+}
+
+Rd.flush(r: self ref Rd)
+{
+ r.buf = nil;
+ sys->sleep(5);
+ for(;;){
+ alt{
+ <-r.c =>
+ ;
+ * =>
+ return;
+ }
+ }
+}
+
+Rd.stop(r: self ref Rd)
+{
+ pid := r.pid;
+ if(pid){
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+ }
+}
+
+cat(a, b: array of byte): array of byte
+{
+ if(len b == 0)
+ return a;
+ if(len a == 0)
+ return b;
+ c := array[len a + len b] of byte;
+ c[0:] = a;
+ c[len a:] = b;
+ return c;
+}
+
+#
+# STK500 communication protocol
+#
+
+STK_SIGN_ON_MESSAGE: con "AVR STK"; # Sign on string for Cmd_STK_GET_SIGN_ON
+
+# Responses
+
+Resp_STK_OK: con byte 16r10;
+Resp_STK_FAILED: con byte 16r11;
+Resp_STK_UNKNOWN: con byte 16r12;
+Resp_STK_NODEVICE: con byte 16r13;
+Resp_STK_INSYNC: con byte 16r14;
+Resp_STK_NOSYNC: con byte 16r15;
+
+Resp_ADC_CHANNEL_ERROR: con byte 16r16;
+Resp_ADC_MEASURE_OK: con byte 16r17;
+Resp_PWM_CHANNEL_ERROR: con byte 16r18;
+Resp_PWM_ADJUST_OK: con byte 16r19;
+
+# Special constants
+
+Sync_CRC_EOP: con byte 16r20;
+
+# Commands
+
+Cmd_STK_GET_SYNC: con byte 16r30;
+Cmd_STK_GET_SIGN_ON: con byte 16r31;
+
+Cmd_STK_SET_PARAMETER: con byte 16r40;
+Cmd_STK_GET_PARAMETER: con byte 16r41;
+Cmd_STK_SET_DEVICE: con byte 16r42;
+Cmd_STK_SET_DEVICE_EXT: con byte 16r45;
+
+Cmd_STK_ENTER_PROGMODE: con byte 16r50;
+Cmd_STK_LEAVE_PROGMODE: con byte 16r51;
+Cmd_STK_CHIP_ERASE: con byte 16r52;
+Cmd_STK_CHECK_AUTOINC: con byte 16r53;
+Cmd_STK_LOAD_ADDRESS: con byte 16r55;
+Cmd_STK_UNIVERSAL: con byte 16r56;
+Cmd_STK_UNIVERSAL_MULTI: con byte 16r57;
+
+Cmd_STK_PROG_FLASH: con byte 16r60;
+Cmd_STK_PROG_DATA: con byte 16r61;
+Cmd_STK_PROG_FUSE: con byte 16r62;
+Cmd_STK_PROG_LOCK: con byte 16r63;
+Cmd_STK_PROG_PAGE: con byte 16r64;
+Cmd_STK_PROG_FUSE_EXT: con byte 16r65;
+
+Cmd_STK_READ_FLASH: con byte 16r70;
+Cmd_STK_READ_DATA: con byte 16r71;
+Cmd_STK_READ_FUSE: con byte 16r72;
+Cmd_STK_READ_LOCK: con byte 16r73;
+Cmd_STK_READ_PAGE: con byte 16r74;
+Cmd_STK_READ_SIGN: con byte 16r75;
+Cmd_STK_READ_OSCCAL: con byte 16r76;
+Cmd_STK_READ_FUSE_EXT: con byte 16r77;
+Cmd_STK_READ_OSCCAL_EXT: con byte 16r78;
+
+# Parameter constants
+
+Parm_STK_HW_VER: con byte 16r80; # ' ' - R
+Parm_STK_SW_MAJOR: con byte 16r81; # ' ' - R
+Parm_STK_SW_MINOR: con byte 16r82; # ' ' - R
+Parm_STK_LEDS: con byte 16r83; # ' ' - R/W
+Parm_STK_VTARGET: con byte 16r84; # ' ' - R/W
+Parm_STK_VADJUST: con byte 16r85; # ' ' - R/W
+Parm_STK_OSC_PSCALE: con byte 16r86; # ' ' - R/W
+Parm_STK_OSC_CMATCH: con byte 16r87; # ' ' - R/W
+Parm_STK_RESET_DURATION: con byte 16r88; # ' ' - R/W
+Parm_STK_SCK_DURATION: con byte 16r89; # ' ' - R/W
+
+Parm_STK_BUFSIZEL: con byte 16r90; # ' ' - R/W, Range {0..255}
+Parm_STK_BUFSIZEH: con byte 16r91; # ' ' - R/W, Range {0..255}
+Parm_STK_DEVICE: con byte 16r92; # ' ' - R/W, Range {0..255}
+Parm_STK_PROGMODE: con byte 16r93; # ' ' - 'P' or 'S'
+Parm_STK_PARAMODE: con byte 16r94; # ' ' - TRUE or FALSE
+Parm_STK_POLLING: con byte 16r95; # ' ' - TRUE or FALSE
+Parm_STK_SELFTIMED: con byte 16r96; # ' ' - TRUE or FALSE
+
+# status bits
+
+Stat_STK_INSYNC: con byte 16r01; # INSYNC status bit, '1' - INSYNC
+Stat_STK_PROGMODE: con byte 16r02; # Programming mode, '1' - PROGMODE
+Stat_STK_STANDALONE: con byte 16r04; # Standalone mode, '1' - SM mode
+Stat_STK_RESET: con byte 16r08; # RESET button, '1' - Pushed
+Stat_STK_PROGRAM: con byte 16r10; # Program button, ' 1' - Pushed
+Stat_STK_LEDG: con byte 16r20; # Green LED status, '1' - Lit
+Stat_STK_LEDR: con byte 16r40; # Red LED status, '1' - Lit
+Stat_STK_LEDBLINK: con byte 16r80; # LED blink ON/OFF, '1' - Blink
+
+ispmode := array[] of {byte 16rAA, byte 16r55, byte 16r55, byte 16rAA, byte 16r17, byte 16r51, byte 16r31, byte 16r13, byte 0}; # last byte is 1 to switch isp on 0 to switch off
+
+ck(r: array of byte)
+{
+ if(r == nil)
+ err("programming failed");
+}
+
+pgmon()
+{
+ ck(rpc(array[] of {Cmd_STK_ENTER_PROGMODE}, 0));
+}
+
+pgmoff()
+{
+ ck(rpc(array[] of {Cmd_STK_LEAVE_PROGMODE}, 0));
+}
+
+setisp0(on: int)
+{
+ rd.flush();
+ buf := array[len ispmode] of byte;
+ buf[0:] = ispmode;
+ buf[8] = byte on;
+ sys->write(sfd, buf, len buf);
+}
+
+setisp(on: int): int
+{
+ rd.flush();
+ buf := array[len ispmode] of byte;
+ buf[0:] = ispmode;
+ buf[8] = byte on;
+ r := send(buf, 2);
+ return len r == 2 && ok(r);
+}
+
+readsig(): array of byte
+{
+ r := send(array[] of {Cmd_STK_READ_SIGN, Sync_CRC_EOP}, 5);
+ # doesn't behave as documented in AVR061: it repeats the command bytes instead
+ if(len r != 5 || r[0] != Cmd_STK_READ_SIGN || r[4] != Sync_CRC_EOP){
+ sys->fprint(sys->fildes(2), "bad reply %s\n", dump(r));
+ return nil;
+ }
+ return r[1:len r-1]; # trim proto bytes
+}
+
+pgrpc(a: array of byte, repn: int): array of byte
+{
+ pgmon();
+ r := rpc(a, repn);
+ pgmoff();
+ return r;
+}
+
+eop := array[] of {Sync_CRC_EOP};
+
+rpc(a: array of byte, repn: int): array of byte
+{
+ r := send(cat(a, eop), repn+2);
+ if(!ok(r)){
+ if(len r >= 2 && r[0] == Resp_STK_INSYNC && r[len r-1] == Resp_STK_NODEVICE)
+ err("internal error: programming parameters not correctly set");
+ if(len r >= 1 && r[0] == Resp_STK_NOSYNC)
+ err("lost synchronisation");
+ sys->fprint(sys->fildes(2), "bad reply %s\n", dump(r));
+ return nil;
+ }
+ return r[1:len r-1]; # trim sync bytes
+}
+
+send(a: array of byte, repn: int): array of byte
+{
+ if(debug)
+ sys->print("-> %s\n", dump(a));
+ if(sys->write(sfd, a, len a) != len a)
+ err(sys->sprint("write error: %r"));
+ return rd.readn(repn, 2000);
+}
+
+ok(r: array of byte): int
+{
+ return len r >= 2 && r[0] == Resp_STK_INSYNC && r[len r -1] == Resp_STK_OK;
+}
+
+universal(req: array of byte): int
+{
+ r := pgrpc(cat(array[] of {Cmd_STK_UNIVERSAL}, req), 1);
+ if(r == nil)
+ return -1;
+ return int r[0];
+}
+
+setdevice(d: Avr)
+{
+ b := array[] of {
+ Cmd_STK_SET_DEVICE,
+ byte d.id,
+ byte d.rev,
+ byte 0, # prog type (CHECK)
+ byte d.fullpar,
+ byte d.polling,
+ byte d.selftimed,
+ byte d.lockbytes,
+ byte d.fusebytes,
+ byte d.fpoll,
+ byte d.fpoll,
+ byte d.epoll1,
+ byte d.epoll2,
+ byte (d.pagesize >> 8), byte d.pagesize,
+ byte (d.eepromsize>>8), byte d.eepromsize,
+ byte (d.flashsize>>24), byte (d.flashsize>>16), byte (d.flashsize>>8), byte d.flashsize
+ };
+ ck(rpc(b, 0));
+ if(mib510)
+ return;
+ b = array[] of {
+ Cmd_STK_SET_DEVICE_EXT,
+ byte 4,
+ byte d.eeprompagesize,
+ byte d.signalpagel,
+ byte d.signalbs2,
+ byte 0 # ResetDisable
+ };
+ ck(rpc(b, 0));
+}
+
+chiperase()
+{
+ ck(pgrpc(array[] of {Cmd_STK_CHIP_ERASE}, 0));
+}
+
+readfuselow(): int
+{
+ return universal(array[] of {byte 16r50, byte 0, byte 0, byte 0});
+}
+
+readfusehigh(): int
+{
+ return universal(array[] of {byte 16r58, byte 8, byte 0, byte 0});
+}
+
+readfuseext(): int
+{
+ return universal(array[] of {byte 16r50, byte 8, byte 0, byte 0});
+}
+
+readlockfuse(): int
+{
+ return universal(array[] of {byte 16r58, byte 0, byte 0, byte 0});
+}
+
+readflashpage(addr: int, nb: int): array of byte
+{
+ return readmem('F', addr/2, nb);
+}
+
+readeeprompage(addr: int, nb: int): array of byte
+{
+ return readmem('E', addr, nb);
+}
+
+readmem(memtype: int, addr: int, nb: int): array of byte
+{
+ if(nb > 256)
+ nb = 256;
+ pgmon();
+ r := rpc(array[] of {Cmd_STK_LOAD_ADDRESS, byte addr, byte (addr>>8)}, 0);
+ if(r != nil){
+ r = send(array[] of {Cmd_STK_READ_PAGE, byte (nb>>8), byte nb, byte memtype, Sync_CRC_EOP}, nb+2);
+ l := len r;
+ # AVR601 says last byte should be Resp_STK_OK but it's not, at least on MIB; check for both
+ if(l >= 2 && r[0] == Resp_STK_INSYNC && (r[l-1] == Resp_STK_INSYNC || r[l-1] == Resp_STK_OK))
+ r = r[1:l-1]; # trim framing bytes
+ else{
+ sys->print("bad reply: %s\n", dump(r));
+ r = nil;
+ }
+ if(len r < nb)
+ sys->print("short [%d@%4.4ux]\n", nb, addr);
+ }
+ pgmoff();
+ return r;
+}
+
+writeflashpage(addr: int, data: array of byte): int
+{
+ return writemem('F', addr/2, data);
+}
+
+writeeeprompage(addr: int, data: array of byte): int
+{
+ return writemem('E', addr, data);
+}
+
+writemem(memtype: int, addr: int, data: array of byte): int
+{
+ nb := len data;
+ if(nb > 256){
+ nb = 256;
+ data = data[0:nb];
+ }
+ pgmon();
+ r := rpc(array[] of {Cmd_STK_LOAD_ADDRESS, byte addr, byte (addr>>8)}, 0);
+ if(r != nil){
+ r = rpc(cat(array[] of {Cmd_STK_PROG_PAGE, byte (nb>>8), byte nb, byte memtype},data), 0);
+ if(r == nil)
+ nb = -1;
+ }
+ pgmoff();
+ return nb;
+}
+
+writefuseext(v: int): int
+{
+ return universal(array[] of {byte 16rAC, byte 16rA4, byte 16rFF, byte v});
+}
+
+writefuselow(v: int): int
+{
+ return universal(array[] of {byte 16rAC, byte 16rA0, byte 16rFF, byte v});
+}
+
+writefusehigh(v: int): int
+{
+ return universal(array[] of {byte 16rAC, byte 16rA8, byte 16rFF, byte v});
+}
diff --git a/appl/cmd/avr/mkfile b/appl/cmd/avr/mkfile
new file mode 100644
index 00000000..2c6a5a33
--- /dev/null
+++ b/appl/cmd/avr/mkfile
@@ -0,0 +1,10 @@
+<../../../mkconfig
+
+TARG=\
+ burn.dis\
+
+SYSMODULES=\
+
+DISBIN=$ROOT/dis/avr
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/basename.b b/appl/cmd/basename.b
new file mode 100644
index 00000000..8d0ad5a8
--- /dev/null
+++ b/appl/cmd/basename.b
@@ -0,0 +1,50 @@
+implement Basename;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "names.m";
+ names: Names;
+
+include "arg.m";
+
+Basename: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ names = load Names Names->PATH;
+ arg := load Arg Arg->PATH;
+
+ dirname := 0;
+ arg->init(args);
+ arg->setusage("basename [-d] string [suffix]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' =>
+ dirname = 1;
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil || tl args != nil && (dirname || tl tl args != nil))
+ arg->usage();
+ arg = nil;
+
+ if(dirname){
+ s := names->dirname(hd args);
+ if(s == nil)
+ s = ".";
+ sys->print("%s\n", s);
+ exit;
+ }
+ suffix: string;
+ if(tl args != nil)
+ suffix = hd tl args;
+ sys->print("%s\n", names->basename(hd args, suffix));
+}
diff --git a/appl/cmd/bind.b b/appl/cmd/bind.b
new file mode 100644
index 00000000..fa6c734b
--- /dev/null
+++ b/appl/cmd/bind.b
@@ -0,0 +1,66 @@
+implement Bind;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+Bind: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+stderr: ref Sys->FD;
+
+usage()
+{
+ sys->fprint(stderr, "usage: bind [-a|-b|-c|-ac|-bc] [-q] source target\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ stderr = sys->fildes(2);
+ flags := 0;
+ qflag := 0;
+ if(args != nil)
+ args = tl args;
+ while(args != nil && (a := hd args) != "" && a[0] == '-'){
+ args = tl args;
+ if(a == "--")
+ break;
+ for(o := 1; o < len a; o++)
+ case a[o] {
+ 'a' =>
+ flags |= Sys->MAFTER;
+ 'b' =>
+ flags |= Sys->MBEFORE;
+ 'c' =>
+ flags |= Sys->MCREATE;
+ 'q' =>
+ qflag = 1;
+ * =>
+ usage();
+ }
+ }
+ if(len args != 2 || flags&Sys->MAFTER && flags&Sys->MBEFORE)
+ usage();
+
+ f1 := hd args;
+ f2 := hd tl args;
+ if(sys->bind(f1, f2, flags) < 0){
+ if(qflag)
+ exit;
+ # try to improve the error message
+ err := sys->sprint("%r");
+ if(sys->stat(f1).t0 < 0)
+ sys->fprint(stderr, "bind: %s: %r\n", f1);
+ else if(sys->stat(f2).t0 < 0)
+ sys->fprint(stderr, "bind: %s: %r\n", f2);
+ else
+ sys->fprint(stderr, "bind: cannot bind %s onto %s: %s\n", f1, f2, err);
+ raise "fail:bind";
+ }
+}
diff --git a/appl/cmd/bit2gif.b b/appl/cmd/bit2gif.b
new file mode 100644
index 00000000..52788e76
--- /dev/null
+++ b/appl/cmd/bit2gif.b
@@ -0,0 +1,86 @@
+#
+# bit2gif -
+#
+# A simple command line utility for converting inferno bitmaps
+# to gif images.
+#
+# Craig Newell, Jan. 1999 CraigN@cheque.uq.edu.au
+#
+implement bit2gif;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Display: import draw;
+include "string.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "imagefile.m";
+
+bit2gif : module
+{
+ init: fn(ctx: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->print("usage: bit2gif <inferno bitmap>\n");
+ exit;
+}
+
+init(ctx: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ # check arguments
+ if (argv == nil)
+ usage();
+ argv = tl argv;
+ if (argv == nil)
+ usage();
+ s := hd argv;
+ if (len s && s[0] == '-')
+ usage();
+
+ # load the modules
+ str := load String String->PATH;
+ draw = load Draw Draw->PATH;
+ bufio = load Bufio Bufio->PATH;
+ imgfile := load WImagefile WImagefile->WRITEGIFPATH;
+ imgfile->init(bufio);
+
+ # open the display
+ display: ref Draw->Display;
+ if (ctx == nil) {
+ display = Display.allocate(nil);
+ } else {
+ display = ctx.display;
+ }
+
+ # process all the files
+ while (argv != nil) {
+
+ # get the filenames
+ bit_name := hd argv;
+ (gif_name, nil) := str->splitstrl(bit_name, ".bit");
+ gif_name = gif_name + ".gif";
+
+ # load inferno bitmap
+ img := display.open(bit_name);
+ if (img == nil) {
+ sys->print("bit2gif: unable to read <%s>\n", bit_name);
+ } else {
+ # save as gif
+ o := bufio->create(gif_name, Bufio->OWRITE, 8r644);
+ if (o != nil) {
+ imgfile->writeimage(o, img);
+ o.close();
+ }
+ }
+
+ # next argument
+ argv = tl argv;
+ }
+}
diff --git a/appl/cmd/broke.b b/appl/cmd/broke.b
new file mode 100644
index 00000000..41f2dd89
--- /dev/null
+++ b/appl/cmd/broke.b
@@ -0,0 +1,84 @@
+implement Broke;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+Broke: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ fd := sys->open("/prog", Sys->OREAD);
+ if(fd == nil)
+ err(sys->sprint("can't open /prog: %r"));
+ killed := "";
+ for(;;){
+ (n, dir) := sys->dirread(fd);
+ if(n <= 0){
+ if(n < 0)
+ err(sys->sprint("error reading /prog: %r"));
+ break;
+ }
+ for(i := 0; i < n; i++)
+ if(isbroken(dir[i].name) && kill(dir[i].name))
+ killed += sys->sprint(" %s", dir[i].name);
+ }
+ if(killed != nil)
+ sys->print("%s\n", killed);
+}
+
+isbroken(pid: string): int
+{
+ statf := "/prog/" + pid + "/status";
+ fd := sys->open(statf, Sys->OREAD);
+ if (fd == nil)
+ return 0;
+ buf := array[256] of byte;
+ n := sys->read(fd, buf, len buf);
+ if (n < 0) { # process died or is exiting
+ # sys->fprint(stderr(), "broke: can't read %s: %r\n", statf);
+ return 0;
+ }
+ (nf, l) := sys->tokenize(string buf[0:n], " ");
+ return nf >= 5 && hd tl tl tl tl l == "broken";
+}
+
+kill(pid: string): int
+{
+ ctl := "/prog/" + pid + "/ctl";
+ fd := sys->open(ctl, sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "kill") < 0){
+ sys->fprint(stderr(), "broke: can't kill %s: %r\n", pid); # but press on
+ return 0;
+ }
+ return 1;
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "broke: %s\n", s);
+ raise "fail:error";
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil)
+ return "inferno";
+
+ buf := array[64] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ return "inferno";
+
+ return string buf[0:n];
+}
diff --git a/appl/cmd/bytes.b b/appl/cmd/bytes.b
new file mode 100644
index 00000000..e45c4fe4
--- /dev/null
+++ b/appl/cmd/bytes.b
@@ -0,0 +1,212 @@
+implement Bytes;
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+stdin, stdout: ref Iobuf;
+
+Bytes: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(stderr, "usage: bytes start end [bytes]\n");
+ raise "fail:usage";
+}
+
+END: con 16r7fffffff;
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) {
+ sys->fprint(stderr, "bytes: cannot load %s: %r\n", Bufio->PATH);
+ raise "fail:bad module";
+ }
+ stdin = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ stdout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ start := end := END;
+ if (len argv < 3)
+ usage();
+ argv = tl argv;
+ if (hd argv != "end")
+ start = int hd argv;
+ argv = tl argv;
+ if (hd argv != "end")
+ end = int hd argv;
+ if (end < start) {
+ sys->fprint(stderr, "bytes: out of order range\n");
+ raise "fail:bad range";
+ }
+ argv = tl argv;
+ if (argv == nil)
+ showbytes(start, end);
+ else {
+ if (tl argv != nil)
+ usage();
+ b := s2bytes(hd argv);
+ setbytes(start, end, b);
+ }
+ stdout.close();
+}
+
+showbytes(start, end: int)
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ hold := array[Sys->UTFmax] of byte;
+ tot := 0;
+ nhold := 0;
+ while (tot < end && (n := stdin.read(buf[nhold:], len buf - nhold)) > 0) {
+ sys->fprint(stderr, "bytes: read %d bytes\n", n);
+ if (tot + n < start)
+ continue;
+ sb := 0;
+ eb := n;
+ if (start > tot)
+ sb = start - tot;
+ if (tot + n > end)
+ eb = end - tot;
+ nhold = putbytes(buf[sb:eb], hold);
+ buf[0:] = hold[0:nhold];
+ tot += n - nhold;
+ }
+ sys->fprint(stderr, "out of loop\n");
+ flushbytes(hold[0:nhold]);
+}
+
+setbytes(start, end: int, d: array of byte)
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ tot := 0;
+ while ((n := stdin.read(buf, len buf)) > 0) {
+ if (tot + n < start || tot >= end) {
+ stdout.write(buf, n);
+ continue;
+ }
+ if (tot <= start) {
+ stdout.write(buf[0:start-tot], start-tot);
+ stdout.write(d, len d);
+ if (end == END)
+ return;
+ }
+ if (tot + n >= end)
+ stdout.write(buf[end - tot:], n - (end - tot));
+ tot += n;
+ }
+ if (tot == start || start == END)
+ stdout.write(d, len d);
+}
+
+putbytes(d: array of byte, hold: array of byte): int
+{
+ i := 0;
+ while (i < len d) {
+ (c, n, ok) := sys->byte2char(d, i);
+ if (ok && n > 0) {
+ if (c == '\\')
+ stdout.putc('\\');
+ stdout.putc(c);
+ } else {
+ if (n == 0) {
+ hold[0:] = d[i:];
+ return len d - i;
+ } else {
+ putbyte(d[i]);
+ n = 1;
+ }
+ }
+ i += n;
+ }
+ return 0;
+}
+
+flushbytes(hold: array of byte)
+{
+ for (i := 0; i < len hold; i++)
+ putbyte(hold[i]);
+}
+
+putbyte(b: byte)
+{
+ stdout.puts(sys->sprint("\\%2.2X", int b));
+}
+
+isbschar(c: int): int
+{
+ case c {
+ 'n' or 'r' or 't' or 'v' =>
+ return 1;
+ }
+ return 0;
+}
+
+s2bytes(s: string): array of byte
+{
+ d := array[len s + 2] of byte;
+ j := 0;
+ for (i := 0; i < len s; i++) {
+ if (s[i] == '\\') {
+ if (i >= len s - 1 || (!isbschar(s[i+1]) && i >= len s - 2)) {
+ sys->fprint(stderr, "bytes: invalid backslash sequence\n");
+ raise "fail:bad args";
+ }
+ d = assure(d, j + 1);
+ if (isbschar(s[i+1])) {
+ case s[i+1] {
+ 'n' => d[j++] = byte '\n';
+ 'r' => d[j++] = byte '\r';
+ 't' => d[j++] = byte '\t';
+ 'v' => d[j++] = byte '\v';
+ '\\' => d[j++] = byte '\\';
+ * =>
+ sys->fprint(stderr, "bytes: invalid backslash sequence\n");
+ raise "fail:bad args";
+ }
+ i++;
+ } else if (!ishex(s[i+1]) || !ishex(s[i+2])) {
+ sys->fprint(stderr, "bytes: invalid backslash sequence\n");
+ raise "fail:bad args";
+ } else {
+ d[j++] = byte ((hex(s[i+1]) << 4) + hex(s[i+2]));
+ i += 2;
+ }
+ } else {
+ d = assure(d, j + 3);
+ j += sys->char2byte(s[i], d, j);
+ }
+ }
+ return d[0:j];
+}
+
+assure(d: array of byte, n: int): array of byte
+{
+ if (len d >= n)
+ return d;
+ nd := array[n] of byte;
+ nd[0:] = d;
+ return nd;
+}
+
+ishex(c: int): int
+{
+ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+}
+
+hex(c: int): int
+{
+ case c {
+ '0' to '9' =>
+ return c - '0';
+ 'a' to 'f' =>
+ return c - 'a' + 10;
+ 'A' to 'F' =>
+ return c- 'A' + 10;
+ }
+ return 0;
+}
diff --git a/appl/cmd/cal.b b/appl/cmd/cal.b
new file mode 100644
index 00000000..90c4f777
--- /dev/null
+++ b/appl/cmd/cal.b
@@ -0,0 +1,295 @@
+implement Cal;
+
+#
+# Copyright © 1995-2002 Lucent Technologies Inc. All rights reserved.
+# Limbo transliteration 2003 by Vita Nuova
+# This software is subject to the Plan 9 Open Source Licence.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ daytime: Daytime;
+ Tm: import daytime;
+
+Cal: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+dayw := " S M Tu W Th F S";
+smon := array[] of {
+ "January", "February", "March", "April",
+ "May", "June", "July", "August",
+ "September", "October", "November", "December",
+};
+
+mon := array[] of {
+ 0,
+ 31, 29, 31, 30,
+ 31, 30, 31, 31,
+ 30, 31, 30, 31,
+};
+
+bout: ref Iobuf;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ y, m: int;
+
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+
+ argc := len args;
+ if(argc > 3){
+ sys->fprint(sys->fildes(2), "usage: cal [month] [year]\n");
+ raise "fail:usage";
+ }
+ bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+
+#
+# no arg, print current month
+#
+ if(argc <= 1) {
+ m = curmo();
+ y = curyr();
+ return xshort(m, y);
+ }
+ args = tl args;
+
+#
+# one arg
+# if looks like a month, print month
+# else print year
+#
+ if(argc == 2) {
+ y = number(hd args);
+ if(y < 0)
+ y = -y;
+ if(y >= 1 && y <= 12)
+ return xshort(y, curyr());
+ return xlong(y);
+ }
+
+#
+# two arg, month and year
+#
+ m = number(hd args);
+ if(m < 0)
+ m = -m;
+ y = number(hd tl args);
+ return xshort(m, y);
+}
+
+#
+# print out just month
+#
+xshort(m: int, y: int)
+{
+ if(m < 1 || m > 12)
+ badarg();
+ if(y < 1 || y > 9999)
+ badarg();
+ bout.puts(sys->sprint(" %s %ud\n", smon[m-1], y));
+ bout.puts(sys->sprint("%s\n", dayw));
+ lines := cal(m, y);
+ for(i := 0; i < len lines; i++){
+ bout.puts(lines[i]);
+ bout.putc('\n');
+ }
+ bout.flush();
+}
+
+#
+# print out complete year
+#
+xlong(y: int)
+{
+ if(y<1 || y>9999)
+ badarg();
+ bout.puts("\n\n\n");
+ bout.puts(sys->sprint(" %ud\n", y));
+ bout.putc('\n');
+ months := array[3] of array of string;
+ for(i:=0; i<12; i+=3) {
+ bout.puts(sys->sprint(" %.3s", smon[i]));
+ bout.puts(sys->sprint(" %.3s", smon[i+1]));
+ bout.puts(sys->sprint(" %.3s\n", smon[i+2]));
+ bout.puts(sys->sprint("%s %s %s\n", dayw, dayw, dayw));
+ for(j := 0; j < 3; j++)
+ months[j] = cal(i+j+1, y);
+ for(l := 0; l < 6; l++){
+ s := "";
+ for(j = 0; j < 3; j++)
+ s += sys->sprint("%-20.20s ", months[j][l]);
+ for(j = len s; j > 0 && s[j-1] == ' ';)
+ j--;
+ bout.puts(s[0:j]);
+ bout.putc('\n');
+ }
+ }
+ bout.flush();
+}
+
+badarg()
+{
+ sys->fprint(sys->fildes(2), "cal: bad argument\n");
+ raise "fail:bad argument";
+}
+
+dict := array[] of {
+ ("january", 1),
+ ("february", 2),
+ ("march", 3),
+ ("april", 4),
+ ("may", 5),
+ ("june", 6),
+ ("july", 7),
+ ("august", 8),
+ ("sept", 9),
+ ("september", 9),
+ ("october", 10),
+ ("november", 11),
+ ("december", 12),
+};
+
+#
+# convert to a number.
+# if its a dictionary word,
+# return negative number
+#
+number(s: string): int
+{
+ if(len s >= 3){
+ for(n:=0; n < len dict; n++){
+ (word, val) := dict[n];
+ if(s == word || s == word[0:3])
+ return -val;
+ }
+ }
+ n := 0;
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ if(c<'0' || c>'9')
+ badarg();
+ n = n*10 + c-'0';
+ }
+ return n;
+}
+
+pstr(str: string, n: int)
+{
+ bout.puts(sys->sprint("%-*.*s\n", n, n, str));
+}
+
+cal(m: int, y: int): array of string
+{
+ d := jan1(y);
+ mon[9] = 30;
+
+ case (jan1(y+1)+7-d)%7 {
+
+ #
+ # non-leap year
+ #
+ 1 =>
+ mon[2] = 28;
+
+ #
+ # leap year
+ #
+ 2 =>
+ mon[2] = 29;
+
+ #
+ # 1752
+ #
+ * =>
+ mon[2] = 29;
+ mon[9] = 19;
+ }
+ for(i:=1; i<m; i++)
+ d += mon[i];
+ d %= 7;
+ lines := array[6] of string;
+ l := 0;
+ s := "";
+ for(i = 0; i < d; i++)
+ s += " ";
+ for(i=1; i<=mon[m]; i++) {
+ if(i==3 && mon[m]==19) {
+ i += 11;
+ mon[m] += 11;
+ }
+ s += sys->sprint("%2d", i);
+ if(++d == 7) {
+ d = 0;
+ lines[l++] = s;
+ s = "";
+ }else
+ s[len s] = ' ';
+ }
+ if(s != nil){
+ while(s[len s-1] == ' ')
+ s = s[:len s-1];
+ lines[l] = s;
+ }
+ return lines;
+}
+
+#
+# return day of the week
+# of jan 1 of given year
+#
+jan1(y: int): int
+{
+#
+# normal gregorian calendar
+# one extra day per four years
+#
+
+ d := 4+y+(y+3)/4;
+
+#
+# julian calendar
+# regular gregorian
+# less three days per 400
+#
+
+ if(y > 1800) {
+ d -= (y-1701)/100;
+ d += (y-1601)/400;
+ }
+
+#
+# great calendar changeover instant
+#
+
+ if(y > 1752)
+ d += 3;
+
+ return d%7;
+}
+
+#
+# get current month and year
+#
+curmo(): int
+{
+ tm := daytime->local(daytime->now());
+ return tm.mon+1;
+}
+
+curyr(): int
+{
+ tm := daytime->local(daytime->now());
+ return tm.year+1900;
+}
diff --git a/appl/cmd/cat.b b/appl/cmd/cat.b
new file mode 100644
index 00000000..24d62372
--- /dev/null
+++ b/appl/cmd/cat.b
@@ -0,0 +1,57 @@
+implement Cat;
+
+include "sys.m";
+include "draw.m";
+
+Cat: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+sys: Sys;
+stdout: ref Sys->FD;
+
+init(nil: ref Draw->Context, argl: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ stdout = sys->fildes(1);
+
+ argl = tl argl;
+ if(argl == nil)
+ argl = "-" :: nil;
+ while(argl != nil) {
+ cat(hd argl);
+ argl = tl argl;
+ }
+}
+
+cat(file: string)
+{
+ n: int;
+ fd: ref Sys->FD;
+ buf := array[8192] of byte;
+
+ if(file == "-")
+ fd = sys->fildes(0);
+ else {
+ fd = sys->open(file, sys->OREAD);
+ if(fd == nil) {
+ sys->fprint(sys->fildes(2), "cat: cannot open %s: %r\n", file);
+ raise "fail:bad open";
+ }
+ }
+ for(;;) {
+ n = sys->read(fd, buf, len buf);
+ if(n <= 0)
+ break;
+ if(sys->write(stdout, buf, n) < n) {
+ sys->fprint(sys->fildes(2), "cat: write error: %r\n");
+ raise "fail:write error";
+ }
+ }
+ if(n < 0) {
+ sys->fprint(sys->fildes(2), "cat: read error: %r\n");
+ raise "fail:read error";
+ }
+}
diff --git a/appl/cmd/cd.b b/appl/cmd/cd.b
new file mode 100644
index 00000000..57c94aba
--- /dev/null
+++ b/appl/cmd/cd.b
@@ -0,0 +1,48 @@
+implement Cd;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+Cd: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ stderr = sys->fildes(2);
+
+ argv = tl argv;
+ if(argv == nil)
+ argv = "/usr/"+user() :: nil;
+
+ if(tl argv != nil) {
+ sys->fprint(stderr, "Usage: cd [directory]\n");
+ raise "fail:usage";
+ }
+
+ if(sys->chdir(hd argv) < 0) {
+ sys->fprint(stderr, "cd: %s: %r\n", hd argv);
+ raise "fail:failed";
+ }
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil)
+ return "inferno";
+
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ return "inferno";
+
+ return string buf[0:n];
+}
diff --git a/appl/cmd/chgrp.b b/appl/cmd/chgrp.b
new file mode 100644
index 00000000..ec473759
--- /dev/null
+++ b/appl/cmd/chgrp.b
@@ -0,0 +1,58 @@
+implement Chgrp;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+Chgrp: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: chgrp [-uo] group file ...\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil){
+ sys->fprint(sys->fildes(2), "chgrp: can't load %s: %r\n", Arg->PATH);
+ raise "fail:load";
+ }
+ setuser := 0;
+ arg->init(args);
+ while((o := arg->opt()) != 0)
+ case o {
+ 'o' or 'u' =>
+ setuser = 1;
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+ if(args == nil)
+ usage();
+ id := hd args;
+ err := 0;
+ while((args = tl args) != nil){
+ d := sys->nulldir;
+ if(setuser)
+ d.uid = id;
+ else
+ d.gid = id;
+ if(sys->wstat(hd args, d) < 0){
+ sys->fprint(sys->fildes(2), "chgrp: can't change %s: %r\n", hd args);
+ err = 1;
+ }
+ }
+ if(err)
+ raise "fail:error";
+}
diff --git a/appl/cmd/chmod.b b/appl/cmd/chmod.b
new file mode 100644
index 00000000..de7ecf2c
--- /dev/null
+++ b/appl/cmd/chmod.b
@@ -0,0 +1,125 @@
+implement Chmod;
+
+include "sys.m";
+include "draw.m";
+include "string.m";
+
+Chmod: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+sys: Sys;
+stderr: ref Sys->FD;
+
+str: String;
+
+User: con 8r700;
+Group: con 8r070;
+Other: con 8r007;
+All: con User | Group | Other;
+
+Read: con 8r444;
+Write: con 8r222;
+Exec: con 8r111;
+
+usage()
+{
+ sys->fprint(stderr, "usage: chmod [8r]777 file ... or chmod [augo][+-=][rwxal] file ...\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ str = load String String->PATH;
+ if(str == nil){
+ sys->fprint(stderr, "chmod: cannot load %s: %r\n", String->PATH);
+ raise "fail:bad module";
+ }
+
+ if(len argv < 3)
+ usage();
+ argv = tl argv;
+ m := hd argv;
+ argv = tl argv;
+
+ mask := All;
+ if (str->prefix("8r", m))
+ m = m[2:];
+ (mode, s) := str->toint(m, 8);
+ if(s != "" || m == ""){
+ ok := 0;
+ (ok, mask, mode) = parsemode(m);
+ if(!ok){
+ sys->fprint(stderr, "chmod: bad mode '%s'\n", m);
+ usage();
+ }
+ }
+ ndir := sys->nulldir;
+ for(; argv != nil; argv = tl argv){
+ f := hd argv;
+ (ok, dir) := sys->stat(f);
+ if(ok < 0){
+ sys->fprint(stderr, "chmod: cannot stat %s: %r\n", f);
+ continue;
+ }
+ ndir.mode = (dir.mode & ~mask) | (mode & mask);
+ if(sys->wstat(f, ndir) < 0)
+ sys->fprint(stderr, "chmod: cannot wstat %s: %r\n", f);
+ }
+}
+
+parsemode(spec: string): (int, int, int)
+{
+ mask := Sys->DMAPPEND | Sys->DMEXCL | Sys->DMTMP;
+loop: for(i := 0; i < len spec; i++){
+ case spec[i] {
+ 'u' =>
+ mask |= User;
+ 'g' =>
+ mask |= Group;
+ 'o' =>
+ mask |= Other;
+ 'a' =>
+ mask |= All;
+ * =>
+ break loop;
+ }
+ }
+ if(i == len spec)
+ return (0, 0, 0);
+ if(i == 0)
+ mask |= All;
+
+ op := spec[i++];
+ if(op != '+' && op != '-' && op != '=')
+ return (0, 0, 0);
+
+ mode := 0;
+ for(; i < len spec; i++){
+ case spec[i]{
+ 'r' =>
+ mode |= Read;
+ 'w' =>
+ mode |= Write;
+ 'x' =>
+ mode |= Exec;
+ 'a' =>
+ mode |= Sys->DMAPPEND;
+ 'l' =>
+ mode |= Sys->DMEXCL;
+ 't' =>
+ mode |= Sys->DMTMP;
+ * =>
+ return (0, 0, 0);
+ }
+ }
+ if(op == '+' || op == '-')
+ mask &= mode;
+ if(op == '-')
+ mode = ~mode;
+ return (1, mask, mode);
+}
diff --git a/appl/cmd/cleanname.b b/appl/cmd/cleanname.b
new file mode 100644
index 00000000..0883e600
--- /dev/null
+++ b/appl/cmd/cleanname.b
@@ -0,0 +1,45 @@
+implement Cleanname;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "names.m";
+ names: Names;
+
+include "arg.m";
+
+Cleanname: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ names = load Names Names->PATH;
+ arg := load Arg Arg->PATH;
+
+ dir: string;
+ arg->init(args);
+ arg->setusage("cleanname [-d pwd] name ...");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' =>
+ dir = arg->earg();
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ for(; args != nil; args = tl args){
+ n := hd args;
+ if(dir != nil && n != nil && n[0] != '/' && n[0] != '#')
+ n = dir+"/"+n;
+ sys->print("%s\n", names->cleanname(n)); # %q?
+ }
+}
diff --git a/appl/cmd/cmp.b b/appl/cmd/cmp.b
new file mode 100644
index 00000000..ce631b93
--- /dev/null
+++ b/appl/cmd/cmp.b
@@ -0,0 +1,151 @@
+implement Cmp;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "arg.m";
+
+BUF: con 65536;
+stderr: ref Sys->FD;
+
+Cmp: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ lflag := Lflag := sflag := 0;
+ buf1 := array[BUF] of byte;
+ buf2 := array[BUF] of byte;
+
+ stderr = sys->fildes(2);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil){
+ sys->fprint(stderr, "cmp: cannot load %s: %r\n", Arg->PATH);
+ raise "fail:load";
+ }
+ arg->init(args);
+ while((op := arg->opt()) != 0)
+ case op {
+ 'l' => lflag = 1;
+ 'L' => Lflag = 1;
+ 's' => sflag = 1;
+ * => usage();
+ }
+ args = arg->argv();
+ arg = nil;
+ if(args == nil)
+ usage();
+
+ if(len args < 2)
+ usage();
+ name1 := hd args;
+ args = tl args;
+
+ if((f1 := sys->open(name1, Sys->OREAD)) == nil){
+ sys->fprint(stderr, "cmp: can't open %s: %r\n",name1);
+ raise "fail:open";
+ }
+ name2 := hd args;
+ args = tl args;
+
+ if((f2 := sys->open(name2, Sys->OREAD)) == nil){
+ sys->fprint(stderr, "cmp: can't open %s: %r\n",name2);
+ raise "fail:open";
+ }
+
+ if(args != nil){
+ o := big hd args;
+ if(sys->seek(f1, o, 0) < big 0){
+ sys->fprint(stderr, "cmp: seek by offset1 failed: %r\n");
+ raise "fail:seek 1";
+ }
+ args = tl args;
+ }
+
+ if(args != nil){
+ o := big hd args;
+ if(sys->seek(f2, o, 0) < big 0){
+ sys->fprint(stderr, "cmp: seek by offset2 failed: %r");
+ raise "fail:seek 2";
+ }
+ args = tl args;
+ }
+ if(args != nil)
+ usage();
+ nc := big 1;
+ l := big 1;
+ diff := 0;
+ b1, b2: array of byte;
+ for(;;){
+ if(len b1 == 0){
+ nr := sys->read(f1, buf1, BUF);
+ if(nr < 0){
+ if(!sflag)
+ sys->print("error on %s after %bd bytes\n", name1, nc-big 1);
+ raise "fail:read error";
+ }
+ b1 = buf1[0: nr];
+ }
+ if(len b2 == 0){
+ nr := sys->read(f2, buf2, BUF);
+ if(nr < 0){
+ if(!sflag)
+ sys->print("error on %s after %bd bytes\n", name2, nc-big 1);
+ raise "fail:read error";
+ }
+ b2 = buf2[0: nr];
+ }
+ n := len b2;
+ if(n > len b1)
+ n = len b1;
+ if(n == 0)
+ break;
+ for(i:=0; i<n; i++){
+ if(Lflag && b1[i]== byte '\n')
+ l++;
+ if(b1[i] != b2[i]){
+ if(!lflag){
+ if(!sflag){
+ sys->print("%s %s differ: char %bd", name1, name2, nc+big i);
+ if(Lflag)
+ sys->print(" line %bd\n", l);
+ else
+ sys->print("\n");
+ }
+ raise "fail:differ";
+ }
+ sys->print("%6bd 0x%.2x 0x%.2x\n", nc+big i, int b1[i], int b2[i]);
+ diff = 1;
+ }
+ }
+ nc += big n;
+ b1 = b1[n:];
+ b2 = b2[n:];
+ }
+ if(len b1 != len b2) {
+ nc--;
+ if(len b1 > len b2)
+ sys->print("EOF on %s after %bd bytes\n", name2, nc);
+ else
+ sys->print("EOF on %s after %bd bytes\n", name1, nc);
+ raise "fail:EOF";
+ }
+ if(diff)
+ raise "fail:differ";
+ exit;
+}
+
+
+usage()
+{
+ sys->fprint(stderr, "Usage: cmp [-lsL] file1 file2 [offset1 [offset2] ]\n");
+ raise "fail:usage";
+}
diff --git a/appl/cmd/comm.b b/appl/cmd/comm.b
new file mode 100755
index 00000000..1e56310e
--- /dev/null
+++ b/appl/cmd/comm.b
@@ -0,0 +1,124 @@
+implement Comm;
+
+# Copyright © 2002 Lucent Technologies Inc.
+# Subject to the Lucent Public Licence 1.02
+# Limbo translation by Vita Nuova 2004; bug fixed.
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+
+Comm: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+One, Two, Three: con 1<<iota;
+cols := One|Two|Three;
+ldr := array[3] of {"", "\t", "\t\t"};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("comm [-123] file1 file2");
+ while((c := arg->opt()) != 0){
+ case c {
+ '1' to '3' =>
+ cols &= ~(1 << (c-'1'));
+ * =>
+ arg->usage();
+ }
+ }
+ args = arg->argv();
+ if(len args != 2)
+ arg->usage();
+ arg = nil;
+
+ if((cols & One) == 0){
+ ldr[1] = "";
+ ldr[2] = ldr[2][1:];
+ }
+ if((cols & Two) == 0)
+ ldr[2] = ldr[2][1:];
+
+ ib1 := openfil(hd args);
+ ib2 := openfil(hd tl args);
+ if((lb1 := ib1.gets('\n')) == nil){
+ if((lb2 := ib2.gets('\n')) == nil)
+ exit;
+ copy(ib2, lb2, 2);
+ }
+ if((lb2 := ib2.gets('\n')) == nil)
+ copy(ib1, lb1, 1);
+ for(;;)
+ case compare(lb1, lb2) {
+ 0 =>
+ wr(lb1, 3);
+ if((lb1 = ib1.gets('\n')) == nil){
+ if((lb2 = ib2.gets('\n')) == nil)
+ exit;
+ copy(ib2, lb2, 2);
+ }
+ if((lb2 = ib2.gets('\n')) == nil)
+ copy(ib1, lb1, 1);
+ 1 =>
+ wr(lb1, 1);
+ if((lb1 = ib1.gets('\n')) == nil)
+ copy(ib2, lb2, 2);
+ 2 =>
+ wr(lb2, 2);
+ if((lb2 = ib2.gets('\n')) == nil)
+ copy(ib1, lb1, 1);
+ }
+}
+
+wr(str: string, n: int)
+{
+ if(cols & (1<<(n-1)))
+ sys->print("%s%s", ldr[n-1], str);
+}
+
+copy(ibuf: ref Iobuf, lbuf: string, n: int)
+{
+ do
+ wr(lbuf, n);
+ while((lbuf = ibuf.gets('\n')) != nil);
+ exit;
+}
+
+compare(a: string, b: string): int
+{
+ for(i := 0; i < len a; i++){
+ if(i >= len b || a[i] < b[i])
+ return 1;
+ if(a[i] != b[i])
+ return 2;
+ }
+ if(i == len b)
+ return 0;
+ return 2;
+}
+
+openfil(s: string): ref Iobuf
+{
+ if(s == "-")
+ b := bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ else
+ b = bufio->open(s, Bufio->OREAD);
+ if(b != nil)
+ return b;
+ sys->fprint(sys->fildes(2), "comm: cannot open %s: %r\n", s);
+ raise "fail:open";
+}
+
diff --git a/appl/cmd/cook.b b/appl/cmd/cook.b
new file mode 100644
index 00000000..0d333a4d
--- /dev/null
+++ b/appl/cmd/cook.b
@@ -0,0 +1,1924 @@
+implement Cook;
+
+include "sys.m";
+ sys: Sys;
+ FD: import Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "bufio.m";
+ B: Bufio;
+ Iobuf: import B;
+
+include "string.m";
+ S: String;
+ splitl, splitr, splitstrl, drop, take, in, prefix, tolower : import S;
+
+include "brutus.m";
+ Size6, Size8, Size10, Size12, Size16, NSIZE,
+ Roman, Italic, Bold, Type, NFONT, NFONTTAG,
+ Example, Caption, List, Listelem, Label, Labelref,
+ Exercise, Heading, Nofill, Author, Title,
+ Index, Indextopic,
+ DefFont, DefSize, TitleFont, TitleSize, HeadingFont, HeadingSize: import Brutus;
+
+# following are needed for types in brutusext.m
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+
+include "brutusext.m";
+ SGML, Text, Par, Extension, Float, Special, Celem,
+ FLatex, FLatexProc, FLatexBook, FLatexPart, FLatexSlides, FHtml: import Brutusext;
+
+include "strinttab.m";
+ T: StringIntTab;
+
+Cook: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+# keep this sorted by name
+tagstringtab := array[] of { T->StringInt
+ ("Author", Author),
+ ("Bold.10", Bold*NSIZE + Size10),
+ ("Bold.12", Bold*NSIZE + Size12),
+ ("Bold.16", Bold*NSIZE + Size16),
+ ("Bold.6", Bold*NSIZE + Size6),
+ ("Bold.8", Bold*NSIZE + Size8),
+ ("Caption", Caption),
+ ("Example", Example),
+ ("Exercise", Exercise),
+ ("Extension", Extension),
+ ("Float", Float),
+ ("Heading", Heading),
+ ("Index", Index),
+ ("Index-topic", Indextopic),
+ ("Italic.10", Italic*NSIZE + Size10),
+ ("Italic.12", Italic*NSIZE + Size12),
+ ("Italic.16", Italic*NSIZE + Size16),
+ ("Italic.6", Italic*NSIZE + Size6),
+ ("Italic.8", Italic*NSIZE + Size8),
+ ("Label", Label),
+ ("Label-ref", Labelref),
+ ("List", List),
+ ("List-elem", Listelem),
+ ("No-fill", Nofill),
+ ("Par", Par),
+ ("Roman.10", Roman*NSIZE + Size10),
+ ("Roman.12", Roman*NSIZE + Size12),
+ ("Roman.16", Roman*NSIZE + Size16),
+ ("Roman.6", Roman*NSIZE + Size6),
+ ("Roman.8", Roman*NSIZE + Size8),
+ ("SGML", SGML),
+ ("Title", Title),
+ ("Type.10", Type*NSIZE + Size10),
+ ("Type.12", Type*NSIZE + Size12),
+ ("Type.16", Type*NSIZE + Size16),
+ ("Type.6", Type*NSIZE + Size6),
+ ("Type.8", Type*NSIZE + Size8),
+};
+
+# This table must be sorted
+fmtstringtab := array[] of { T->StringInt
+ ("html", FHtml),
+ ("latex", FLatex),
+ ("latexbook", FLatexBook),
+ ("latexpart", FLatexPart),
+ ("latexproc", FLatexProc),
+ ("latexslides", FLatexSlides),
+};
+
+Transtab: adt
+{
+ ch: int;
+ trans: string;
+};
+
+# Order doesn't matter for these table
+
+ltranstab := array[] of { Transtab
+ ('$', "\\textdollar{}"),
+ ('&', "\\&"),
+ ('%', "\\%"),
+ ('#', "\\#"),
+ ('_', "\\textunderscore{}"),
+ ('{', "\\{"),
+ ('}', "\\}"),
+ ('~', "\\textasciitilde{}"),
+ ('^', "\\textasciicircum{}"),
+ ('\\', "\\textbackslash{}"),
+ ('+', "\\textplus{}"),
+ ('=', "\\textequals{}"),
+ ('|', "\\textbar{}"),
+ ('<', "\\textless{}"),
+ ('>', "\\textgreater{}"),
+ (' ', "~"),
+ ('-', "-"), # needs special case ligature treatment
+ ('\t', " "), # needs special case treatment
+};
+
+htranstab := array[] of { Transtab
+ ('α', "&alpha;"),
+ ('Æ', "&AElig;"),
+ ('Á', "&Aacute;"),
+ ('Â', "&Acirc;"),
+ ('À', "&Agrave;"),
+ ('Å', "&Aring;"),
+ ('Ã', "&Atilde;"),
+ ('Ä', "&Auml;"),
+ ('Ç', "&Ccedil;"),
+ ('Ð', "&ETH;"),
+ ('É', "&Eacute;"),
+ ('Ê', "&Ecirc;"),
+ ('È', "&Egrave;"),
+ ('Ë', "&Euml;"),
+ ('Í', "&Iacute;"),
+ ('Î', "&Icirc;"),
+ ('Ì', "&Igrave;"),
+ ('Ï', "&Iuml;"),
+ ('Ñ', "&Ntilde;"),
+ ('Ó', "&Oacute;"),
+ ('Ô', "&Ocirc;"),
+ ('Ò', "&Ograve;"),
+ ('Ø', "&Oslash;"),
+ ('Õ', "&Otilde;"),
+ ('Ö', "&Ouml;"),
+ ('Þ', "&THORN;"),
+ ('Ú', "&Uacute;"),
+ ('Û', "&Ucirc;"),
+ ('Ù', "&Ugrave;"),
+ ('Ü', "&Uuml;"),
+ ('Ý', "&Yacute;"),
+ ('æ', "&aElig;"),
+ ('á', "&aacute;"),
+ ('â', "&acirc;"),
+ ('à', "&agrave;"),
+ ('α', "&alpha;"),
+ ('&', "&amp;"),
+ ('å', "&aring;"),
+ ('ã', "&atilde;"),
+ ('ä', "&auml;"),
+ ('β', "&beta;"),
+ ('ç', "&ccedil;"),
+ ('⋯', "&cdots;"),
+ ('χ', "&chi;"),
+ ('©', "&copy;"),
+ ('⋱', "&ddots;"),
+ ('δ', "&delta;"),
+ ('é', "&eacute;"),
+ ('ê', "&ecirc;"),
+ ('è', "&egrave;"),
+ ('—', "&emdash;"),
+ (' ', "&emsp;"),
+ ('–', "&endash;"),
+ ('ε', "&epsilon;"),
+ ('η', "&eta;"),
+ ('ð', "&eth;"),
+ ('ë', "&euml;"),
+ ('γ', "&gamma;"),
+ ('>', "&gt;"),
+ ('í', "&iacute;"),
+ ('î', "&icirc;"),
+ ('ì', "&igrave;"),
+ ('ι', "&iota;"),
+ ('ï', "&iuml;"),
+ ('κ', "&kappa;"),
+ ('λ', "&lambda;"),
+ ('…', "&ldots;"),
+ ('<', "&lt;"),
+ ('μ', "&mu;"),
+ (' ', "&nbsp;"),
+ ('ñ', "&ntilde;"),
+ ('ν', "&nu;"),
+ ('ó', "&oacute;"),
+ ('ô', "&ocirc;"),
+ ('ò', "&ograve;"),
+ ('ω', "&omega;"),
+ ('ο', "&omicron;"),
+ ('ø', "&oslash;"),
+ ('õ', "&otilde;"),
+ ('ö', "&ouml;"),
+ ('φ', "&phi;"),
+ ('π', "&pi;"),
+ ('ψ', "&psi;"),
+ (' ', "&quad;"),
+ ('"', "&quot;"),
+ ('®', "&reg;"),
+ ('ρ', "&rho;"),
+ ('­', "&shy;"),
+ ('σ', "&sigma;"),
+ ('ß', "&szlig;"),
+ ('τ', "&tau;"),
+ ('θ', "&theta;"),
+ (' ', "&thinsp;"),
+ ('þ', "&thorn;"),
+ ('™', "&trade;"),
+ ('ú', "&uacute;"),
+ ('û', "&ucirc;"),
+ ('ù', "&ugrave;"),
+ ('υ', "&upsilon;"),
+ ('ü', "&uuml;"),
+ ('∈', "&varepsilon;"),
+ ('ϕ', "&varphi;"),
+ ('ϖ', "&varpi;"),
+ ('ϱ', "&varrho;"),
+ ('⋮', "&vdots;"),
+ ('ς', "&vsigma;"),
+ ('ϑ', "&vtheta;"),
+ ('ξ', "&xi;"),
+ ('ý', "&yacute;"),
+ ('ÿ', "&yuml;"),
+ ('ζ', "&zeta;"),
+ ('−', "-"),
+};
+
+# For speedy lookups of ascii char translation, use asciitrans.
+# It should be initialized by ascii elements from one of above tables
+asciitrans := array[128] of string;
+
+stderr: ref FD;
+infilename := "";
+outfilename := "";
+linenum := 0;
+fin : ref Iobuf = nil;
+fout : ref Iobuf = nil;
+debug := 0;
+fmt := FLatex;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ S = load String String->PATH;
+ B = load Bufio Bufio->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ T = load StringIntTab StringIntTab->PATH;
+ stderr = sys->fildes(2);
+
+ for(argv = tl argv; argv != nil; ) {
+ s := hd argv;
+ tlargv := tl argv;
+ case s {
+ "-f" =>
+ if(tlargv == nil)
+ usage();
+ fnd: int;
+ (fnd, fmt) = T->lookup(fmtstringtab, hd(tlargv));
+ if(!fnd) {
+ sys->fprint(stderr, "unknown format: %s\n", hd(tlargv));
+ exit;
+ }
+ argv = tlargv;
+ "-o" =>
+ if(tlargv == nil)
+ usage();
+ outfilename = hd(tlargv);
+ argv = tlargv;
+ "-d" =>
+ debug = 1;
+ "-dd" =>
+ debug = 2;
+ * =>
+ if(tlargv == nil)
+ infilename = s;
+ else
+ usage();
+ }
+ argv = tl argv;
+ }
+ if(infilename == "") {
+ fin = B->fopen(sys->fildes(0), sys->OREAD);
+ infilename = "<stdin>";
+ }
+ else
+ fin = B->open(infilename, sys->OREAD);
+ if(fin == nil) {
+ sys->fprint(stderr, "cook: error opening %s: %r\n", infilename);
+ exit;
+ }
+ if(outfilename == "") {
+ fout = B->fopen(sys->fildes(1), sys->OWRITE);
+ outfilename = "<stdout>";
+ }
+ else
+ fout = B->create(outfilename, sys->OWRITE, 8r664);
+ if(fout == nil) {
+ sys->fprint(stderr, "cook: error creating %s: %r\n", outfilename);
+ exit;
+ }
+ line0 := fin.gets('\n');
+ if(line0 != "<SGML>\n") {
+ parse_err("not an SGML file\n");
+ exit;
+ }
+ linenum = 1;
+ e := parse(SGML);
+ findpars(e, 1, nil);
+ e = delemptystrs(e);
+ (e, nil) = canonfonts(e, DefFont*NSIZE+DefSize, DefFont*NSIZE+DefSize);
+ mergeadjs(e);
+ findfloats(e);
+ cleanexts(e);
+ cleanpars(e);
+ if(debug) {
+ fout.puts("After Initial transformations:\n");
+ printelem(e, "", 1);
+ fout.flush();
+ }
+ case fmt {
+ FLatex or FLatexProc or FLatexBook or FLatexPart or FLatexSlides =>
+ latexconv(e);
+ FHtml =>
+ htmlconv(e);
+ }
+ fin.close();
+ fout.close();
+}
+
+usage()
+{
+ sys->fprint(stderr, "Usage: cook [-f (latex|html)] [-o outfile] [infile]\n");
+ exit;
+}
+
+parse_err(msg: string)
+{
+ sys->fprint(stderr, "%s:%d: %s\n", infilename, linenum, msg);
+}
+
+# Parse into elements.
+# Assumes tags are balanced.
+# String elements are split so that there is never an internal newline.
+parse(id: int) : ref Celem
+{
+ els : ref Celem = nil;
+ elstail : ref Celem = nil;
+ for(;;) {
+ c := fin.getc();
+ if(c == Bufio->EOF) {
+ if(id == SGML)
+ break;
+ else {
+ parse_err(sys->sprint("EOF while parsing %s", tagname(id)));
+ return nil;
+ }
+ }
+ if(c == '<') {
+ tag := "";
+ start := 1;
+ i := 0;
+ for(;;) {
+ c = fin.getc();
+ if(c == Bufio->EOF) {
+ parse_err("EOF in middle of tag");
+ return nil;
+ }
+ if(c == '\n') {
+ linenum++;
+ parse_err("newline in middle of tag");
+ break;
+ }
+ if(c == '>')
+ break;
+ if(i == 0 && c == '/')
+ start = 0;
+ else
+ tag[i++] = c;
+ }
+ (fnd, tid) := T->lookup(tagstringtab, tag);
+ if(!fnd) {
+ if(prefix("Extension ", tag)) {
+ el := ref Celem(Extension, tag[10:], nil, nil, nil, nil);
+ if(els == nil) {
+ els = el;
+ elstail = el;
+ }
+ else {
+ el.prev = elstail;
+ elstail.next = el;
+ elstail = el;
+ }
+ }
+ else
+ parse_err(sys->sprint("unknown tag <%s>\n", tag));
+ continue;
+ }
+ if(start) {
+ el := parse(tid);
+ if(el == nil)
+ return nil;
+ if(els == nil) {
+ els = el;
+ elstail = el;
+ }
+ else {
+ el.prev = elstail;
+ elstail.next = el;
+ elstail = el;
+ }
+ }
+ else {
+ if(tid != id) {
+ parse_err(sys->sprint("<%s> ended by </%s>",
+ tagname(id), tag));
+ continue;
+ }
+ break;
+ }
+ }
+ else {
+ s := "";
+ i := 0;
+ for(;;) {
+ if(c == Bufio->EOF)
+ break;
+ if(c == '<') {
+ fin.ungetc();
+ break;
+ }
+ if(c == ';' && i >=3 && s[i-1] == 't' && s[i-2] == 'l' && s[i-3] == '&') {
+ i -= 2;
+ s[i-1] = '<';
+ s = s[0:i];
+ }
+ else
+ s[i++] = c;
+ if(c == '\n') {
+ linenum++;
+ break;
+ }
+ else
+ c = fin.getc();
+ }
+ if(s != "") {
+ el := ref Celem(Text, s, nil, nil, nil, nil);
+ if(els == nil) {
+ els = el;
+ elstail = el;
+ }
+ else {
+ el.prev = elstail;
+ elstail.next = el;
+ elstail = el;
+ }
+ }
+ }
+ }
+ ans := ref Celem(id, "", els, nil, nil, nil);
+ if(els != nil)
+ els.parent = ans;
+ return ans;
+}
+
+# Modify tree e so that blank lines become Par elements.
+# Only do it if parize is set, and unset parize when descending into TExample's.
+# Pass in most recent TString or TPar element, and return updated most-recent-TString/TPar.
+# This function may set some TString strings to ""
+findpars(e: ref Celem, parize: int, prevspe: ref Celem) : ref Celem
+{
+ while(e != nil) {
+ prevnl := 0;
+ prevpar := 0;
+ if(prevspe != nil) {
+ if(prevspe.tag == Text && len prevspe.s != 0
+ && prevspe.s[(len prevspe.s)-1] == '\n')
+ prevnl = 1;
+ else if(prevspe.tag == Par)
+ prevpar = 1;
+ }
+ if(e.tag == Text) {
+ if(parize && (prevnl || prevpar) && e.s[0] == '\n') {
+ if(prevnl)
+ prevspe.s = prevspe.s[0 : (len prevspe.s)-1];
+ e.tag = Par;
+ e.s = nil;
+ }
+ prevspe = e;
+ }
+ else {
+ nparize := parize;
+ if(e.tag == Example)
+ nparize = 0;
+ prevspe = findpars(e.contents, nparize, prevspe);
+ }
+ e = e.next;
+ }
+ return prevspe;
+}
+
+# Delete any empty strings from e's tree and return modified e.
+# Also, delete any entity that has empty contents, except the
+# Par ones
+delemptystrs(e: ref Celem) : ref Celem
+{
+ if(e.tag == Text) {
+ if(e.s == "")
+ return nil;
+ else
+ return e;
+ }
+ if(e.tag == Par || e.tag == Extension || e.tag == Special)
+ return e;
+ h := e.contents;
+ while(h != nil) {
+ hnext := h.next;
+ hh := delemptystrs(h);
+ if(hh == nil)
+ delete(h);
+ h = hnext;
+ }
+ if(e.contents == nil)
+ return nil;
+ return e;
+}
+
+# Change tree under e so that any font elems contain only strings
+# (by pushing the font changes down).
+# Answer an be a list, so return beginning and end of list.
+# Leave strings bare if font change would be to deffont,
+# and adjust deffont appropriately when entering Title and
+# Heading environments.
+canonfonts(e: ref Celem, curfont, deffont: int) : (ref Celem, ref Celem)
+{
+ f := curfont;
+ head : ref Celem = nil;
+ tail : ref Celem = nil;
+ tocombine : ref Celem = nil;
+ if(e.tag == Text) {
+ if(f == deffont) {
+ head = e;
+ tail = e;
+ }
+ else {
+ head = ref Celem(f, nil, e, nil, nil, nil);
+ e.parent = head;
+ tail = head;
+ }
+ }
+ else if(e.contents == nil) {
+ head = e;
+ tail = e;
+ }
+ else if(e.tag < NFONTTAG) {
+ f = e.tag;
+ allstrings := 1;
+ for(g := e.contents; g != nil; g = g.next) {
+ if(g.tag != Text)
+ allstrings = 0;
+ tail = g;
+ }
+ if(allstrings) {
+ if(f == deffont)
+ head = e.contents;
+ else {
+ head = e;
+ tail = e;
+ }
+ }
+ }
+ if(head == nil) {
+ if(e.tag == Title)
+ deffont = TitleFont*NSIZE+TitleSize;
+ else if(e.tag == Heading)
+ deffont = HeadingFont*NSIZE+HeadingSize;
+ for(h := e.contents; h != nil; ) {
+ prev := h.prev;
+ next := h.next;
+ excise(h);
+ (e1, en) := canonfonts(h, f, deffont);
+ splicebetween(e1, en, prev, next);
+ if(prev == nil)
+ head = e1;
+ tail = en;
+ h = next;
+ }
+ tocombine = head;
+ if(e.tag >= NFONTTAG) {
+ e.contents = head;
+ head.parent = e;
+ head = e;
+ tail = e;
+ }
+ }
+ if(tocombine != nil) {
+ # combine adjacent font changes to same font
+ r := tocombine;
+ while(r != nil) {
+ if(r.tag < NFONTTAG && r.next != nil && r.next.tag == r.tag) {
+ for(v := r.next; v != nil; v = v.next) {
+ if(v.tag != r.tag)
+ break;
+ if(v == tail)
+ tail = r;
+ }
+ # now r up to, not including v, all change to same font
+ for(p := r.next; p != v; p = p.next) {
+ append(r.contents, p.contents);
+ }
+ r.next = v;
+ if(v != nil)
+ v.prev = r;
+ r = v;
+ }
+ else
+ r = r.next;
+ }
+ }
+ head.parent = nil;
+ return (head, tail);
+}
+
+# Remove Pars that appear just before or just after Heading, Title, Examples, Extensions
+# Really should worry about this happening at different nesting levels, but in
+# practice this happens all at the same nesting level
+cleanpars(e: ref Celem)
+{
+ for(h := e.contents; h != nil; h = h.next) {
+ cleanpars(h);
+ if(h.tag == Title || h.tag == Heading || h.tag == Example || h.tag == Extension) {
+ hp := h.prev;
+ hn := h.next;
+ if(hp !=nil && hp.tag == Par)
+ delete(hp);
+ if(hn != nil && hn.tag == Par)
+ delete(hn);
+ }
+ }
+}
+
+# Remove a single tab if it appears before an Extension
+cleanexts(e: ref Celem)
+{
+ for(h := e.contents; h != nil; h = h.next) {
+ cleanexts(h);
+ if(h.tag == Extension) {
+ hp := h.prev;
+ if(hp != nil && stringof(hp) == "\t")
+ delete(hp);
+ }
+ }
+}
+
+mergeable := array[] of { List, Exercise, Caption,Index, Indextopic };
+
+# Merge some adjacent elements (which were probably created separate
+# because of font changes)
+mergeadjs(e: ref Celem)
+{
+ for(h := e.contents; h != nil; h = h.next) {
+ hn := h.next;
+ domerge := 0;
+ if(hn != nil) {
+ for(i := 0; i < len mergeable; i++) {
+ mi := mergeable[i];
+ if(h.tag == mi && hn.tag == mi)
+ domerge = 1;
+ }
+ }
+ if(domerge) {
+ append(h.contents, hn.contents);
+ delete(hn);
+ }
+ else
+ mergeadjs(h);
+ }
+}
+
+# Find floats: they are paragraphs with Captions at the end.
+findfloats(e: ref Celem)
+{
+ lastpar : ref Celem = nil;
+ for(h := e.contents; h != nil; h = h.next) {
+ if(h.tag == Par)
+ lastpar = h;
+ else if(h.tag == Caption) {
+ ne := ref Celem(Float, "", nil, nil, nil, nil);
+ if(lastpar == nil)
+ flhead := e.contents;
+ else
+ flhead = lastpar.next;
+ insertbefore(ne, flhead);
+ # now move flhead ... h into contents of ne
+ ne.contents = flhead;
+ flhead.parent = ne;
+ flhead.prev = nil;
+ ne.next = h.next;
+ if(ne.next != nil)
+ ne.next.prev = ne;
+ h.next = nil;
+ h = ne;
+ }
+ else
+ findfloats(h);
+ }
+}
+
+insertbefore(e, ebefore: ref Celem)
+{
+ e.prev = ebefore.prev;
+ if(e.prev == nil) {
+ e.parent = ebefore.parent;
+ ebefore.parent = nil;
+ e.parent.contents = e;
+ }
+ else
+ e.prev.next = e;
+ e.next = ebefore;
+ ebefore.prev = e;
+}
+
+insertafter(e, eafter: ref Celem)
+{
+ e.next = eafter.next;
+ if(e.next != nil)
+ e.next.prev = e;
+ e.prev = eafter;
+ eafter.next = e;
+}
+
+# remove e from its list, leaving siblings disconnected
+excise(e: ref Celem)
+{
+ next := e. next;
+ prev := e.prev;
+ e.next = nil;
+ e.prev = nil;
+ if(prev != nil)
+ prev.next = nil;
+ if(next != nil)
+ next.prev = nil;
+ e.parent = nil;
+}
+
+splicebetween(e1, en, prev, next: ref Celem)
+{
+ if(prev != nil)
+ prev.next = e1;
+ e1.prev = prev;
+ en.next = next;
+ if(next != nil)
+ next.prev = en;
+}
+
+append(e1, e2: ref Celem)
+{
+ e1last := last(e1);
+ e1last.next = e2;
+ e2.prev = e1last;
+ e2.parent = nil;
+}
+
+last(e: ref Celem) : ref Celem
+{
+ if(e != nil)
+ while(e.next != nil)
+ e = e.next;
+ return e;
+}
+
+succ(e: ref Celem) : ref Celem
+{
+ if(e == nil)
+ return nil;
+ if(e.next != nil)
+ return e.next;
+ return succ(e.parent);
+}
+
+delete(e: ref Celem)
+{
+ ep := e.prev;
+ en := e.next;
+ eu := e.parent;
+ if(ep == nil) {
+ if(eu != nil)
+ eu.contents = en;
+ if(en != nil)
+ en.parent = eu;
+ }
+ else
+ ep.next = en;
+ if(en != nil)
+ en.prev = ep;
+}
+
+# return string represented by e, peering through font changes
+stringof(e: ref Celem) : string
+{
+ if(e != nil) {
+ if(e.tag == Text)
+ return e.s;
+ if(e.tag < NFONTTAG)
+ return stringof(e.contents);
+ }
+ return "";
+}
+
+# remove any initial whitespace from e and its sucessors,
+dropwhite(e: ref Celem)
+{
+ if(e == nil)
+ return;
+ del := 0;
+ if(e.tag == Text) {
+ e.s = drop(e.s, " \t\n");
+ if(e.s == "")
+ del = 1;;
+ }
+ else if(e.tag < NFONTTAG) {
+ dropwhite(e.contents);
+ if(e.contents == nil)
+ del = 1;
+ }
+ if(del) {
+ enext := e.next;
+ delete(e);
+ dropwhite(enext);
+ }
+
+}
+
+firstchar(e: ref Celem) : int
+{
+ s := stringof(e);
+ if(len s >= 1)
+ return s[0];
+ return -1;
+}
+
+lastchar(e: ref Celem) : int
+{
+ if(e == nil)
+ return -1;
+ while(e.next != nil)
+ e = e.next;
+ s := stringof(e);
+ if(len s >= 1)
+ return s[len s -1];
+ return -1;
+}
+
+tlookup(t: array of Transtab, v: int) : string
+{
+ n := len t;
+ for(i := 0; i < n; i++)
+ if(t[i].ch == v)
+ return t[i].trans;
+ return "";
+}
+
+initasciitrans(t: array of Transtab)
+{
+ n := len t;
+ for(i := 0; i < n; i++) {
+ c := t[i].ch;
+ if(c < 128)
+ asciitrans[c] = t[i].trans;
+ }
+}
+
+tagname(id: int) : string
+{
+ name := T->revlookup(tagstringtab, id);
+ if(name == nil)
+ name = "_unknown_";
+ return name;
+}
+
+printelem(e: ref Celem, indent: string, recurse: int)
+{
+ fout.puts(indent);
+ if(debug > 1) {
+ fout.puts(sys->sprint("%x: ", e));
+ if(e != nil && e.parent != nil)
+ fout.puts(sys->sprint("(parent %x): ", e.parent));
+ }
+ if(e == nil)
+ fout.puts("NIL\n");
+ else if(e.tag == Text || e.tag == Special || e.tag == Extension) {
+ if(e.tag == Special)
+ fout.puts("S");
+ else if(e.tag == Extension)
+ fout.puts("E");
+ fout.puts("«");
+ fout.puts(e.s);
+ fout.puts("»\n");
+ }
+ else {
+ name := tagname(e.tag);
+ fout.puts("<" + name + ">\n");
+ if(recurse && e.contents != nil)
+ printelems(e.contents, indent + " ", recurse);
+ }
+}
+
+printelems(els: ref Celem, indent: string, recurse: int)
+{
+ for(; els != nil; els = els.next)
+ printelem(els, indent, recurse);
+}
+
+check(e: ref Celem, msg: string)
+{
+ err := checke(e);
+ if(err != "") {
+ fout.puts(msg + ": tree is inconsistent:\n" + err);
+ printelem(e, "", 1);
+ fout.flush();
+ exit;
+ }
+}
+
+checke(e: ref Celem) : string
+{
+ err := "";
+ if(e.tag == SGML && e.next != nil)
+ err = sys->sprint("root %x has a next field\n", e);
+ ec := e.contents;
+ if(ec != nil) {
+ if(ec.parent != e)
+ err += sys->sprint("node %x contents %x has bad parent %x\n", e, ec, e.parent);
+ if(ec.prev != nil)
+ err += sys->sprint("node %x contents %x has non-nil prev %x\n", e, ec, e.prev);
+ p := ec;
+ for(h := ec.next; h != nil; h = h.next) {
+ if(h.prev != p)
+ err += sys->sprint("node %x comes after %x, but prev is %x\n", h, p, h.prev);
+ if(h.parent != nil)
+ err += sys->sprint("node %x, not first in siblings, has parent %x\n", h, h.parent);
+ p = h;
+ }
+ for(h = ec; h != nil; h = h.next) {
+ err2 := checke(h);
+ if(err2 != nil)
+ err += err2;
+ }
+ }
+ return err;
+}
+
+# Translation to Latex
+
+# state bits
+SLT, SLB, SLI, SLS6, SLS8, SLS12, SLS16, SLE, SLO, SLF : con (1<<iota);
+
+SLFONTMASK : con SLT|SLB|SLI|SLS6|SLS8|SLS12|SLS16;
+SLSIZEMASK : con SLS6|SLS8|SLS12|SLS16;
+
+# fonttag-to-state-bit table
+lftagtostate := array[NFONTTAG] of {
+ Roman*NSIZE+Size6 => SLS6,
+ Roman*NSIZE+Size8 => SLS8,
+ Roman*NSIZE+Size10 => 0,
+ Roman*NSIZE+Size12 => SLS12,
+ Roman*NSIZE+Size16 => SLS16,
+ Italic*NSIZE+Size6 => SLI | SLS6,
+ Italic*NSIZE+Size8 => SLI | SLS8,
+ Italic*NSIZE+Size10 => SLI,
+ Italic*NSIZE+Size12 => SLI | SLS12,
+ Italic*NSIZE+Size16 => SLI | SLS16,
+ Bold*NSIZE+Size6 => SLB | SLS6,
+ Bold*NSIZE+Size8 => SLB | SLS8,
+ Bold*NSIZE+Size10 => SLB,
+ Bold*NSIZE+Size12 => SLB | SLS12,
+ Bold*NSIZE+Size16 => SLB | SLS16,
+ Type*NSIZE+Size6 => SLT | SLS6,
+ Type*NSIZE+Size8 => SLT | SLS8,
+ Type*NSIZE+Size10 => SLT,
+ Type*NSIZE+Size12 => SLT | SLS12,
+ Type*NSIZE+Size16 => SLT | SLS16
+};
+
+lsizecmd := array[] of { "\\footnotesize", "\\small", "\\normalsize", "\\large", "\\Large"};
+llinepos : int;
+lslidenum : int;
+LTABSIZE : con 4;
+
+latexconv(e: ref Celem)
+{
+ initasciitrans(ltranstab);
+
+ case fmt {
+ FLatex or FLatexProc =>
+ if(fmt == FLatex) {
+ fout.puts("\\documentclass{article}\n");
+ fout.puts("\\def\\encodingdefault{T1}\n");
+ }
+ else {
+ fout.puts("\\documentclass[10pt,twocolumn]{article}\n");
+ fout.puts("\\def\\encodingdefault{T1}\n");
+ fout.puts("\\usepackage{latex8}\n");
+ fout.puts("\\bibliographystyle{latex8}\n");
+ }
+ fout.puts("\\usepackage{times}\n");
+ fout.puts("\\usepackage{brutus}\n");
+ fout.puts("\\usepackage{unicode}\n");
+ fout.puts("\\usepackage{epsf}\n");
+ title := lfindtitle(e);
+ authors := lfindauthors(e);
+ abstract := lfindabstract(e);
+ fout.puts("\\begin{document}\n");
+ if(title != nil) {
+ fout.puts("\\title{");
+ llinepos = 0;
+ lconvl(title, 0);
+ fout.puts("}\n");
+ if(authors != nil) {
+ fout.puts("\\author{");
+ for(l := authors; l != nil; l = tl l) {
+ llinepos = 0;
+ lconvl(hd l, SLO|SLI);
+ if(tl l != nil)
+ fout.puts("\n\\and\n");
+ }
+ fout.puts("}\n");
+ }
+ fout.puts("\\maketitle\n");
+ }
+ fout.puts("\\pagestyle{empty}\\thispagestyle{empty}\n");
+ if(abstract != nil) {
+ if(fmt == FLatexProc) {
+ fout.puts("\\begin{abstract}\n");
+ llinepos = 0;
+ lconvl(abstract, 0);
+ fout.puts("\\end{abstract}\n");
+ }
+ else {
+ fout.puts("\\section*{Abstract}\n");
+ llinepos = 0;
+ lconvl(abstract, 0);
+ }
+ }
+ FLatexBook =>
+ fout.puts("\\documentclass{ibook}\n");
+ fout.puts("\\usepackage{brutus}\n");
+ fout.puts("\\usepackage{epsf}\n");
+ fout.puts("\\begin{document}\n");
+ FLatexSlides =>
+ fout.puts("\\documentclass[portrait]{seminar}\n");
+ fout.puts("\\def\\encodingdefault{T1}\n");
+ fout.puts("\\usepackage{times}\n");
+ fout.puts("\\usepackage{brutus}\n");
+ fout.puts("\\usepackage{unicode}\n");
+ fout.puts("\\usepackage{epsf}\n");
+ fout.puts("\\centerslidesfalse\n");
+ fout.puts("\\slideframe{none}\n");
+ fout.puts("\\slidestyle{empty}\n");
+ fout.puts("\\pagestyle{empty}\n");
+ fout.puts("\\begin{document}\n");
+ lslidenum = 0;
+ }
+
+ llinepos = 0;
+ if(e.tag == SGML)
+ lconvl(e.contents, 0);
+
+ if(fmt == FLatexSlides && lslidenum > 0)
+ fout.puts("\\vfill\\end{slide*}\n");
+ if(fmt != FLatexPart)
+ fout.puts("\\end{document}\n");
+}
+
+lconvl(el: ref Celem, state: int)
+{
+ for(e := el; e != nil; e = e.next) {
+ tag := e.tag;
+ op := "";
+ cl := "";
+ parlike := 1;
+ nstate := state;
+ if(tag < NFONTTAG) {
+ parlike = 0;
+ ss := lftagtostate[tag];
+ if((state & SLFONTMASK) != ss) {
+ t := state & SLT;
+ b := state & SLB;
+ i := state & SLI;
+ newt := ss & SLT;
+ newb := ss & SLB;
+ newi := ss & SLI;
+ op = "{";
+ cl = "}";
+ if(t && !newt)
+ op += "\\rmfamily";
+ else if(!t && newt)
+ op += "\\ttfamily";
+ if(b && !newb)
+ op += "\\mdseries";
+ else if(!b && newb)
+ op += "\\bfseries";
+ if(i && !newi)
+ op += "\\upshape";
+ else if(!i && newi) {
+ op += "\\itshape";
+ bc := lastchar(e.contents);
+ ac := firstchar(e.next);
+ if(bc != -1 && bc != ' ' && bc != '\n' && ac != -1 && ac != '.' && ac != ',')
+ cl = "\\/}";
+ }
+ if((state & SLSIZEMASK) != (ss & SLSIZEMASK)) {
+ nsize := 2;
+ if(ss & SLS6)
+ nsize = 0;
+ else if(ss & SLS8)
+ nsize = 1;
+ else if(ss & SLS12)
+ nsize = 3;
+ else if(ss & SLS16)
+ nsize = 4;
+ # examples shrunk one size
+ if((state & SLE) && nsize > 0)
+ nsize--;
+ op += lsizecmd[nsize];
+ }
+ fc := firstchar(e.contents);
+ if(fc == ' ')
+ op += "{}";
+ else
+ op += " ";
+ nstate = (state & ~SLFONTMASK) | ss;
+ }
+ }
+ else
+ case tag {
+ Text =>
+ parlike = 0;
+ if(state & SLO) {
+ asciitrans[' '] = "\\ ";
+ asciitrans['\n'] = "\\\\\n";
+ }
+ s := e.s;
+ n := len s;
+ for(k := 0; k < n; k++) {
+ c := s[k];
+ x := "";
+ if(c < 128)
+ x = asciitrans[c];
+ else
+ x = tlookup(ltranstab, c);
+ if(x == "") {
+ fout.putc(c);
+ if(c == '\n')
+ llinepos = 0;
+ else
+ llinepos++;
+ }
+ else {
+ # split up ligatures
+ if(c == '-' && k < n-1 && s[k+1] == '-')
+ x = "-{}";
+ # Avoid the 'no line to end here' latex error
+ if((state&SLO) && c == '\n' && llinepos == 0)
+ fout.puts("\\ ");
+ else if((state&SLO) && c == '\t') {
+ nspace := LTABSIZE - llinepos%LTABSIZE;
+ llinepos += nspace;
+ while(nspace-- > 0)
+ fout.puts("\\ ");
+
+ }
+ else {
+ fout.puts(x);
+ if(x[len x - 1] == '\n')
+ llinepos = 0;
+ else
+ llinepos++;
+ }
+ }
+ }
+ if(state & SLO) {
+ asciitrans[' '] = nil;
+ asciitrans['\n'] = nil;
+ }
+ Example =>
+ if(!(state&SLE)) {
+ op = "\\begin{example}";
+ cl = "\\end{example}\\noindent ";
+ nstate |= SLE | SLO;
+ }
+ List =>
+ (n, bigle) := lfindbigle(e.contents);
+ if(n <= 2) {
+ op = "\\begin{itemize}\n";
+ cl = "\\end{itemize}";
+ }
+ else {
+ fout.puts("\\begin{itemizew}{");
+ lconvl(bigle.contents, nstate);
+ op = "}\n";
+ cl = "\\end{itemizew}";
+ }
+ Listelem =>
+ op = "\\item[{";
+ cl = "}]";
+ Heading =>
+ if(fmt == FLatexProc)
+ op = "\n\\Section{";
+ else
+ op = "\n\\section{";
+ cl = "}\n";
+ nstate = (state & ~SLFONTMASK) | (SLB | SLS12);
+ Nofill =>
+ op = "\\begin{nofill}";
+ cl = "\\end{nofill}\\noindent ";
+ nstate |= SLO;
+ Title =>
+ if(fmt == FLatexSlides) {
+ op = "\\begin{slide*}\n" +
+ "\\begin{center}\\Large\\bfseries ";
+ if(lslidenum > 0)
+ op = "\\vfill\\end{slide*}\n" + op;
+ cl = "\\end{center}\n";
+ lslidenum++;
+ }
+ else {
+ if(stringof(e.contents) == "Index") {
+ op = "\\printindex\n";
+ e.contents = nil;
+ }
+ else {
+ op = "\\chapter{";
+ cl = "}\n";
+ }
+ }
+ nstate = (state & ~SLFONTMASK) | (SLB | SLS16);
+ Par =>
+ op = "\n\\par\n";
+ while(e.next != nil && e.next.tag == Par)
+ e = e.next;
+ Extension =>
+ e.contents = convextension(e.s);
+ if(e.contents != nil)
+ e.contents.parent = e;
+ Special =>
+ fout.puts(e.s);
+ Float =>
+ if(!(state&SLF)) {
+ isfig := lfixfloat(e);
+ if(isfig) {
+ op = "\\begin{figure}\\begin{center}\\leavevmode ";
+ cl = "\\end{center}\\end{figure}";
+ }
+ else {
+ op = "\\begin{table}\\begin{center}\\leavevmode ";
+ cl = "\\end{center}\\end{table}";
+ }
+ nstate |= SLF;
+ }
+ Caption=>
+ if(state&SLF) {
+ op = "\\caption{";
+ cl = "}";
+ nstate = (state & ~SLFONTMASK) | SLS8;
+ }
+ else {
+ op = "\\begin{center}";
+ cl = "\\end{center}";
+ }
+ Label or Labelref =>
+ parlike = 0;
+ if(tag == Label)
+ op = "\\label";
+ else
+ op = "\\ref";
+ cl = "{" + stringof(e.contents) + "}";
+ e.contents = nil;
+ Exercise =>
+ lfixexercise(e);
+ op = "\\begin{exercise}";
+ cl = "\\end{exercise}";
+ Index or Indextopic =>
+ parlike = 0;
+ if(tag == Index)
+ lconvl(e.contents, nstate);
+ fout.puts("\\showidx{");
+ lconvl(e.contents, nstate);
+ fout.puts("}");
+ lconvindex(e.contents, nstate);
+ e.contents = nil;
+ }
+ if(op != "")
+ fout.puts(op);
+ if(e.contents != nil) {
+ if(parlike)
+ llinepos = 0;
+ lconvl(e.contents, nstate);
+ if(parlike)
+ llinepos = 0;
+ }
+ if(cl != "")
+ fout.puts(cl);
+ }
+}
+
+lfixfloat(e: ref Celem) : int
+{
+ dropwhite(e.contents);
+ fstart := e.contents;
+ fend := last(fstart);
+ hasfig := 0;
+ hastab := 0;
+ if(fend.tag == Caption) {
+ dropwhite(fend.prev);
+ if(fend.prev != nil && stringof(fstart) == "\t")
+ delete(fend.prev);
+ # If fend.contents is "YYY " <Label> "." rest
+ # where YYY is Figure or Table,
+ # then replace it with just rest, and move <Label>
+ # after the caption.
+ # Probably should be more robust about what to accept.
+ ec := fend.contents;
+ s := stringof(ec);
+ if(s == "Figure ")
+ hasfig = 1;
+ else if(s == "Table ")
+ hastab = 1;
+ if(hasfig || hastab) {
+ ec2 := ec.next;
+ ec3 : ref Celem = nil;
+ ec4 : ref Celem = nil;
+ if(ec2 != nil && ec2.tag == Label) {
+ ec3 = ec2.next;
+ if(ec3 != nil && stringof(ec3) == ".")
+ ec4 = ec3.next;
+ }
+ if(ec4 != nil) {
+ dropwhite(ec4);
+ ec4 = ec3.next;
+ if(ec4 != nil) {
+ excise(ec);
+ excise(ec2);
+ excise(ec3);
+ fend.contents = ec4;
+ ec4.parent = fend;
+ insertafter(ec2, fend);
+ }
+ }
+ }
+ }
+ return !hastab;
+}
+
+lfixexercise(e: ref Celem)
+{
+ dropwhite(e.contents);
+ ec := e.contents;
+ # Expect:
+ # "Exercise " <Label> ":" rest
+ # If so, drop the first and third.
+ # Or
+ # "Exercise:" rest
+ # If so, drop the first.
+ s := stringof(ec);
+ if(s == "Exercise ") {
+ ec2 := ec.next;
+ ec3 : ref Celem = nil;
+ ec4 : ref Celem = nil;
+ if(ec2 != nil && ec2.tag == Label) {
+ ec3 = ec2.next;
+ if(ec3 != nil && stringof(ec3) == ":")
+ ec4 = ec3.next;
+ }
+ if(ec4 != nil) {
+ dropwhite(ec4);
+ ec4 = ec3.next;
+ if(ec4 != nil) {
+ excise(ec);
+ excise(ec3);
+ e.contents = ec2;
+ ec2.parent = e;
+ ec2.next = ec4;
+ ec4.prev = ec2;
+ }
+ }
+ }
+ else if(s == "Exercise:") {
+ dropwhite(ec.next);
+ e.contents = ec.next;
+ excise(ec);
+ if(e.contents != nil)
+ e.contents.parent = e;
+ }
+}
+
+# convert content list headed by e to \\index{...}
+lconvindex(e: ref Celem, state: int)
+{
+ fout.puts("\\index{");
+ g := lsplitind(e);
+ gp := g;
+ needat := 0;
+ while(g != nil) {
+ gnext := g.next;
+ s := stringof(g);
+ if(s == "!" || s == "|") {
+ if(gp != g) {
+ g.next = nil;
+ g.s = "";
+ lprintindsort(gp);
+ if(needat) {
+ fout.puts("@");
+ lconvl(gp, state);
+ }
+ }
+ fout.puts(s);
+ gp = gnext;
+ needat = 0;
+ if(s == "|") {
+ if(g == nil)
+ break;
+ g = gnext;
+ # don't lconvl the Text items, so
+ # that "see{" and "}" come out untranslated.
+ # (code is wrong if stuff inside see is plain
+ # text but with special tex characters)
+ while(g != nil) {
+ gnext = g.next;
+ g.next = nil;
+ if(g.tag != Text)
+ lconvl(g, state);
+ else
+ fout.puts(g.s);
+ g = gnext;
+ }
+ gp = nil;
+ break;
+ }
+ }
+ else {
+ if(g.tag != Text)
+ needat = 1;
+ }
+ g = gnext;
+ }
+ if(gp != nil) {
+ lprintindsort(gp);
+ if(needat) {
+ fout.puts("@");
+ lconvl(gp, state);
+ }
+ }
+ fout.puts("}");
+}
+
+lprintindsort(e: ref Celem)
+{
+ while(e != nil) {
+ fout.puts(stringof(e));
+ e = e.next;
+ }
+}
+
+# return copy of e
+lsplitind(e: ref Celem) : ref Celem
+{
+ dummy := ref Celem;
+ for( ; e != nil; e = e.next) {
+ te := e;
+ if(e.tag < NFONTTAG)
+ te = te.contents;
+ if(te.tag != Text)
+ continue;
+ s := te.s;
+ i := 0;
+ for(j := 0; j < len s; j++) {
+ if(s[j] == '!' || s[j] == '|') {
+ if(j > i) {
+ nte := ref Celem(Text, s[i:j], nil, nil, nil, nil);
+ if(e == te)
+ ne := nte;
+ else
+ ne = ref Celem(e.tag, nil, nte, nil, nil, nil);
+ append(dummy, ne);
+ }
+ append(dummy, ref Celem(Text, s[j:j+1], nil, nil, nil, nil));
+ i = j+1;
+ }
+ }
+ if(j > i) {
+ nte := ref Celem(Text, s[i:j], nil, nil, nil, nil);
+ if(e == te)
+ ne := nte;
+ else
+ ne = ref Celem(e.tag, nil, nte, nil, nil, nil);
+ append(dummy, ne);
+ }
+ }
+ return dummy.next;
+}
+
+# return key part of an index entry corresponding to e list
+indexkey(e: ref Celem) : string
+{
+ s := "";
+ while(e != nil) {
+ s += stringof(e);
+ e = e.next;
+ }
+ return s;
+}
+
+# find title, excise it from e, and return contents as list
+lfindtitle(e: ref Celem) : ref Celem
+{
+ if(e.tag == Title) {
+ ans := e.contents;
+ delete(e);
+ return ans;
+ }
+ else if (e.contents != nil) {
+ for(h := e.contents; h != nil; h = h.next) {
+ a := lfindtitle(h);
+ if(a != nil)
+ return a;
+ }
+ }
+ return nil;
+}
+
+# find authors, excise them from e, and return as list of lists
+lfindauthors(e: ref Celem) : list of ref Celem
+{
+ if(e.tag == Author) {
+ a := e.contents;
+ en := e.next;
+ delete(e);
+ rans : list of ref Celem = a :: nil;
+ if(en != nil) {
+ e = en;
+ while(e != nil) {
+ if(e.tag == Par) {
+ en = e.next;
+ if(en.tag == Author) {
+ delete(e);
+ a = en.contents;
+ for(y := a; y != nil; ) {
+ yn := y.next;
+ if(y.tag == Par)
+ delete(y);
+ y = yn;
+ }
+ e = en.next;
+ delete(en);
+ rans = a :: rans;
+ }
+ else
+ break;
+ }
+ else
+ break;
+ }
+ }
+ ans : list of ref Celem = nil;
+ while(rans != nil) {
+ ans = hd rans :: ans;
+ rans = tl rans;
+ }
+ return ans;
+ }
+ else if (e.contents != nil) {
+ for(h := e.contents; h != nil; h = h.next) {
+ a := lfindauthors(h);
+ if(a != nil)
+ return a;
+ }
+ }
+ return nil;
+}
+
+# find section called abstract, excise it from e, and return as list
+lfindabstract(e: ref Celem) : ref Celem
+{
+ if(e.tag == Heading) {
+ c := e.contents;
+ if(c.tag == Text && c.s == "Abstract") {
+ for(h2 := e.next; h2 != nil; h2 = h2.next) {
+ if(h2.tag == Heading)
+ break;
+ }
+ ans := e.next;
+ ans.prev = nil;
+ ep := e.prev;
+ eu := e.parent;
+ if(ep == nil) {
+ if(eu != nil)
+ eu.contents = h2;
+ if(h2 != nil)
+ h2.parent = eu;
+ }
+ else
+ ep.next = h2;
+ if(h2 != nil) {
+ ansend := h2.prev;
+ ansend.next = nil;
+ h2.prev = ep;
+ }
+ return ans;
+ }
+ }
+ else if (e.contents != nil) {
+ for(h := e.contents; h != nil; h = h.next) {
+ a := lfindabstract(h);
+ if(a != nil)
+ return a;
+ }
+ }
+ return nil;
+}
+
+# find biggest list element with longest contents in e list
+lfindbigle(e: ref Celem) : (int, ref Celem)
+{
+ ans : ref Celem = nil;
+ maxlen := 0;
+ for(h := e; h != nil; h = h.next) {
+ if(h.tag == Listelem) {
+ n := 0;
+ for(p := h.contents; p != nil; p = p.next) {
+ if(p.tag == Text)
+ n += len p.s;
+ else if(p.tag < NFONTTAG) {
+ q := p.contents;
+ if(q.tag == Text)
+ n += len q.s;
+ }
+ }
+ if(n > maxlen) {
+ maxlen = n;
+ ans = h;
+ }
+ }
+ }
+ return (maxlen, ans);
+}
+
+# Translation to HTML
+
+# state bits
+SHA, SHO, SHFL, SHDT: con (1<<iota);
+
+htmlconv(e: ref Celem)
+{
+ initasciitrans(htranstab);
+
+ fout.puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n");
+ fout.puts("<HTML>\n");
+
+ if(e.tag == SGML) {
+ # Conforming 3.2 documents require a Title.
+ # Use the Title tag both for the document title and
+ # for an H1-level heading.
+ # (SHDT state bit enforces: Font change markup, etc., not allowed in Title)
+ fout.puts("<TITLE>\n");
+ title := hfindtitle(e);
+ if(title != nil)
+ hconvl(title.contents, SHDT);
+ else if(infilename != "")
+ fout.puts(infilename);
+ else
+ fout.puts("An HTML document");
+ fout.puts("</TITLE>\n");
+ fout.puts("<BODY>\n");
+ hconvl(e.contents, 0);
+ fout.puts("</BODY>\n");
+ }
+
+ fout.puts("</HTML>\n");
+}
+
+hconvl(el: ref Celem, state: int)
+{
+ for(e := el; e != nil; e = e.next) {
+ tag := e.tag;
+ op := "";
+ cl := "";
+ nstate := state;
+ if(tag == Text) {
+ s := e.s;
+ n := len s;
+ for(k := 0; k < n; k++) {
+ c := s[k];
+ x := "";
+ if(c < 128) {
+ if(c == '\n' && (state&SHO))
+ x = "\n\t";
+ else
+ x = asciitrans[c];
+ }
+ else
+ x = tlookup(htranstab, c);
+ if(x == "")
+ fout.putc(c);
+ else
+ fout.puts(x);
+ }
+ }
+ else if(!(state&SHDT))
+ case tag {
+ Roman*NSIZE+Size6 =>
+ op = "<FONT SIZE=1>";
+ cl = "</FONT>";
+ nstate |= SHA;
+ Roman*NSIZE+Size8 =>
+ op = "<FONT SIZE=2>";
+ cl = "</FONT>";
+ nstate |= SHA;
+ Roman*NSIZE+Size10 =>
+ if(state & SHA) {
+ op = "<FONT SIZE=3>";
+ cl = "</FONT>";
+ nstate &= ~SHA;
+ }
+ Roman*NSIZE+Size12 =>
+ op = "<FONT SIZE=4>";
+ cl = "</FONT>";
+ nstate |= SHA;
+ Roman*NSIZE+Size16 =>
+ op = "<FONT SIZE=5>";
+ cl = "</FONT>";
+ nstate |= SHA;
+ Italic*NSIZE+Size6 =>
+ op = "<I><FONT SIZE=1>";
+ cl = "</FONT></I>";
+ nstate |= SHA;
+ Italic*NSIZE+Size8 =>
+ op = "<I><FONT SIZE=2>";
+ cl = "</FONT></I>";
+ nstate |= SHA;
+ Italic*NSIZE+Size10 =>
+ if(state & SHA) {
+ op = "<I><FONT SIZE=3>";
+ cl = "</FONT></I>";
+ nstate &= ~SHA;
+ }
+ else {
+ op = "<I>";
+ cl = "</I>";
+ }
+ Italic*NSIZE+Size12 =>
+ op = "<I><FONT SIZE=4>";
+ cl = "</FONT></I>";
+ nstate |= SHA;
+ Italic*NSIZE+Size16 =>
+ op = "<I><FONT SIZE=5>";
+ cl = "</FONT></I>";
+ nstate |= SHA;
+ Bold*NSIZE+Size6 =>
+ op = "<B><FONT SIZE=1>";
+ cl = "</FONT></B>";
+ nstate |= SHA;
+ Bold*NSIZE+Size8 =>
+ op = "<B><FONT SIZE=2>";
+ cl = "</FONT></B>";
+ nstate |= SHA;
+ Bold*NSIZE+Size10 =>
+ if(state & SHA) {
+ op = "<B><FONT SIZE=3>";
+ cl = "</FONT></B>";
+ nstate &= ~SHA;
+ }
+ else {
+ op = "<B>";
+ cl = "</B>";
+ }
+ Bold*NSIZE+Size12 =>
+ op = "<B><FONT SIZE=4>";
+ cl = "</FONT></B>";
+ nstate |= SHA;
+ Bold*NSIZE+Size16 =>
+ op = "<B><FONT SIZE=5>";
+ cl = "</FONT></B>";
+ nstate |= SHA;
+ Type*NSIZE+Size6 =>
+ op = "<TT><FONT SIZE=1>";
+ cl = "</FONT></TT>";
+ nstate |= SHA;
+ Type*NSIZE+Size8 =>
+ op = "<TT><FONT SIZE=2>";
+ cl = "</FONT></TT>";
+ nstate |= SHA;
+ Type*NSIZE+Size10 =>
+ if(state & SHA) {
+ op = "<TT><FONT SIZE=3>";
+ cl = "</FONT></TT>";
+ nstate &= ~SHA;
+ }
+ else {
+ op = "<TT>";
+ cl = "</TT>";
+ }
+ Type*NSIZE+Size12 =>
+ op = "<TT><FONT SIZE=4>";
+ cl = "</FONT></TT>";
+ nstate |= SHA;
+ Type*NSIZE+Size16 =>
+ op = "<TT><FONT SIZE=5>";
+ cl = "</FONT></TT>";
+ nstate |= SHA;
+ Example =>
+ op = "<P><PRE>\t";
+ cl = "</PRE><P>\n";
+ nstate |= SHO;
+ List =>
+ op = "<DL>";
+ cl = "</DD></DL>";
+ nstate |= SHFL;
+ Listelem =>
+ if(state & SHFL)
+ op = "<DT>";
+ else
+ op = "</DD><DT>";
+ cl = "</DT><DD>";
+ # change first-list-elem state for this level
+ state &= ~SHFL;
+ Heading =>
+ op = "<H2>";
+ cl = "</H2>\n";
+ Nofill =>
+ op = "<P><PRE>";
+ cl = "</PRE>";
+ Title =>
+ op = "<H1>";
+ cl = "</H1>\n";
+ Par =>
+ op = "<P>\n";
+ Extension =>
+ e.contents = convextension(e.s);
+ Special =>
+ fout.puts(e.s);
+ }
+ if(op != "")
+ fout.puts(op);
+ hconvl(e.contents, nstate);
+ if(cl != "")
+ fout.puts(cl);
+ }
+}
+
+# find title, if there is one, and return it (but leave it in contents too)
+hfindtitle(e: ref Celem) : ref Celem
+{
+ if(e.tag == Title)
+ return e;
+ else if (e.contents != nil) {
+ for(h := e.contents; h != nil; h = h.next) {
+ a := hfindtitle(h);
+ if(a != nil)
+ return a;
+ }
+ }
+ return nil;
+}
+
+Exten: adt
+{
+ name: string;
+ mod: Brutusext;
+};
+
+extens: list of Exten = nil;
+
+convextension(s: string) : ref Celem
+{
+ for(i:=0; i<len s; i++)
+ if(s[i] == ' ')
+ break;
+ if(i == len s) {
+ sys->fprint(stderr, "badly formed extension %s\n", s);
+ return nil;
+ }
+ modname := s[0:i];
+ s = s[i+1:];
+ mod: Brutusext = nil;
+ for(le := extens; le != nil; le = tl le) {
+ el := hd le;
+ if(el.name == modname)
+ mod = el.mod;
+ }
+ if(mod == nil) {
+ file := modname;
+ if(i < 4 || file[i-4:i] != ".dis")
+ file += ".dis";
+ if(file[0] != '/')
+ file = "/dis/wm/brutus/" + file;
+ mod = load Brutusext file;
+ if(mod == nil) {
+ sys->fprint(stderr, "can't load extension module %s: %r\n", file);
+ return nil;
+ }
+ mod->init(sys, draw, B, tk, nil);
+ extens = Exten(modname, mod) :: extens;
+ }
+ f := infilename;
+ if(f == "<stdin>")
+ f = "";
+ (ans, err) := mod->cook(f, fmt, s);
+ if(err != "") {
+ sys->fprint(stderr, "extension module %s cook error: %s\n", modname, err);
+ return nil;
+ }
+ return ans;
+}
diff --git a/appl/cmd/cp.b b/appl/cmd/cp.b
new file mode 100644
index 00000000..23c49a15
--- /dev/null
+++ b/appl/cmd/cp.b
@@ -0,0 +1,237 @@
+implement Cp;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+include "arg.m";
+
+include "readdir.m";
+ readdir: Readdir;
+
+Cp: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+stderr: ref Sys->FD;
+errors := 0;
+gflag := 0;
+uflag := 0;
+xflag := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ arg := load Arg Arg->PATH;
+ recursive := 0;
+ arg->init(args);
+ arg->setusage("\tcp [-gux] src target\n\tcp [-r] [-gux] src ... directory");
+ while((opt := arg->opt()) != 0)
+ case opt {
+ 'r' => recursive = 1;
+ 'g' => gflag = 1;
+ 'u' => uflag = gflag = 1;
+ 'x' => xflag = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ argc := len args;
+ if(argc < 2)
+ arg->usage();
+ arg = nil;
+
+ dst: string;
+ for(t := args; t != nil; t = tl t)
+ dst = hd t;
+
+ (ok, dir) := sys->stat(dst);
+ todir := (ok != -1 && (dir.mode & Sys->DMDIR));
+ if(argc > 2 && !todir){
+ sys->fprint(stderr, "cp: %s not a directory\n", dst);
+ raise "fail:error";
+ }
+ if(recursive)
+ cpdir(args, dst);
+ else{
+ for(; tl args != nil; args = tl args){
+ if(todir)
+ cp(hd args, dst, basename(hd args));
+ else
+ cp(hd args, dst, nil);
+ }
+ }
+ if(errors)
+ raise "fail:error";
+}
+
+basename(s: string): string
+{
+ for((nil, ls) := sys->tokenize(s, "/"); ls != nil; ls = tl ls)
+ s = hd ls;
+ return s;
+}
+
+cp(src, dst: string, newname: string)
+{
+ dd: Sys->Dir;
+
+ if(newname != nil)
+ dst += "/" + newname;
+ (ok, ds) := sys->stat(src);
+ if(ok < 0){
+ warning(sys->sprint("%s: %r", src));
+ return;
+ }
+ if(ds.mode & Sys->DMDIR){
+ warning(src + " is a directory");
+ return;
+ }
+ (ok, dd) = sys->stat(dst);
+ if(ok != -1 && samefile(ds, dd)){
+ warning(src + " and " + dst + " are the same file");
+ return;
+ }
+ sfd := sys->open(src, Sys->OREAD);
+ if(sfd == nil){
+ warning(sys->sprint("cannot open %s: %r", src));
+ return;
+ }
+ dfd := sys->create(dst, Sys->OWRITE, ds.mode & 8r777);
+ if(dfd == nil){
+ warning(sys->sprint("cannot create %s: %r", dst));
+ return;
+ }
+ if(copy(sfd, dfd, src, dst)!=0)
+ return;
+ if(wstat(dfd, ds, 0) < 0)
+ warning(sys->sprint("can't wstat %s: %r", src));
+}
+
+copy(sfd, dfd: ref Sys->FD, src, dst: string): int
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ while((r := sys->read(sfd, buf, len buf)) > 0){
+ if(sys->write(dfd, buf, r) != r){
+ warning(sys->sprint("error writing %s: %r", dst));
+ return -1;
+ }
+ }
+ if(r < 0){
+ warning(sys->sprint("error reading %s: %r", src));
+ return -1;
+ }
+ return 0;
+}
+
+cpdir(args: list of string, dst: string)
+{
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil){
+ sys->fprint(stderr, "cp: cannot load %s: %r\n", Readdir->PATH);
+ raise "fail:bad module";
+ }
+ cache = array[NCACHE] of list of ref Sys->Dir;
+ dexists := 0;
+ (ok, dd) := sys->stat(dst);
+ # destination file exists
+ if(ok != -1){
+ if((dd.mode & Sys->DMDIR) == 0){
+ warning(dst + ": destination not a directory");
+ return;
+ }
+ dexists = 1;
+ }
+ for(; tl args != nil; args = tl args){
+ ds: Sys->Dir;
+ src := hd args;
+ (ok, ds) = sys->stat(src);
+ if(ok < 0){
+ warning(sys->sprint("can't stat %s: %r", src));
+ continue;
+ }
+ if((ds.mode & Sys->DMDIR) == 0){
+ cp(hd args, dst, basename(hd args));
+ } else if(dexists){
+ if(samefile(ds, dd)){
+ warning("cannot copy " + src + " into itself");
+ continue;
+ }
+ copydir(src, dst + "/" + basename(src), ds);
+ } else
+ copydir(src, dst, ds);
+ }
+}
+
+copydir(src, dst: string, srcd: Sys->Dir)
+{
+ (ok, nil) := sys->stat(dst);
+ if(ok != -1){
+ warning("cannot copy " + src + " onto another directory");
+ return;
+ }
+ tmode := srcd.mode | 8r777; # Fix for Nt
+ dfd := sys->create(dst, Sys->OREAD, Sys->DMDIR | tmode);
+ if(dfd == nil){
+ warning(sys->sprint("cannot make directory %s: %r", dst));
+ return;
+ }
+ (entries, n) := readdir->init(src, Readdir->COMPACT);
+ for(i := 0; i < n; i++){
+ e := entries[i];
+ path := src + "/" + e.name;
+ if((e.mode & Sys->DMDIR) == 0)
+ cp(path, dst, e.name);
+ else if(seen(e))
+ warning(path + ": directory loop found");
+ else
+ copydir(path, dst + "/" + e.name, *e);
+ }
+ if(wstat(dfd, srcd, 1) < 0)
+ warning(sys->sprint("can't wstat %s: %r", dst));
+}
+
+wstat(dfd: ref Sys->FD, ds: Sys->Dir, mflag: int): int
+{
+ if(!xflag && !gflag && !uflag && !mflag)
+ return 0;
+ d := sys->nulldir;
+ if(xflag)
+ d.mtime = ds.mtime;
+ if(xflag || mflag)
+ d.mode = ds.mode;
+ if(uflag)
+ d.uid = ds.uid;
+ if(gflag)
+ d.gid = ds.gid;
+ return sys->fwstat(dfd, d);
+}
+
+samefile(d1: Sys->Dir, d2: Sys->Dir): int
+{
+ return d1.dtype == d2.dtype && d1.dev == d2.dev &&
+ d1.qid.qtype == d2.qid.qtype && d1.qid.path == d2.qid.path &&
+ d1.qid.vers == d2.qid.vers;
+}
+
+# Avoid loops in tangled namespaces. (from du.b)
+NCACHE: con 64; # must be power of two
+cache: array of list of ref sys->Dir;
+
+seen(dir: ref sys->Dir): int
+{
+ savlist := cache[int dir.qid.path&(NCACHE-1)];
+ for(c := savlist; c!=nil; c = tl c)
+ if(samefile(*dir, *hd c))
+ return 1;
+ cache[int dir.qid.path&(NCACHE-1)] = dir :: savlist;
+ return 0;
+}
+
+warning(e: string)
+{
+ sys->fprint(stderr, "cp: %s\n", e);
+ errors++;
+}
diff --git a/appl/cmd/cprof.b b/appl/cmd/cprof.b
new file mode 100644
index 00000000..846a75bd
--- /dev/null
+++ b/appl/cmd/cprof.b
@@ -0,0 +1,190 @@
+implement Prof;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+ arg: Arg;
+include "profile.m";
+ profile: Profile;
+include "sh.m";
+
+stderr: ref Sys->FD;
+
+Prof: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+ init0: fn(nil: ref Draw->Context, argv: list of string): Profile->Coverage;
+};
+
+exits(e: string)
+{
+ if(profile != nil)
+ profile->end();
+ raise "fail:" + e;
+}
+
+pfatal(s: string)
+{
+ sys->fprint(stderr, "cprof: %s: %s\n", s, profile->lasterror());
+ exits("error");
+}
+
+badmodule(p: string)
+{
+ sys->fprint(stderr, "cprof: cannot load %s: %r\n", p);
+ exits("bad module");
+}
+
+usage(s: string)
+{
+ sys->fprint(stderr, "cprof: %s\n", s);
+ sys->fprint(stderr, "usage: cprof [-fner] [-m modname]... cmd [arg ... ]");
+ exits("usage");
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ init0(ctxt, argv);
+}
+
+init0(ctxt: ref Draw->Context, argv: list of string): Profile->Coverage
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ badmodule(Arg->PATH);
+ arg->init(argv);
+ profile = load Profile Profile->PATH;
+ if(profile == nil)
+ badmodule(Profile->PATH);
+ if(profile->init() < 0)
+ pfatal("cannot initialize profile device");
+
+ v := 0;
+ ep := 0;
+ rec := 0;
+ wm := 0;
+ exec, mods: list of string;
+ while((c := arg->opt()) != 0){
+ case c {
+ 'n' => v |= profile->FULLHDR;
+ 'f' => v |= profile->FREQUENCY;
+ 'm' =>
+ if((s := arg->arg()) == nil)
+ usage("missing module/file");
+ mods = s :: mods;
+ 'e' =>
+ ep = 1;
+ 'r' =>
+ rec = 1;
+ 'g' =>
+ wm = 1;
+ * =>
+ usage(sys->sprint("unknown option -%c", c));
+ }
+ }
+ exec = arg->argv();
+ # if(exec == nil)
+ # usage("nothing to execute");
+ for( ; mods != nil; mods = tl mods)
+ profile->profile(hd mods);
+ if(ep && exec != nil)
+ profile->profile(disname(hd exec));
+ if(exec != nil){
+ wfd := openwait(sys->pctl(0, nil));
+ ci := chan of int;
+ spawn execute(ctxt, hd exec, exec, ci);
+ epid := <- ci;
+ if(profile->cpstart(epid) < 0){
+ ci <-= 0;
+ pfatal("cannot start profiling");
+ }
+ ci <-= 1;
+ wait(wfd, epid);
+ if(profile->stop() < 0)
+ pfatal("cannot stop profiling");
+ }
+ if(exec == nil)
+ modl := profile->cpfstats(v);
+ else
+ modl = profile->cpstats(rec, v);
+ if(modl.mods == nil)
+ pfatal("no profile information");
+ if(wm){
+ cvr := profile->coverage(modl, v);
+ profile->end();
+ return cvr;
+ }
+ if(!rec && profile->cpshow(modl, v) < 0)
+ pfatal("cannot show profile");
+ profile->end();
+ return nil;
+}
+
+disname(cmd: string): string
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+ if(exists(file))
+ return file;
+ if(file[0]!='/' && file[0:2]!="./")
+ file = "/dis/"+file;
+ # if(exists(file))
+ # return file;
+ return file;
+}
+
+execute(ctxt: ref Draw->Context, cmd : string, argl : list of string, ci: chan of int)
+{
+ ci <-= sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: stderr.fd :: nil);
+ file := cmd;
+ err := "";
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+ c := load Command file;
+ if(c == nil) {
+ err = sys->sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ }
+ if(<- ci){
+ if(c == nil)
+ sys->fprint(stderr, "cprof: %s: %s\n", cmd, err);
+ else
+ c->init(ctxt, argl);
+ }
+}
+
+openwait(pid : int) : ref Sys->FD
+{
+ w := sys->sprint("#p/%d/wait", pid);
+ fd := sys->open(w, Sys->OREAD);
+ if (fd == nil)
+ pfatal("fd == nil in wait");
+ return fd;
+}
+
+wait(wfd : ref Sys->FD, wpid : int)
+{
+ n : int;
+
+ buf := array[Sys->WAITLEN] of byte;
+ status := "";
+ for(;;) {
+ if ((n = sys->read(wfd, buf, len buf)) < 0)
+ pfatal("bad read in wait");
+ status = string buf[0:n];
+ if (int status == wpid)
+ break;
+ }
+}
+
+exists(f: string): int
+{
+ return sys->open(f, Sys->OREAD) != nil;
+}
diff --git a/appl/cmd/cpu.b b/appl/cmd/cpu.b
new file mode 100644
index 00000000..57a62fdf
--- /dev/null
+++ b/appl/cmd/cpu.b
@@ -0,0 +1,168 @@
+implement CPU;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+ Context: import Draw;
+include "string.m";
+ str: String;
+include "arg.m";
+include "keyring.m";
+include "security.m";
+
+DEFCMD: con "/dis/sh";
+
+CPU: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+badmodule(p: string)
+{
+ sys->fprint(stderr, "cpu: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+usage()
+{
+ sys->fprint(stderr, "Usage: cpu [-C cryptoalg] mach command args...\n");
+ raise "fail:usage";
+}
+
+# The default level of security is NOSSL, unless
+# the keyring directory doesn't exist, in which case
+# it's disallowed.
+init(nil: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ arg := load Arg Arg->PATH;
+ if (arg == nil) badmodule(Arg->PATH);
+
+ str = load String String->PATH;
+ if (str == nil) badmodule(String->PATH);
+
+ au := load Auth Auth->PATH;
+ if (au == nil) badmodule(Auth->PATH);
+
+ kr := load Keyring Keyring->PATH;
+ if (kr == nil) badmodule(Keyring->PATH);
+
+ arg->init(argv);
+ alg := "";
+ while ((opt := arg->opt()) != 0) {
+ if (opt == 'C') {
+ alg = arg->arg();
+ } else
+ usage();
+ }
+ argv = arg->argv();
+ args := "auxi/cpuslave";
+# if(ctxt != nil && ctxt.screen != nil)
+# args += " -s" + string ctxt.screen.id;
+# else
+ args += " --";
+
+ mach: string;
+ case len argv {
+ 0 =>
+ usage();
+ 1 =>
+ mach = hd argv;
+ args += " " + DEFCMD;
+ * =>
+ mach = hd argv;
+ args += " " + str->quoted(tl argv);
+ }
+
+ user := getuser();
+ kd := "/usr/" + user + "/keyring/";
+ cert := kd + netmkaddr(mach, "tcp", "");
+ if (!exists(cert)) {
+ cert = kd + "default";
+ if (!exists(cert)) {
+ sys->fprint(stderr, "cpu: cannot find certificate in %s; use getauthinfo\n", kd);
+ raise "fail:no certificate";
+ }
+ }
+
+ # To make visible remotely
+ if(!exists("/dev/draw/new"))
+ sys->bind("#d", "/dev", Sys->MBEFORE);
+
+ (ok, c) := sys->dial(netmkaddr(mach, "net", "rstyx"), nil);
+ if(ok < 0){
+ sys->fprint(stderr, "Error: cpu: dial: %r\n");
+ return;
+ }
+
+ ai := kr->readauthinfo(cert);
+
+ if (alg == nil)
+ alg = "none";
+ err := au->init();
+ if(err != nil) {
+ sys->fprint(stderr, "cpu: cannot initialise auth module: %s\n", err);
+ raise "fail:auth init failed";
+ }
+
+ fd := ref Sys->FD;
+ #sys->fprint(stderr, "cpu: authenticating using alg '%s'\n", alg);
+ (fd, err) = au->client(alg, ai, c.dfd);
+ if(fd == nil) {
+ sys->fprint(stderr, "cpu: authentication failed: %s\n", err);
+ raise "fail:authentication failure";
+ }
+
+ t := array of byte sys->sprint("%d\n%s\n", len (array of byte args)+1, args);
+ if(sys->write(fd, t, len t) != len t){
+ sys->fprint(stderr, "cpu: export args write error: %r\n");
+ raise "fail:write error";
+ }
+
+ if(sys->export(fd, "/", sys->EXPWAIT) < 0){
+ sys->fprint(stderr, "cpu: export failed: %r\n");
+ raise "fail:export error";
+ }
+}
+
+exists(file: string): int
+{
+ (ok, nil) := sys->stat(file);
+ return ok != -1;
+}
+
+getuser(): string
+{
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil){
+ sys->fprint(stderr, "cpu: cannot open /dev/user: %r\n");
+ raise "fail:no user id";
+ }
+
+ buf := array[50] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0){
+ sys->fprint(stderr, "cpu: cannot read /dev/user: %r\n");
+ raise "fail:no user id";
+ }
+
+ return string buf[0:n];
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/cmd/crypt.b b/appl/cmd/crypt.b
new file mode 100644
index 00000000..a478abfc
--- /dev/null
+++ b/appl/cmd/crypt.b
@@ -0,0 +1,234 @@
+implement Crypt;
+
+# encrypt/decrypt from stdin to stdout
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+include "keyring.m";
+ keyring: Keyring;
+include "security.m";
+ ssl: SSL;
+include "arg.m";
+
+Crypt: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+Ehungup: con "i/o on hungup channel";
+
+ALGSTR: con "alg ";
+DEFAULTALG: con "md5/ideacbc";
+usage()
+{
+ sys->fprint(stderr, "usage: crypt [-?] [-d] [-k secret] [-f secretfile] [-a alg[/alg]]\n");
+ sys->fprint(stderr, "available algorithms:\n");
+ showalgs(stderr);
+ fail("bad usage");
+}
+
+badmodule(m: string)
+{
+ sys->fprint(stderr, "crypt: cannot load %s: %r\n", m);
+ fail("bad module");
+}
+
+headers: con 1;
+verbose := 0;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ ssl = load SSL SSL->PATH;
+ if (ssl == nil)
+ badmodule(SSL->PATH);
+ keyring = load Keyring Keyring->PATH;
+ if (keyring == nil)
+ badmodule(SSL->PATH);
+
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(SSL->PATH);
+
+ decrypt := 0;
+ secret: array of byte;
+ alg := DEFAULTALG;
+
+ arg->init(argv);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'd' =>
+ decrypt = 1;
+ 'k' =>
+ if ((s := arg->arg()) == nil)
+ usage();
+ secret = array of byte s;
+ 'f' =>
+ if ((f := arg->arg()) == nil)
+ usage();
+ secret = readfile(f);
+ 'a' =>
+ if ((alg = arg->arg()) == nil)
+ usage();
+ '?' =>
+ showalgs(sys->fildes(1));
+ return;
+ 'v' =>
+ verbose = 1;
+ * =>
+ usage();
+ }
+ }
+ argv = arg->argv();
+ if (argv != nil)
+ usage();
+ if(secret == nil){
+ sys->fprint(stderr, "crypt: no secret given\n");
+ usage();
+ }
+ sk := array[Keyring->SHA1dlen] of byte;
+ keyring->sha1(secret, len secret, sk, nil);
+ if (headers) {
+ # deal with header - the header encodes the algorithm along with the data.
+ if (decrypt) {
+ msg := keyring->getmsg(sys->fildes(0));
+ if (msg != nil)
+ alg = string msg;
+ if (msg == nil || len alg < len ALGSTR || alg[0:len ALGSTR] != ALGSTR)
+ error("couldn't get decrypt algorithm");
+ alg = alg[len ALGSTR:];
+ } else {
+ msg := array of byte ("alg " + alg);
+ e := keyring->sendmsg(sys->fildes(1), msg, len msg);
+ if (e == -1)
+ error("couldn't write algorithm string");
+ }
+ }
+ fd := docrypt(decrypt, alg, sk);
+ if (decrypt) {
+ # if decrypting, don't use stream, as we want to catch
+ # decryption or checksum errors when they happen.
+ buf := array[Sys->ATOMICIO] of byte;
+ stdout := sys->fildes(1);
+ while ((n := sys->read(fd, buf, len buf)) > 0)
+ sys->write(stdout, buf, n);
+
+ if (n == -1) {
+ err := sys->sprint("%r");
+ if (err != Ehungup)
+ error("decryption failed: " + err);
+ }
+ } else {
+ stream(fd, sys->fildes(1), Sys->ATOMICIO);
+ }
+}
+
+docrypt(decrypt: int, alg: string, sk: array of byte): ref Sys->FD
+{
+ if (verbose)
+ sys->fprint(stderr, "%scrypting with alg %s\n", (array[] of {"en", "de"})[decrypt!=0], alg);
+ (err, fds, nil, nil) := cryptpipe(decrypt, alg, sk);
+ if (err != nil)
+ error(err);
+
+ spawn stream(sys->fildes(0), fds[1], Sys->ATOMICIO);
+ return fds[0];
+}
+
+# set up an encrypt/decrypt session; if decrypt is non-zero, then
+# decrypt, else encrypt. alg is the algorithm to use; sk is the
+# used as the secret key.
+# returns tuple (err, fds, cfd, dir)
+# where err is non-nil on failure;
+# otherwise fds is an array of two fds; writing to fds[1] will make
+# crypted/decrypted data available to be read on fds[0].
+# dir is the ssl directory in question.
+cryptpipe(decrypt: int, alg: string, sk: array of byte): (string, array of ref Sys->FD, ref Sys->FD, string)
+{
+ pfd := array[2] of ref Sys->FD;
+ if (sys->pipe(pfd) == -1)
+ return ("pipe failed", nil, nil, nil);
+
+ (err, c) := ssl->connect(pfd[1]);
+ if (err != nil)
+ return ("could not connect ssl: "+sys->sprint("%r"), nil, nil, nil);
+ pfd[1] = nil;
+ err = ssl->secret(c, sk, sk);
+ if (err != nil)
+ return ("could not write secret: "+sys->sprint("%r"), nil, nil, nil);
+
+ if (alg != nil)
+ if (sys->fprint(c.cfd, "alg %s", alg) == -1)
+ return (sys->sprint("bad algorithm %s: %r", alg), nil, nil, nil);
+
+ fds := array[2] of ref Sys->FD;
+ if (decrypt) {
+ fds[1] = pfd[0];
+ fds[0] = c.dfd;
+ } else {
+ fds[1] = c.dfd;
+ fds[0] = pfd[0];
+ }
+ return (nil, fds, c.cfd, c.dir);
+}
+
+algnames := array[] of {("crypt", "encalgs"), ("hash", "hashalgs")};
+
+# find available algorithms and return as tuple of two lists:
+# (err, hashalgs, cryptalgs)
+algs(): (string, array of list of string)
+{
+ (err, nil, nil, dir) := cryptpipe(0, nil, array[100] of byte);
+ if (err != nil)
+ return (err, nil);
+ alglists := array[len algnames] of list of string;
+ for (i := 0; i < len algnames; i++) {
+ (nil, f) := algnames[i];
+ (nil, alglists[i]) = sys->tokenize(string readfile(dir + "/" + f), " ");
+ }
+ return (nil, alglists);
+}
+
+showalgs(fd: ref Sys->FD)
+{
+ (err, alglists) := algs();
+ if (err != nil)
+ error("cannot get algorithms: " + err);
+ for (j := 0; j < len alglists; j++) {
+ (name, nil) := algnames[j];
+ sys->fprint(fd, "%s:", name);
+ for (l := alglists[j]; l != nil; l = tl l)
+ sys->fprint(fd, " %s", hd l);
+ sys->fprint(fd, "\n");
+ }
+}
+
+stream(src, dst: ref Sys->FD, bufsize: int)
+{
+ sys->stream(src, dst, bufsize);
+}
+
+readfile(f: string): array of byte
+{
+ fd := sys->open(f, Sys->OREAD);
+ if (fd == nil)
+ error(sys->sprint("cannot read %s: %r", f));
+ buf := array[8192] of byte; # >8K key? get real!
+ n := sys->read(fd, buf, len buf);
+ if (n <= 0)
+ return nil;
+ return buf[0:n];
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "crypt: %s\n", s);
+ fail("error");
+}
+
+fail(e: string)
+{
+ raise "fail: "+e;
+}
diff --git a/appl/cmd/date.b b/appl/cmd/date.b
new file mode 100644
index 00000000..83068e0d
--- /dev/null
+++ b/appl/cmd/date.b
@@ -0,0 +1,71 @@
+implement Date;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+include "daytime.m";
+include "arg.m";
+
+Date: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: date [-un] [seconds]\n");
+ raise "fail:usage";
+}
+
+nomod(m: string)
+{
+ sys->fprint(sys->fildes(2), "date: cannot load %s: %r", m);
+ raise "fail:load";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ daytime := load Daytime Daytime->PATH;
+ if (daytime == nil)
+ nomod(Daytime->PATH);
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ nomod(Arg->PATH);
+ nflag := uflag := 0;
+ arg->init(argv);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'n' =>
+ nflag = 1;
+ 'u' =>
+ uflag = 1;
+ * =>
+ usage();
+ }
+ }
+ argv = arg->argv();
+ arg = nil;
+ if (argv != nil && (tl argv != nil || !isnumeric(hd argv)))
+ usage();
+ now: int;
+ if (argv != nil)
+ now = int hd argv;
+ else
+ now = daytime->now();
+ if (nflag)
+ sys->print("%d\n", now);
+ else if (uflag)
+ sys->print("%s\n", daytime->text(daytime->gmt(now)));
+ else
+ sys->print("%s\n", daytime->text(daytime->local(now)));
+}
+
+isnumeric(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] < '0' || s[i] > '9')
+ return 0;
+ return 1;
+}
diff --git a/appl/cmd/dbfs.b b/appl/cmd/dbfs.b
new file mode 100644
index 00000000..9482e8df
--- /dev/null
+++ b/appl/cmd/dbfs.b
@@ -0,0 +1,518 @@
+implement Dbfs;
+
+#
+# Copyright © 1999 Vita Nuova Limited. All rights reserved.
+# Revisions copyright © 2002 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+ Qid: import Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+
+include "styxservers.m";
+ styxservers: Styxservers;
+ Fid, Styxserver, Navigator, Navop: import styxservers;
+ Enotfound, Eperm, Ebadarg: import styxservers;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Record: adt {
+ id: int; # file number in directory
+ x: int; # index in file
+ dirty: int; # modified but not written
+ vers: int; # version
+ data: array of byte;
+
+ new: fn(x: array of byte): ref Record;
+ print: fn(r: self ref Record, fd: ref Sys->FD);
+ qid: fn(r: self ref Record): Sys->Qid;
+};
+
+Database: adt {
+ name: string;
+ file: ref Iobuf;
+ records: array of ref Record;
+ dirty: int;
+ vers: int;
+ nextid: int;
+
+ findrec: fn(db: self ref Database, id: int): ref Record;
+};
+
+Dbfs: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Qdir, Qnew, Qdata: con iota;
+
+clockfd: ref Sys->FD;
+stderr: ref Sys->FD;
+database: ref Database;
+user: string;
+Eremoved: con "file removed";
+
+usage()
+{
+ sys->fprint(stderr, "Usage: dbfs [-a|-b|-ac|-bc] [-D] file mountpoint\n");
+ raise "fail:usage";
+}
+
+nomod(s: string)
+{
+ sys->fprint(stderr, "dbfs: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ stderr = sys->fildes(2);
+ styx = load Styx Styx->PATH;
+ if(styx == nil)
+ nomod(Styx->PATH);
+ styx->init();
+ styxservers = load Styxservers Styxservers->PATH;
+ if(styxservers == nil)
+ nomod(Styxservers->PATH);
+ styxservers->init(styx);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ nomod(Bufio->PATH);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ arg->init(args);
+ flags := Sys->MREPL;
+ copt := 0;
+ empty := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' => flags = Sys->MAFTER;
+ 'b' => flags = Sys->MBEFORE;
+ 'c' => copt = 1;
+ 'e' => empty = 1;
+ 'D' => styxservers->traceset(1);
+ * => usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(len args != 2)
+ usage();
+ if(copt)
+ flags |= Sys->MCREATE;
+ file := hd args;
+ args = tl args;
+ mountpt := hd args;
+
+ df := bufio->open(file, Sys->OREAD);
+ if(df == nil && empty){
+ (rc, d) := sys->stat(file);
+ if(rc < 0)
+ df = bufio->create(file, Sys->OREAD, 8r600);
+ }
+ if(df == nil){
+ sys->fprint(stderr, "dbfs: can't open %s: %r\n", file);
+ raise "fail:open";
+ }
+ (db, err) := dbread(ref Database(file, df, nil, 0, 0, 0));
+ if(db == nil){
+ sys->fprint(stderr, "dbfs: can't read %s: %s\n", file, err);
+ raise "fail:dbread";
+ }
+ db.file = nil;
+# dbprint(db);
+ database = db;
+
+ sys->pctl(Sys->FORKFD, nil);
+
+ user = rf("/dev/user");
+ if(user == nil)
+ user = "inferno";
+
+ fds := array[2] of ref Sys->FD;
+ if(sys->pipe(fds) < 0){
+ sys->fprint(stderr, "dbfs: can't create pipe: %r\n");
+ raise "fail:pipe";
+ }
+
+ navops := chan of ref Navop;
+ spawn navigator(navops);
+
+ (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qdir);
+ fds[0] = nil;
+
+ pidc := chan of int;
+ spawn serveloop(tchan, srv, pidc, navops);
+ <-pidc;
+
+ if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) {
+ sys->fprint(stderr, "dbfs: mount failed: %r\n");
+ raise "fail:mount";
+ }
+}
+
+rf(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ b := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, b, len b);
+ if(n < 0)
+ return nil;
+ return string b[0:n];
+}
+
+dbread(db: ref Database): (ref Database, string)
+{
+ db.file.seek(big 0, Sys->SEEKSTART);
+ rl: list of ref Record;
+ n := 0;
+ for(;;){
+ (r, err) := getrec(db);
+ if(err != nil)
+ return (nil, err); # could press on without it, or make it the `file' contents
+ if(r == nil)
+ break;
+ rl = r :: rl;
+ n++;
+ }
+ db.nextid = n;
+ db.records = array[n] of ref Record;
+ for(; rl != nil; rl = tl rl){
+ r := hd rl;
+ n--;
+ r.id = n;
+ r.x = n;
+ db.records[n] = r;
+ }
+ return (db, nil);
+}
+
+#
+# a record is (.+\n)*\n
+#
+getrec(db: ref Database): (ref Record, string)
+{
+ r := ref Record(-1, -1, 0, 0, nil);
+ data := "";
+ for(;;){
+ s := db.file.gets('\n');
+ if(s == nil){
+ if(data == nil)
+ return (nil, nil); # BUG: distinguish i/o error from EOF?
+ break;
+ }
+ if(s[len s - 1] != '\n')
+# return (nil, "file missing newline"); # possibly truncated
+ s += "\n";
+ if(s == "\n")
+ break;
+ data += s;
+ }
+ r.data = array of byte data;
+ return (r, nil);
+}
+
+dbsync(db: ref Database): int
+{
+ if(db.dirty){
+ db.file = bufio->create(db.name, Sys->OWRITE, 8r666);
+ if(db.file == nil)
+ return -1;
+ for(i := 0; i < len db.records; i++){
+ r := db.records[i];
+ if(r != nil && r.data != nil){
+ if(db.file.write(r.data, len r.data) != len r.data)
+ return -1;
+ db.file.putc('\n');
+ }
+ }
+ if(db.file.flush())
+ return -1;
+ db.file = nil;
+ db.dirty = 0;
+ }
+ return 0;
+}
+
+dbprint(db: ref Database)
+{
+ stdout := sys->fildes(1);
+ for(i := 0; i < len db.records; i++){
+ db.records[i].print(stdout);
+ sys->print("\n");
+ }
+}
+
+Database.findrec(db: self ref Database, id: int): ref Record
+{
+ for(i:=0; i<len db.records; i++)
+ if((r := db.records[i]) != nil && r.id == id)
+ return r;
+ return nil;
+}
+
+Record.new(fields: array of byte): ref Record
+{
+ n := len database.records;
+ r := ref Record(n, n, 0, 0, fields);
+ a := array[n+1] of ref Record;
+ if(n)
+ a[0:] = database.records[0:];
+ a[n] = r;
+ database.records = a;
+ database.vers++;
+ return r;
+}
+
+Record.print(r: self ref Record, fd: ref Sys->FD)
+{
+ if(r.data != nil)
+ sys->write(fd, r.data, len r.data);
+}
+
+Record.qid(r: self ref Record): Sys->Qid
+{
+ return Sys->Qid(QPATH(r.x, Qdata), r.vers, Sys->QTFILE);
+}
+
+serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop)
+{
+ pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::nil);
+Serve:
+ while((gm := <-tchan) != nil){
+ pick m := gm {
+ Readerror =>
+ sys->fprint(stderr, "dbfs: fatal read error: %s\n", m.error);
+ break Serve;
+ Open =>
+ c := srv.getfid(m.fid);
+ if(c == nil || TYPE(c.path) != Qnew){
+ srv.open(m); # default action
+ break;
+ }
+ if(c.uname != user) {
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ break;
+ }
+ mode := styxservers->openmode(m.mode);
+ if(mode < 0) {
+ srv.reply(ref Rmsg.Error(m.tag, Ebadarg));
+ break;
+ }
+ # generate new file, change Fid's qid to match
+ r := Record.new(array[0] of byte);
+ qid := r.qid();
+ c.open(mode, qid);
+ srv.reply(ref Rmsg.Open(m.tag, qid, srv.iounit()));
+ Read =>
+ (c, err) := srv.canread(m);
+ if(c == nil){
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ if(c.qtype & Sys->QTDIR){
+ srv.read(m); # does readdir
+ break;
+ }
+ r := database.records[FILENO(c.path)];
+ if(r == nil)
+ srv.reply(ref Rmsg.Error(m.tag, Eremoved));
+ else
+ srv.reply(styxservers->readbytes(m, r.data));
+ Write =>
+ (c, merr) := srv.canwrite(m);
+ if(c == nil){
+ srv.reply(ref Rmsg.Error(m.tag, merr));
+ break;
+ }
+ (value, err) := data2rec(m.data);
+ if(err != nil){
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ fno := FILENO(c.path);
+ r := database.records[fno];
+ if(r == nil){
+ srv.reply(ref Rmsg.Error(m.tag, Eremoved));
+ break;
+ }
+ r.data = value;
+ r.vers++;
+ database.dirty++;
+ if(dbsync(database) == 0)
+ srv.reply(ref Rmsg.Write(m.tag, len m.data));
+ else
+ srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ Clunk =>
+ # a transaction-oriented dbfs could delay updating the record until clunk
+ srv.clunk(m);
+ Remove =>
+ c := srv.getfid(m.fid);
+ if(c == nil || c.qtype & Sys->QTDIR || TYPE(c.path) != Qdata){
+ # let it diagnose all the errors
+ srv.remove(m);
+ break;
+ }
+ r := database.records[FILENO(c.path)];
+ if(r != nil)
+ r.data = nil;
+ database.dirty++;
+ srv.delfid(c);
+ if(dbsync(database) == 0)
+ srv.reply(ref Rmsg.Remove(m.tag));
+ else
+ srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ Wstat =>
+ srv.default(gm); # TO DO?
+ * =>
+ srv.default(gm);
+ }
+ }
+ navops <-= nil; # shut down navigator
+}
+
+dirslot(n: int): int
+{
+ for(i := 0; i < len database.records; i++){
+ r := database.records[i];
+ if(r != nil && r.data != nil){
+ if(n == 0)
+ return i;
+ n--;
+ }
+ }
+ return -1;
+}
+
+dir(qid: Sys->Qid, name: string, length: big, uid: string, perm: int): ref Sys->Dir
+{
+ d := ref sys->zerodir;
+ d.qid = qid;
+ if(qid.qtype & Sys->QTDIR)
+ perm |= Sys->DMDIR;
+ d.mode = perm;
+ d.name = name;
+ d.uid = uid;
+ d.gid = uid;
+ d.length = length;
+ return d;
+}
+
+dirgen(p: big): (ref Sys->Dir, string)
+{
+ case TYPE(p) {
+ Qdir =>
+ return (dir(Qid(QPATH(0, Qdir),database.vers,Sys->QTDIR), "/", big 0, user, 8r700), nil);
+ Qnew =>
+ return (dir(Qid(QPATH(0, Qnew),0,Sys->QTFILE), "new", big 0, user, 8r600), nil);
+ * =>
+ n := FILENO(p);
+ if(n < 0 || n >= len database.records)
+ return (nil, nil);
+ r := database.records[n];
+ if(r == nil || r.data == nil)
+ return (nil, Enotfound);
+ return (dir(r.qid(), sys->sprint("%d", r.id), big len r.data, user, 8r600), nil);
+ }
+}
+
+navigator(navops: chan of ref Navop)
+{
+ while((m := <-navops) != nil){
+ pick n := m {
+ Stat =>
+ n.reply <-= dirgen(n.path);
+ Walk =>
+ if(int n.path != Qdir){
+ n.reply <-= (nil, "not a directory");
+ break;
+ }
+ case n.name {
+ ".." =>
+ ; # nop
+ "new" =>
+ n.path = QPATH(0, Qnew);
+ * =>
+ if(len n.name < 1 || !(n.name[0]>='0' && n.name[0]<='9')){ # weak test for now
+ n.reply <-= (nil, Enotfound);
+ continue;
+ }
+ r := database.findrec(int n.name);
+ if(r == nil){
+ n.reply <-= (nil, Enotfound);
+ continue;
+ }
+ n.path = QPATH(r.x, Qdata);
+ }
+ n.reply <-= dirgen(n.path);
+ Readdir =>
+ if(int m.path != Qdir){
+ n.reply <-= (nil, "not a directory");
+ break;
+ }
+ i := n.offset;
+ if(i == 0)
+ n.reply <-= dirgen(QPATH(0,Qnew));
+ for(; --n.count >= 0 && (j := dirslot(i)) >= 0; i++)
+ n.reply <-= dirgen(QPATH(j,Qdata)); # n² but the file will be small
+ n.reply <-= (nil, nil);
+ }
+ }
+}
+
+QPATH(w, q: int): big
+{
+ return big ((w<<8)|q);
+}
+
+TYPE(path: big): int
+{
+ return int path & 16rFF;
+}
+
+FILENO(path: big) : int
+{
+ return (int path >> 8) & 16rFFFFFF;
+}
+
+#
+# a record is (.+\n)*, without final empty line
+#
+data2rec(data: array of byte): (array of byte, string)
+{
+ s: string;
+ for(b := data; len b > 0;){
+ (b, s) = getline(b);
+ if(s == nil || s[len s - 1] != '\n' || s == "\n")
+ return (nil, "partial or malformed record"); # possibly truncated
+ }
+ return (data, nil);
+}
+
+getline(b: array of byte): (array of byte, string)
+{
+ n := len b;
+ for(i := 0; i < n; i++){
+ (ch, l, nil) := sys->byte2char(b, i);
+ i += l;
+ if(l == 0 || ch == '\n')
+ break;
+ }
+ return (b[i:], string b[0:i]);
+}
diff --git a/appl/cmd/dbm/delete.b b/appl/cmd/dbm/delete.b
new file mode 100755
index 00000000..dcdde8c0
--- /dev/null
+++ b/appl/cmd/dbm/delete.b
@@ -0,0 +1,34 @@
+implement Dbmdelete;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "dbm.m";
+ dbm: Dbm;
+ Datum, Dbf: import dbm;
+
+Dbmdelete: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ dbm = load Dbm Dbm->PATH;
+
+ dbm->init();
+
+ args = tl args;
+ db := Dbf.open(hd args, Sys->ORDWR);
+ if(db == nil){
+ sys->fprint(sys->fildes(2), "dbm/delete: %s: %r\n", hd args);
+ raise "fail:open";
+ }
+ args = tl args;
+ key := hd args;
+ if(db.delete(array of byte key) < 0)
+ sys->fprint(sys->fildes(2), "not found\n");
+}
diff --git a/appl/cmd/dbm/fetch.b b/appl/cmd/dbm/fetch.b
new file mode 100755
index 00000000..3ebbc7d6
--- /dev/null
+++ b/appl/cmd/dbm/fetch.b
@@ -0,0 +1,37 @@
+implement Dbmfetch;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "dbm.m";
+ dbm: Dbm;
+ Datum, Dbf: import dbm;
+
+Dbmfetch: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ dbm = load Dbm Dbm->PATH;
+
+ dbm->init();
+
+ args = tl args;
+ db := Dbf.open(hd args, Sys->OREAD);
+ if(db == nil){
+ sys->fprint(sys->fildes(2), "dbm/fetch: %s: %r\n", hd args);
+ raise "fail:open";
+ }
+ args = tl args;
+ key := hd args;
+ data := db.fetch(array of byte key);
+ if(data == nil)
+ sys->fprint(sys->fildes(2), "not found\n");
+ else
+ sys->write(sys->fildes(1), data, len data);
+}
diff --git a/appl/cmd/dbm/keys.b b/appl/cmd/dbm/keys.b
new file mode 100755
index 00000000..750d734c
--- /dev/null
+++ b/appl/cmd/dbm/keys.b
@@ -0,0 +1,32 @@
+implement Dbmkeys;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "dbm.m";
+ dbm: Dbm;
+ Datum, Dbf: import dbm;
+
+Dbmkeys: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ dbm = load Dbm Dbm->PATH;
+
+ dbm->init();
+
+ args = tl args;
+ db := Dbf.open(hd args, Sys->OREAD);
+ if(db == nil){
+ sys->fprint(sys->fildes(2), "dbm/keys: %s: %r\n", hd args);
+ raise "fail:open";
+ }
+ for(key := db.firstkey(); key != nil; key = db.nextkey(key))
+ sys->print("%s\n", string key);
+}
diff --git a/appl/cmd/dbm/list.b b/appl/cmd/dbm/list.b
new file mode 100755
index 00000000..6c0e71f5
--- /dev/null
+++ b/appl/cmd/dbm/list.b
@@ -0,0 +1,34 @@
+implement Dbmlist;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "dbm.m";
+ dbm: Dbm;
+ Datum, Dbf: import dbm;
+
+Dbmlist: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ dbm = load Dbm Dbm->PATH;
+
+ dbm->init();
+
+ args = tl args;
+ db := Dbf.open(hd args, Sys->OREAD);
+ if(db == nil){
+ sys->fprint(sys->fildes(2), "dbm/list: %s: %r\n", hd args);
+ raise "fail:open";
+ }
+ for(key := db.firstkey(); key != nil; key = db.nextkey(key)){
+ d := db.fetch(key);
+ sys->print("%s %s\n", string key, string d);
+ }
+}
diff --git a/appl/cmd/dbm/mkfile b/appl/cmd/dbm/mkfile
new file mode 100644
index 00000000..7ab434c9
--- /dev/null
+++ b/appl/cmd/dbm/mkfile
@@ -0,0 +1,19 @@
+<../../../mkconfig
+
+TARG=\
+ fetch.dis\
+ delete.dis\
+ keys.dis\
+ list.dis\
+ store.dis\
+
+SYSMODULES=\
+ arg.m\
+ sys.m\
+ draw.m\
+ bufio.m\
+ dbm.m\
+
+DISBIN=$ROOT/dis/dbm
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/dbm/store.b b/appl/cmd/dbm/store.b
new file mode 100755
index 00000000..0587c4b7
--- /dev/null
+++ b/appl/cmd/dbm/store.b
@@ -0,0 +1,69 @@
+implement Dbmstore;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "dbm.m";
+ dbm: Dbm;
+ Datum, Dbf: import dbm;
+
+Dbmstore: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ dbm = load Dbm Dbm->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ dbm->init();
+
+ args = tl args;
+ db := Dbf.open(hd args, Sys->ORDWR);
+ if(db == nil){
+ sys->fprint(sys->fildes(2), "dbm/store: %s: %r\n", hd args);
+ raise "fail:open";
+ }
+ args = tl args;
+ if(args == nil){
+ err := 0;
+ f := bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ while((s := f.gets('\n')) != nil){
+ s = s[0:len s-1];
+ key: string;
+ for(i :=0; i < len s; i++)
+ if(s[i] == ' ' || s[i] == '\t'){
+ key = s[0:i];
+ s = s[i+1:];
+ break;
+ }
+ if(key == nil){
+ sys->fprint(sys->fildes(2), "dbm/store: bad input\n");
+ raise "fail:error";
+ }
+ if(store(db, key, s))
+ err = 1;
+ }
+ if(err)
+ raise "fail:store";
+ }else if(store(db, hd args, hd tl args))
+ raise "fail:store";
+}
+
+store(db: ref Dbf, key: string, dat: string): int
+{
+ r := db.store(array of byte key, array of byte dat, 0);
+ if(r < 0)
+ sys->fprint(sys->fildes(2), "bad store\n");
+ else if(r)
+ sys->fprint(sys->fildes(2), "%q exists\n", key);
+ return r;
+}
diff --git a/appl/cmd/dd.b b/appl/cmd/dd.b
new file mode 100644
index 00000000..cba8067c
--- /dev/null
+++ b/appl/cmd/dd.b
@@ -0,0 +1,625 @@
+implement dd;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+
+include "draw.m";
+
+dd: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+BIG: con 2147483647;
+LCASE,
+UCASE,
+SWAB,
+NERR ,
+SYNC : con (1<<iota);
+
+NULL,
+CNULL,
+EBCDIC,
+IBM,
+ASCII,
+BLOCK,
+UNBLOCK: con iota;
+
+cflag: int;
+ctype: int;
+
+fflag: int;
+arg: string;
+ifile: string;
+ofile: string;
+ibuf: array of byte;
+obuf: array of byte;
+op: int;
+skip: int;
+oseekn: int;
+iseekn: int;
+count: int;
+files:= 1;
+ibs:= 512;
+obs:= 512;
+bs: int;
+cbs: int;
+ibc: int;
+obc: int;
+cbc: int;
+nifr: int;
+nipr: int;
+nofr: int;
+nopr: int;
+ntrunc: int;
+ibf: ref Sys->FD;
+obf: ref Sys->FD;
+nspace: int;
+
+iskey(key:string, s: string): int
+{
+ return key[0] == '-' && key[1:] == s;
+}
+
+exits(msg: string)
+{
+ if(msg == nil)
+ exit;
+
+ raise "fail:"+msg;
+}
+
+perror(msg: string)
+{
+ sys->fprint(stderr, "%s: %r\n", msg);
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if(sys == nil)
+ return;
+ stderr = sys->fildes(2);
+
+ ctype = NULL;
+ argv = tl argv;
+ while(argv != nil) {
+ key := hd argv;
+ argv = tl argv;
+ if(argv == nil){
+ sys->fprint(stderr, "dd: arg %s needs a value\n", key);
+ exits("arg");
+ }
+ arg = hd argv;
+ argv = tl argv;
+ if(iskey(key, "ibs")) {
+ ibs = number(BIG);
+ continue;
+ }
+ if(iskey(key, "obs")) {
+ obs = number(BIG);
+ continue;
+ }
+ if(iskey(key, "cbs")) {
+ cbs = number(BIG);
+ continue;
+ }
+ if(iskey(key, "bs")) {
+ bs = number(BIG);
+ continue;
+ }
+ if(iskey(key, "if")) {
+ ifile = arg[0:];
+ continue;
+ }
+ if(iskey(key, "of")) {
+ ofile = arg[0:];
+ continue;
+ }
+ if(iskey(key, "skip")) {
+ skip = number(BIG);
+ continue;
+ }
+ if(iskey(key, "seek") || iskey(key, "oseek")) {
+ oseekn = number(BIG);
+ continue;
+ }
+ if(iskey(key, "iseek")) {
+ iseekn = number(BIG);
+ continue;
+ }
+ if(iskey(key, "count")) {
+ count = number(BIG);
+ continue;
+ }
+ if(iskey(key, "files")) {
+ files = number(BIG);
+ continue;
+ }
+ if(iskey(key, "conv")) {
+ do {
+ if(arg == nil)
+ break;
+ if(match(","))
+ continue;
+ if(match("ebcdic")) {
+ ctype = EBCDIC;
+ continue;
+ }
+ if(match("ibm")) {
+ ctype = IBM;
+ continue;
+ }
+ if(match("ascii")) {
+ ctype = ASCII;
+ continue;
+ }
+ if(match("block")) {
+ ctype = BLOCK;
+ continue;
+ }
+ if(match("unblock")) {
+ ctype = UNBLOCK;
+ continue;
+ }
+ if(match("lcase")) {
+ cflag |= LCASE;
+ continue;
+ }
+ if(match("ucase")) {
+ cflag |= UCASE;
+ continue;
+ }
+ if(match("swab")) {
+ cflag |= SWAB;
+ continue;
+ }
+ if(match("noerror")) {
+ cflag |= NERR;
+ continue;
+ }
+ if(match("sync")) {
+ cflag |= SYNC;
+ continue;
+ }
+ } while(1);
+ continue;
+ }
+ sys->fprint(stderr, "dd: bad arg: %s\n", key);
+ exits("arg");
+ }
+ if(ctype == NULL && cflag&(LCASE|UCASE))
+ ctype = CNULL;
+ if(ifile != nil)
+ ibf = sys->open(ifile, Sys->OREAD);
+ else
+ ibf = sys->fildes(sys->dup(0, -1));
+
+ if(ibf == nil) {
+ sys->fprint(stderr, "dd: open %s: %r\n", ifile);
+ exits("open");
+ }
+
+ if(ofile != nil){
+ obf = sys->create(ofile, Sys->OWRITE, 8r664);
+ if(obf == nil) {
+ sys->fprint(stderr, "dd: create %s: %r\n", ofile);
+ exits("create");
+ }
+ }else{
+ obf = sys->fildes(sys->dup(1, -1));
+ if(obf == nil) {
+ sys->fprint(stderr, "dd: can't dup file descriptor: %r\n");
+ exits("dup");
+ }
+ }
+ if(bs)
+ ibs = obs = bs;
+ if(ibs == obs && ctype == NULL)
+ fflag++;
+ if(ibs == 0 || obs == 0) {
+ sys->fprint(stderr, "dd: counts: cannot be zero\n");
+ exits("counts");
+ }
+ ibuf = array[ibs] of byte;
+ obuf = array[obs] of byte;
+
+ if(fflag)
+ obuf = ibuf;
+
+ sys->seek(obf, big obs*big oseekn, Sys->SEEKRELA);
+ sys->seek(ibf, big ibs*big iseekn, Sys->SEEKRELA);
+ while(skip) {
+ sys->read(ibf, ibuf, ibs);
+ skip--;
+ }
+
+ ibc = 0;
+ obc = 0;
+ cbc = 0;
+ op = 0;
+ ip := 0;
+ do {
+ if(ibc-- == 0) {
+ ibc = 0;
+ if(count==0 || nifr+nipr!=count) {
+ if(cflag&(NERR|SYNC))
+ for(ip=0; ip < len ibuf; ip++)
+ ibuf[ip] = byte 0;
+ ibc = sys->read(ibf, ibuf, ibs);
+ }
+ if(ibc == -1) {
+ perror("read");
+ if((cflag&NERR) == 0) {
+ flsh();
+ term();
+ }
+ ibc = 0;
+ for(c:=0; c<ibs; c++)
+ if(ibuf[c] != byte 0)
+ ibc = c;
+ stats();
+ }
+ if(ibc == 0 && --files<=0) {
+ flsh();
+ term();
+ }
+ if(ibc != ibs) {
+ nipr++;
+ if(cflag&SYNC)
+ ibc = ibs;
+ } else
+ nifr++;
+ ip = 0;
+ c := (ibc>>1) & ~1;
+ if(cflag&SWAB && c) do {
+ a := ibuf[ip++];
+ ibuf[ip-1] = ibuf[ip];
+ ibuf[ip++] = a;
+ } while(--c);
+ if(fflag) {
+ obc = ibc;
+ flsh();
+ ibc = 0;
+ }
+ continue;
+ }
+ c := 0;
+ c |= int ibuf[ip++];
+ c &= 8r377;
+ conv(c);
+ } while(1);
+}
+
+conv(c: int)
+{
+ case ctype {
+ NULL => null(c);
+ CNULL => cnull(c);
+ EBCDIC => ebcdic(c);
+ IBM => ibm(c);
+ ASCII => ascii(c);
+ BLOCK => block(c);
+ UNBLOCK => unblock(c);
+ }
+}
+
+flsh()
+{
+ if(obc) {
+ if(obc == obs)
+ nofr++;
+ else
+ nopr++;
+ c := sys->write(obf, obuf, obc);
+ if(c != obc) {
+ perror("write");
+ term();
+ }
+ obc = 0;
+ }
+}
+
+match(s: string): int
+{
+ if(len s > len arg)
+ return 0;
+ if(arg[:len s] == s) {
+ arg = arg[len s:];
+ return 1;
+ }
+ return 0;
+}
+
+
+number(bignum: int): int
+{
+ n := 0;
+ i := 0;
+ while(i < len arg && arg[i] >= '0' && arg[i] <= '9')
+ n = n*10 + arg[i++] - '0';
+ for(;i<len arg; i++) case(arg[i]) {
+ 'k' =>
+ n *= 1024;
+ 'b' =>
+ n *= 512;
+ 'x' =>
+ arg = arg[i:];
+ n *= number(BIG);
+ }
+ if(n>=bignum || n<0) {
+ sys->fprint(stderr, "dd: argument out of range\n");
+ exits("range");
+ }
+ return n;
+}
+
+cnull(cc: int)
+{
+ c := cc;
+ if((cflag&UCASE) && c>='a' && c<='z')
+ c += 'A'-'a';
+ if((cflag&LCASE) && c>='A' && c<='Z')
+ c += 'a'-'A';
+ null(c);
+}
+
+null(c: int)
+{
+ obuf[op++] = byte c;
+ if(++obc >= obs) {
+ flsh();
+ op = 0;
+ }
+}
+
+ascii(cc: int)
+{
+ c := etoa[cc];
+ if(cbs == 0) {
+ cnull(int c);
+ return;
+ }
+ if(c == byte ' ')
+ nspace++;
+ else {
+ while(nspace > 0) {
+ null(' ');
+ nspace--;
+ }
+ cnull(int c);
+ }
+
+ if(++cbc >= cbs) {
+ null('\n');
+ cbc = 0;
+ nspace = 0;
+ }
+}
+
+unblock(cc: int)
+{
+ c := cc & 8r377;
+ if(cbs == 0) {
+ cnull(c);
+ return;
+ }
+ if(c == ' ')
+ nspace++;
+ else {
+ while(nspace > 0) {
+ null(' ');
+ nspace--;
+ }
+ cnull(c);
+ }
+
+ if(++cbc >= cbs) {
+ null('\n');
+ cbc = 0;
+ nspace = 0;
+ }
+}
+
+ebcdic(cc: int)
+{
+
+ c := cc;
+ if(cflag&UCASE && c>='a' && c<='z')
+ c += 'A'-'a';
+ if(cflag&LCASE && c>='A' && c<='Z')
+ c += 'a'-'A';
+ c = int atoe[c];
+ if(cbs == 0) {
+ null(c);
+ return;
+ }
+ if(cc == '\n') {
+ while(cbc < cbs) {
+ null(int atoe[' ']);
+ cbc++;
+ }
+ cbc = 0;
+ return;
+ }
+ if(cbc == cbs)
+ ntrunc++;
+ cbc++;
+ if(cbc <= cbs)
+ null(c);
+}
+
+ibm(cc: int)
+{
+ c := cc;
+ if(cflag&UCASE && c>='a' && c<='z')
+ c += 'A'-'a';
+ if(cflag&LCASE && c>='A' && c<='Z')
+ c += 'a'-'A';
+ c = int atoibm[c] & 8r377;
+ if(cbs == 0) {
+ null(c);
+ return;
+ }
+ if(cc == '\n') {
+ while(cbc < cbs) {
+ null(int atoibm[' ']);
+ cbc++;
+ }
+ cbc = 0;
+ return;
+ }
+ if(cbc == cbs)
+ ntrunc++;
+ cbc++;
+ if(cbc <= cbs)
+ null(c);
+}
+
+block(cc: int)
+{
+ c := cc;
+ if(cflag&UCASE && c>='a' && c<='z')
+ c += 'A'-'a';
+ if(cflag&LCASE && c>='A' && c<='Z')
+ c += 'a'-'A';
+ c &= 8r377;
+ if(cbs == 0) {
+ null(c);
+ return;
+ }
+ if(cc == '\n') {
+ while(cbc < cbs) {
+ null(' ');
+ cbc++;
+ }
+ cbc = 0;
+ return;
+ }
+ if(cbc == cbs)
+ ntrunc++;
+ cbc++;
+ if(cbc <= cbs)
+ null(c);
+}
+
+term()
+{
+ stats();
+ exits(nil);
+}
+
+stats()
+{
+ sys->fprint(stderr, "%ud+%ud records in\n", nifr, nipr);
+ sys->fprint(stderr, "%ud+%ud records out\n", nofr, nopr);
+ if(ntrunc)
+ sys->fprint(stderr, "%ud truncated records\n", ntrunc);
+}
+
+etoa := array[] of
+{
+ byte 8r000,byte 8r001,byte 8r002,byte 8r003,byte 8r234,byte 8r011,byte 8r206,byte 8r177,
+ byte 8r227,byte 8r215,byte 8r216,byte 8r013,byte 8r014,byte 8r015,byte 8r016,byte 8r017,
+ byte 8r020,byte 8r021,byte 8r022,byte 8r023,byte 8r235,byte 8r205,byte 8r010,byte 8r207,
+ byte 8r030,byte 8r031,byte 8r222,byte 8r217,byte 8r034,byte 8r035,byte 8r036,byte 8r037,
+ byte 8r200,byte 8r201,byte 8r202,byte 8r203,byte 8r204,byte 8r012,byte 8r027,byte 8r033,
+ byte 8r210,byte 8r211,byte 8r212,byte 8r213,byte 8r214,byte 8r005,byte 8r006,byte 8r007,
+ byte 8r220,byte 8r221,byte 8r026,byte 8r223,byte 8r224,byte 8r225,byte 8r226,byte 8r004,
+ byte 8r230,byte 8r231,byte 8r232,byte 8r233,byte 8r024,byte 8r025,byte 8r236,byte 8r032,
+ byte 8r040,byte 8r240,byte 8r241,byte 8r242,byte 8r243,byte 8r244,byte 8r245,byte 8r246,
+ byte 8r247,byte 8r250,byte 8r133,byte 8r056,byte 8r074,byte 8r050,byte 8r053,byte 8r041,
+ byte 8r046,byte 8r251,byte 8r252,byte 8r253,byte 8r254,byte 8r255,byte 8r256,byte 8r257,
+ byte 8r260,byte 8r261,byte 8r135,byte 8r044,byte 8r052,byte 8r051,byte 8r073,byte 8r136,
+ byte 8r055,byte 8r057,byte 8r262,byte 8r263,byte 8r264,byte 8r265,byte 8r266,byte 8r267,
+ byte 8r270,byte 8r271,byte 8r174,byte 8r054,byte 8r045,byte 8r137,byte 8r076,byte 8r077,
+ byte 8r272,byte 8r273,byte 8r274,byte 8r275,byte 8r276,byte 8r277,byte 8r300,byte 8r301,
+ byte 8r302,byte 8r140,byte 8r072,byte 8r043,byte 8r100,byte 8r047,byte 8r075,byte 8r042,
+ byte 8r303,byte 8r141,byte 8r142,byte 8r143,byte 8r144,byte 8r145,byte 8r146,byte 8r147,
+ byte 8r150,byte 8r151,byte 8r304,byte 8r305,byte 8r306,byte 8r307,byte 8r310,byte 8r311,
+ byte 8r312,byte 8r152,byte 8r153,byte 8r154,byte 8r155,byte 8r156,byte 8r157,byte 8r160,
+ byte 8r161,byte 8r162,byte 8r313,byte 8r314,byte 8r315,byte 8r316,byte 8r317,byte 8r320,
+ byte 8r321,byte 8r176,byte 8r163,byte 8r164,byte 8r165,byte 8r166,byte 8r167,byte 8r170,
+ byte 8r171,byte 8r172,byte 8r322,byte 8r323,byte 8r324,byte 8r325,byte 8r326,byte 8r327,
+ byte 8r330,byte 8r331,byte 8r332,byte 8r333,byte 8r334,byte 8r335,byte 8r336,byte 8r337,
+ byte 8r340,byte 8r341,byte 8r342,byte 8r343,byte 8r344,byte 8r345,byte 8r346,byte 8r347,
+ byte 8r173,byte 8r101,byte 8r102,byte 8r103,byte 8r104,byte 8r105,byte 8r106,byte 8r107,
+ byte 8r110,byte 8r111,byte 8r350,byte 8r351,byte 8r352,byte 8r353,byte 8r354,byte 8r355,
+ byte 8r175,byte 8r112,byte 8r113,byte 8r114,byte 8r115,byte 8r116,byte 8r117,byte 8r120,
+ byte 8r121,byte 8r122,byte 8r356,byte 8r357,byte 8r360,byte 8r361,byte 8r362,byte 8r363,
+ byte 8r134,byte 8r237,byte 8r123,byte 8r124,byte 8r125,byte 8r126,byte 8r127,byte 8r130,
+ byte 8r131,byte 8r132,byte 8r364,byte 8r365,byte 8r366,byte 8r367,byte 8r370,byte 8r371,
+ byte 8r060,byte 8r061,byte 8r062,byte 8r063,byte 8r064,byte 8r065,byte 8r066,byte 8r067,
+ byte 8r070,byte 8r071,byte 8r372,byte 8r373,byte 8r374,byte 8r375,byte 8r376,byte 8r377,
+};
+atoe := array[] of
+{
+ byte 8r000,byte 8r001,byte 8r002,byte 8r003,byte 8r067,byte 8r055,byte 8r056,byte 8r057,
+ byte 8r026,byte 8r005,byte 8r045,byte 8r013,byte 8r014,byte 8r015,byte 8r016,byte 8r017,
+ byte 8r020,byte 8r021,byte 8r022,byte 8r023,byte 8r074,byte 8r075,byte 8r062,byte 8r046,
+ byte 8r030,byte 8r031,byte 8r077,byte 8r047,byte 8r034,byte 8r035,byte 8r036,byte 8r037,
+ byte 8r100,byte 8r117,byte 8r177,byte 8r173,byte 8r133,byte 8r154,byte 8r120,byte 8r175,
+ byte 8r115,byte 8r135,byte 8r134,byte 8r116,byte 8r153,byte 8r140,byte 8r113,byte 8r141,
+ byte 8r360,byte 8r361,byte 8r362,byte 8r363,byte 8r364,byte 8r365,byte 8r366,byte 8r367,
+ byte 8r370,byte 8r371,byte 8r172,byte 8r136,byte 8r114,byte 8r176,byte 8r156,byte 8r157,
+ byte 8r174,byte 8r301,byte 8r302,byte 8r303,byte 8r304,byte 8r305,byte 8r306,byte 8r307,
+ byte 8r310,byte 8r311,byte 8r321,byte 8r322,byte 8r323,byte 8r324,byte 8r325,byte 8r326,
+ byte 8r327,byte 8r330,byte 8r331,byte 8r342,byte 8r343,byte 8r344,byte 8r345,byte 8r346,
+ byte 8r347,byte 8r350,byte 8r351,byte 8r112,byte 8r340,byte 8r132,byte 8r137,byte 8r155,
+ byte 8r171,byte 8r201,byte 8r202,byte 8r203,byte 8r204,byte 8r205,byte 8r206,byte 8r207,
+ byte 8r210,byte 8r211,byte 8r221,byte 8r222,byte 8r223,byte 8r224,byte 8r225,byte 8r226,
+ byte 8r227,byte 8r230,byte 8r231,byte 8r242,byte 8r243,byte 8r244,byte 8r245,byte 8r246,
+ byte 8r247,byte 8r250,byte 8r251,byte 8r300,byte 8r152,byte 8r320,byte 8r241,byte 8r007,
+ byte 8r040,byte 8r041,byte 8r042,byte 8r043,byte 8r044,byte 8r025,byte 8r006,byte 8r027,
+ byte 8r050,byte 8r051,byte 8r052,byte 8r053,byte 8r054,byte 8r011,byte 8r012,byte 8r033,
+ byte 8r060,byte 8r061,byte 8r032,byte 8r063,byte 8r064,byte 8r065,byte 8r066,byte 8r010,
+ byte 8r070,byte 8r071,byte 8r072,byte 8r073,byte 8r004,byte 8r024,byte 8r076,byte 8r341,
+ byte 8r101,byte 8r102,byte 8r103,byte 8r104,byte 8r105,byte 8r106,byte 8r107,byte 8r110,
+ byte 8r111,byte 8r121,byte 8r122,byte 8r123,byte 8r124,byte 8r125,byte 8r126,byte 8r127,
+ byte 8r130,byte 8r131,byte 8r142,byte 8r143,byte 8r144,byte 8r145,byte 8r146,byte 8r147,
+ byte 8r150,byte 8r151,byte 8r160,byte 8r161,byte 8r162,byte 8r163,byte 8r164,byte 8r165,
+ byte 8r166,byte 8r167,byte 8r170,byte 8r200,byte 8r212,byte 8r213,byte 8r214,byte 8r215,
+ byte 8r216,byte 8r217,byte 8r220,byte 8r232,byte 8r233,byte 8r234,byte 8r235,byte 8r236,
+ byte 8r237,byte 8r240,byte 8r252,byte 8r253,byte 8r254,byte 8r255,byte 8r256,byte 8r257,
+ byte 8r260,byte 8r261,byte 8r262,byte 8r263,byte 8r264,byte 8r265,byte 8r266,byte 8r267,
+ byte 8r270,byte 8r271,byte 8r272,byte 8r273,byte 8r274,byte 8r275,byte 8r276,byte 8r277,
+ byte 8r312,byte 8r313,byte 8r314,byte 8r315,byte 8r316,byte 8r317,byte 8r332,byte 8r333,
+ byte 8r334,byte 8r335,byte 8r336,byte 8r337,byte 8r352,byte 8r353,byte 8r354,byte 8r355,
+ byte 8r356,byte 8r357,byte 8r372,byte 8r373,byte 8r374,byte 8r375,byte 8r376,byte 8r377,
+};
+atoibm := array[] of
+{
+ byte 8r000,byte 8r001,byte 8r002,byte 8r003,byte 8r067,byte 8r055,byte 8r056,byte 8r057,
+ byte 8r026,byte 8r005,byte 8r045,byte 8r013,byte 8r014,byte 8r015,byte 8r016,byte 8r017,
+ byte 8r020,byte 8r021,byte 8r022,byte 8r023,byte 8r074,byte 8r075,byte 8r062,byte 8r046,
+ byte 8r030,byte 8r031,byte 8r077,byte 8r047,byte 8r034,byte 8r035,byte 8r036,byte 8r037,
+ byte 8r100,byte 8r132,byte 8r177,byte 8r173,byte 8r133,byte 8r154,byte 8r120,byte 8r175,
+ byte 8r115,byte 8r135,byte 8r134,byte 8r116,byte 8r153,byte 8r140,byte 8r113,byte 8r141,
+ byte 8r360,byte 8r361,byte 8r362,byte 8r363,byte 8r364,byte 8r365,byte 8r366,byte 8r367,
+ byte 8r370,byte 8r371,byte 8r172,byte 8r136,byte 8r114,byte 8r176,byte 8r156,byte 8r157,
+ byte 8r174,byte 8r301,byte 8r302,byte 8r303,byte 8r304,byte 8r305,byte 8r306,byte 8r307,
+ byte 8r310,byte 8r311,byte 8r321,byte 8r322,byte 8r323,byte 8r324,byte 8r325,byte 8r326,
+ byte 8r327,byte 8r330,byte 8r331,byte 8r342,byte 8r343,byte 8r344,byte 8r345,byte 8r346,
+ byte 8r347,byte 8r350,byte 8r351,byte 8r255,byte 8r340,byte 8r275,byte 8r137,byte 8r155,
+ byte 8r171,byte 8r201,byte 8r202,byte 8r203,byte 8r204,byte 8r205,byte 8r206,byte 8r207,
+ byte 8r210,byte 8r211,byte 8r221,byte 8r222,byte 8r223,byte 8r224,byte 8r225,byte 8r226,
+ byte 8r227,byte 8r230,byte 8r231,byte 8r242,byte 8r243,byte 8r244,byte 8r245,byte 8r246,
+ byte 8r247,byte 8r250,byte 8r251,byte 8r300,byte 8r117,byte 8r320,byte 8r241,byte 8r007,
+ byte 8r040,byte 8r041,byte 8r042,byte 8r043,byte 8r044,byte 8r025,byte 8r006,byte 8r027,
+ byte 8r050,byte 8r051,byte 8r052,byte 8r053,byte 8r054,byte 8r011,byte 8r012,byte 8r033,
+ byte 8r060,byte 8r061,byte 8r032,byte 8r063,byte 8r064,byte 8r065,byte 8r066,byte 8r010,
+ byte 8r070,byte 8r071,byte 8r072,byte 8r073,byte 8r004,byte 8r024,byte 8r076,byte 8r341,
+ byte 8r101,byte 8r102,byte 8r103,byte 8r104,byte 8r105,byte 8r106,byte 8r107,byte 8r110,
+ byte 8r111,byte 8r121,byte 8r122,byte 8r123,byte 8r124,byte 8r125,byte 8r126,byte 8r127,
+ byte 8r130,byte 8r131,byte 8r142,byte 8r143,byte 8r144,byte 8r145,byte 8r146,byte 8r147,
+ byte 8r150,byte 8r151,byte 8r160,byte 8r161,byte 8r162,byte 8r163,byte 8r164,byte 8r165,
+ byte 8r166,byte 8r167,byte 8r170,byte 8r200,byte 8r212,byte 8r213,byte 8r214,byte 8r215,
+ byte 8r216,byte 8r217,byte 8r220,byte 8r232,byte 8r233,byte 8r234,byte 8r235,byte 8r236,
+ byte 8r237,byte 8r240,byte 8r252,byte 8r253,byte 8r254,byte 8r255,byte 8r256,byte 8r257,
+ byte 8r260,byte 8r261,byte 8r262,byte 8r263,byte 8r264,byte 8r265,byte 8r266,byte 8r267,
+ byte 8r270,byte 8r271,byte 8r272,byte 8r273,byte 8r274,byte 8r275,byte 8r276,byte 8r277,
+ byte 8r312,byte 8r313,byte 8r314,byte 8r315,byte 8r316,byte 8r317,byte 8r332,byte 8r333,
+ byte 8r334,byte 8r335,byte 8r336,byte 8r337,byte 8r352,byte 8r353,byte 8r354,byte 8r355,
+ byte 8r356,byte 8r357,byte 8r372,byte 8r373,byte 8r374,byte 8r375,byte 8r376,byte 8r377,
+};
diff --git a/appl/cmd/dial.b b/appl/cmd/dial.b
new file mode 100644
index 00000000..c562a570
--- /dev/null
+++ b/appl/cmd/dial.b
@@ -0,0 +1,148 @@
+implement Dial;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+include "keyring.m";
+ keyring: Keyring;
+include "security.m";
+ auth: Auth;
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+
+Dial: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+badmodule(p: string)
+{
+ sys->fprint(stderr(), "dial: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+DEFAULTALG := "none";
+
+verbose := 0;
+
+init(drawctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ keyring = load Keyring Keyring->PATH;
+ auth = load Auth Auth->PATH;
+ if (auth == nil)
+ badmodule(Auth->PATH);
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+ sh = load Sh Sh->PATH;
+ if (sh == nil)
+ badmodule(Sh->PATH);
+
+ auth->init();
+ alg: string;
+ keyfile: string;
+ doauth := 1;
+ arg->init(argv);
+ arg->setusage("dial [-A] [-k keyfile] [-a alg] addr command [arg...]");
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'A' =>
+ doauth = 0;
+ 'a' =>
+ alg = arg->earg();
+ 'f' or
+ 'k' =>
+ keyfile = arg->earg();
+ if (! (keyfile[0] == '/' || (len keyfile > 2 && keyfile[0:2] == "./")))
+ keyfile = "/usr/" + user() + "/keyring/" + keyfile;
+ 'v' =>
+ verbose = 1;
+ * =>
+ arg->usage();
+ }
+ }
+ argv = arg->argv();
+ if (len argv < 2)
+ arg->usage();
+ arg = nil;
+ (addr, shcmd) := (hd argv, tl argv);
+
+ if (doauth && alg == nil)
+ alg = DEFAULTALG;
+
+ if (alg != nil && keyfile == nil) {
+ kd := "/usr/" + user() + "/keyring/";
+ if (exists(kd + addr))
+ keyfile = kd + addr;
+ else
+ keyfile = kd + "default";
+ }
+ cert: ref Keyring->Authinfo;
+ if (alg != nil) {
+ cert = keyring->readauthinfo(keyfile);
+ if (cert == nil) {
+ sys->fprint(stderr(), "dial: cannot read %s: %r\n", keyfile);
+ raise "fail:bad keyfile";
+ }
+ }
+
+ (ok, c) := sys->dial(addr, nil);
+ if (ok == -1) {
+ sys->fprint(stderr(), "dial: cannot dial %s:: %r\n", addr);
+ raise "fail:errors";
+ }
+ user: string;
+ if (alg != nil) {
+ err: string;
+ (c.dfd, err) = auth->client(alg, cert, c.dfd);
+ if (c.dfd == nil) {
+ sys->fprint(stderr(), "dial: authentication failed: %s\n", err);
+ raise "fail:errors";
+ }
+ user = err;
+ }
+ sys->dup(c.dfd.fd, 0);
+ sys->dup(c.dfd.fd, 1);
+ c.dfd = c.cfd = nil;
+ ctxt := Context.new(drawctxt);
+ if (user != nil)
+ ctxt.set("user", sh->stringlist2list(user :: nil));
+ else
+ ctxt.set("user", nil);
+ ctxt.set("net", ref Sh->Listnode(nil, c.dir) :: nil);
+ ctxt.run(sh->stringlist2list(shcmd), 1);
+}
+
+exists(f: string): int
+{
+ (ok, nil) := sys->stat(f);
+ return ok != -1;
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+user(): string
+{
+ u := readfile("/dev/user");
+ if (u == nil)
+ return "nobody";
+ return u;
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, sys->OREAD);
+ if(fd == nil)
+ return nil;
+
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return nil;
+
+ return string buf[0:n];
+}
diff --git a/appl/cmd/diff.b b/appl/cmd/diff.b
new file mode 100644
index 00000000..4ef3ab32
--- /dev/null
+++ b/appl/cmd/diff.b
@@ -0,0 +1,858 @@
+implement Diff;
+
+# diff - differential file comparison
+#
+# Uses an algorithm due to Harold Stone, which finds
+# a pair of longest identical subsequences in the two
+# files.
+#
+# The major goal is to generate the match vector J.
+# J[i] is the index of the line in file1 corresponding
+# to line i file0. J[i] = 0 if there is no
+# such line in file1.
+#
+# Lines are hashed so as to work in core. All potential
+# matches are located by sorting the lines of each file
+# on the hash (called value). In particular, this
+# collects the equivalence classes in file1 together.
+# Subroutine equiv replaces the value of each line in
+# file0 by the index of the first element of its
+# matching equivalence in (the reordered) file1.
+# To save space equiv squeezes file1 into a single
+# array member in which the equivalence classes
+# are simply concatenated, except that their first
+# members are flagged by changing sign.
+#
+# Next the indices that point into member are unsorted into
+# array class according to the original order of file0.
+#
+# The cleverness lies in routine stone. This marches
+# through the lines of file0, developing a vector klist
+# of "k-candidates". At step i a k-candidate is a matched
+# pair of lines x,y (x in file0 y in file1) such that
+# there is a common subsequence of lenght k
+# between the first i lines of file0 and the first y
+# lines of file1, but there is no such subsequence for
+# any smaller y. x is the earliest possible mate to y
+# that occurs in such a subsequence.
+#
+# Whenever any of the members of the equivalence class of
+# lines in file1 matable to a line in file0 has serial number
+# less than the y of some k-candidate, that k-candidate
+# with the smallest such y is replaced. The new
+# k-candidate is chained (via pred) to the current
+# k-1 candidate so that the actual subsequence can
+# be recovered. When a member has serial number greater
+# that the y of all k-candidates, the klist is extended.
+# At the end, the longest subsequence is pulled out
+# and placed in the array J by unravel.
+#
+# With J in hand, the matches there recorded are
+# check'ed against reality to assure that no spurious
+# matches have crept in due to hashing. If they have,
+# they are broken, and "jackpot " is recorded--a harmless
+# matter except that a true match for a spuriously
+# mated line may now be unnecessarily reported as a change.
+#
+# Much of the complexity of the program comes simply
+# from trying to minimize core utilization and
+# maximize the range of doable problems by dynamically
+# allocating what is needed and reusing what is not.
+# The core requirements for problems larger than somewhat
+# are (in words) 2*length(file0) + length(file1) +
+# 3*(number of k-candidates installed), typically about
+# 6n words for files of length n.
+#
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "bufio.m";
+ bufio : Bufio;
+Iobuf : import bufio;
+
+include "draw.m";
+ draw: Draw;
+include "readdir.m";
+ readdir : Readdir;
+include "string.m";
+ str : String;
+include "arg.m";
+
+Diff : module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+mode : int; # '\0', 'e', 'f', 'h'
+bflag : int; # ignore multiple and trailing blanks
+rflag : int; # recurse down directory trees
+mflag : int; # pseudo flag: doing multiple files, one dir
+
+REG,
+BIN: con iota;
+
+HALFINT : con 16;
+Usage : con "usage: diff [ -efbwr ] file1 ... file2";
+
+cand : adt {
+ x : int;
+ y : int;
+ pred : int;
+};
+
+line : adt {
+ serial : int;
+ value : int;
+};
+
+out : ref Iobuf;
+file := array[2] of array of line;
+sfile := array[2] of array of line; # shortened by pruning common prefix and suffix
+slen := array[2] of int;
+ilen := array[2] of int;
+pref, suff, clen : int; # length of prefix and suffix
+firstchange : int;
+clist : array of cand; # merely a free storage pot for candidates
+J : array of int; # will be overlaid on class
+ixold, ixnew : array of int;
+input := array[2] of ref Iobuf ;
+file1, file2 : string;
+tmpname := array[] of {"/tmp/diff1", "/tmp/diff2"};
+whichtmp : int;
+anychange := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ draw = load Draw Draw->PATH;
+ bufio = load Bufio Bufio->PATH;
+ readdir = load Readdir Readdir->PATH;
+ str = load String String->PATH;
+ if (bufio==nil)
+ fatal(sys->sprint("cannot load %s: %r", Bufio->PATH));
+ if (readdir==nil)
+ fatal(sys->sprint("cannot load %s: %r", Readdir->PATH));
+ if (str==nil)
+ fatal(sys->sprint("cannot load %s: %r", String->PATH));
+ arg := load Arg Arg->PATH;
+ if (arg==nil)
+ fatal(sys->sprint("cannot load %s: %r", Arg->PATH));
+ fsb, tsb : Sys->Dir;
+ arg->init(args);
+ while((o := arg->opt()) != 0)
+ case o {
+ 'e' or 'f' =>
+ mode = o;
+ 'w' =>
+ bflag = 2;
+ 'b' =>
+ bflag = 1;
+ 'r' =>
+ rflag = 1;
+ 'm' =>
+ mflag = 1;
+ * =>
+ fatal(Usage);
+ }
+ tmp := arg->argv();
+ arg = nil;
+ j := len tmp;
+ if (j < 2)
+ fatal(Usage);
+ arr := array[j] of string;
+ for(i:=0;i<j;i++){
+ arr[i]= hd tmp;
+ tmp = tl tmp;
+ }
+
+ (i,tsb)=sys->stat(arr[j-1]);
+ if (i == -1)
+ fatal(sys->sprint("can't stat %s: %r", arr[j-1]));
+ if (j > 2) {
+ if (!(tsb.qid.qtype&Sys->QTDIR))
+ fatal(Usage);
+ mflag = 1;
+ }
+ else {
+ (i,fsb)=sys->stat(arr[0]);
+ if (i == -1)
+ fatal(sys->sprint("can't stat %s: %r", arr[0]));
+ if ((fsb.qid.qtype&Sys->QTDIR) && (tsb.qid.qtype&Sys->QTDIR))
+ mflag = 1;
+ }
+ out=bufio->fopen(sys->fildes(1),Bufio->OWRITE);
+ for (i = 0; i < j-1; i++) {
+ diff(arr[i], arr[j-1], 0);
+ rmtmpfiles();
+ }
+ rmtmpfiles();
+ out.flush();
+ if (anychange)
+ raise "fail:some";
+}
+
+############################# diffreg from here ....
+
+# shellsort CACM #201
+
+sort(a : array of line, n : int)
+{
+ w : line;
+ j1:=0;
+ m := 0;
+ for (i := 1; i <= n; i *= 2)
+ m = 2*i - 1;
+ for (m /= 2; m != 0; m /= 2) {
+ for (j := 1; j <= n-m ; j++) {
+ ai:=j;
+ aim:=j+m;
+ do {
+ if (a[aim].value > a[ai].value ||
+ a[aim].value == a[ai].value &&
+ a[aim].serial > a[ai].serial)
+ break;
+ w = a[ai];
+ a[ai] = a[aim];
+ a[aim] = w;
+ aim=ai;
+ ai-=m;
+ } while (ai > 0 && aim >= ai);
+ }
+ }
+}
+
+unsort(f : array of line, l : int) : array of int
+{
+ i : int;
+ a := array[l+1] of int;
+ for(i=1;i<=l;i++)
+ a[f[i].serial] = f[i].value;
+ return a;
+}
+
+prune()
+{
+ for(pref=0;pref< ilen[0]&&pref< ilen[1]&&
+ file[0][pref+1].value==file[1][pref+1].value;
+ pref++ ) ;
+ for(suff=0;suff< ilen[0]-pref&&suff< ilen[1]-pref&&
+ file[0][ilen[0]-suff].value==file[1][ilen[1]-suff].value;
+ suff++) ;
+ for(j:=0;j<2;j++) {
+ sfile[j] = file[j][pref:];
+ slen[j]= ilen[j]-pref-suff;
+ for(i:=0;i<=slen[j];i++)
+ sfile[j][i].serial = i;
+ }
+}
+
+equiv(a: array of line, n:int , b: array of line, m: int, c : array of int)
+{
+ i := 1;
+ j := 1;
+ while(i<=n && j<=m) {
+ if(a[i].value < b[j].value)
+ a[i++].value = 0;
+ else if(a[i].value == b[j].value)
+ a[i++].value = j;
+ else
+ j++;
+ }
+ while(i <= n)
+ a[i++].value = 0;
+ b[m+1].value = 0; # huh ?
+ j = 1;
+ while(j <= m) {
+ c[j] = -b[j].serial;
+ while(b[j+1].value == b[j].value) {
+ j++;
+ c[j] = b[j].serial;
+ }
+ j++;
+ }
+ c[j] = -1;
+}
+
+newcand(x, y, pred : int) : int
+{
+ if (clen==len clist){
+ q := array[clen*2] of cand;
+ q[0:]=clist;
+ clist= array[clen*2] of cand;
+ clist[0:]=q;
+ q=nil;
+ }
+ clist[clen].x=x;
+ clist[clen].y=y;
+ clist[clen].pred=pred;
+ return clen++;
+}
+
+search(c : array of int, k,y : int) : int
+{
+ if(clist[c[k]].y < y) # quick look for typical case
+ return k+1;
+ i := 0;
+ j := k+1;
+ while((l:=(i+j)/2) > i) {
+ t := clist[c[l]].y;
+ if(t > y)
+ j = l;
+ else if(t < y)
+ i = l;
+ else
+ return l;
+ }
+ return l+1;
+}
+
+stone(a : array of int ,n : int, b: array of int , c : array of int) : int
+{
+ oldc, oldl, tc, l ,y : int;
+ k := 0;
+ c[0] = newcand(0,0,0);
+ for(i:=1; i<=n; i++) {
+ j := a[i];
+ if(j==0)
+ continue;
+ y = -b[j];
+ oldl = 0;
+ oldc = c[0];
+ do {
+ if(y <= clist[oldc].y)
+ continue;
+ l = search(c, k, y);
+ if(l!=oldl+1)
+ oldc = c[l-1];
+ if(l<=k) {
+ if(clist[c[l]].y <= y)
+ continue;
+ tc = c[l];
+ c[l] = newcand(i,y,oldc);
+ oldc = tc;
+ oldl = l;
+ } else {
+ c[l] = newcand(i,y,oldc);
+ k++;
+ break;
+ }
+ } while((y=b[j+=1]) > 0);
+ }
+ return k;
+}
+
+unravel(p : int)
+{
+ for(i:=0; i<=ilen[0]; i++) {
+ if (i <= pref)
+ J[i] = i;
+ else if (i > ilen[0]-suff)
+ J[i] = i+ ilen[1]-ilen[0];
+ else
+ J[i] = 0;
+ }
+ for(q:=clist[p];q.y!=0;q=clist[q.pred])
+ J[q.x+pref] = q.y+pref;
+}
+
+output()
+{
+ i1: int;
+ m := ilen[0];
+ J[0] = 0;
+ J[m+1] = ilen[1]+1;
+ if (mode != 'e') {
+ for (i0 := 1; i0 <= m; i0 = i1+1) {
+ while (i0 <= m && J[i0] == J[i0-1]+1)
+ i0++;
+ j0 := J[i0-1]+1;
+ i1 = i0-1;
+ while (i1 < m && J[i1+1] == 0)
+ i1++;
+ j1 := J[i1+1]-1;
+ J[i1] = j1;
+ change(i0, i1, j0, j1);
+ }
+ }
+ else {
+ for (i0 := m; i0 >= 1; i0 = i1-1) {
+ while (i0 >= 1 && J[i0] == J[i0+1]-1 && J[i0])
+ i0--;
+ j0 := J[i0+1]-1;
+ i1 = i0+1;
+ while (i1 > 1 && J[i1-1] == 0)
+ i1--;
+ j1 := J[i1-1]+1;
+ J[i1] = j1;
+ change(i1 , i0, j1, j0);
+ }
+ }
+ if (m == 0)
+ change(1, 0, 1, ilen[1]);
+ out.flush();
+}
+
+diffreg(f,t : string)
+{
+ k : int;
+
+ (b0, b0type) := prepare(0, f);
+ if (b0==nil)
+ return;
+ (b1, b1type) := prepare(1, t);
+ if (b1==nil) {
+ b0=nil;
+ return;
+ }
+ if (b0type == BIN || b1type == BIN) {
+ if (cmp(b0, b1)) {
+ out.puts(sys->sprint("Binary files %s %s differ\n", f, t));
+ anychange = 1;
+ }
+ b0 = nil;
+ b1 = nil;
+ return;
+ }
+ clen=0;
+ prune();
+ file[0]=nil;
+ file[1]=nil;
+ sort(sfile[0],slen[0]);
+ sort(sfile[1],slen[1]);
+ member := array[slen[1]+2] of int;
+ equiv(sfile[0], slen[0],sfile[1],slen[1], member);
+ class:=unsort(sfile[0],slen[0]);
+ sfile[0]=nil;
+ sfile[1]=nil;
+ klist := array[slen[0]+2] of int;
+ clist = array[1] of cand;
+ k = stone(class, slen[0], member, klist);
+ J = array[ilen[0]+2] of int;
+ unravel(klist[k]);
+ clist=nil;
+ klist=nil;
+ class=nil;
+ member=nil;
+ ixold = array[ilen[0]+2] of int;
+ ixnew = array[ilen[1]+2] of int;
+
+ b0.seek(big 0, 0);
+ b1.seek(big 0, 0);
+ check(b0, b1);
+ output();
+ ixold=nil;
+ ixnew=nil;
+ b0=nil;
+ b1=nil;
+}
+
+######################## diffio starts here...
+
+
+# hashing has the effect of
+# arranging line in 7-bit bytes and then
+# summing 1-s complement in 16-bit hunks
+
+readhash(bp : ref Iobuf) : int
+{
+ sum := 1;
+ shift := 0;
+ buf := bp.gets('\n');
+ if (buf == nil)
+ return 0;
+ buf = buf[0:len buf -1];
+ p := 0;
+ case bflag {
+ # various types of white space handling
+ 0 =>
+ while (p< len buf) {
+ sum += (buf[p] << (shift &= (HALFINT-1)));
+ p++;
+ shift += 7;
+ }
+ 1 =>
+
+ # coalesce multiple white-space
+
+ for (space := 0; p< len buf; p++) {
+ if (buf[p]==' ' || buf[p]=='\t') {
+ space++;
+ continue;
+ }
+ if (space) {
+ shift += 7;
+ space = 0;
+ }
+ sum += (buf[p] << (shift &= (HALFINT-1)));
+ p++;
+ shift += 7;
+ }
+ * =>
+
+ # strip all white-space
+
+ while (p< len buf) {
+ if (buf[p]==' ' || buf[p]=='\t') {
+ p++;
+ continue;
+ }
+ sum += (buf[p] << (shift &= (HALFINT-1)));
+ p++;
+ shift += 7;
+ }
+ }
+ return sum;
+}
+
+prepare(i : int, arg : string) : (ref Iobuf, int)
+{
+ h : int;
+ bp := bufio->open(arg,Bufio->OREAD);
+ if (bp==nil) {
+ error(sys->sprint("cannot open %s: %r", arg));
+ return (nil, 0);
+ }
+ buf := array[1024] of byte;
+ n :=bp.read(buf, len buf);
+ str1 := string buf[0:n];
+ for (j:=0;j<len str1 -2;j++)
+ if (str1[j] == Sys->UTFerror)
+ return (bp, BIN);
+ bp.seek(big 0, Sys->SEEKSTART);
+ p := array[4] of line;
+ for (j = 0; h = readhash(bp); p[j].value = h){
+ j++;
+ if (j+3>=len p){
+ newp:=array[len p*2] of line;
+ newp[0:]=p[0:];
+ p=array[len p*2] of line;
+ p=newp;
+ newp=nil;
+ }
+ }
+ ilen[i]=j;
+ file[i] = p;
+ input[i] = bp;
+ if (i == 0) {
+ file1 = arg;
+ firstchange = 0;
+ }
+ else
+ file2 = arg;
+ return (bp, REG);
+}
+
+squishspace(buf : string) : string
+{
+ q:=0;
+ p:=0;
+ for (space := 0; q<len buf; q++) {
+ if (buf[q]==' ' || buf[q]=='\t') {
+ space++;
+ continue;
+ }
+ if (space && bflag == 1) {
+ buf[p] = ' ';
+ p++;
+ space = 0;
+ }
+ buf[p]=buf[q];
+ p++;
+ }
+ buf=buf[0:p];
+ return buf;
+}
+
+
+# need to fix up for unexpected EOF's
+
+ftell(b: ref Iobuf): int
+{
+ return int b.offset();
+}
+
+check(bf, bt : ref Iobuf)
+{
+ fbuf, tbuf : string;
+ f:=1;
+ t:=1;
+ ixold[0] = ixnew[0] = 0;
+ for (; f < ilen[0]; f++) {
+ fbuf = bf.gets('\n');
+ if (fbuf!=nil)
+ fbuf=fbuf[0:len fbuf -1];
+ ixold[f] = ftell(bf);
+ if (J[f] == 0)
+ continue;
+ tbuflen: int;
+ do {
+ tbuf = bt.gets('\n');
+ if (tbuf!=nil)
+ tbuf=tbuf[0:len tbuf -1];
+ tbuflen = len array of byte tbuf;
+ ixnew[t] = ftell(bt);
+ } while (t++ < J[f]);
+ if (bflag) {
+ fbuf = squishspace(fbuf);
+ tbuf = squishspace(tbuf);
+ }
+ if (len fbuf != len tbuf || fbuf!=tbuf)
+ J[f] = 0;
+ }
+ while (t < ilen[1]) {
+ tbuf = bt.gets('\n');
+ if (tbuf!=nil)
+ tbuf=tbuf[0:len tbuf -1];
+ ixnew[t] = ftell(bt);
+ t++;
+ }
+}
+
+range(a, b : int, separator : string)
+{
+ if (a>b)
+ out.puts(sys->sprint("%d", b));
+ else
+ out.puts(sys->sprint("%d", a));
+ if (a < b)
+ out.puts(sys->sprint("%s%d", separator, b));
+}
+
+fetch(f : array of int, a,b : int , bp : ref Iobuf, s : string)
+{
+ buf : string;
+ bp.seek(big f[a-1], 0);
+ while (a++ <= b) {
+ buf=bp.gets('\n');
+ out.puts(s);
+ out.puts(buf);
+ }
+}
+
+change(a, b, c, d : int)
+{
+ if (a > b && c > d)
+ return;
+ anychange = 1;
+ if (mflag && firstchange == 0) {
+ out.puts(sys->sprint( "diff %s %s\n", file1, file2));
+ firstchange = 1;
+ }
+ if (mode != 'f') {
+ range(a, b, ",");
+ if (a>b)
+ out.putc('a');
+ else if (c>d)
+ out.putc('d');
+ else
+ out.putc('c');
+ if (mode != 'e')
+ range(c, d, ",");
+ }
+ else {
+ if (a>b)
+ out.putc('a');
+ else if (c>d)
+ out.putc('d');
+ else
+ out.putc('c');
+ range(a, b, " ");
+ }
+ out.putc('\n');
+ if (mode == 0) {
+ fetch(ixold, a, b, input[0], "< ");
+ if (a <= b && c <= d)
+ out.puts("---\n");
+ }
+ if (mode==0)
+ fetch(ixnew, c, d, input[1], "> ");
+ else
+ fetch(ixnew, c, d, input[1], "");
+
+ if (mode != 0 && c <= d)
+ out.puts(".\n");
+}
+
+
+######################### diffdir starts here ......
+
+scandir(name : string) : array of string
+{
+ (db,nitems):= readdir->init(name,Readdir->NAME);
+ cp := array[nitems] of string;
+ for(i:=0;i<nitems;i++)
+ cp[i]=db[i].name;
+ return cp;
+}
+
+
+diffdir(f, t : string, level : int)
+{
+ df, dt : array of string;
+ fb, tb : string;
+ i:=0;
+ j:=0;
+ df = scandir(f);
+ dt = scandir(t);
+ while ((i<len df) || (j<len dt)) {
+ if ((j==len dt) || (i<len df && df[i] < dt[j])) {
+ if (mode == 0)
+ out.puts(sys->sprint("Only in %s: %s\n", f, df[i]));
+ i++;
+ continue;
+ }
+ if ((i==len df) || (j<len dt && df[i] > dt[j])) {
+ if (mode == 0)
+ out.puts(sys->sprint("Only in %s: %s\n", t, dt[j]));
+ j++;
+ continue;
+ }
+ fb=sys->sprint("%s/%s", f, df[i]);
+ tb=sys->sprint("%s/%s", t, dt[j]);
+ diff(fb, tb, level+1);
+ i++; j++;
+ }
+}
+
+cmp(b0, b1: ref Iobuf): int
+{
+ b0.seek(big 0, Sys->SEEKSTART);
+ b1.seek(big 0, Sys->SEEKSTART);
+ buf0 := array[1024] of byte;
+ buf1 := array[1024] of byte;
+ for (;;) {
+ n0 := b0.read(buf0, len buf0);
+ n1 := b1.read(buf1, len buf1);
+
+ if (n0 != n1)
+ return 1;
+
+ if (n0 == 0)
+ return 0;
+
+ for (i := 0; i < n0; i++)
+ if (buf0[i] != buf1[i])
+ return 1;
+ }
+}
+
+################## main from here.....
+
+REGULAR_FILE(s : Sys->Dir) : int
+{
+ # both pipes and networks contain non-zero-length files
+ # which are not seekable.
+ return (s.qid.qtype&Sys->QTDIR) == 0 &&
+ s.dtype != '|' &&
+ s.dtype != 'I';
+# && s.length > 0; device files have zero length.
+}
+
+rmtmpfiles()
+{
+ while (whichtmp > 0) {
+ whichtmp--;
+ sys->remove(tmpname[whichtmp]);
+ }
+}
+
+mktmpfile(inputf : ref Sys->FD) : (string, Sys->Dir)
+{
+ i, j : int;
+ sb : Sys->Dir;
+ p : string;
+ buf := array[8192] of byte;
+
+ p = tmpname[whichtmp++];
+ fd := sys->create(p, Sys->OWRITE, 8r600);
+ if (fd == nil) {
+ error(sys->sprint("cannot create %s: %r", p));
+ return (nil, sb);
+ }
+ while ((i = sys->read(inputf, buf, len buf)) > 0) {
+ if ((i = sys->write(fd, buf, i)) < 0)
+ break;
+ }
+ (j,sb)=sys->fstat(fd);
+ if (i < 0 || j < 0) {
+ error(sys->sprint("cannot read/write %s: %r", p));
+ return (nil, sb);
+ }
+ return (p, sb);
+}
+
+
+statfile(file : string) : (string,Sys->Dir)
+{
+ (ret,sb):=sys->stat(file);
+ if (ret==-1) {
+ if (file == "-") {
+ (ret,sb)= sys->fstat(sys->fildes(0));
+ if (ret == -1) {
+ error(sys->sprint("cannot stat %s: %r", file));
+ return (nil,sb);
+ }
+ }
+ (file, sb) = mktmpfile(sys->fildes(0));
+ }
+ else if (!REGULAR_FILE(sb) && !(sb.qid.qtype&Sys->QTDIR)) {
+ if ((i := sys->open(file, Sys->OREAD)) == nil) {
+ error(sys->sprint("cannot open %s: %r", file));
+ return (nil, sb);
+ }
+ (file, sb) = mktmpfile(i);
+ }
+ return (file,sb);
+}
+
+diff(f, t : string, level : int)
+{
+ fp,tp,p,rest,fb,tb : string;
+ fsb, tsb : Sys->Dir;
+ (fp,fsb) = statfile(f);
+ if (fp == nil)
+ return;
+ (tp,tsb) = statfile(t);
+ if (tp == nil)
+ return;
+ if ((fsb.qid.qtype&Sys->QTDIR) && (tsb.qid.qtype&Sys->QTDIR)) {
+ if (rflag || level == 0)
+ diffdir(fp, tp, level);
+ else
+ out.puts(sys->sprint("Common subdirectories: %s and %s\n", fp, tp));
+ }
+ else if (REGULAR_FILE(fsb) && REGULAR_FILE(tsb)){
+ diffreg(fp, tp);
+ } else {
+ if (!(fsb.qid.qtype&Sys->QTDIR)) {
+ (p,rest)=str->splitr(f,"/");
+ if (rest!=nil)
+ p = rest;
+ tb=sys->sprint("%s/%s", tp, p);
+ diffreg(fp, tb);
+ }
+ else {
+ (p,rest)=str->splitr(t,"/");
+ if (rest!=nil)
+ p = rest;
+ fb=sys->sprint("%s/%s", fp, p);
+ diffreg(fb, tp);
+ }
+ }
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "diff: %s\n", s);
+ raise "fail:error";
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "diff: %s\n", s);
+}
diff --git a/appl/cmd/disdep.b b/appl/cmd/disdep.b
new file mode 100644
index 00000000..0a12c617
--- /dev/null
+++ b/appl/cmd/disdep.b
@@ -0,0 +1,250 @@
+implement Disdep;
+
+#
+# Copyright © 2000 Vita Nuova Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+ print, sprint: import sys;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+ arg: Arg;
+
+include "dis.m";
+ dis: Dis;
+ Mod: import dis;
+
+include "hash.m";
+ hash: Hash;
+ HashTable, HashVal: import hash;
+
+Disdep: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Item: adt {
+ name: string;
+ needs: cyclic list of ref Item;
+ visited: int;
+
+ find: fn(s: string): ref Item;
+};
+
+bout: ref Iobuf;
+pending: list of ref Item;
+roots: list of ref Item;
+tab: ref HashTable;
+aflag := 0; # display all non-recursive dependencies
+oflag := 0; # only list the immediate (outer) dependencies
+sflag := 0; # include $system modules
+pflag := 0; # show dependency sets as pairs, one per line
+showdepth := 0; # indent to show the dependency structure
+
+noload(mod: string)
+{
+ sys->fprint(sys->fildes(2), "disdep: can't load %s: %r\n", mod);
+ raise "fail:load";
+}
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "Usage: disdep [-a] [-d] [-o] [-p] [-s] file.dis ...\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ noload(Bufio->PATH);
+
+ str = load String String->PATH;
+ if(str == nil)
+ noload(String->PATH);
+
+ hash = load Hash Hash->PATH;
+ if(hash == nil)
+ noload(Hash->PATH);
+
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ noload(Arg->PATH);
+
+ dis = load Dis Dis->PATH;
+ if(dis == nil)
+ noload(Dis->PATH);
+ dis->init();
+
+ arg->init(argv);
+ while((opt := arg->opt()) != 0)
+ case opt {
+ 'a' => aflag = 1; showdepth = 1;
+ 'o' => oflag = 1;
+ 's' => sflag = 1;
+ 'd' => showdepth = 1;
+ 'p' => pflag = 1;
+ * => usage();
+ }
+
+ argv = arg->argv();
+ if(argv == nil)
+ usage();
+
+ tab = hash->new(521);
+
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ for(l := rev(argv); l != nil; l = tl l)
+ roots = Item.find(hd l) :: roots;
+ pending = roots;
+ while(pending != nil){
+ f := hd pending;
+ pending = tl pending;
+ (m, s) := dis->loadobj(f.name);
+ if(s != nil){
+ sys->fprint(sys->fildes(2), "disdep: can't open %s: %s\n", f.name, s);
+ continue;
+ }
+ f.needs = disfind(m);
+ for(nl := f.needs; nl != nil; nl = tl nl){
+ n := hd nl;
+ if(!n.visited){
+ n.visited = 1;
+ if(!oflag && !isdol(n.name))
+ pending = n :: pending;
+ }
+ }
+ }
+
+ if(pflag){
+ for(i := 0; i < nextitem; i++){
+ f := items[i];
+ if(f.needs != nil){
+ for(nl := f.needs; nl != nil; nl = tl nl){
+ bout.puts(f.name);
+ bout.putc(' ');
+ bout.puts((hd nl).name);
+ bout.putc('\n');
+ }
+ }else{
+ bout.puts(f.name);
+ bout.putc('\n');
+ }
+ }
+ }else{
+ unvisited();
+ for(; roots != nil; roots = tl roots){
+ if(aflag)
+ unvisited();
+ f := hd roots;
+ depth := 0;
+ if(showdepth){
+ bout.puts(f.name);
+ bout.putc('\n');
+ depth = 1;
+ }
+ prdep(hd roots, depth);
+ }
+ }
+ bout.flush();
+}
+
+disfind(m: ref Mod): list of ref Item
+{
+ needs: list of ref Item;
+ for(d := m.data; d != nil; d = tl d) {
+ pick dat := hd d {
+ String =>
+ if(isdisfile(dat.str) || sflag && isdol(dat.str))
+ needs = Item.find(dat.str) :: needs;
+ }
+ }
+ return rev(needs);
+}
+
+prdep(f: ref Item, depth: int)
+{
+ f.visited = 1; # short-circuit self-reference
+ for(nl := f.needs; nl != nil; nl = tl nl){
+ n := hd nl;
+ if(!n.visited){
+ n.visited = 1;
+ name(n.name, depth);
+ prdep(n, depth+1);
+ }else if(aflag)
+ name(n.name, depth);
+ }
+}
+
+items := array[100] of ref Item;
+nextitem := 0;
+
+Item.find(name: string): ref Item
+{
+ k := tab.find(name);
+ if(k != nil)
+ return items[k.i];
+ if(nextitem >= len items){
+ a := array[len items + 100] of ref Item;
+ a[0:] = items;
+ items = a;
+ }
+ f := ref Item;
+ f.name = name;
+ f.visited = 0;
+ items[nextitem] = f;
+ tab.insert(name, HashVal(nextitem, 0.0, nil));
+ nextitem++;
+ return f;
+}
+
+unvisited()
+{
+ for(i := 0; i < nextitem; i++)
+ items[i].visited = 0;
+}
+
+name(s: string, depth: int)
+{
+ if(showdepth)
+ for(i:=0; i<depth; i++)
+ bout.putc('\t');
+ bout.puts(s);
+ bout.putc('\n');
+}
+
+isdisfile(s: string): int
+{
+ if(len s > 4 && s[len s-4:]==".dis"){ # worth a look
+ for(i := 0; i < len s; i++)
+ if(s[i] <= ' ' || s[i] == '%')
+ return 0;
+ return 1;
+ }
+ return 0;
+}
+
+isdol(s: string): int
+{
+ return len s > 1 && s[0] == '$' && s[1]>='A' && s[1]<='Z'; # reasonable guess
+}
+
+rev[T](l: list of T): list of T
+{
+ t: list of T;
+ for(; l != nil; l = tl l)
+ t = hd l :: t;
+ return t;
+}
+
diff --git a/appl/cmd/disdump.b b/appl/cmd/disdump.b
new file mode 100644
index 00000000..2bc9763f
--- /dev/null
+++ b/appl/cmd/disdump.b
@@ -0,0 +1,52 @@
+implement Disdump;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "dis.m";
+ dis: Dis;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Disdump: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr := sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) {
+ sys->fprint(stderr, "dis: cannot load %s: %r\n", Bufio->PATH);
+ raise "fail:bad module";
+ }
+
+ dis = load Dis Dis->PATH;
+ if (dis == nil) {
+ sys->fprint(stderr, "dis: cannot load %s: %r\n", Dis->PATH);
+ raise "fail:bad module";
+ }
+
+ if (len argv < 2) {
+ sys->fprint(stderr, "usage: dis module...\n");
+ raise "fail:usage";
+ }
+ dis->init();
+ out := bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ errs := 0;
+ for (argv = tl argv; argv != nil; argv = tl argv) {
+ (mod, err) := dis->loadobj(hd argv);
+ if (mod == nil) {
+ sys->fprint(stderr, "dis: failed to load %s: %s\n", hd argv, err);
+ errs++;
+ continue;
+ }
+ for (i := 0; i < len mod.inst; i++)
+ out.puts(dis->inst2s(mod.inst[i])+"\n");
+ }
+ out.close();
+ if (errs)
+ raise "fail:errors";
+}
diff --git a/appl/cmd/disk/format.b b/appl/cmd/disk/format.b
new file mode 100644
index 00000000..80fee62c
--- /dev/null
+++ b/appl/cmd/disk/format.b
@@ -0,0 +1,755 @@
+implement Format;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "disks.m";
+ disks: Disks;
+ Disk: import disks;
+
+include "arg.m";
+
+Format: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+#
+# floppy types (all MFM encoding)
+#
+Type: adt {
+ name: string;
+ bytes: int; # bytes/sector
+ sectors: int; # sectors/track
+ heads: int; # number of heads
+ tracks: int; # tracks/disk
+ media: int; # media descriptor byte
+ cluster: int; # default cluster size
+};
+
+floppytype := array[] of {
+ Type ( "3½HD", 512, 18, 2, 80, 16rf0, 1 ),
+ Type ( "3½DD", 512, 9, 2, 80, 16rf9, 2 ),
+ Type ( "3½QD", 512, 36, 2, 80, 16rf9, 2 ), # invented
+ Type ( "5¼HD", 512, 15, 2, 80, 16rf9, 1 ),
+ Type ( "5¼DD", 512, 9, 2, 40, 16rfd, 2 ),
+ Type ( "hard", 512, 0, 0, 0, 16rf8, 4 ),
+};
+
+# offsets in DOS boot area
+DB_MAGIC : con 0;
+DB_VERSION : con 3;
+DB_SECTSIZE : con 11;
+DB_CLUSTSIZE : con 13;
+DB_NRESRV : con 14;
+DB_NFATS : con 16;
+DB_ROOTSIZE : con 17;
+DB_VOLSIZE : con 19;
+DB_MEDIADESC: con 21;
+DB_FATSIZE : con 22;
+DB_TRKSIZE : con 24;
+DB_NHEADS : con 26;
+DB_NHIDDEN : con 28;
+DB_BIGVOLSIZE: con 32;
+DB_DRIVENO : con 36;
+DB_RESERVED0: con 37;
+DB_BOOTSIG : con 38;
+DB_VOLID : con 39;
+DB_LABEL : con 43;
+DB_TYPE : con 54;
+
+DB_VERSIONSIZE: con 8;
+DB_LABELSIZE : con 11;
+DB_TYPESIZE : con 8;
+DB_SIZE : con 62;
+
+# offsets in DOS directory
+DD_NAME : con 0;
+DD_EXT : con 8;
+DD_ATTR : con 11;
+DD_RESERVED : con 12;
+DD_TIME : con 22;
+DD_DATE : con 24;
+DD_START : con 26;
+DD_LENGTH : con 28;
+
+DD_NAMESIZE : con 8;
+DD_EXTSIZE : con 3;
+DD_SIZE : con 32;
+
+DRONLY : con 16r01;
+DHIDDEN : con 16r02;
+DSYSTEM : con byte 16r04;
+DVLABEL : con byte 16r08;
+DDIR : con byte 16r10;
+DARCH : con byte 16r20;
+
+# the boot program for the boot sector.
+bootprog := array[512] of {
+16r000 =>
+ byte 16rEB, byte 16r3C, byte 16r90, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00,
+ byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00,
+16r03E =>
+ byte 16rFA, byte 16rFC, byte 16r8C, byte 16rC8, byte 16r8E, byte 16rD8, byte 16r8E, byte 16rD0,
+ byte 16rBC, byte 16r00, byte 16r7C, byte 16rBE, byte 16r77, byte 16r7C, byte 16rE8, byte 16r19,
+ byte 16r00, byte 16r33, byte 16rC0, byte 16rCD, byte 16r16, byte 16rBB, byte 16r40, byte 16r00,
+ byte 16r8E, byte 16rC3, byte 16rBB, byte 16r72, byte 16r00, byte 16rB8, byte 16r34, byte 16r12,
+ byte 16r26, byte 16r89, byte 16r07, byte 16rEA, byte 16r00, byte 16r00, byte 16rFF, byte 16rFF,
+ byte 16rEB, byte 16rD6, byte 16rAC, byte 16r0A, byte 16rC0, byte 16r74, byte 16r09, byte 16rB4,
+ byte 16r0E, byte 16rBB, byte 16r07, byte 16r00, byte 16rCD, byte 16r10, byte 16rEB, byte 16rF2,
+ byte 16rC3, byte 'N', byte 'o', byte 't', byte ' ', byte 'a', byte ' ', byte 'b',
+ byte 'o', byte 'o', byte 't', byte 'a', byte 'b', byte 'l', byte 'e', byte ' ',
+ byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'o', byte 'r', byte ' ',
+ byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'e', byte 'r', byte 'r',
+ byte 'o', byte 'r', byte '\r', byte '\n', byte 'P', byte 'r', byte 'e', byte 's',
+ byte 's', byte ' ', byte 'a', byte 'l', byte 'm', byte 'o', byte 's', byte 't',
+ byte ' ', byte 'a', byte 'n', byte 'y', byte ' ', byte 'k', byte 'e', byte 'y',
+ byte ' ', byte 't', byte 'o', byte ' ', byte 'r', byte 'e', byte 'b', byte 'o',
+ byte 'o', byte 't', byte '.', byte '.', byte '.', byte 16r00, byte 16r00, byte 16r00,
+16r1F0 =>
+ byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00,
+ byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r55, byte 16rAA,
+* =>
+ byte 16r00,
+};
+
+dev: string;
+clustersize := 0;
+fat: array of byte; # the fat
+fatbits: int;
+fatsecs: int;
+fatlast: int; # last cluster allocated
+clusters: int;
+volsecs: int;
+root: array of byte; # first block of root
+rootsecs: int;
+rootfiles: int;
+rootnext: int;
+chatty := 0;
+xflag := 0;
+nresrv := 1;
+dos := 0;
+fflag := 0;
+file: string; # output file name
+pbs: string;
+typ: string;
+
+Sof: con 1; # start of file
+Eof: con 2; # end of file
+
+stdin, stdout, stderr: ref Sys->FD;
+
+fatal(str: string)
+{
+ sys->fprint(stderr, "format: %s\n", str);
+ if(fflag && file != nil)
+ sys->remove(file);
+ raise "fail:error";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ daytime = load Daytime Daytime->PATH;
+ disks = load Disks Disks->PATH;
+ arg := load Arg Arg->PATH;
+ stdin = sys->fildes(0);
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+
+ disks->init();
+
+ fflag = 0;
+ typ = nil;
+ clustersize = 0;
+ writepbs := 0;
+ label := array[DB_LABELSIZE] of {* => byte ' '};
+ label[0:] = array of byte "CYLINDRICAL";
+ arg->init(args);
+ arg->setusage("disk/format [-df] [-b bootblock] [-c csize] [-l label] [-r nresrv] [-t type] disk [files ...]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'b' =>
+ pbs = arg->earg();
+ writepbs = 1;
+ 'd' =>
+ dos = 1;
+ writepbs = 1;
+ 'c' =>
+ clustersize = int arg->earg();
+ 'f' =>
+ fflag = 1;
+ 'l' =>
+ a := array of byte arg->earg();
+ if(len a > len label)
+ a = a[0:len label];
+ label[0:] = a;
+ for(i := len a; i < len label; i++)
+ label[i] = byte ' ';
+ 'r' =>
+ nresrv = int arg->earg();
+ 't' =>
+ typ = arg->earg();
+ 'v' =>
+ chatty = 1;
+ 'x' =>
+ xflag = 1;
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ dev = hd args;
+ disk := Disk.open(dev, Sys->ORDWR, 0);
+ if(disk == nil){
+ if(fflag){
+ fd := sys->create(dev, Sys->ORDWR, 8r666);
+ if(fd != nil){
+ fd = nil;
+ disk = Disk.open(dev, Sys->ORDWR, 0);
+ }
+ }
+ if(disk == nil)
+ fatal(sys->sprint("opendisk %q: %r", dev));
+ }
+
+ if(disk.dtype == "file")
+ fflag = 1;
+
+ if(typ == nil){
+ case disk.dtype {
+ "file" =>
+ typ = "3½HD";
+ "floppy" =>
+ sys->seek(disk.ctlfd, big 0, 0);
+ buf := array[10] of byte;
+ n := sys->read(disk.ctlfd, buf, len buf);
+ if(n <= 0 || n >= 10)
+ fatal("reading floppy type");
+ typ = string buf[0:n];
+ "sd" =>
+ typ = "hard";
+ * =>
+ typ = "unknown";
+ }
+ }
+
+ if(!fflag && disk.dtype == "floppy")
+ if(sys->fprint(disk.ctlfd, "format %s", typ) < 0)
+ fatal(sys->sprint("formatting floppy as %s: %r", typ));
+
+ if(disk.dtype != "floppy" && !xflag)
+ sanitycheck(disk);
+
+ # check that everything will succeed
+ dosfs(dos, writepbs, disk, label, tl args, 0);
+
+ # commit
+ dosfs(dos, writepbs, disk, label, tl args, 1);
+
+ sys->print("used %bd bytes\n", big fatlast*big clustersize*big disk.secsize);
+ exit;
+}
+
+#
+# look for a partition table on sector 1, as would be the
+# case if we were erroneously formatting 9fat without -r 2.
+# if it's there and nresrv is not big enough, complain and exit.
+# i've blown away my partition table too many times.
+#
+sanitycheck(disk: ref Disk)
+{
+ buf := array[512] of byte;
+ bad := 0;
+ if(dos && nresrv < 2 && sys->seek(disk.fd, big disk.secsize, 0) == big disk.secsize &&
+ sys->read(disk.fd, buf, len buf) >= 5 && string buf[0:5] == "part "){
+ sys->fprint(sys->fildes(2), "there's a plan9 partition on the disk\n"+
+ "and you didn't specify -r 2 (or greater).\n" +
+ "either specify -r 2 or -x to disable this check.\n");
+ bad = 1;
+ }
+
+ if(disk.dtype == "sd" && disk.offset == big 0){
+ sys->fprint(sys->fildes(2), "you're attempting to format your disk (/dev/sdXX/data)\n"+
+ "rather than a partition such as /dev/sdXX/9fat;\n" +
+ "this is probably a mistake. specify -x to disable this check.\n");
+ bad = 1;
+ }
+
+ if(bad)
+ raise "fail:failed disk sanity check";
+}
+
+#
+# return the BIOS driver number for the disk.
+# 16r80 is the first fixed disk, 16r81 the next, etc.
+# We map sdC0=16r80, sdC1=16r81, sdD0=16r82, sdD1=16r83
+#
+getdriveno(disk: ref Disk): int
+{
+ if(disk.dtype != "sd")
+ return 16r80; # first hard disk
+
+ name := sys->fd2path(disk.fd);
+ if(len name < 3)
+ return 16r80;
+
+ #
+ # The name is of the format #SsdC0/foo
+ # or /dev/sdC0/foo.
+ # So that we can just look for /sdC0, turn
+ # #SsdC0/foo into #/sdC0/foo.
+ #
+ if(name[0:1] == "#S")
+ name[1] = '/';
+
+ for(p := name; len p >= 4; p = p[1:])
+ if(p[0:2] == "sd" && (p[2]=='C' || p[2]=='D') && (p[3]=='0' || p[3]=='1'))
+ return 16r80 + (p[2]-'c')*2 + (p[3]-'0');
+
+ return 16r80;
+}
+
+writen(fd: ref Sys->FD, buf: array of byte, n: int): int
+{
+ # write 8k at a time, to be nice to the disk subsystem
+ m: int;
+ for(tot:=0; tot<n; tot+=m){
+ m = n - tot;
+ if(m > 8192)
+ m = 8192;
+ if(sys->write(fd, buf[tot:], m) != m)
+ break;
+ }
+ return tot;
+}
+
+dosfs(dofat: int, dopbs: int, disk: ref Disk, label: array of byte, arg: list of string, commit: int)
+{
+ if(dofat == 0 && dopbs == 0)
+ return;
+
+ for(i := 0; i < len floppytype; i++)
+ if(typ == floppytype[i].name)
+ break;
+ if(i == len floppytype)
+ fatal(sys->sprint("unknown floppy type %q", typ));
+
+ t := floppytype[i];
+ if(t.sectors == 0 && typ == "hard"){
+ t.sectors = disk.s;
+ t.heads = disk.h;
+ t.tracks = disk.c;
+ }
+
+ if(t.sectors == 0 && dofat)
+ fatal(sys->sprint("cannot format fat with type %s: geometry unknown", typ));
+
+ if(fflag){
+ disk.size = big (t.bytes*t.sectors*t.heads*t.tracks);
+ disk.secsize = t.bytes;
+ disk.secs = disk.size / big disk.secsize;
+ }
+
+ secsize := disk.secsize;
+ length := disk.size;
+
+ #
+ # make disk full size if a file
+ #
+ if(fflag && disk.dtype == "file"){
+ (ok, d) := sys->fstat(disk.wfd);
+ if(ok < 0)
+ fatal(sys->sprint("fstat disk: %r"));
+ if(commit && d.length < disk.size){
+ if(sys->seek(disk.wfd, disk.size-big 1, 0) < big 0)
+ fatal(sys->sprint("seek to 9: %r"));
+ if(sys->write(disk.wfd, array[] of {0 => byte '9'}, 1) < 0)
+ fatal(sys->sprint("writing 9: @%bd %r", sys->seek(disk.wfd, big 0, 1)));
+ }
+ }
+
+ buf := array[secsize] of byte;
+
+ #
+ # start with initial sector from disk
+ #
+ if(sys->seek(disk.fd, big 0, 0) < big 0)
+ fatal(sys->sprint("seek to boot sector: %r"));
+ if(commit && sys->read(disk.fd, buf, secsize) != secsize)
+ fatal(sys->sprint("reading boot sector: %r"));
+
+ if(dofat)
+ memset(buf, 0, DB_SIZE);
+
+ #
+ # Jump instruction and OEM name
+ #
+ b := buf; # hmm.
+ b[DB_MAGIC+0] = byte 16rEB;
+ b[DB_MAGIC+1] = byte 16r3C;
+ b[DB_MAGIC+2] = byte 16r90;
+ memmove(b[DB_VERSION: ], array of byte "Plan9.00", DB_VERSIONSIZE);
+
+ #
+ # Add bootstrapping code; assume it starts
+ # at 16r3E (the destination of the jump we just
+ # wrote to b[DB_MAGIC]
+ #
+ if(dopbs){
+ pbsbuf := array[secsize] of byte;
+ npbs: int;
+ if(pbs != nil){
+ if((sysfd := sys->open(pbs, Sys->OREAD)) == nil)
+ fatal(sys->sprint("open %s: %r", pbs));
+ npbs = sys->read(sysfd, pbsbuf, len pbsbuf);
+ if(npbs < 0)
+ fatal(sys->sprint("read %s: %r", pbs));
+ if(npbs > secsize-2)
+ fatal("boot block too large");
+ }else{
+ pbsbuf[0:] = bootprog;
+ npbs = len bootprog;
+ }
+ if(npbs <= 16r3E)
+ sys->fprint(sys->fildes(2), "warning: pbs too small\n");
+ else
+ buf[16r3E:] = pbsbuf[16r3E:npbs];
+ }
+
+ #
+ # Add FAT BIOS parameter block
+ #
+ if(dofat){
+ if(commit){
+ sys->print("Initializing FAT file system\n");
+ sys->print("type %s, %d tracks, %d heads, %d sectors/track, %d bytes/sec\n",
+ t.name, t.tracks, t.heads, t.sectors, secsize);
+ }
+
+ if(clustersize == 0)
+ clustersize = t.cluster;
+ #
+ # the number of fat bits depends on how much disk is left
+ # over after you subtract out the space taken up by the fat tables.
+ # try both. what a crock.
+ #
+ for(fatbits = 12;;){
+ volsecs = int (length/big secsize);
+ #
+ # here's a crock inside a crock. even having fixed fatbits,
+ # the number of fat sectors depends on the number of clusters,
+ # but of course we don't know yet. maybe iterating will get us there.
+ # or maybe it will cycle.
+ #
+ clusters = 0;
+ for(i=0;; i++){
+ fatsecs = (fatbits*clusters + 8*secsize - 1)/(8*secsize);
+ rootsecs = volsecs/200;
+ rootfiles = rootsecs * (secsize/DD_SIZE);
+ if(rootfiles > 512){
+ rootfiles = 512;
+ rootsecs = rootfiles/(secsize/DD_SIZE);
+ }
+ data := nresrv + 2*fatsecs + (rootfiles*DD_SIZE + secsize-1)/secsize;
+ newclusters := 2 + (volsecs - data)/clustersize;
+ if(newclusters == clusters)
+ break;
+ clusters = newclusters;
+ if(i > 10)
+ fatal(sys->sprint("can't decide how many clusters to use (%d? %d?)", clusters, newclusters));
+if(chatty) sys->print("clusters %d\n", clusters);
+if(clusters <= 1) raise "trap";
+ }
+
+if(chatty) sys->print("try %d fatbits => %d clusters of %d\n", fatbits, clusters, clustersize);
+ if(clusters < 4087 || fatbits > 12)
+ break;
+ fatbits = 16;
+ }
+ if(clusters >= 65527)
+ fatal("disk too big; implement fat32");
+
+ putshort(b[DB_SECTSIZE: ], secsize);
+ b[DB_CLUSTSIZE] = byte clustersize;
+ putshort(b[DB_NRESRV: ], nresrv);
+ b[DB_NFATS] = byte 2;
+ putshort(b[DB_ROOTSIZE: ], rootfiles);
+ if(volsecs < (1<<16))
+ putshort(b[DB_VOLSIZE: ], volsecs);
+ b[DB_MEDIADESC] = byte t.media;
+ putshort(b[DB_FATSIZE: ], fatsecs);
+ putshort(b[DB_TRKSIZE: ], t.sectors);
+ putshort(b[DB_NHEADS: ], t.heads);
+ putlong(b[DB_NHIDDEN: ], int disk.offset);
+ putlong(b[DB_BIGVOLSIZE: ], volsecs);
+
+ #
+ # Extended BIOS Parameter Block
+ #
+ if(t.media == 16rF8)
+ dno := getdriveno(disk);
+ else
+ dno = 0;
+if(chatty) sys->print("driveno = %ux\n", dno);
+ b[DB_DRIVENO] = byte dno;
+ b[DB_BOOTSIG] = byte 16r29;
+ x := int (disk.offset + big b[DB_NFATS]*big fatsecs + big nresrv);
+ putlong(b[DB_VOLID:], x);
+if(chatty) sys->print("volid = %ux\n", x);
+ b[DB_LABEL:] = label;
+ r := sys->aprint("FAT%d ", fatbits);
+ if(len r > DB_TYPESIZE)
+ r = r[0:DB_TYPESIZE];
+ b[DB_TYPE:] = r;
+ }
+
+ b[secsize-2] = byte Disks->Magic0;
+ b[secsize-1] = byte Disks->Magic1;
+
+ if(commit){
+ if(sys->seek(disk.wfd, big 0, 0) < big 0)
+ fatal(sys->sprint("seek to boot sector: %r\n"));
+ if(sys->write(disk.wfd, b, secsize) != secsize)
+ fatal(sys->sprint("writing to boot sector: %r"));
+ }
+
+ #
+ # if we were only called to write the PBS, leave now
+ #
+ if(dofat == 0)
+ return;
+
+ #
+ # allocate an in memory fat
+ #
+ if(sys->seek(disk.wfd, big (nresrv*secsize), 0) < big 0)
+ fatal(sys->sprint("seek to fat: %r"));
+if(chatty) sys->print("fat @%buX\n", sys->seek(disk.wfd, big 0, 1));
+ fat = array[fatsecs*secsize] of {* => byte 0};
+ if(fat == nil)
+ fatal("out of memory");
+ fat[0] = byte t.media;
+ fat[1] = byte 16rff;
+ fat[2] = byte 16rff;
+ if(fatbits == 16)
+ fat[3] = byte 16rff;
+ fatlast = 1;
+ if(sys->seek(disk.wfd, big (2*fatsecs*secsize), 1) < big 0) # 2 fats
+ fatal(sys->sprint("seek to root: %r"));
+if(chatty) sys->print("root @%buX\n", sys->seek(disk.wfd, big 0, 1));
+
+ #
+ # allocate an in memory root
+ #
+ root = array[rootsecs*secsize] of {* => byte 0};
+ if(sys->seek(disk.wfd, big (rootsecs*secsize), 1) < big 0) # rootsecs
+ fatal(sys->sprint("seek to files: %r"));
+if(chatty) sys->print("files @%buX\n", sys->seek(disk.wfd, big 0, 1));
+
+ #
+ # Now positioned at the Files Area.
+ # If we have any arguments, process
+ # them and write out.
+ #
+ for(p := 0; arg != nil; arg = tl arg){
+ if(p >= rootsecs*secsize)
+ fatal("too many files in root");
+ #
+ # Open the file and get its length.
+ #
+ if((sysfd := sys->open(hd arg, Sys->OREAD)) == nil)
+ fatal(sys->sprint("open %s: %r", hd arg));
+ (ok, d) := sys->fstat(sysfd);
+ if(ok < 0)
+ fatal(sys->sprint("stat %s: %r", hd arg));
+ if(d.length >= big 16r7FFFFFFF)
+ fatal(sys->sprint("file %s too big (%bd bytes)", hd arg, d.length));
+ if(commit)
+ sys->print("Adding file %s, length %bd\n", hd arg, d.length);
+
+ x: int;
+ length = d.length;
+ if(length > big 0){
+ #
+ # Allocate a buffer to read the entire file into.
+ # This must be rounded up to a cluster boundary.
+ #
+ # Read the file and write it out to the Files Area.
+ #
+ length += big (secsize*clustersize - 1);
+ length /= big (secsize*clustersize);
+ length *= big (secsize*clustersize);
+ fbuf := array[int length] of byte;
+ if((nr := sys->read(sysfd, fbuf, int d.length)) != int d.length){
+ if(nr >= 0)
+ sys->werrstr("short read");
+ fatal(sys->sprint("read %s: %r", hd arg));
+ }
+ for(; nr < len fbuf; nr++)
+ fbuf[nr] = byte 0;
+if(chatty) sys->print("%q @%buX\n", d.name, sys->seek(disk.wfd, big 0, 1));
+ if(commit && writen(disk.wfd, fbuf, len fbuf) != len fbuf)
+ fatal(sys->sprint("write %s: %r", hd arg));
+ fbuf = nil;
+
+ #
+ # Allocate the FAT clusters.
+ # We're assuming here that where we
+ # wrote the file is in sync with
+ # the cluster allocation.
+ # Save the starting cluster.
+ #
+ length /= big (secsize*clustersize);
+ x = clustalloc(Sof);
+ for(n := 0; n < int length-1; n++)
+ clustalloc(0);
+ clustalloc(Eof);
+ }
+ else
+ x = 0;
+
+ #
+ # Add the filename to the root.
+ #
+sys->fprint(sys->fildes(2), "add %s at clust %ux\n", d.name, x);
+ addrname(root[p:], d, hd arg, x);
+ p += DD_SIZE;
+ }
+
+ #
+ # write the fats and root
+ #
+ if(commit){
+ if(sys->seek(disk.wfd, big (nresrv*secsize), 0) < big 0)
+ fatal(sys->sprint("seek to fat #1: %r"));
+ if(sys->write(disk.wfd, fat, fatsecs*secsize) < 0)
+ fatal(sys->sprint("writing fat #1: %r"));
+ if(sys->write(disk.wfd, fat, fatsecs*secsize) < 0)
+ fatal(sys->sprint("writing fat #2: %r"));
+ if(sys->write(disk.wfd, root, rootsecs*secsize) < 0)
+ fatal(sys->sprint("writing root: %r"));
+ }
+}
+
+#
+# allocate a cluster
+#
+clustalloc(flag: int): int
+{
+ o, x: int;
+
+ if(flag != Sof){
+ if (flag == Eof)
+ x =16rffff;
+ else
+ x = fatlast+1;
+ if(fatbits == 12){
+ x &= 16rfff;
+ o = (3*fatlast)/2;
+ if(fatlast & 1){
+ fat[o] = byte ((int fat[o] & 16r0f) | (x<<4));
+ fat[o+1] = byte (x>>4);
+ } else {
+ fat[o] = byte x;
+ fat[o+1] = byte ((int fat[o+1] & 16rf0) | ((x>>8) & 16r0F));
+ }
+ } else {
+ o = 2*fatlast;
+ fat[o] = byte x;
+ fat[o+1] = byte (x>>8);
+ }
+ }
+
+ if(flag == Eof)
+ return 0;
+ if(++fatlast >= clusters)
+ fatal(sys->sprint("data does not fit on disk (%d %d)", fatlast, clusters));
+ return fatlast;
+}
+
+putname(p: string, buf: array of byte)
+{
+ memset(buf[DD_NAME: ], ' ', DD_NAMESIZE+DD_EXTSIZE);
+ for(i := 0; i < DD_NAMESIZE && i < len p && p[i] != '.'; i++){
+ c := p[i];
+ if(c >= 'a' && c <= 'z')
+ c += 'A'-'a';
+ buf[DD_NAME+i] = byte c;
+ }
+ for(i = 0; i < len p; i++)
+ if(p[i] == '.'){
+ p = p[i+1:];
+ for(i = 0; i < DD_EXTSIZE && i < len p; i++){
+ c := p[i];
+ if(c >= 'a' && c <= 'z')
+ c += 'A'-'a';
+ buf[DD_EXT+i] = byte c;
+ }
+ break;
+ }
+}
+
+puttime(buf: array of byte)
+{
+ t := daytime->local(daytime->now());
+ x := (t.hour<<11) | (t.min<<5) | (t.sec>>1);
+ buf[DD_TIME+0] = byte x;
+ buf[DD_TIME+1] = byte (x>>8);
+ x = ((t.year-80)<<9) | ((t.mon+1)<<5) | t.mday;
+ buf[DD_DATE+0] = byte x;
+ buf[DD_DATE+1] = byte (x>>8);
+}
+
+addrname(buf: array of byte, dir: Sys->Dir, name: string, start: int)
+{
+ s := name;
+ for(i := len s; --i >= 0;)
+ if(s[i] == '/'){
+ s = s[i+1:];
+ break;
+ }
+ putname(s, buf);
+ if(s == "9load")
+ buf[DD_ATTR] = byte DSYSTEM;
+ else
+ buf[DD_ATTR] = byte 0;
+ puttime(buf);
+ buf[DD_START+0] = byte start;
+ buf[DD_START+1] = byte (start>>8);
+ buf[DD_LENGTH+0] = byte dir.length;
+ buf[DD_LENGTH+1] = byte (dir.length>>8);
+ buf[DD_LENGTH+2] = byte (dir.length>>16);
+ buf[DD_LENGTH+3] = byte (dir.length>>24);
+}
+
+memset(d: array of byte, v: int, n: int)
+{
+ for (i := 0; i < n; i++)
+ d[i] = byte v;
+}
+
+memmove(d: array of byte, s: array of byte, n: int)
+{
+ d[0:] = s[0:n];
+}
+
+putshort(b: array of byte, v: int)
+{
+ b[1] = byte (v>>8);
+ b[0] = byte v;
+}
+
+putlong(b: array of byte, v: int)
+{
+ putshort(b, v);
+ putshort(b[2: ], v>>16);
+}
diff --git a/appl/cmd/disk/ftl.b b/appl/cmd/disk/ftl.b
new file mode 100644
index 00000000..750defb7
--- /dev/null
+++ b/appl/cmd/disk/ftl.b
@@ -0,0 +1,911 @@
+#
+# basic Flash Translation Layer driver
+# see for instance the Intel technical paper
+# ``Understanding the Flash Translation Layer (FTL) Specification''
+# Order number 297816-001 (online at www.intel.com)
+#
+# a public driver by David Hinds, dhinds@allegro.stanford.edu
+# further helps with some details.
+#
+# this driver uses the common simplification of never storing
+# the VBM on the medium (a waste of precious flash!) but
+# rather building it on the fly as the block maps are read.
+#
+# Plan 9 driver (c) 1997 by C H Forsyth (forsyth@caldo.demon.co.uk)
+# This driver may be used or adapted by anyone for any non-commercial purpose.
+#
+# adapted for Inferno 1998 by C H Forsyth, Vita Nuova Limited, York, England (byteles@vitanuova.com)
+#
+# C H Forsyth and Vita Nuova Limited expressly allow Lucent Technologies
+# to use this driver freely for any Inferno-related purposes whatever,
+# including commercial applications.
+#
+# TO DO:
+# check error handling details for get/put flash
+# bad block handling
+# reserved space in formatted size
+# possibly block size as parameter
+# fetch parameters from header on init
+#
+# Adapted to a ftl formatter for Inferno 2000 by J R Firth, Vita Nuova Limited
+# usage : ftl flashsize secsize inputfile outputfile
+# outputfile will then be a ftl image of inputfile
+# nb assumes the base address is zero
+#
+# Converted to limbo for Inferno 2000 by JR Firth, Vita Nuova Holdings Limited
+#
+
+implement Ftlimage;
+
+include "sys.m";
+include "draw.m";
+
+sys : Sys;
+ OREAD, OWRITE, FD, open, create, read, write, print, fprint : import sys;
+
+Ftlimage : module
+{
+ init : fn(nil : ref Draw->Context, argv : list of string);
+};
+
+stderr : ref FD;
+
+flashsize, secsize : int;
+flashm : array of byte;
+trace : int = 0;
+
+Eshift : con 18; # 2^18=256k; log2(eraseunit)
+Flashseg : con 1<<Eshift;
+Bshift : con 9; # 2^9=512
+Bsize : con 1<<Bshift;
+BAMoffset : con 16r100;
+Nolimit : con ~0;
+USABLEPCT : con 95; # release only this % to client
+
+FTLDEBUG : con 0;
+
+# erase unit header (defined by FTL specification)
+# offsets into Merase
+O_LINKTUPLE : con 0;
+O_ORGTUPLE : con 5;
+O_NXFER : con 15;
+O_NERASE : con 16;
+O_ID : con 20;
+O_BSHIFT : con 22;
+O_ESHIFT : con 23;
+O_PSTART : con 24;
+O_NUNITS : con 26;
+O_PSIZE : con 28;
+O_VBMBASE : con 32;
+O_NVBM : con 36;
+O_FLAGS : con 38;
+O_CODE : con 39;
+O_SERIAL : con 40;
+O_ALTOFFSET : con 44;
+O_BAMOFFSET : con 48;
+O_RSV2 : con 52;
+
+ERASEHDRLEN : con 64;
+
+# special unit IDs
+XferID : con 16rffff;
+XferBusy : con 16r7fff;
+
+# special BAM addresses
+Bfree : con -1; #16rffffffff
+Bwriting : con -2; #16rfffffffe
+Bdeleted : con 0;
+
+# block types
+TypeShift : con 7;
+BlockType : con (1<<TypeShift)-1;
+ControlBlock : con 16r30;
+DataBlock : con 16r40;
+ReplacePage : con 16r60;
+BadBlock : con 16r70;
+
+BNO(va : int) : int
+{
+ return va>>Bshift;
+}
+MKBAM(b : int,t : int) : int
+{
+ return (b<<Bshift)|t;
+}
+
+Terase : adt {
+ x : int;
+ id : int;
+ offset : int;
+ bamoffset : int;
+ nbam : int;
+ bam : array of byte;
+ bamx : int;
+ nfree : int;
+ nused : int;
+ ndead : int;
+ nbad : int;
+ nerase : int;
+};
+
+Ftl : adt {
+ base : int; # base of flash region
+ size : int; # size of flash region
+ segsize : int; # size of flash segment (erase unit)
+ eshift : int; # log2(erase-unit-size)
+ bshift : int; # log2(bsize)
+ bsize : int;
+ nunit : int; # number of segments (erase units)
+ unit : array of ref Terase;
+ lastx : int; # index in unit of last allocation
+ xfer : int; # index in unit of current transfer unit (-1 if none)
+ nfree : int; # total free space in blocks
+ nblock : int; # total space in blocks
+ rwlimit : int; # user-visible block limit (`formatted size')
+ vbm : array of int; # virtual block map
+ fstart : int; # address of first block of data in a segment
+ trace : int; # (debugging) trace of read/write actions
+ detach : int; # free Ftl on last close
+
+ # scavenging variables
+ needspace : int;
+ hasproc : int;
+};
+
+# Ftl.detach
+Detached : con 1; # detach on close
+Deferred : con 2; # scavenger must free it
+
+ftls : ref Ftl;
+
+ftlstat(sz : int)
+{
+ print("16r%x:16r%x:16r%x\n", ftls.rwlimit*Bsize, sz, flashsize);
+ print("%d:%d:%d in 512b blocks\n", ftls.rwlimit, sz>>Bshift, flashsize>>Bshift);
+}
+
+ftlread(buf : array of byte, n : int, offset : int) : int
+{
+ ftl : ref Ftl;
+ e : ref Terase;
+ nb : int;
+ a : int;
+ pb : int;
+ mapb : int;
+
+ if(n <= 0 || n%Bsize || offset%Bsize) {
+ fprint(stderr, "ftl: bad read\n");
+ exit;
+ }
+ ftl = ftls;
+ nb = n/Bsize;
+ offset /= Bsize;
+ if(offset >= ftl.rwlimit)
+ return 0;
+ if(offset+nb > ftl.rwlimit)
+ nb = ftl.rwlimit - offset;
+ a = 0;
+ for(n = 0; n < nb; n++){
+ (mapb, e, pb) = mapblk(ftl, offset+n);
+ if(mapb)
+ getflash(ftl, buf[a:], e.offset + pb*Bsize, Bsize);
+ else
+ memset(buf[a:], 0, Bsize);
+ a += Bsize;
+ }
+ return a;
+}
+
+ftlwrite(buf : array of byte, n : int, offset : int) : int
+{
+ ns, nb : int;
+ a : int;
+ e, oe : ref Terase;
+ ob, v : int;
+ ftl : ref Ftl;
+ mapb : int;
+
+ if(n <= 0)
+ return 0;
+ ftl = ftls;
+ if(n <= 0 || n%Bsize || offset%Bsize) {
+ fprint(stderr, "ftl: bad write\n");
+ exit;
+ }
+ nb = n/Bsize;
+ offset /= Bsize;
+ if(offset >= ftl.rwlimit)
+ return 0;
+ if(offset+nb > ftl.rwlimit)
+ nb = ftl.rwlimit - offset;
+ a = 0;
+ for(n = 0; n < nb; n++){
+ ns = 0;
+ while((v = allocblk(ftl)) == 0)
+ if(!scavenge(ftl) || ++ns > 3){
+ fprint(stderr, "ftl: flash memory full\n");
+ }
+ (mapb, oe, ob) = mapblk(ftl, offset+n);
+ if(!mapb)
+ oe = nil;
+ e = ftl.unit[v>>16];
+ v &= 16rffff;
+ putflash(ftl, e.offset + v*Bsize, buf[a:], Bsize);
+ putbam(ftl, e, v, MKBAM(offset+n, DataBlock));
+ # both old and new block references exist in this window (can't be closed?)
+ ftl.vbm[offset+n] = (e.x<<16) | v;
+ if(oe != nil){
+ putbam(ftl, oe, ob, Bdeleted);
+ oe.ndead++;
+ }
+ a += Bsize;
+ }
+ return a;
+}
+
+mkftl(fname : string, base : int, size : int, eshift : int, op : string) : ref Ftl
+{
+ i, j, nov, segblocks : int;
+ limit : int;
+ e : ref Terase;
+
+ ftl := ref Ftl;
+ ftl.lastx = 0;
+ ftl.detach = 0;
+ ftl.needspace = 0;
+ ftl.hasproc = 0;
+ ftl.trace = 0;
+ limit = flashsize;
+ if(size == Nolimit)
+ size = limit-base;
+ if(base >= limit || size > limit || base+size > limit || eshift < 8 || (1<<eshift) > size) {
+ fprint(stderr, "bad flash space parameters");
+ exit;
+ }
+ if(FTLDEBUG || ftl.trace || trace)
+ print("%s flash %s #%x:#%x limit #%x\n", op, fname, base, size, limit);
+ ftl.base = base;
+ ftl.size = size;
+ ftl.bshift = Bshift;
+ ftl.bsize = Bsize;
+ ftl.eshift = eshift;
+ ftl.segsize = 1<<eshift;
+ ftl.nunit = size>>eshift;
+ nov = ((ftl.segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize; # number of overhead blocks per segment (header, and BAM itself)
+ ftl.fstart = nov;
+ segblocks = ftl.segsize/Bsize - nov;
+ ftl.nblock = ftl.nunit*segblocks;
+ if(ftl.nblock >= 16r10000)
+ ftl.nblock = 16r10000;
+ ftl.vbm = array[ftl.nblock] of int;
+ ftl.unit = array[ftl.nunit] of ref Terase;
+ if(ftl.vbm == nil || ftl.unit == nil) {
+ fprint(stderr, "out of mem");
+ exit;
+ }
+ for(i=0; i<ftl.nblock; i++)
+ ftl.vbm[i] = 0;
+ if(op == "format"){
+ for(i=0; i<ftl.nunit-1; i++)
+ eraseinit(ftl, i*ftl.segsize, i, 1);
+ eraseinit(ftl, i*ftl.segsize, XferID, 1);
+ }
+ ftl.xfer = -1;
+ for(i=0; i<ftl.nunit; i++){
+ e = eraseload(ftl, i, i*ftl.segsize);
+ if(e == nil){
+ fprint(stderr, "ftl: logical segment %d: bad format\n", i);
+ continue;
+ }
+ if(e.id == XferBusy){
+ e.nerase++;
+ eraseinit(ftl, e.offset, XferID, e.nerase);
+ e.id = XferID;
+ }
+ for(j=0; j<ftl.nunit; j++)
+ if(ftl.unit[j] != nil && ftl.unit[j].id == e.id){
+ fprint(stderr, "ftl: duplicate erase unit #%x\n", e.id);
+ erasefree(e);
+ e = nil;
+ break;
+ }
+ if(e != nil){
+ ftl.unit[e.x] = e;
+ if(e.id == XferID)
+ ftl.xfer = e.x;
+ if (FTLDEBUG || ftl.trace || trace)
+ fprint(stderr, "ftl: unit %d:#%x used %d free %d dead %d bad %d nerase %d\n",
+ e.x, e.id, e.nused, e.nfree, e.ndead, e.nbad, e.nerase);
+ }
+ }
+ if(ftl.xfer < 0 && ftl.nunit <= 0 || ftl.xfer >= 0 && ftl.nunit <= 1) {
+ fprint(stderr, "ftl: no valid flash data units");
+ exit;
+ }
+ if(ftl.xfer < 0)
+ fprint(stderr, "ftl: no transfer unit: device is WORM\n");
+ else
+ ftl.nblock -= segblocks; # discount transfer segment
+ if(ftl.nblock >= 1000)
+ ftl.rwlimit = ftl.nblock-100; # TO DO: variable reserve
+ else
+ ftl.rwlimit = ftl.nblock*USABLEPCT/100;
+ return ftl;
+}
+
+ftlfree(ftl : ref Ftl)
+{
+ if(ftl != nil){
+ ftl.unit = nil;
+ ftl.vbm = nil;
+ ftl = nil;
+ }
+}
+
+#
+# this simple greedy algorithm weighted by nerase does seem to lead
+# to even wear of erase units (cf. the eNVy file system)
+#
+
+bestcopy(ftl : ref Ftl) : ref Terase
+{
+ e, be : ref Terase;
+ i : int;
+
+ be = nil;
+ for(i=0; i<ftl.nunit; i++)
+ if((e = ftl.unit[i]) != nil && e.id != XferID && e.id != XferBusy && e.ndead+e.nbad &&
+ (be == nil || e.nerase <= be.nerase && e.ndead >= be.ndead))
+ be = e;
+ return be;
+}
+
+copyunit(ftl : ref Ftl, from : ref Terase, too : ref Terase) : int
+{
+ i, nb : int;
+ id := array[2] of byte;
+ bam : array of byte;
+ buf : array of byte;
+ v, bno : int;
+
+ if(FTLDEBUG || ftl.trace || trace)
+ print("ftl: copying %d (#%x) to #%x\n", from.id, from.offset, too.offset);
+ too.nbam = 0;
+ too.bam = nil;
+ bam = nil;
+ buf = array[Bsize] of byte;
+ if(buf == nil)
+ return 0;
+ PUT2(id, XferBusy);
+ putflash(ftl, too.offset+O_ID, id, 2);
+ # make new BAM
+ nb = from.nbam*4;
+ bam = array[nb] of byte;
+ memmove(bam, from.bam, nb);
+ too.nused = 0;
+ too.nbad = 0;
+ too.nfree = 0;
+ too.ndead = 0;
+ for(i = 0; i < from.nbam; i++)
+ bv := GET4(bam[4*i:]);
+ case(bv){
+ Bwriting or
+ Bdeleted or
+ Bfree =>
+ PUT4(bam[4*i:], Bfree);
+ too.nfree++;
+ break;
+ * =>
+ case(bv&BlockType){
+ DataBlock or
+ ReplacePage =>
+ v = bv;
+ bno = BNO(v & ~BlockType);
+ if(i < ftl.fstart || bno >= ftl.nblock){
+ print("ftl: unit %d:#%x bad bam[%d]=#%x\n", from.x, from.id, i, v);
+ too.nfree++;
+ PUT4(bam[4*i:], Bfree);
+ break;
+ }
+ getflash(ftl, buf, from.offset+i*Bsize, Bsize);
+ putflash(ftl, too.offset+i*Bsize, buf, Bsize);
+ too.nused++;
+ break;
+ ControlBlock =>
+ too.nused++;
+ break;
+ * =>
+ # case BadBlock: # it isn't necessarily bad in this unit
+ too.nfree++;
+ PUT4(bam[4*i:], Bfree);
+ break;
+ }
+ }
+ # for(i=0; i<from.nbam; i++){
+ # v = GET4(bam[4*i:]);
+ # if(v != Bfree && ftl.trace > 1)
+ # print("to[%d]=#%x\n", i, v);
+ # PUT4(bam[4*i:], v);
+ # }
+ putflash(ftl, too.bamoffset, bam, nb); # BUG: PUT4 ? IS IT ?
+ # for(i=0; i<from.nbam; i++){
+ # v = GET4(bam[4*i:]);
+ # PUT4(bam[4*i:], v);
+ # }
+ too.id = from.id;
+ PUT2(id, too.id);
+ putflash(ftl, too.offset+O_ID, id, 2);
+ too.nbam = from.nbam;
+ too.bam = bam;
+ ftl.nfree += too.nfree - from.nfree;
+ buf = nil;
+ return 1;
+}
+
+mustscavenge(a : ref Ftl) : int
+{
+ return a.needspace || a.detach == Deferred;
+}
+
+donescavenge(a : ref Ftl) : int
+{
+ return a.needspace == 0;
+}
+
+scavengeproc(arg : ref Ftl)
+{
+ ftl : ref Ftl;
+ i : int;
+ e, ne : ref Terase;
+
+ ftl = arg;
+ if(mustscavenge(ftl)){
+ if(ftl.detach == Deferred){
+ ftlfree(ftl);
+ fprint(stderr, "scavenge out of memory\n");
+ exit;
+ }
+ if(FTLDEBUG || ftl.trace || trace)
+ print("ftl: scavenge %d\n", ftl.nfree);
+ e = bestcopy(ftl);
+ if(e == nil || ftl.xfer < 0 || (ne = ftl.unit[ftl.xfer]) == nil || ne.id != XferID || e == ne)
+ ;
+ else if(copyunit(ftl, e, ne)){
+ i = ne.x; ne.x = e.x; e.x = i;
+ ftl.unit[ne.x] = ne;
+ ftl.unit[e.x] = e;
+ ftl.xfer = e.x;
+ e.id = XferID;
+ e.nbam = 0;
+ e.bam = nil;
+ e.bamx = 0;
+ e.nerase++;
+ eraseinit(ftl, e.offset, XferID, e.nerase);
+ }
+ if(FTLDEBUG || ftl.trace || trace)
+ print("ftl: end scavenge %d\n", ftl.nfree);
+ ftl.needspace = 0;
+ }
+}
+
+scavenge(ftl : ref Ftl) : int
+{
+ if(ftl.xfer < 0 || bestcopy(ftl) == nil)
+ return 0; # you worm!
+
+ if(!ftl.hasproc){
+ ftl.hasproc = 1;
+ }
+ ftl.needspace = 1;
+
+ scavengeproc(ftls);
+
+ return ftl.nfree;
+}
+
+putbam(ftl : ref Ftl, e : ref Terase, n : int, entry : int)
+{
+ b := array[4] of byte;
+
+ PUT4(e.bam[4*n:], entry);
+ PUT4(b, entry);
+ putflash(ftl, e.bamoffset + n*4, b, 4);
+}
+
+allocblk(ftl : ref Ftl) : int
+{
+ e : ref Terase;
+ i, j : int;
+
+ i = ftl.lastx;
+ do{
+ e = ftl.unit[i];
+ if(e != nil && e.id != XferID && e.nfree){
+ ftl.lastx = i;
+ for(j=e.bamx; j<e.nbam; j++)
+ if(GET4(e.bam[4*j:])== Bfree){
+ putbam(ftl, e, j, Bwriting);
+ ftl.nfree--;
+ e.nfree--;
+ e.bamx = j+1;
+ return (e.x<<16) | j;
+ }
+ e.nfree = 0;
+ print("ftl: unit %d:#%x nfree %d but not free in BAM\n", e.x, e.id, e.nfree);
+ }
+ if(++i >= ftl.nunit)
+ i = 0;
+ }while(i != ftl.lastx);
+ return 0;
+}
+
+mapblk(ftl : ref Ftl, bno : int) : (int, ref Terase, int)
+{
+ v : int;
+ x : int;
+
+ if(bno < ftl.nblock){
+ v = ftl.vbm[bno];
+ if(v == 0 || v == ~0)
+ return (0, nil, 0);
+ x = v>>16;
+ if(x >= ftl.nunit || x == ftl.xfer || ftl.unit[x] == nil){
+ print("ftl: corrupt format: bad block mapping %d . unit #%x\n", bno, x);
+ return (0, nil, 0);
+ }
+ return (1, ftl.unit[x], v & 16rFFFF);
+ }
+ return (0, nil, 0);
+}
+
+eraseinit(ftl : ref Ftl, offset : int, id : int, nerase : int)
+{
+ m : array of byte;
+ bam : array of byte;
+ i, nov : int;
+
+ nov = ((ftl.segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize; # number of overhead blocks (header, and BAM itself)
+ if(nov*Bsize >= ftl.segsize) {
+ fprint(stderr, "ftl -- too small for files");
+ exit;
+ }
+ eraseflash(ftl, offset);
+ m = array[ERASEHDRLEN] of byte;
+ if(m == nil) {
+ fprint(stderr, "nomem\n");
+ exit;
+ }
+ memset(m, 16rFF, len m);
+ m[O_LINKTUPLE+0] = byte 16r13;
+ m[O_LINKTUPLE+1] = byte 16r3;
+ memmove(m[O_LINKTUPLE+2:], array of byte "CIS", 3);
+ m[O_ORGTUPLE+0] = byte 16r46;
+ m[O_ORGTUPLE+1] = byte 16r57;
+ m[O_ORGTUPLE+2] = byte 16r00;
+ memmove(m[O_ORGTUPLE+3:], array of byte "FTL100\0", 7);
+ m[O_NXFER] = byte 1;
+ PUT4(m[O_NERASE:], nerase);
+ PUT2(m[O_ID:], id);
+ m[O_BSHIFT] = byte ftl.bshift;
+ m[O_ESHIFT] = byte ftl.eshift;
+ PUT2(m[O_PSTART:], 0);
+ PUT2(m[O_NUNITS:], ftl.nunit);
+ PUT4(m[O_PSIZE:], ftl.size - nov*Bsize);
+ PUT4(m[O_VBMBASE:], -1); # we always calculate the VBM (16rffffffff)
+ PUT2(m[O_NVBM:], 0);
+ m[O_FLAGS] = byte 0;
+ m[O_CODE] = byte 16rFF;
+ memmove(m[O_SERIAL:], array of byte "Inf1", 4);
+ PUT4(m[O_ALTOFFSET:], 0);
+ PUT4(m[O_BAMOFFSET:], BAMoffset);
+ putflash(ftl, offset, m, ERASEHDRLEN);
+ m = nil;
+ if(id == XferID)
+ return;
+ nov *= 4; # now bytes of BAM
+ bam = array[nov] of byte;
+ if(bam == nil) {
+ fprint(stderr, "nomem");
+ exit;
+ }
+ for(i=0; i<nov; i += 4)
+ PUT4(bam[i:], ControlBlock); # reserve them
+ putflash(ftl, offset+BAMoffset, bam, nov);
+ bam = nil;
+}
+
+eraseload(ftl : ref Ftl, x : int, offset : int) : ref Terase
+{
+ m : array of byte;
+ e : ref Terase;
+ i, nbam : int;
+ bno, v : int;
+
+ m = array[ERASEHDRLEN] of byte;
+ if(m == nil) {
+ fprint(stderr, "nomem");
+ exit;
+ }
+ getflash(ftl, m, offset, ERASEHDRLEN);
+ if(memcmp(m[O_ORGTUPLE+3:], array of byte "FTL100\0", 7) != 0 ||
+ memcmp(m[O_SERIAL:], array of byte "Inf1", 4) != 0){
+ m = nil;
+ return nil;
+ }
+ e = ref Terase;
+ if(e == nil){
+ m = nil;
+ fprint(stderr, "nomem");
+ exit;
+ }
+ e.x = x;
+ e.id = GET2(m[O_ID:]);
+ e.offset = offset;
+ e.bamoffset = GET4(m[O_BAMOFFSET:]);
+ e.nerase = GET4(m[O_NERASE:]);
+ e.bamx = 0;
+ e.nfree = 0;
+ e.nused = 0;
+ e.ndead = 0;
+ e.nbad = 0;
+ m = nil;
+ if(e.bamoffset != BAMoffset){
+ e = nil;
+ return nil;
+ }
+ e.bamoffset += offset;
+ if(e.id == XferID || e.id == XferBusy){
+ e.bam = nil;
+ e.nbam = 0;
+ return e;
+ }
+ nbam = ftl.segsize/Bsize;
+ e.bam = array[4*nbam] of byte;
+ e.nbam = nbam;
+ getflash(ftl, e.bam, e.bamoffset, nbam*4);
+ # scan BAM to build VBM
+ e.bamx = 0;
+ for(i=0; i<nbam; i++){
+ v = GET4(e.bam[4*i:]);
+ if(v == Bwriting || v == Bdeleted)
+ e.ndead++;
+ else if(v == Bfree){
+ if(e.bamx == 0)
+ e.bamx = i;
+ e.nfree++;
+ ftl.nfree++;
+ }else{
+ case(v & BlockType){
+ ControlBlock =>
+ break;
+ DataBlock =>
+ # add to VBM
+ if(v & (1<<31))
+ break; # negative => VBM page, ignored
+ bno = BNO(v & ~BlockType);
+ if(i < ftl.fstart || bno >= ftl.nblock){
+ print("ftl: unit %d:#%x bad bam[%d]=#%x\n", e.x, e.id, i, v);
+ e.nbad++;
+ break;
+ }
+ ftl.vbm[bno] = (e.x<<16) | i;
+ e.nused++;
+ break;
+ ReplacePage =>
+ # replacement VBM page; ignored
+ break;
+ BadBlock =>
+ e.nbad++;
+ break;
+ * =>
+ print("ftl: unit %d:#%x bad bam[%d]=%x\n", e.x, e.id, i, v);
+ }
+ }
+ }
+ return e;
+}
+
+erasefree(e : ref Terase)
+{
+ e.bam = nil;
+ e = nil;
+}
+
+eraseflash(ftl : ref Ftl, offset : int)
+{
+ offset += ftl.base;
+ if(FTLDEBUG || ftl.trace || trace)
+ print("ftl: erase seg @#%x\n", offset);
+ memset(flashm[offset:], 16rff, secsize);
+}
+
+putflash(ftl : ref Ftl, offset : int, buf : array of byte, n : int)
+{
+ offset += ftl.base;
+ if(ftl.trace || trace)
+ print("ftl: write(#%x, %d)\n", offset, n);
+ memmove(flashm[offset:], buf, n);
+}
+
+getflash(ftl : ref Ftl, buf : array of byte, offset : int, n : int)
+{
+ offset += ftl.base;
+ if(ftl.trace || trace)
+ print("ftl: read(#%x, %d)\n", offset, n);
+ memmove(buf, flashm[offset:], n);
+}
+
+BUFSIZE : con 8192;
+
+main(argv : list of string)
+{
+ k, r, sz, offset : int = 0;
+ buf, buf1 : array of byte;
+ fd1, fd2 : ref FD;
+
+ if (len argv != 5) {
+ fprint(stderr, "usage: %s flashsize secsize kfsfile flashfile\n", hd argv);
+ exit;
+ }
+ flashsize = atoi(hd tl argv);
+ secsize = atoi(hd tl tl argv);
+ fd1 = open(hd tl tl tl argv, OREAD);
+ fd2 = create(hd tl tl tl tl argv, OWRITE, 8r644);
+ if (fd1 == nil || fd2 == nil) {
+ fprint(stderr, "bad io files\n");
+ exit;
+ }
+ if(secsize == 0 || secsize > flashsize || secsize&(secsize-1) || 0&(secsize-1) || flashsize == 0 || flashsize != Nolimit && flashsize&(secsize-1)) {
+ fprint(stderr, "ftl: bad sizes\n");
+ exit;
+ }
+ for(k=0; k<32 && (1<<k) != secsize; k++)
+ ;
+ flashm = array[flashsize] of byte;
+ buf = array[BUFSIZE] of byte;
+ if (flashm == nil) {
+ fprint(stderr, "ftl: no mem for flash\n");
+ exit;
+ }
+ ftls = mkftl("FLASH", 0, Nolimit, k, "format");
+ for (;;) {
+ r = read(fd1, buf, BUFSIZE);
+ if (r <= 0)
+ break;
+ if (ftlwrite(buf, r, offset) != r) {
+ fprint(stderr, "ftl: ftlwrite failed - input file too big\n");
+ exit;
+ }
+ offset += r;
+ }
+ write(fd2, flashm, flashsize);
+ fd1 = fd2 = nil;
+ ftlstat(offset);
+ # ftls = mkftl("FLASH", 0, Nolimit, k, "init");
+ sz = offset;
+ offset = 0;
+ buf1 = array[BUFSIZE] of byte;
+ fd1 = open(hd tl tl tl argv, OREAD);
+ for (;;) {
+ r = read(fd1, buf1, BUFSIZE);
+ if (r <= 0)
+ break;
+ if (ftlread(buf, r, offset) != r) {
+ fprint(stderr, "ftl: ftlread failed\n");
+ exit;
+ }
+ if (memcmp(buf, buf1, r) != 0) {
+ fprint(stderr, "ftl: bad read\n");
+ exit;
+ }
+ offset += r;
+ }
+ fd1 = nil;
+ if (offset != sz) {
+ fprint(stderr, "ftl: bad final offset\n");
+ exit;
+ }
+ exit;
+}
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ main(argl);
+}
+
+memset(d : array of byte, v : int, n : int)
+{
+ for (i := 0; i < n; i++)
+ d[i] = byte v;
+}
+
+memmove(d : array of byte, s : array of byte, n : int)
+{
+ d[0:] = s[0:n];
+}
+
+memcmp(s1 : array of byte, s2 : array of byte, n : int) : int
+{
+ for (i := 0; i < n; i++) {
+ if (s1[i] < s2[i])
+ return -1;
+ if (s1[i] > s2[i])
+ return 1;
+ }
+ return 0;
+}
+
+atoi(s : string) : int
+{
+ v : int;
+ base := 10;
+ n := len s;
+ neg := 0;
+
+ for (i := 0; i < n && (s[i] == ' ' || s[i] == '\t'); i++)
+ ;
+ if (s[i] == '+' || s[i] == '-') {
+ if (s[i] == '-')
+ neg = 1;
+ i++;
+ }
+ if (n-i >= 2 && s[i] == '0' && s[i+1] == 'x') {
+ base = 16;
+ i += 2;
+ }
+ else if (n-i >= 1 && s[i] == '0') {
+ base = 8;
+ i++;
+ }
+ m := 0;
+ for(; i < n; i++) {
+ c := s[i];
+ case c {
+ 'a' to 'z' =>
+ v = c - 'a' + 10;
+ 'A' to 'Z' =>
+ v = c - 'A' + 10;
+ '0' to '9' =>
+ v = c - '0';
+ * =>
+ fprint(stderr, "ftl: bad character in number %s\n", s);
+ exit;
+ }
+ if(v >= base) {
+ fprint(stderr, "ftl: character too big for base in %s\n", s);
+ exit;
+ }
+ m = m * base + v;
+ }
+ if(neg)
+ m = -m;
+ return m;
+}
+
+# little endian
+
+GET2(b : array of byte) : int
+{
+ return ((int b[1]) << 8) | (int b[0]);
+}
+
+GET4(b : array of byte) : int
+{
+ return ((int b[3]) << 24) | ((int b[2]) << 16) | ((int b[1]) << 8) | (int b[0]);
+}
+
+PUT2(b : array of byte, v : int)
+{
+ b[1] = byte (v>>8);
+ b[0] = byte v;
+}
+
+PUT4(b : array of byte, v : int)
+{
+ b[3] = byte (v>>24);
+ b[2] = byte (v>>16);
+ b[1] = byte (v>>8);
+ b[0] = byte v;
+}
diff --git a/appl/cmd/disk/kfs.b b/appl/cmd/disk/kfs.b
new file mode 100644
index 00000000..56440205
--- /dev/null
+++ b/appl/cmd/disk/kfs.b
@@ -0,0 +1,3842 @@
+implement Kfs;
+
+#
+# Copyright © 1991-2003 Lucent Technologies Inc.
+# Limbo version Copyright © 2004 Vita Nuova Holdings Limited
+#
+
+#
+# TO DO:
+# - sync proc; Bmod; process structure
+# - swiz?
+
+include "sys.m";
+ sys: Sys;
+ Qid, Dir: import Sys;
+ DMEXCL, DMAPPEND, DMDIR: import Sys;
+ QTEXCL, QTAPPEND, QTDIR: import Sys;
+
+include "draw.m";
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+ NOFID, OEXEC, ORCLOSE, OREAD, OWRITE, ORDWR, OTRUNC: import Styx;
+ IOHDRSZ: import Styx;
+
+include "daytime.m";
+ daytime: Daytime;
+ now: import daytime;
+
+include "arg.m";
+
+Kfs: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+MAXBUFSIZE: con 16*1024;
+
+#
+# fundamental constants
+#
+NAMELEN: con 28; # size of names, including null byte
+NDBLOCK: con 6; # number of direct blocks in Dentry
+MAXFILESIZE: con big 16r7FFFFFFF; # Plan 9's limit (kfs's size is signed)
+
+SUPERADDR: con 1;
+ROOTADDR: con 2;
+
+QPDIR: con int (1<<31);
+QPNONE: con 0;
+QPROOT: con 1;
+QPSUPER: con 2;
+
+#
+# don't change, these are the mode bits on disc
+#
+DALLOC: con 16r8000;
+DDIR: con 16r4000;
+DAPND: con 16r2000;
+DLOCK: con 16r1000;
+DREAD: con 4;
+DWRITE: con 2;
+DEXEC: con 1;
+
+#
+# other constants
+#
+
+MINUTE: con 60;
+TLOCK: con 5*MINUTE;
+NTLOCK: con 200; # number of active file locks
+
+Buffering: con 1;
+
+FID1, FID2, FID3: con 1+iota;
+
+None: con 0; # user ID for "none"
+Noworld: con 9999; # conventional id for "noworld" group
+
+Lock: adt
+{
+ c: chan of int;
+ new: fn(): ref Lock;
+ lock: fn(c: self ref Lock);
+ canlock: fn(c: self ref Lock): int;
+ unlock: fn(c: self ref Lock);
+};
+
+Dentry: adt
+{
+ name: string;
+ uid: int;
+ gid: int;
+ muid: int; # not set by plan 9's kfs
+ mode: int; # mode bits on disc: DALLOC etc
+ qid: Qid; # 9p1 format on disc
+ size: big; # only 32-bits on disc, and Plan 9 limits it to signed
+ atime: int;
+ mtime: int;
+
+ iob: ref Iobuf; # locked block containing directory entry, when in memory
+ buf: array of byte; # pointer into block to packed directory entry, when in memory
+ mod: int; # bits of buf that need updating
+
+ unpack: fn(a: array of byte): ref Dentry;
+ get: fn(p: ref Iobuf, slot: int): ref Dentry;
+ geta: fn(d: ref Device, addr: int, slot: int, qpath: int, mode: int): (ref Dentry, string);
+ getd: fn(f: ref File, mode: int): (ref Dentry, string);
+ put: fn(d: self ref Dentry);
+ access: fn(d: self ref Dentry, f: int, uid: int);
+ change: fn(d: self ref Dentry, f: int);
+ release: fn(d: self ref Dentry);
+ getblk: fn(d: self ref Dentry, a: int, tag: int): ref Iobuf;
+ getblk1: fn(d: self ref Dentry, a: int, tag: int): ref Iobuf;
+ rel2abs: fn(d: self ref Dentry, a: int, tag: int, putb: int): int;
+ trunc: fn(d: self ref Dentry, uid: int);
+ update: fn(d: self ref Dentry);
+ print: fn(d: self ref Dentry);
+};
+
+Uname, Uids, Umode, Uqid, Usize, Utime: con 1<<iota; # Dentry.mod
+
+#
+# disc structure:
+# Tag: pad[2] tag[2] path[4]
+Tagsize: con 2+2+4;
+
+Tag: adt
+{
+ tag: int;
+ path: int;
+
+ unpack: fn(a: array of byte): Tag;
+ pack: fn(t: self Tag, a: array of byte);
+};
+
+Superb: adt
+{
+ iob: ref Iobuf;
+
+ fstart: int;
+ fsize: int;
+ tfree: int;
+ qidgen: int; # generator for unique ids
+
+ fsok: int;
+
+ fbuf: array of byte; # nfree[4] free[FEPERBLK*4]; aliased into containing block
+
+ get: fn(dev: ref Device, flags: int): ref Superb;
+ touched: fn(s: self ref Superb);
+ put: fn(s: self ref Superb);
+ print: fn(s: self ref Superb);
+
+ pack: fn(s: self ref Superb, a: array of byte);
+ unpack: fn(a: array of byte): ref Superb;
+};
+
+Device: adt
+{
+ fd: ref Sys->FD;
+ ronly: int;
+ # could put locks here if necessary
+ # partitioning by ds(3)
+};
+
+#
+# one for each locked qid
+#
+Tlock: adt
+{
+ dev: ref Device;
+ time: int;
+ qpath: int;
+ file: cyclic ref File; # TO DO: probably not needed
+};
+
+File: adt
+{
+ qlock: chan of int;
+ qid: Qid;
+ wpath: ref Wpath;
+ tlock: cyclic ref Tlock; # if file is locked
+ fs: ref Device;
+ addr: int;
+ slot: int;
+ lastra: int; # read ahead address
+ fid: int;
+ uid: int;
+ open: int;
+ cons: int; # if opened by console
+ doffset: big; # directory reading
+ dvers: int;
+ dslot: int;
+
+ new: fn(fid: int): ref File;
+ access: fn(f: self ref File, d: ref Dentry, mode: int): int;
+ lock: fn(f: self ref File);
+ unlock: fn(f: self ref File);
+};
+
+FREAD, FWRITE, FREMOV, FWSTAT: con 1<<iota; # File.open
+
+Chan: adt
+{
+ fd: ref Sys->FD; # fd request came in on
+# rlock, wlock: QLock; # lock for reading/writing messages on cp
+ flags: int;
+ flist: list of ref File; # active files
+ fqlock: chan of int;
+# reflock: RWLock; # lock for Tflush
+ msize: int; # version
+
+ new: fn(fd: ref Sys->FD): ref Chan;
+ getfid: fn(c: self ref Chan, fid: int, flag: int): ref File;
+ putfid: fn(c: self ref Chan, f: ref File);
+ flock: fn(nil: self ref Chan);
+ funlock: fn(nil: self ref Chan);
+};
+
+Hiob: adt
+{
+ link: ref Iobuf; # TO DO: eliminate circular list
+ lk: ref Lock;
+ niob: int;
+
+ newbuf: fn(h: self ref Hiob): ref Iobuf;
+};
+
+Iobuf: adt
+{
+ qlock: chan of int;
+ dev: ref Device;
+ fore: cyclic ref Iobuf; # lru hash chain
+ back: cyclic ref Iobuf; # for lru
+ iobuf: array of byte; # only active while locked
+ xiobuf: array of byte; # "real" buffer pointer
+ addr: int;
+ flags: int;
+
+ get: fn(dev: ref Device, addr: int, flags: int):ref Iobuf;
+ put: fn(iob: self ref Iobuf);
+ lock: fn(iob: self ref Iobuf);
+ canlock: fn(iob: self ref Iobuf): int;
+ unlock: fn(iob: self ref Iobuf);
+
+ checktag: fn(iob: self ref Iobuf, tag: int, qpath: int): int;
+ settag: fn(iob: self ref Iobuf, tag: int, qpath: int);
+};
+
+Wpath: adt
+{
+ up: cyclic ref Wpath; # pointer upwards in path
+ addr: int; # directory entry addr
+ slot: int; # directory entry slot
+};
+
+#
+# error codes generated from the file server
+#
+Eaccess: con "access permission denied";
+Ealloc: con "phase error -- directory entry not allocated";
+Eauth: con "authentication failed";
+Eauthmsg: con "kfs: authentication not required";
+Ebadspc: con "attach -- bad specifier";
+Ebadu: con "attach -- privileged user";
+Ebroken: con "close/read/write -- lock is broken";
+Echar: con "bad character in directory name";
+Econvert: con "protocol botch";
+Ecount: con "read/write -- count too big";
+Edir1: con "walk -- in a non-directory";
+Edir2: con "create -- in a non-directory";
+Edot: con "create -- . and .. illegal names";
+Eempty: con "remove -- directory not empty";
+Eentry: con "directory entry not found";
+Eexist: con "create -- file exists";
+Efid: con "unknown fid";
+Efidinuse: con "fid already in use";
+Efull: con "file system full";
+Elocked: con "open/create -- file is locked";
+Emode: con "open/create -- unknown mode";
+Ename: con "create/wstat -- bad character in file name";
+Enotd: con "wstat -- attempt to change directory";
+Enotg: con "wstat -- not in group";
+Enotl: con "wstat -- attempt to change length";
+Enotm: con "wstat -- unknown type/mode";
+Enotu: con "wstat -- not owner";
+Eoffset: con "read/write -- offset negative";
+Eopen: con "read/write -- on non open fid";
+Ephase: con "phase error -- cannot happen";
+Eqid: con "phase error -- qid does not match";
+Eqidmode: con "wstat -- qid.qtype/dir.mode mismatch";
+Eronly: con "file system read only";
+Ersc: con "it's russ's fault. bug him.";
+Esystem: con "kfs system error";
+Etoolong: con "name too long";
+Etoobig: con "write -- file size limit";
+Ewalk: con "walk -- too many (system wide)";
+
+#
+# tags on block
+#
+Tnone,
+Tsuper, # the super block
+Tdir, # directory contents
+Tind1, # points to blocks
+Tind2, # points to Tind1
+Tfile, # file contents
+Tfree, # in free list
+Tbuck, # cache fs bucket
+Tvirgo, # fake worm virgin bits
+Tcache, # cw cache things
+MAXTAG: con iota;
+
+#
+# flags to Iobuf.get
+#
+ Bread, # read the block if miss
+ Bprobe, # return null if miss
+ Bmod, # set modified bit in buffer
+ Bimm, # set immediate bit in buffer
+ Bres: # never renamed
+ con 1<<iota;
+
+#
+# check flags
+#
+ Crdall, # read all files
+ Ctag, # rebuild tags
+ Cpfile, # print files
+ Cpdir, # print directories
+ Cfree, # rebuild free list
+ Cream, # clear all bad tags
+ Cbad, # clear all bad blocks
+ Ctouch, # touch old dir and indir
+ Cquiet: # report just nasty things
+ con 1<<iota;
+
+#
+# buffer size variables, determined by RBUFSIZE
+#
+RBUFSIZE: int;
+BUFSIZE: int;
+DIRPERBUF: int;
+INDPERBUF: int;
+INDPERBUF2: int;
+FEPERBUF: int;
+
+emptyblock: array of byte;
+
+wrenfd: ref Sys->FD;
+thedevice: ref Device;
+devnone: ref Device;
+wstatallow := 0;
+writeallow := 0;
+writegroup := 0;
+
+ream := 0;
+readonly := 0;
+noatime := 0;
+localfs: con 1;
+conschan: ref Chan;
+consuid := -1;
+consgid := -1;
+debug := 0;
+kfsname: string;
+consoleout: chan of string;
+mainlock: ref Lock;
+pids: list of int;
+
+noqid: Qid;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ styx = load Styx Styx->PATH;
+ daytime = load Daytime Daytime->PATH;
+
+ styx->init();
+
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ error(sys->sprint("can't load %s: %r", Arg->PATH));
+ arg->init(args);
+ arg->setusage("disk/kfs [-r [-b bufsize]] [-cADPRW] [-n name] kfsfile");
+ bufsize := 1024;
+ nocheck := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'c' => nocheck = 1;
+ 'r' => ream = 1;
+ 'b' => bufsize = int arg->earg();
+ 'D' => debug = !debug;
+ 'P' => writeallow = 1;
+ 'W' => wstatallow = 1;
+ 'R' => readonly = 1;
+ 'A' => noatime = 1; # mainly useful for flash
+ 'n' => kfsname = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ devnone = ref Device(nil, 1);
+ mainlock = Lock.new();
+
+ conschan = Chan.new(nil);
+ conschan.msize = Styx->MAXRPC;
+
+ mode := Sys->ORDWR;
+ if(readonly)
+ mode = Sys->OREAD;
+ wrenfd = sys->open(hd args, mode);
+ if(wrenfd == nil)
+ error(sys->sprint("can't open %s: %r", hd args));
+ thedevice = ref Device(wrenfd, readonly);
+ if(ream){
+ if(bufsize <= 0 || bufsize % 512 || bufsize > MAXBUFSIZE)
+ error(sys->sprint("invalid block size %d", bufsize));
+ RBUFSIZE = bufsize;
+ wrenream(thedevice);
+ }else{
+ if(!wreninit(thedevice))
+ error("kfs magic in trouble");
+ }
+ BUFSIZE = RBUFSIZE - Tagsize;
+ DIRPERBUF = BUFSIZE / Dentrysize;
+ INDPERBUF = BUFSIZE / 4;
+ INDPERBUF2 = INDPERBUF * INDPERBUF;
+ FEPERBUF = (BUFSIZE - Super1size - 4) / 4;
+ emptyblock = array[RBUFSIZE] of {* => byte 0};
+
+ iobufinit(30);
+
+ if(ream){
+ superream(thedevice, SUPERADDR);
+ rootream(thedevice, ROOTADDR);
+ wstatallow = writeallow = 1;
+ }
+ if(wrencheck(wrenfd))
+ error("kfs super/root in trouble");
+
+ if(!ream && !superok(0)){
+ sys->print("kfs needs check\n");
+ if(!nocheck)
+ check(thedevice, Cquiet|Cfree);
+ }
+
+ (d, e) := Dentry.geta(thedevice, ROOTADDR, 0, QPROOT, Bread);
+ if(d != nil && !(d.mode & DDIR))
+ e = "not a directory";
+ if(e != nil)
+ error("bad root: "+e);
+ if(debug)
+ d.print();
+ d.put();
+
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+
+ sys->pctl(Sys->NEWFD, wrenfd.fd :: 0 :: 1 :: 2 :: nil);
+ wrenfd = sys->fildes(wrenfd.fd);
+ thedevice.fd = wrenfd;
+
+ c := chan of int;
+
+ if(Buffering){
+ spawn syncproc(c);
+ pid := <-c;
+ if(pid)
+ pids = pid :: pids;
+ }
+ spawn consinit(c);
+ pid := <- c;
+ if(pid)
+ pids = pid :: pids;
+
+ spawn kfs(sys->fildes(0));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "kfs: %s\n", s);
+ for(; pids != nil; pids = tl pids)
+ kill(hd pids);
+ raise "fail:error";
+}
+
+panic(s: string)
+{
+ sys->fprint(sys->fildes(2), "kfs: panic: %s\n", s);
+ for(; pids != nil; pids = tl pids)
+ kill(hd pids);
+ raise "panic";
+}
+
+syncproc(c: chan of int)
+{
+ c <-= 0;
+}
+
+shutdown()
+{
+ for(; pids != nil; pids = tl pids)
+ kill(hd pids);
+ # TO DO: when Bmod deferred, must sync
+ # sync super block
+ if(superok(1)){
+ # ;
+ }
+ iobufclear();
+}
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+}
+
+#
+# limited file system support for console
+#
+kattach(fid: int): string
+{
+ return applycons(ref Tmsg.Attach(1, fid, NOFID, "adm", "")).t1;
+}
+
+kopen(oldfid: int, newfid: int, names: array of string, mode: int): string
+{
+ (r1, e1) := applycons(ref Tmsg.Walk(1, oldfid, newfid, names));
+ if(r1 != nil){
+ pick m := r1 {
+ Walk =>
+ if(len m.qids != len names){
+ kclose(newfid);
+ cprint(Eexist);
+ return Eexist;
+ }
+ * =>
+ return "unexpected reply";
+ }
+ (r1, e1) = applycons(ref Tmsg.Open(1, newfid, mode));
+ if(e1 != nil){
+ kclose(newfid);
+ cprint(sys->sprint("open: %s", e1));
+ }
+ }
+ return e1;
+}
+
+kread(fid: int, offset: int, nbytes: int): (array of byte, string)
+{
+ (r, e) := applycons(ref Tmsg.Read(1, fid, big offset, nbytes));
+ if(r != nil){
+ pick m := r {
+ Read =>
+ return (m.data, nil);
+ * =>
+ return (nil, "unexpected reply");
+ }
+ }
+ cprint(sys->sprint("read error: %s", e));
+ return (nil, e);
+}
+
+kclose(fid: int)
+{
+ applycons(ref Tmsg.Clunk(1, fid));
+}
+
+applycons(t: ref Tmsg): (ref Rmsg, string)
+{
+ r := apply(conschan, t);
+ pick m := r {
+ Error =>
+ if(debug)
+ cprint(sys->sprint("%s: %s\n", t.text(), m.ename));
+ return (nil, m.ename);
+ }
+ return (r, nil);
+}
+
+#
+# always reads /adm/users in userinit(), then
+# optionally serves the command file, if used.
+#
+Req: adt {
+ nbytes: int;
+ rc: chan of (array of byte, string);
+};
+
+consinit(c: chan of int)
+{
+ kattach(FID1);
+ userinit();
+ if(kfsname == nil){
+ c <-= 0;
+ exit;
+ }
+ cfname := "kfs."+kfsname+".cmd";
+ sys->bind("#s", "/chan", Sys->MBEFORE);
+ file := sys->file2chan("/chan", cfname);
+ if(file == nil)
+ error(sys->sprint("can't create /chan/%s: %r", cfname));
+ c <-= sys->pctl(0, nil);
+ consc := chan of string;
+ checkend := chan of int;
+ cdata: array of byte;
+ pending: ref Req;
+ cfid := -1;
+ for(;;) alt{
+ (nil, nbytes, fid, rc) := <-file.read =>
+ if(rc == nil)
+ break;
+ if(cfid == -1)
+ cfid = fid;
+ if(fid != cfid || pending != nil){
+ rc <-= (nil, "kfs.cmd is busy");
+ break;
+ }
+ if(cdata != nil){
+ cdata = reply(rc, nbytes, cdata);
+ break;
+ }
+ if(nbytes <= 0 || consoleout == nil){
+ rc <-= (nil, nil);
+ break;
+ }
+ pending = ref Req(nbytes, rc);
+ consc = consoleout;
+ (nil, data, fid, wc) := <-file.write =>
+ if(cfid == -1)
+ cfid = fid;
+ if(wc == nil){
+ if(fid == cfid){
+ cfid = -1;
+ pending = nil;
+ cdata = nil; # discard unread data from last command
+ if((consc = consoleout) == nil)
+ consc = chan of string;
+ }
+ break;
+ }
+ if(fid != cfid){
+ wc <-= (0, "kfs.cmd is busy");
+ break;
+ }
+ (nf, fld) := sys->tokenize(string data, " \t\n\r");
+ if(nf < 1){
+ wc <-= (0, "illegal kfs request");
+ break;
+ }
+ case hd fld {
+ "check" =>
+ if(consoleout != nil){
+ wc <-= (0, "check in progress");
+ break;
+ }
+ f := 0;
+ if(nf > 1){
+ f = checkflags(hd tl fld);
+ if(f < 0){
+ wc <-= (0, "illegal check flag: "+hd tl fld);
+ break;
+ }
+ }
+ consoleout = chan of string;
+ spawn checkproc(checkend, f);
+ wc <-= (len data, nil);
+ consc = consoleout;
+ "users" or "user" =>
+ cmd_users();
+ wc <-= (len data, nil);
+ "sync" =>
+ # nothing TO DO until writes are buffered
+ wc <-= (len data, nil);
+ "allow" =>
+ wstatallow = writeallow = 1;
+ wc <-= (len data, nil);
+ "allowoff" or "disallow" =>
+ wstatallow = writeallow = 0;
+ wc <-= (len data, nil);
+ * =>
+ wc <-= (0, "unknown kfs request");
+ continue;
+ }
+ <-checkend =>
+ consoleout = nil;
+ consc = chan of string;
+ s := <-consc =>
+ #sys->print("<-%s\n", s);
+ req := pending;
+ pending = nil;
+ if(req != nil)
+ cdata = reply(req.rc, req.nbytes, array of byte s);
+ else
+ cdata = array of byte s;
+ if(cdata != nil && cfid != -1)
+ consc = chan of string;
+ }
+}
+
+reply(rc: chan of (array of byte, string), nbytes: int, a: array of byte): array of byte
+{
+ if(len a < nbytes)
+ nbytes = len a;
+ rc <-= (a[0:nbytes], nil);
+ if(nbytes == len a)
+ return nil;
+ return a[nbytes:];
+}
+
+checkproc(c: chan of int, flags: int)
+{
+ mainlock.lock();
+ check(thedevice, flags);
+ mainlock.unlock();
+ c <-= 1;
+}
+
+#
+# normal kfs service
+#
+kfs(rfd: ref Sys->FD)
+{
+ cp := Chan.new(rfd);
+ while((t := Tmsg.read(rfd, cp.msize)) != nil){
+ if(debug)
+ sys->print("<- %s\n", t.text());
+ r := apply(cp, t);
+ pick m := r {
+ Error =>
+ r.tag = t.tag;
+ }
+ if(debug)
+ sys->print("-> %s\n", r.text());
+ rbuf := r.pack();
+ if(rbuf == nil)
+ panic("Rmsg.pack");
+ if(sys->write(rfd, rbuf, len rbuf) != len rbuf)
+ panic("mount write");
+ }
+ shutdown();
+}
+
+apply(cp: ref Chan, t: ref Tmsg): ref Rmsg
+{
+ mainlock.lock(); # TO DO: this is just to keep console and kfs from colliding
+ r: ref Rmsg;
+ pick m := t {
+ Readerror =>
+ error(sys->sprint("mount read error: %s", m.error));
+ Version =>
+ r = rversion(cp, m);
+ Auth =>
+ r = rauth(cp, m);
+ Flush =>
+ r = rflush(cp, m);
+ Attach =>
+ r = rattach(cp, m);
+ Walk =>
+ r = rwalk(cp, m);
+ Open =>
+ r = ropen(cp, m);
+ Create =>
+ r = rcreate(cp, m);
+ Read =>
+ r = rread(cp, m);
+ Write =>
+ r = rwrite(cp, m);
+ Clunk =>
+ r = rclunk(cp, m);
+ Remove =>
+ r = rremove(cp, m);
+ Stat =>
+ r = rstat(cp, m);
+ Wstat =>
+ r = rwstat(cp, m);
+ * =>
+ panic("Styx mtype");
+ return nil;
+ }
+ mainlock.unlock();
+ return r;
+}
+
+rversion(cp: ref Chan, t: ref Tmsg.Version): ref Rmsg
+{
+ cp.msize = RBUFSIZE+IOHDRSZ;
+ if(cp.msize < Styx->MAXRPC)
+ cp.msize = Styx->MAXRPC;
+ (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION);
+ if(msize < 256)
+ return ref Rmsg.Error(t.tag, "message size too small");
+ return ref Rmsg.Version(t.tag, msize, version);
+}
+
+rauth(nil: ref Chan, t: ref Tmsg.Auth): ref Rmsg
+{
+ return ref Rmsg.Error(t.tag, Eauthmsg);
+}
+
+rflush(nil: ref Chan, t: ref Tmsg.Flush): ref Rmsg
+{
+ # runlock(cp.reflock);
+ # wlock(cp.reflock);
+ # wunlock(cp.reflock);
+ # rlock(cp.reflock);
+ return ref Rmsg.Flush(t.tag);
+}
+
+err(t: ref Tmsg, s: string): ref Rmsg.Error
+{
+ return ref Rmsg.Error(t.tag, s);
+}
+
+ferr(t: ref Tmsg, s: string, file: ref File, p: ref Iobuf): ref Rmsg.Error
+{
+ if(p != nil)
+ p.put();
+ if(file != nil)
+ file.unlock();
+ return ref Rmsg.Error(t.tag, s);
+}
+
+File.new(fid: int): ref File
+{
+ f := ref File;
+ f.qlock = chan[1] of int;
+ f.fid = fid;
+ f.cons = 0;
+ f.tlock = nil;
+ f.wpath = nil;
+ f.doffset = big 0;
+ f.dvers = 0;
+ f.dslot = 0;
+ f.uid = None;
+ f.cons = 0;
+# f.cuid = None;
+ return f;
+}
+
+#
+# returns a locked file structure
+#
+
+Chan.getfid(cp: self ref Chan, fid: int, flag: int): ref File
+{
+ if(fid == NOFID)
+ return nil;
+ cp.flock();
+ for(l := cp.flist; l != nil; l = tl l){
+ f := hd l;
+ if(f.fid == fid){
+ cp.funlock();
+ if(flag)
+ return nil; # fid in use
+ f.lock();
+ if(f.fid == fid)
+ return f;
+ f.unlock();
+ cp.flock();
+ }
+ }
+ if(flag == 0){
+ sys->print("kfs: cannot find %H.%ud", cp, fid);
+ cp.funlock();
+ return nil;
+ }
+ f := File.new(fid);
+ f.lock();
+ cp.flist = f :: cp.flist;
+ cp.funlock();
+ return f;
+}
+
+Chan.putfid(cp: self ref Chan, f: ref File)
+{
+ cp.flock();
+ nl: list of ref File;
+ for(x := cp.flist; x != nil; x = tl x)
+ if(hd x != f)
+ nl = hd x :: nl;
+ cp.flist = nl;
+ cp.funlock();
+ f.unlock();
+}
+
+File.lock(f: self ref File)
+{
+ f.qlock <-= 1;
+}
+
+File.unlock(f: self ref File)
+{
+ <-f.qlock;
+}
+
+Chan.new(fd: ref Sys->FD): ref Chan
+{
+ c := ref Chan;
+ c.fd = fd;
+ c.fqlock = chan[1] of int;
+# rlock, wlock: QLock; # lock for reading/writing messages on cp
+ c.flags = 0;
+# reflock: RWLock; # lock for Tflush
+ c.msize = 0; # set by rversion
+ return c;
+}
+
+Chan.flock(c: self ref Chan)
+{
+ c.fqlock <-= 1;
+}
+
+Chan.funlock(c: self ref Chan)
+{
+ <-c.fqlock;
+}
+
+rattach(cp: ref Chan, t: ref Tmsg.Attach): ref Rmsg
+{
+ if(t.aname != "" && t.aname != "main")
+ return err(t, Ebadspc);
+ file := cp.getfid(t.fid, 1);
+ if(file == nil)
+ return err(t, Efidinuse);
+ p := Iobuf.get(thedevice, ROOTADDR, Bread);
+ if(p == nil){
+ cp.putfid(file);
+ return err(t, "can't access root block");
+ }
+ d := Dentry.get(p, 0);
+ if(d == nil || p.checktag(Tdir, QPROOT) || (d.mode & DALLOC) == 0 || (d.mode & DDIR) == 0){
+ p.put();
+ cp.putfid(file);
+ return err(t, Ealloc);
+ }
+ if(file.access(d, DEXEC)){
+ p.put();
+ cp.putfid(file);
+ return err(t, Eaccess);
+ }
+ d.access(FREAD, file.uid);
+ file.fs = thedevice;
+ file.qid = d.qid;
+ file.addr = p.addr;
+ file.slot = 0;
+ file.open = 0;
+ file.uid = strtouid(t.uname);
+ file.wpath = nil;
+ p.put();
+ qid := file.qid;
+ file.unlock();
+ return ref Rmsg.Attach(t.tag, qid);
+}
+
+clone(nfile: ref File, file: ref File)
+{
+ nfile.qid = file.qid;
+ nfile.wpath = file.wpath;
+ nfile.fs = file.fs;
+ nfile.addr = file.addr;
+ nfile.slot = file.slot;
+ nfile.uid = file.uid;
+# nfile.cuid = None;
+ nfile.open = file.open & ~FREMOV;
+}
+
+walkname(file: ref File, wname: string): (string, Qid)
+{
+ #
+ # File must not have been opened for I/O by an open
+ # or create message and must represent a directory.
+ #
+ if(file.open != 0)
+ return (Emode, noqid);
+
+ (d, e) := Dentry.getd(file, Bread);
+ if(d == nil)
+ return (e, noqid);
+ if(!(d.mode & DDIR)){
+ d.put();
+ return (Edir1, noqid);
+ }
+
+ #
+ # For walked elements the implied user must
+ # have permission to search the directory.
+ #
+ if(file.access(d, DEXEC)){
+ d.put();
+ return (Eaccess, noqid);
+ }
+ d.access(FREAD, file.uid);
+
+ if(wname == "." || wname == ".." && file.wpath == nil){
+ d.put();
+ return (nil, file.qid);
+ }
+
+ d1: ref Dentry; # entry for wname, if found
+ slot: int;
+
+ if(wname == ".."){
+ d.put();
+ addr := file.wpath.addr;
+ slot = file.wpath.slot;
+ (d1, e) = Dentry.geta(file.fs, addr, slot, QPNONE, Bread);
+ if(d1 == nil)
+ return (e, noqid);
+ file.wpath = file.wpath.up;
+ }else{
+
+ Search:
+ for(addr := 0; ; addr++){
+ if(d.iob == nil){
+ (d, e) = Dentry.getd(file, Bread);
+ if(d == nil)
+ return (e, noqid);
+ }
+ p1 := d.getblk1(addr, 0);
+ if(p1 == nil || p1.checktag(Tdir, int d.qid.path)){
+ if(p1 != nil)
+ p1.put();
+ return (Eentry, noqid);
+ }
+ for(slot = 0; slot < DIRPERBUF; slot++){
+ d1 = Dentry.get(p1, slot);
+ if(!(d1.mode & DALLOC))
+ continue;
+ if(wname != d1.name)
+ continue;
+ #
+ # update walk path
+ #
+ file.wpath = ref Wpath(file.wpath, file.addr, file.slot);
+ slot += DIRPERBUF*addr;
+ break Search;
+ }
+ p1.put();
+ }
+ d.put();
+ }
+
+ file.addr = d1.iob.addr;
+ file.slot = slot;
+ file.qid = d1.qid;
+ d1.put();
+ return (nil, file.qid);
+}
+
+rwalk(cp: ref Chan, t: ref Tmsg.Walk): ref Rmsg
+{
+ nfile, tfile: ref File;
+ q: Qid;
+
+ # The file identified by t.fid must be valid in the
+ # current session and must not have been opened for I/O
+ # by an open or create message.
+
+ if((file := cp.getfid(t.fid, 0)) == nil)
+ return err(t, Efid);
+ if(file.open != 0)
+ return ferr(t, Emode, file, nil);
+
+ # If newfid is not the same as fid, allocate a new file;
+ # a side effect is checking newfid is not already in use (error);
+ # if there are no names to walk this will be equivalent to a
+ # simple 'clone' operation.
+ # Otherwise, fid and newfid are the same and if there are names
+ # to walk make a copy of 'file' to be used during the walk as
+ # 'file' must only be updated on success.
+ # Finally, it's a no-op if newfid is the same as fid and t.nwname
+ # is 0.
+
+ nwqid := 0;
+ if(t.newfid != t.fid){
+ if((nfile = cp.getfid(t.newfid, 1)) == nil)
+ return ferr(t, Efidinuse, file, nil);
+ }
+ else if(len t.names != 0)
+ nfile = tfile = File.new(NOFID);
+ else{
+ file.unlock();
+ return ref Rmsg.Walk(t.tag, nil);
+ }
+ clone(nfile, file);
+
+ r := ref Rmsg.Walk(t.tag, array[len t.names] of Qid);
+ error: string;
+ for(nwname := 0; nwname < len t.names; nwname++){
+ (error, q) = walkname(nfile, t.names[nwname]);
+ if(error != nil)
+ break;
+ r.qids[nwqid++] = q;
+ }
+
+ if(len t.names == 0){
+
+ # Newfid must be different to fid (see above)
+ # so this is a simple 'clone' operation - there's
+ # nothing to do except unlock unless there's
+ # an error.
+
+ nfile.unlock();
+ if(error != nil)
+ cp.putfid(nfile);
+ }else if(nwqid < len t.names){
+ #
+ # Didn't walk all elements, 'clunk' nfile
+ # and leave 'file' alone.
+ # Clear error if some of the elements were
+ # walked OK.
+ #
+ if(nfile != tfile)
+ cp.putfid(nfile);
+ if(nwqid != 0)
+ error = nil;
+ r.qids = r.qids[0:nwqid];
+ }else{
+ #
+ # Walked all elements. If newfid is the same
+ # as fid must update 'file' from the temporary
+ # copy used during the walk.
+ # Otherwise just unlock (when using tfile there's
+ # no need to unlock as it's a local).
+ #
+ if(nfile == tfile){
+ file.qid = nfile.qid;
+ file.wpath = nfile.wpath;
+ file.addr = nfile.addr;
+ file.slot = nfile.slot;
+ }else
+ nfile.unlock();
+ }
+ file.unlock();
+
+ if(error != nil)
+ return err(t, error);
+ return r;
+}
+
+ropen(cp: ref Chan, f: ref Tmsg.Open): ref Rmsg
+{
+ wok := cp == conschan || writeallow;
+
+ if((file := cp.getfid(f.fid, 0)) == nil)
+ return err(f, Efid);
+
+ #
+ # if remove on close, check access here
+ #
+ ro := isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup));
+ if(f.mode & ORCLOSE){
+ if(ro)
+ return ferr(f, Eronly, file, nil);
+ #
+ # check on parent directory of file to be deleted
+ #
+ if(file.wpath == nil || file.wpath.addr == file.addr)
+ return ferr(f, Ephase, file, nil);
+ p := Iobuf.get(file.fs, file.wpath.addr, Bread);
+ if(p == nil || p.checktag(Tdir, QPNONE))
+ return ferr(f, Ephase, file, p);
+ if((d := Dentry.get(p, file.wpath.slot)) == nil || !(d.mode & DALLOC))
+ return ferr(f, Ephase, file, p);
+ if(file.access(d, DWRITE))
+ return ferr(f, Eaccess, file, p);
+ p.put();
+ }
+ (d, e) := Dentry.getd(file, Bread);
+ if(d == nil)
+ return ferr(f, e, file, nil);
+ p := d.iob;
+ qid := d.qid;
+ fmod: int;
+ case f.mode & 7 {
+
+ OREAD =>
+ if(file.access(d, DREAD) && !wok)
+ return ferr(f, Eaccess, file, p);
+ fmod = FREAD;
+
+ OWRITE =>
+ if((d.mode & DDIR) || (file.access(d, DWRITE) && !wok))
+ return ferr(f, Eaccess, file, p);
+ if(ro)
+ return ferr(f, Eronly, file, p);
+ fmod = FWRITE;
+
+ ORDWR =>
+ if((d.mode & DDIR)
+ || (file.access(d, DREAD) && !wok)
+ || (file.access(d, DWRITE) && !wok))
+ return ferr(f, Eaccess, file, p);
+ if(ro)
+ return ferr(f, Eronly, file, p);
+ fmod = FREAD+FWRITE;
+
+ OEXEC =>
+ if((d.mode & DDIR) || (file.access(d, DEXEC) && !wok))
+ return ferr(f, Eaccess, file, p);
+ fmod = FREAD;
+
+ * =>
+ return ferr(f, Emode, file, p);
+ }
+ if(f.mode & OTRUNC){
+ if((d.mode & DDIR) || (file.access(d, DWRITE) && !wok))
+ return ferr(f, Eaccess, file, p);
+ if(ro)
+ return ferr(f, Eronly, file, p);
+ }
+ if(d.mode & DLOCK){
+ if((t := tlocked(file, d)) == nil)
+ return ferr(f, Elocked, file, p);
+ file.tlock = t;
+ t.file = file;
+ }
+ if(f.mode & ORCLOSE)
+ fmod |= FREMOV;
+ file.open = fmod;
+ if((f.mode & OTRUNC) && !(d.mode & DAPND)){
+ d.trunc(file.uid);
+ qid.vers = d.qid.vers;
+ }
+ file.lastra = 1;
+ p.put();
+ file.unlock();
+ return ref Rmsg.Open(f.tag, qid, cp.msize-IOHDRSZ);
+}
+
+rcreate(cp: ref Chan, f: ref Tmsg.Create): ref Rmsg
+{
+ wok := cp == conschan || writeallow;
+
+ if((file := cp.getfid(f.fid, 0)) == nil)
+ return err(f, Efid);
+ if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup)))
+ return ferr(f, Eronly, file, nil);
+
+ (d, e) := Dentry.getd(file, Bread);
+ if(e != nil)
+ return ferr(f, e, file, nil);
+ p := d.iob;
+ if(!(d.mode & DDIR))
+ return ferr(f, Edir2, file, p);
+ if(file.access(d, DWRITE) && !wok)
+ return ferr(f, Eaccess, file, p);
+ d.access(FREAD, file.uid);
+
+ #
+ # Check the name is valid and will fit in an old
+ # directory entry.
+ #
+ if((l := checkname9p2(f.name)) == 0)
+ return ferr(f, Ename, file, p);
+ if(l+1 > NAMELEN)
+ return ferr(f, Etoolong, file, p);
+ if(f.name == "." || f.name == "..")
+ return ferr(f, Edot, file, p);
+
+ addr1 := 0; # block with first empty slot, if any
+ slot1 := 0;
+ for(addr := 0; ; addr++){
+ if((p1 := d.getblk(addr, 0)) == nil){
+ if(addr1 != 0)
+ break;
+ p1 = d.getblk(addr, Tdir);
+ }
+ if(p1 == nil)
+ return ferr(f, Efull, file, p);
+ if(p1.checktag(Tdir, int d.qid.path)){
+ p1.put();
+ return ferr(f, Ephase, file, p);
+ }
+ for(slot := 0; slot < DIRPERBUF; slot++){
+ d1 := Dentry.get(p1, slot);
+ if(!(d1.mode & DALLOC)){
+ if(addr1 == 0){
+ addr1 = p1.addr;
+ slot1 = slot + addr*DIRPERBUF;
+ }
+ continue;
+ }
+ if(f.name == d1.name){
+ p1.put();
+ return ferr(f, Eexist, file, p);
+ }
+ }
+ p1.put();
+ }
+
+ fmod: int;
+
+ case f.mode & 7 {
+ OEXEC or
+ OREAD => # seems only useful to make directories
+ fmod = FREAD;
+
+ OWRITE =>
+ fmod = FWRITE;
+
+ ORDWR =>
+ fmod = FREAD+FWRITE;
+
+ * =>
+ return ferr(f, Emode, file, p);
+ }
+ if(f.perm & DMDIR)
+ if((f.mode & OTRUNC) || (f.perm & DMAPPEND) || (fmod & FWRITE))
+ return ferr(f, Eaccess, file, p);
+
+ # do it
+
+ path := qidpathgen(file.fs);
+ if((p1 := Iobuf.get(file.fs, addr1, Bread|Bimm|Bmod)) == nil)
+ return ferr(f, Ephase, file, p);
+ d1 := Dentry.get(p1, slot1);
+ if(d1 == nil || p1.checktag(Tdir, int d.qid.path)){
+ p.put();
+ return ferr(f, Ephase, file, p1);
+ }
+ if(d1.mode & DALLOC){
+ p.put();
+ return ferr(f, Ephase, file, p1);
+ }
+
+ d1.name = f.name;
+ if(cp == conschan){
+ d1.uid = consuid;
+ d1.gid = consgid;
+ }
+ else{
+ d1.uid = file.uid;
+ d1.gid = d.gid;
+ f.perm &= d.mode | ~8r666;
+ if(f.perm & DMDIR)
+ f.perm &= d.mode | ~8r777;
+ }
+ d1.qid.path = big path;
+ d1.qid.vers = 0;
+ d1.mode = DALLOC | (f.perm & 8r777);
+ if(f.perm & DMDIR)
+ d1.mode |= DDIR;
+ if(f.perm & DMAPPEND)
+ d1.mode |= DAPND;
+ t: ref Tlock;
+ if(f.perm & DMEXCL){
+ d1.mode |= DLOCK;
+ t = tlocked(file, d1);
+ # if nil, out of tlock structures
+ }
+ d1.access(FWRITE, file.uid);
+ d1.change(~0);
+ d1.update();
+ qid := mkqid(path, 0, d1.mode);
+ p1.put();
+ d.change(~0);
+ d.access(FWRITE, file.uid);
+ d.update();
+ p.put();
+
+ #
+ # do a walk to new directory entry
+ #
+ file.wpath = ref Wpath(file.wpath, file.addr, file.slot);
+ file.qid = qid;
+ file.tlock = t;
+ if(t != nil)
+ t.file = file;
+ file.lastra = 1;
+ if(f.mode & ORCLOSE)
+ fmod |= FREMOV;
+ file.open = fmod;
+ file.addr = addr1;
+ file.slot = slot1;
+ file.unlock();
+ return ref Rmsg.Create(f.tag, qid, cp.msize-IOHDRSZ);
+}
+
+dirread(cp: ref Chan, f: ref Tmsg.Read, file: ref File, d: ref Dentry): ref Rmsg
+{
+ p1: ref Iobuf;
+ d1: ref Dentry;
+
+ count := f.count;
+ data := array[count] of byte;
+ offset := f.offset;
+ iounit := cp.msize-IOHDRSZ;
+
+ # Pick up where we left off last time if nothing has changed,
+ # otherwise must scan from the beginning.
+
+ addr, slot: int;
+ start: big;
+
+ if(offset == file.doffset){ # && file.qid.vers == file.dvers
+ addr = file.dslot/DIRPERBUF;
+ slot = file.dslot%DIRPERBUF;
+ start = offset;
+ }
+ else{
+ addr = 0;
+ slot = 0;
+ start = big 0;
+ }
+
+ nread := 0;
+Dread:
+ for(;;){
+ if(d.iob == nil){
+ #
+ # This is just a check to ensure the entry hasn't
+ # gone away during the read of each directory block.
+ #
+ e: string;
+ (d, e) = Dentry.getd(file, Bread);
+ if(d == nil)
+ return ferr(f, e, file, nil);
+ }
+ p1 = d.getblk1(addr, 0);
+ if(p1 == nil)
+ break;
+ if(p1.checktag(Tdir, QPNONE))
+ return ferr(f, Ephase, file, p1);
+
+ for(; slot < DIRPERBUF; slot++){
+ d1 = Dentry.get(p1, slot);
+ if(!(d1.mode & DALLOC))
+ continue;
+ dir := dir9p2(d1);
+ n := styx->packdirsize(dir);
+ if(n > count-nread){
+ p1.put();
+ break Dread;
+ }
+ data[nread:] = styx->packdir(dir);
+ start += big n;
+ if(start < offset)
+ continue;
+ if(count < n){
+ p1.put();
+ break Dread;
+ }
+ count -= n;
+ nread += n;
+ offset += big n;
+ }
+ p1.put();
+ slot = 0;
+ addr++;
+ }
+
+ file.doffset = offset;
+ file.dvers = file.qid.vers;
+ file.dslot = slot+DIRPERBUF*addr;
+
+ d.put();
+ file.unlock();
+ return ref Rmsg.Read(f.tag, data[0:nread]);
+}
+
+rread(cp: ref Chan, f: ref Tmsg.Read): ref Rmsg
+{
+ if((file := cp.getfid(f.fid, 0)) == nil)
+ return err(f, Efid);
+ if(!(file.open & FREAD))
+ return ferr(f, Eopen, file, nil);
+ count := f.count;
+ iounit := cp.msize-IOHDRSZ;
+ if(count < 0 || count > iounit)
+ return ferr(f, Ecount, file, nil);
+ offset := f.offset;
+ if(offset < big 0)
+ return ferr(f, Eoffset, file, nil);
+
+ (d, e) := Dentry.getd(file, Bread);
+ if(d == nil)
+ return ferr(f, e, file, nil);
+ if((t := file.tlock) != nil){
+ tim := now();
+ if(t.time < tim || t.file != file){
+ d.put();
+ return ferr(f, Ebroken, file, nil);
+ }
+ # renew the lock
+ t.time = tim + TLOCK;
+ }
+ d.access(FREAD, file.uid);
+ if(d.mode & DDIR)
+ return dirread(cp, f, file, d);
+
+ if(offset+big count > d.size)
+ count = int (d.size - offset);
+ if(count < 0)
+ count = 0;
+ data := array[count] of byte;
+ nread := 0;
+ while(count > 0){
+ if(d.iob == nil){
+ # must check and reacquire entry
+ (d, e) = Dentry.getd(file, Bread);
+ if(d == nil)
+ return ferr(f, e, file, nil);
+ }
+ addr := int (offset / big BUFSIZE);
+ if(addr == file.lastra+1)
+ ; # dbufread(p, d, addr+1);
+ file.lastra = addr;
+ o := int (offset % big BUFSIZE);
+ n := BUFSIZE - o;
+ if(n > count)
+ n = count;
+ p1 := d.getblk1(addr, 0);
+ if(p1 != nil){
+ if(p1.checktag(Tfile, QPNONE)){
+ p1.put();
+ return ferr(f, Ephase, file, nil);
+ }
+ data[nread:] = p1.iobuf[o:o+n];
+ p1.put();
+ }else
+ data[nread:] = emptyblock[0:n];
+ count -= n;
+ nread += n;
+ offset += big n;
+ }
+ d.put();
+ file.unlock();
+ return ref Rmsg.Read(f.tag, data[0:nread]);
+}
+
+rwrite(cp: ref Chan, f: ref Tmsg.Write): ref Rmsg
+{
+ if((file := cp.getfid(f.fid, 0)) == nil)
+ return err(f, Efid);
+ if(!(file.open & FWRITE))
+ return ferr(f, Eopen, file, nil);
+ if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup)))
+ return ferr(f, Eronly, file, nil);
+ count := len f.data;
+ if(count < 0 || count > cp.msize-IOHDRSZ)
+ return ferr(f, Ecount, file, nil);
+ offset := f.offset;
+ if(offset < big 0)
+ return ferr(f, Eoffset, file, nil);
+
+ (d, e) := Dentry.getd(file, Bread|Bmod);
+ if(d == nil)
+ return ferr(f, e, file, nil);
+ if((t := file.tlock) != nil){
+ tim := now();
+ if(t.time < tim || t.file != file){
+ d.put();
+ return ferr(f, Ebroken, file, nil);
+ }
+ # renew the lock
+ t.time = tim + TLOCK;
+ }
+ d.access(FWRITE, file.uid);
+ if(d.mode & DAPND)
+ offset = d.size;
+ end := offset + big count;
+ if(end > d.size){
+ if(end > MAXFILESIZE)
+ return ferr(f, Etoobig, file, nil);
+ d.size = end;
+ d.change(Usize);
+ }
+ d.update();
+
+ nwrite := 0;
+ while(count > 0){
+ if(d.iob == nil){
+ # must check and reacquire entry
+ (d, e) = Dentry.getd(file, Bread|Bmod);
+ if(d == nil)
+ return ferr(f, e, file, nil);
+ }
+ addr := int (offset / big BUFSIZE);
+ o := int (offset % big BUFSIZE);
+ n := BUFSIZE - o;
+ if(n > count)
+ n = count;
+ qpath := int d.qid.path;
+ p1 := d.getblk1(addr, Tfile);
+ if(p1 == nil)
+ return ferr(f, Efull, file, nil);
+ if(p1.checktag(Tfile, qpath)){
+ p1.put();
+ return ferr(f, Ealloc, file, nil);
+ }
+ p1.iobuf[o:] = f.data[nwrite:nwrite+n];
+ p1.flags |= Bmod;
+ p1.put();
+ count -= n;
+ nwrite += n;
+ offset += big n;
+ }
+ d.put();
+ file.unlock();
+ return ref Rmsg.Write(f.tag, nwrite);
+}
+
+doremove(f: ref File, iscon: int): string
+{
+ if(isro(f.fs) || f.cons == 0 && (writegroup && !ingroup(f.uid, writegroup)))
+ return Eronly;
+ #
+ # check permission on parent directory of file to be deleted
+ #
+ if(f.wpath == nil || f.wpath.addr == f.addr)
+ return Ephase;
+ (d1, e1) := Dentry.geta(f.fs, f.wpath.addr, f.wpath.slot, QPNONE, Bread);
+ if(e1 != nil)
+ return e1;
+ if(!iscon && f.access(d1, DWRITE)){
+ d1.put();
+ return Eaccess;
+ }
+ d1.access(FWRITE, f.uid);
+ d1.put();
+
+ #
+ # check on file to be deleted
+ #
+ (d, e) := Dentry.getd(f, Bread);
+ if(e != nil)
+ return e;
+
+ #
+ # if deleting a directory, make sure it is empty
+ #
+ if(d.mode & DDIR)
+ for(addr:=0; (p1 := d.getblk(addr, 0)) != nil; addr++){
+ if(p1.checktag(Tdir, int d.qid.path)){
+ p1.put();
+ d.put();
+ return Ephase;
+ }
+ for(slot:=0; slot<DIRPERBUF; slot++){
+ d1 = Dentry.get(p1, slot);
+ if(!(d1.mode & DALLOC))
+ continue;
+ p1.put();
+ d.put();
+ return Eempty;
+ }
+ p1.put();
+ }
+
+ #
+ # do it
+ #
+ d.trunc(f.uid);
+ d.buf[0:] = emptyblock[0:Dentrysize];
+ d.put();
+ return nil;
+}
+
+clunk(cp: ref Chan, file: ref File, remove: int, wok: int): string
+{
+ if((t := file.tlock) != nil){
+ if(t.file == file)
+ t.time = 0; # free the lock
+ file.tlock = nil;
+ }
+ if(remove)
+ error := doremove(file, wok);
+ file.open = 0;
+ file.wpath = nil;
+ cp.putfid(file);
+
+ return error;
+}
+
+rclunk(cp: ref Chan, t: ref Tmsg.Clunk): ref Rmsg
+{
+ if((file := cp.getfid(t.fid, 0)) == nil)
+ return err(t, Efid);
+ clunk(cp, file, file.open & FREMOV, 0);
+ return ref Rmsg.Clunk(t.tag);
+}
+
+rremove(cp: ref Chan, t: ref Tmsg.Remove): ref Rmsg
+{
+ if((file := cp.getfid(t.fid, 0)) == nil)
+ return err(t, Efid);
+ e := clunk(cp, file, 1, cp == conschan);
+ if(e != nil)
+ return err(t, e);
+ return ref Rmsg.Remove(t.tag);
+}
+
+rstat(cp: ref Chan, f: ref Tmsg.Stat): ref Rmsg
+{
+ if((file := cp.getfid(f.fid, 0)) == nil)
+ return err(f, Efid);
+ (d, e) := Dentry.getd(file, Bread);
+ if(d == nil)
+ return ferr(f, e, file, nil);
+ dir := dir9p2(d);
+ if(d.qid.path == big QPROOT) # stat of root gives time
+ dir.atime = now();
+ d.put();
+ if(styx->packdirsize(dir) > cp.msize-IOHDRSZ)
+ return ferr(f, Ersc, file, nil);
+ file.unlock();
+
+ return ref Rmsg.Stat(f.tag, dir);
+}
+
+rwstat(cp: ref Chan, f: ref Tmsg.Wstat): ref Rmsg
+{
+ if((file := cp.getfid(f.fid, 0)) == nil)
+ return err(f, Efid);
+
+ # if user none, can't do anything unless in allow mode
+
+ if(file.uid == None && !wstatallow)
+ return ferr(f, Eaccess, file, nil);
+
+ if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup)))
+ return ferr(f, Eronly, file, nil);
+
+ #
+ # first get parent
+ #
+ p1: ref Iobuf;
+ d1: ref Dentry;
+ if(file.wpath != nil){
+ p1 = Iobuf.get(file.fs, file.wpath.addr, Bread);
+ if(p1 == nil)
+ return ferr(f, Ephase, file, p1);
+ d1 = Dentry.get(p1, file.wpath.slot);
+ if(d1 == nil || p1.checktag(Tdir, QPNONE) || !(d1.mode & DALLOC))
+ return ferr(f, Ephase, file, p1);
+ }
+
+ #
+ # now the file
+ #
+ (d, e) := Dentry.getd(file, Bread);
+ if(d == nil)
+ return ferr(f, e, file, p1);
+
+ #
+ # Convert the message and fix up
+ # fields not to be changed.
+ #
+ dir := f.stat;
+ if(dir.uid == nil)
+ uid := d.uid;
+ else
+ uid = strtouid(dir.uid);
+ if(dir.gid == nil)
+ gid := d.gid;
+ else
+ gid = strtouid(dir.gid);
+ if(dir.name == nil)
+ dir.name = d.name;
+ else{
+ if((l := checkname9p2(dir.name)) == 0){
+ d.put();
+ return ferr(f, Ename, file, p1);
+ }
+ if(l+1 > NAMELEN){
+ d.put();
+ return ferr(f, Etoolong, file, p1);
+ }
+ }
+
+ # Before doing sanity checks, find out what the
+ # new 'mode' should be:
+ # if 'type' and 'mode' are both defaults, take the
+ # new mode from the old directory entry;
+ # else if 'type' is the default, use the new mode entry;
+ # else if 'mode' is the default, create the new mode from
+ # 'type' or'ed with the old directory mode;
+ # else neither are defaults, use the new mode but check
+ # it agrees with 'type'.
+
+ if(dir.qid.qtype == 16rFF && dir.mode == ~0){
+ dir.mode = d.mode & 8r777;
+ if(d.mode & DLOCK)
+ dir.mode |= DMEXCL;
+ if(d.mode & DAPND)
+ dir.mode |= DMAPPEND;
+ if(d.mode & DDIR)
+ dir.mode |= DMDIR;
+ }
+ else if(dir.qid.qtype == 16rFF){
+ # nothing to do
+ }
+ else if(dir.mode == ~0)
+ dir.mode = (dir.qid.qtype<<24)|(d.mode & 8r777);
+ else if(dir.qid.qtype != ((dir.mode>>24) & 16rFF)){
+ d.put();
+ return ferr(f, Eqidmode, file, p1);
+ }
+
+ # Check for unknown type/mode bits
+ # and an attempt to change the directory bit.
+
+ if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|8r777)){
+ d.put();
+ return ferr(f, Enotm, file, p1);
+ }
+ if(d.mode & DDIR)
+ mode := DMDIR;
+ else
+ mode = 0;
+ if((dir.mode^mode) & DMDIR){
+ d.put();
+ return ferr(f, Enotd, file, p1);
+ }
+
+ if(dir.mtime == ~0)
+ dir.mtime = d.mtime;
+ if(dir.length == ~big 0)
+ dir.length = big d.size;
+
+
+ # Currently, can't change length.
+
+ if(dir.length != big d.size){
+ d.put();
+ return ferr(f, Enotl, file, p1);
+ }
+
+
+ # if chown,
+ # must be god
+ # wstatallow set to allow chown during boot
+
+ if(uid != d.uid && !wstatallow){
+ d.put();
+ return ferr(f, Enotu, file, p1);
+ }
+
+ # if chgroup,
+ # must be either
+ # a) owner and in new group
+ # b) leader of both groups
+ # wstatallow and writeallow are set to allow chgrp during boot
+
+ while(gid != d.gid){
+ if(wstatallow || writeallow)
+ break;
+ if(d.uid == file.uid && ingroup(file.uid, gid))
+ break;
+ if(leadgroup(file.uid, gid))
+ if(leadgroup(file.uid, d.gid))
+ break;
+ d.put();
+ return ferr(f, Enotg, file, p1);
+ }
+
+ # if rename,
+ # must have write permission in parent
+
+ while(d.name != dir.name){
+
+ # drop entry to prevent deadlock, then
+ # check that destination name is valid and unique
+
+ d.put();
+ if(checkname9p2(dir.name) == 0 || d1 == nil)
+ return ferr(f, Ename, file, p1);
+ if(dir.name == "." || dir.name == "..")
+ return ferr(f, Edot, file, p1);
+
+
+ for(addr := 0; ; addr++){
+ if((p := d1.getblk(addr, 0)) == nil)
+ break;
+ if(p.checktag(Tdir, int d1.qid.path)){
+ p.put();
+ continue;
+ }
+ for(slot := 0; slot < DIRPERBUF; slot++){
+ d = Dentry.get(p, slot);
+ if(!(d.mode & DALLOC))
+ continue;
+ if(dir.name == d.name){
+ p.put();
+ return ferr(f, Eexist, file, p1);
+ }
+ }
+ p.put();
+ }
+
+ # reacquire entry
+
+ (d, nil) = Dentry.getd(file, Bread);
+ if(d == nil)
+ return ferr(f, Ephase, file, p1);
+
+ if(wstatallow || writeallow) # set to allow rename during boot
+ break;
+ if(d1 == nil || file.access(d1, DWRITE)){
+ d.put();
+ return ferr(f, Eaccess, file, p1);
+ }
+ break;
+ }
+
+ # if mode/time, either
+ # a) owner
+ # b) leader of either group
+
+ mode = dir.mode & 8r777;
+ if(dir.mode & DMAPPEND)
+ mode |= DAPND;
+ if(dir.mode & DMEXCL)
+ mode |= DLOCK;
+ while(d.mtime != dir.mtime || ((d.mode^mode) & (DAPND|DLOCK|8r777))){
+ if(wstatallow) # set to allow chmod during boot
+ break;
+ if(d.uid == file.uid)
+ break;
+ if(leadgroup(file.uid, gid))
+ break;
+ if(leadgroup(file.uid, d.gid))
+ break;
+ d.put();
+ return ferr(f, Enotu, file, p1);
+ }
+ d.mtime = dir.mtime;
+ d.uid = uid;
+ d.gid = gid;
+ d.mode = (mode & (DAPND|DLOCK|8r777)) | (d.mode & (DALLOC|DDIR));
+
+ d.name = dir.name;
+ d.access(FWSTAT, file.uid);
+ d.change(~0);
+ d.put();
+
+ if(p1 != nil)
+ p1.put();
+ file.unlock();
+
+ return ref Rmsg.Wstat(f.tag);
+}
+
+superok(set: int): int
+{
+ sb := Superb.get(thedevice, Bread|Bmod|Bimm);
+ ok := sb.fsok;
+ sb.fsok = set;
+ if(debug)
+ sb.print();
+ sb.touched();
+ sb.put();
+ return ok;
+}
+
+# little-endian
+get2(a: array of byte, o: int): int
+{
+ return (int a[o+1]<<8) | int a[o];
+}
+
+get2s(a: array of byte, o: int): int
+{
+ v := (int a[o+1]<<8) | int a[o];
+ if(v & 16r8000)
+ v |= ~0 << 8;
+ return v;
+}
+
+get4(a: array of byte, o: int): int
+{
+ return (int a[o+3]<<24) | (int a[o+2] << 16) | (int a[o+1]<<8) | int a[o];
+}
+
+put2(a: array of byte, o: int, v: int)
+{
+ a[o] = byte v;
+ a[o+1] = byte (v>>8);
+}
+
+put4(a: array of byte, o: int, v: int)
+{
+ a[o] = byte v;
+ a[o+1] = byte (v>>8);
+ a[o+2] = byte (v>>16);
+ a[o+3] = byte (v>>24);
+}
+
+Tag.unpack(a: array of byte): Tag
+{
+ return Tag(get2(a,2), get4(a,4));
+}
+
+Tag.pack(t: self Tag, a: array of byte)
+{
+ put2(a, 0, 0);
+ put2(a, 2, t.tag);
+ if(t.path != QPNONE)
+ put4(a, 4, t.path & ~QPDIR);
+}
+
+Superb.get(dev: ref Device, flags: int): ref Superb
+{
+ p := Iobuf.get(dev, SUPERADDR, flags);
+ if(p == nil)
+ return nil;
+ if(p.checktag(Tsuper, QPSUPER)){
+ p.put();
+ return nil;
+ }
+ sb := Superb.unpack(p.iobuf);
+ sb.iob = p;
+ return sb;
+}
+
+Superb.touched(s: self ref Superb)
+{
+ s.iob.flags |= Bmod;
+}
+
+Superb.put(sb: self ref Superb)
+{
+ if(sb.iob == nil)
+ return;
+ if(sb.iob.flags & Bmod)
+ sb.pack(sb.iob.iobuf);
+ sb.iob.put();
+ sb.iob = nil;
+}
+
+# this is the disk structure
+# Superb:
+# Super1;
+# Fbuf fbuf;
+# Fbuf:
+# nfree[4]
+# free[] # based on BUFSIZE
+# Super1:
+# long fstart;
+# long fsize;
+# long tfree;
+# long qidgen; # generator for unique ids
+# long fsok; # file system ok
+# long roraddr; # dump root addr
+# long last; # last super block addr
+# long next; # next super block addr
+
+Ofstart: con 0;
+Ofsize: con Ofstart+4;
+Otfree: con Ofsize+4;
+Oqidgen: con Otfree+4;
+Ofsok: con Oqidgen+4;
+Ororaddr: con Ofsok+4;
+Olast: con Ororaddr+4;
+Onext: con Olast+4;
+Super1size: con Onext+4;
+
+Superb.unpack(a: array of byte): ref Superb
+{
+ s := ref Superb;
+ s.fstart = get4(a, Ofstart);
+ s.fsize = get4(a, Ofsize);
+ s.tfree = get4(a, Otfree);
+ s.qidgen = get4(a, Oqidgen);
+ s.fsok = get4(a, Ofsok);
+ s.fbuf = a[Super1size:];
+ return s;
+}
+
+Superb.pack(s: self ref Superb, a: array of byte)
+{
+ put4(a, Ofstart, s.fstart);
+ put4(a, Ofsize, s.fsize);
+ put4(a, Otfree, s.tfree);
+ put4(a, Oqidgen, s.qidgen);
+ put4(a, Ofsok, s.fsok);
+}
+
+Superb.print(sb: self ref Superb)
+{
+ sys->print("fstart=%ud fsize=%ud tfree=%ud qidgen=%ud fsok=%d\n",
+ sb.fstart, sb.fsize, sb.tfree, sb.qidgen, sb.fsok);
+}
+
+Dentry.get(p: ref Iobuf, slot: int): ref Dentry
+{
+ if(p == nil)
+ return nil;
+ buf := p.iobuf[(slot%DIRPERBUF)*Dentrysize:];
+ d := Dentry.unpack(buf);
+ d.iob = p;
+ d.buf = buf;
+ return d;
+}
+
+Dentry.geta(fs: ref Device, addr: int, slot: int, qpath: int, mode: int): (ref Dentry, string)
+{
+ p := Iobuf.get(fs, addr, mode);
+ if(p == nil || p.checktag(Tdir, qpath)){
+ if(p != nil)
+ p.put();
+ return (nil, Ealloc);
+ }
+ d := Dentry.get(p, slot);
+ if(d == nil || !(d.mode & DALLOC)){
+ p.put();
+ return (nil, Ealloc);
+ }
+ return (d, nil);
+}
+
+Dentry.getd(file: ref File, mode: int): (ref Dentry, string)
+{
+ (d, e) := Dentry.geta(file.fs, file.addr, file.slot, QPNONE, mode); # QPNONE should be file.wpath's path
+ if(e != nil)
+ return (nil, e);
+ if(file.qid.path != d.qid.path || (file.qid.qtype&QTDIR) != (d.qid.qtype&QTDIR)){
+ d.put();
+ return (nil, Eqid);
+ }
+ return (d, nil);
+}
+
+# this is the disk structure:
+# char name[NAMELEN];
+# short uid;
+# short gid; [2*2]
+# ushort mode;
+# #define DALLOC 0x8000
+# #define DDIR 0x4000
+# #define DAPND 0x2000
+# #define DLOCK 0x1000
+# #define DREAD 0x4
+# #define DWRITE 0x2
+# #define DEXEC 0x1
+# [ushort muid] [2*2]
+# Qid.path; [4]
+# Qid.version; [4]
+# long size; [4]
+# long dblock[NDBLOCK];
+# long iblock;
+# long diblock;
+# long atime;
+# long mtime;
+
+Oname: con 0;
+Ouid: con Oname+NAMELEN;
+Ogid: con Ouid+2;
+Omode: con Ogid+2;
+Omuid: con Omode+2;
+Opath: con Omuid+2;
+Overs: con Opath+4;
+Osize: con Overs+4;
+Odblock: con Osize+4;
+Oiblock: con Odblock+NDBLOCK*4;
+Odiblock: con Oiblock+4;
+Oatime: con Odiblock+4;
+Omtime: con Oatime+4;
+Dentrysize: con Omtime+4;
+
+Dentry.unpack(a: array of byte): ref Dentry
+{
+ d := ref Dentry;
+ for(i:=0; i<NAMELEN; i++)
+ if(int a[i] == 0)
+ break;
+ d.name = string a[0:i];
+ d.uid = get2s(a, Ouid);
+ d.gid = get2s(a, Ogid);
+ d.mode = get2(a, Omode);
+ d.muid = get2(a, Omuid); # note: not set by Plan 9's kfs
+ d.qid = mkqid(get4(a, Opath), get4(a, Overs), d.mode);
+ d.size = big get4(a, Osize) & big 16rFFFFFFFF;
+ d.atime = get4(a, Oatime);
+ d.mtime = get4(a, Omtime);
+ d.mod = 0;
+ return d;
+}
+
+Dentry.change(d: self ref Dentry, f: int)
+{
+ d.mod |= f;
+}
+
+Dentry.update(d: self ref Dentry)
+{
+ f := d.mod;
+ d.mod = 0;
+ if(d.iob == nil || (d.iob.flags & Bmod) == 0){
+ if(f != 0)
+ panic("Dentry.update");
+ return;
+ }
+ a := d.buf;
+ if(f & Uname){
+ b := array of byte d.name;
+ for(i := 0; i < NAMELEN; i++)
+ if(i < len b)
+ a[i] = b[i];
+ else
+ a[i] = byte 0;
+ }
+ if(f & Uids){
+ put2(a, Ouid, d.uid);
+ put2(a, Ogid, d.gid);
+ }
+ if(f & Umode)
+ put2(a, Omode, d.mode);
+ if(f & Uqid){
+ path := int d.qid.path;
+ if(d.mode & DDIR)
+ path |= QPDIR;
+ put4(a, Opath, path);
+ put4(a, Overs, d.qid.vers);
+ }
+ if(f & Usize)
+ put4(a, Osize, int d.size);
+ if(f & Utime){
+ put4(a, Omtime, d.mtime);
+ put4(a, Oatime, d.atime);
+ }
+ d.iob.flags |= Bmod;
+}
+
+Dentry.access(d: self ref Dentry, f: int, uid: int)
+{
+ if((p := d.iob) != nil && !readonly){
+ if((f & (FWRITE|FWSTAT)) == 0 && noatime)
+ return;
+ if(f & (FREAD|FWRITE|FWSTAT)){
+ d.atime = now();
+ put4(d.buf, Oatime, d.atime);
+ p.flags |= Bmod;
+ }
+ if(f & FWRITE){
+ d.mtime = now();
+ put4(d.buf, Omtime, d.mtime);
+ d.muid = uid;
+ put2(d.buf, Omuid, uid);
+ d.qid.vers++;
+ put4(d.buf, Overs, d.qid.vers);
+ p.flags |= Bmod;
+ }
+ }
+}
+
+#
+# release the directory entry buffer and thus the
+# lock on both buffer and entry, typically during i/o,
+# to be reacquired later if needed
+#
+Dentry.release(d: self ref Dentry)
+{
+ if(d.iob != nil){
+ d.update();
+ d.iob.put();
+ d.iob = nil;
+ d.buf = nil;
+ }
+}
+
+Dentry.getblk(d: self ref Dentry, a: int, tag: int): ref Iobuf
+{
+ addr := d.rel2abs(a, tag, 0);
+ if(addr == 0)
+ return nil;
+ return Iobuf.get(thedevice, addr, Bread);
+}
+
+#
+# same as Dentry.buf but calls d.release
+# to reduce interference.
+#
+Dentry.getblk1(d: self ref Dentry, a: int, tag: int): ref Iobuf
+{
+ addr := d.rel2abs(a, tag, 1);
+ if(addr == 0)
+ return nil;
+ return Iobuf.get(thedevice, addr, Bread);
+}
+
+Dentry.rel2abs(d: self ref Dentry, a: int, tag: int, putb: int): int
+{
+ if(a < 0){
+ sys->print("Dentry.rel2abs: neg\n");
+ return 0;
+ }
+ p := d.iob;
+ if(p == nil || d.buf == nil)
+ panic("nil iob");
+ data := d.buf;
+ qpath := int d.qid.path;
+ dev := p.dev;
+ if(a < NDBLOCK){
+ addr := get4(data, Odblock+a*4);
+ if(addr == 0 && tag){
+ addr = balloc(dev, tag, qpath);
+ put4(data, Odblock+a*4, addr);
+ p.flags |= Bmod|Bimm;
+ }
+ if(putb)
+ d.release();
+ return addr;
+ }
+ a -= NDBLOCK;
+ if(a < INDPERBUF){
+ addr := get4(data, Oiblock);
+ if(addr == 0 && tag){
+ addr = balloc(dev, Tind1, qpath);
+ put4(data, Oiblock, addr);
+ p.flags |= Bmod|Bimm;
+ }
+ if(putb)
+ d.release();
+ return indfetch(dev, qpath, addr, a, Tind1, tag);
+ }
+ a -= INDPERBUF;
+ if(a < INDPERBUF2){
+ addr := get4(data, Odiblock);
+ if(addr == 0 && tag){
+ addr = balloc(dev, Tind2, qpath);
+ put4(data, Odiblock, addr);
+ p.flags |= Bmod|Bimm;
+ }
+ if(putb)
+ d.release();
+ addr = indfetch(dev, qpath, addr, a/INDPERBUF, Tind2, Tind1);
+ return indfetch(dev, qpath, addr, a%INDPERBUF, Tind1, tag);
+ }
+ if(putb)
+ d.release();
+ sys->print("Dentry.buf: trip indirect\n");
+ return 0;
+}
+
+indfetch(dev: ref Device, path: int, addr: int, a: int, itag: int, tag: int): int
+{
+ if(addr == 0)
+ return 0;
+ bp := Iobuf.get(dev, addr, Bread);
+ if(bp == nil){
+ sys->print("ind fetch bp = nil\n");
+ return 0;
+ }
+ if(bp.checktag(itag, path)){
+ sys->print("ind fetch tag\n");
+ bp.put();
+ return 0;
+ }
+ addr = get4(bp.iobuf, a*4);
+ if(addr == 0 && tag){
+ addr = balloc(dev, tag, path);
+ if(addr != 0){
+ put4(bp.iobuf, a*4, addr);
+ bp.flags |= Bmod;
+ if(localfs || tag == Tdir)
+ bp.flags |= Bimm;
+ bp.settag(itag, path);
+ }
+ }
+ bp.put();
+ return addr;
+}
+
+balloc(dev: ref Device, tag: int, qpath: int): int
+{
+ # TO DO: cache superblock to reduce pack/unpack
+ sb := Superb.get(dev, Bread|Bmod);
+ if(sb == nil)
+ panic("balloc: super block");
+ n := get4(sb.fbuf, 0);
+ n--;
+ sb.tfree--;
+ if(n < 0 || n >= FEPERBUF)
+ panic("balloc: bad freelist");
+ a := get4(sb.fbuf, 4+n*4);
+ if(n == 0){
+ if(a == 0){
+ sb.tfree = 0;
+ sb.touched();
+ sb.put();
+ return 0;
+ }
+ bp := Iobuf.get(dev, a, Bread);
+ if(bp == nil || bp.checktag(Tfree, QPNONE)){
+ if(bp != nil)
+ bp.put();
+ sb.put();
+ return 0;
+ }
+ sb.fbuf[0:] = bp.iobuf[0:(FEPERBUF+1)*4];
+ sb.touched();
+ bp.put();
+ }else{
+ put4(sb.fbuf, 0, n);
+ sb.touched();
+ }
+ bp := Iobuf.get(dev, a, Bmod);
+ bp.iobuf[0:] = emptyblock;
+ bp.settag(tag, qpath);
+ if(tag == Tind1 || tag == Tind2 || tag == Tdir)
+ bp.flags |= Bimm;
+ bp.put();
+ sb.put();
+ return a;
+}
+
+bfree(dev: ref Device, addr: int, d: int)
+{
+ if(addr == 0)
+ return;
+ if(d > 0){
+ d--;
+ p := Iobuf.get(dev, addr, Bread);
+ if(p != nil){
+ for(i:=INDPERBUF-1; i>=0; i--){
+ a := get4(p.iobuf, i*4);
+ bfree(dev, a, d);
+ }
+ p.put();
+ }
+ }
+
+ # stop outstanding i/o
+ p := Iobuf.get(dev, addr, Bprobe);
+ if(p != nil){
+ p.flags &= ~(Bmod|Bimm);
+ p.put();
+ }
+
+ s := Superb.get(dev, Bread|Bmod);
+ if(s == nil)
+ panic("bfree: super block");
+ addfree(dev, addr, s);
+ s.put();
+}
+
+addfree(dev: ref Device, addr: int, sb: ref Superb)
+{
+ if(addr >= sb.fsize){
+ sys->print("addfree: bad addr %ud\n", addr);
+ return;
+ }
+ n := get4(sb.fbuf, 0);
+ if(n < 0 || n > FEPERBUF)
+ panic("addfree: bad freelist");
+ if(n >= FEPERBUF){
+ p := Iobuf.get(dev, addr, Bmod);
+ if(p == nil)
+ panic("addfree: Iobuf.get");
+ p.iobuf[0:] = sb.fbuf[0:(1+FEPERBUF)*4];
+ sb.fbuf[0:] = emptyblock[0:(1+FEPERBUF)*4]; # clear it for debugging
+ p.settag(Tfree, QPNONE);
+ p.put();
+ n = 0;
+ }
+ put4(sb.fbuf, 4+n*4, addr);
+ put4(sb.fbuf, 0, n+1);
+ sb.tfree++;
+ if(addr >= sb.fsize)
+ sb.fsize = addr+1;
+ sb.touched();
+}
+
+qidpathgen(dev: ref Device): int
+{
+ sb := Superb.get(dev, Bread|Bmod);
+ if(sb == nil)
+ panic("qidpathgen: super block");
+ sb.qidgen++;
+ path := sb.qidgen;
+ sb.touched();
+ sb.put();
+ return path;
+}
+
+Dentry.trunc(d: self ref Dentry, uid: int)
+{
+ p := d.iob;
+ data := d.buf;
+ bfree(p.dev, get4(data, Odiblock), 2);
+ put4(data, Odiblock, 0);
+ bfree(p.dev, get4(data, Oiblock), 1);
+ put4(data, Oiblock, 0);
+ for(i:=NDBLOCK-1; i>=0; i--){
+ bfree(p.dev, get4(data, Odblock+i*4), 0);
+ put4(data, Odblock+i*4, 0);
+ }
+ d.size = big 0;
+ d.change(Usize);
+ p.flags |= Bmod|Bimm;
+ d.access(FWRITE, uid);
+ d.update();
+}
+
+Dentry.put(d: self ref Dentry)
+{
+ p := d.iob;
+ if(p == nil || d.buf == nil)
+ return;
+ d.update();
+ p.put();
+ d.iob = nil;
+ d.buf = nil;
+}
+
+Dentry.print(d: self ref Dentry)
+{
+ sys->print("name=%#q uid=%d gid=%d mode=#%8.8ux qid.path=#%bux qid.vers=%ud size=%bud\n",
+ d.name, d.uid, d.gid, d.mode, d.qid.path, d.qid.vers, d.size);
+ p := d.iob;
+ if(p != nil && (data := p.iobuf) != nil){
+ sys->print("\tdblock=");
+ for(i := 0; i < NDBLOCK; i++)
+ sys->print(" %d", get4(data, Odblock+i*4));
+ sys->print(" iblock=%ud diblock=%ud\n", get4(data, Oiblock), get4(data, Odiblock));
+ }
+}
+
+HWidth: con 5; # buffers per line
+
+hiob: array of ref Hiob;
+
+iobufinit(niob: int)
+{
+ nhiob := niob/HWidth;
+ while(!prime(nhiob))
+ nhiob++;
+ hiob = array[nhiob] of {* => ref Hiob(nil, Lock.new(), 0)};
+ # allocate the buffers now
+ for(i := 0; i < len hiob; i++){
+ h := hiob[i];
+ while(h.niob < HWidth)
+ h.newbuf();
+ }
+}
+
+iobufclear()
+{
+ # eliminate the cyclic references
+ for(i := 0; i < len hiob; i++){
+ h := hiob[i];
+ while(--h.niob >= 0){
+ p := hiob[i].link;
+ hiob[i].link = p.fore;
+ p.fore = p.back = nil;
+ p = nil;
+ }
+ }
+}
+
+prime(n: int): int
+{
+ if((n%2) == 0)
+ return 0;
+ for(i:=3;; i+=2) {
+ if((n%i) == 0)
+ return 0;
+ if(i*i >= n)
+ return 1;
+ }
+}
+
+Hiob.newbuf(hb: self ref Hiob): ref Iobuf
+{
+ # hb must be locked
+ p := ref Iobuf;
+ p.qlock = chan[1] of int;
+ q := hb.link;
+ if(q != nil){
+ p.fore = q;
+ p.back = q.back;
+ q.back = p;
+ p.back.fore = p;
+ }else{
+ hb.link = p;
+ p.fore = p;
+ p.back = p;
+ }
+ p.dev = devnone;
+ p.addr = -1;
+ p.flags = 0;
+ p.xiobuf = array[RBUFSIZE] of byte;
+ hb.niob++;
+ return p;
+}
+
+Iobuf.get(dev: ref Device, addr: int, flags: int): ref Iobuf
+{
+ hb := hiob[addr%len hiob];
+ p: ref Iobuf;
+Search:
+ for(;;){
+ hb.lk.lock();
+ s := hb.link;
+
+ # see if it's active
+ p = s;
+ do{
+ if(p.addr == addr && p.dev == dev){
+ if(p != s){
+ p.back.fore = p.fore;
+ p.fore.back = p.back;
+ p.fore = s;
+ p.back = s.back;
+ s.back = p;
+ p.back.fore = p;
+ hb.link = p;
+ }
+ hb.lk.unlock();
+ p.lock();
+ if(p.addr != addr || p.dev != dev){
+ # lost race
+ p.unlock();
+ continue Search;
+ }
+ p.flags |= flags;
+ p.iobuf = p.xiobuf;
+ return p;
+ }
+ }while((p = p.fore) != s);
+ if(flags == Bprobe){
+ hb.lk.unlock();
+ return nil;
+ }
+
+ # steal the oldest unlocked buffer
+ do{
+ p = s.back;
+ if(p.canlock()){
+ # TO DO: if Bmod, write it out and restart Hashed
+ # for now we needn't because Iobuf.put is synchronous
+ if(p.flags & Bmod)
+ sys->print("Bmod unexpected (%ud)\n", p.addr);
+ hb.link = p;
+ p.dev = dev;
+ p.addr = addr;
+ p.flags = flags;
+ break Search;
+ }
+ s = p;
+ }while(p != hb.link);
+
+ # no unlocked blocks available; add a new one
+ p = hb.newbuf();
+ p.lock(); # return it locked
+ break;
+ }
+
+ p.dev = dev;
+ p.addr = addr;
+ p.flags = flags;
+ hb.lk.unlock();
+ p.iobuf = p.xiobuf;
+ if(flags & Bread){
+ if(wrenread(dev.fd, addr, p.iobuf)){
+ eprint(sys->sprint("error reading block %ud: %r", addr));
+ p.flags = 0;
+ p.dev = devnone;
+ p.addr = -1;
+ p.iobuf = nil;
+ p.unlock();
+ return nil;
+ }
+ }
+ return p;
+}
+
+Iobuf.put(p: self ref Iobuf)
+{
+ if(p.flags & Bmod)
+ p.flags |= Bimm; # temporary; see comment in Iobuf.get
+ if(p.flags & Bimm){
+ if(!(p.flags & Bmod))
+ eprint(sys->sprint("imm and no mod (%d)", p.addr));
+ if(!wrenwrite(p.dev.fd, p.addr, p.iobuf))
+ p.flags &= ~(Bmod|Bimm);
+ else
+ panic(sys->sprint("error writing block %ud: %r", p.addr));
+ }
+ p.iobuf = nil;
+ p.unlock();
+}
+
+Iobuf.lock(p: self ref Iobuf)
+{
+ p.qlock <-= 1;
+}
+
+Iobuf.canlock(p: self ref Iobuf): int
+{
+ alt{
+ p.qlock <-= 1 =>
+ return 1;
+ * =>
+ return 0;
+ }
+}
+
+Iobuf.unlock(p: self ref Iobuf)
+{
+ <-p.qlock;
+}
+
+File.access(f: self ref File, d: ref Dentry, m: int): int
+{
+ if(wstatallow)
+ return 0;
+
+ # none gets only other permissions
+
+ if(f.uid != None){
+ if(f.uid == d.uid) # owner
+ if((m<<6) & d.mode)
+ return 0;
+ if(ingroup(f.uid, d.gid)) # group membership
+ if((m<<3) & d.mode)
+ return 0;
+ }
+
+ #
+ # other access for everyone except members of group "noworld"
+ #
+ if(m & d.mode){
+ #
+ # walk directories regardless.
+ # otherwise it's impossible to get
+ # from the root to noworld's directories.
+ #
+ if((d.mode & DDIR) && (m == DEXEC))
+ return 0;
+ if(!ingroup(f.uid, Noworld))
+ return 0;
+ }
+ return 1;
+}
+
+tagname(t: int): string
+{
+ case t {
+ Tnone => return "Tnone";
+ Tsuper => return "Tsuper";
+ Tdir => return "Tdir";
+ Tind1 => return "Tind1";
+ Tind2 => return "Tind2";
+ Tfile => return "Tfile";
+ Tfree => return "Tfree";
+ Tbuck => return "Tbuck";
+ Tvirgo => return "Tvirgo";
+ Tcache => return "Tcache";
+ * => return sys->sprint("%d", t);
+ }
+}
+
+Iobuf.checktag(p: self ref Iobuf, tag: int, qpath: int): int
+{
+ t := Tag.unpack(p.iobuf[BUFSIZE:]);
+ if(t.tag != tag){
+ if(1)
+ eprint(sys->sprint(" tag = %s; expected %s; addr = %ud\n",
+ tagname(t.tag), tagname(tag), p.addr));
+ return 2;
+ }
+ if(qpath != QPNONE){
+ qpath &= ~QPDIR;
+ if(qpath != t.path){
+ if(qpath == (t.path&~QPDIR)) # old bug
+ return 0;
+ if(1)
+ eprint(sys->sprint(" tag/path = %ux; expected %s/%ux\n",
+ t.path, tagname(tag), qpath));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+Iobuf.settag(p: self ref Iobuf, tag: int, qpath: int)
+{
+ Tag(tag, qpath).pack(p.iobuf[BUFSIZE:]);
+ p.flags |= Bmod;
+}
+
+badmagic := 0;
+wmagic := "kfs wren device\n";
+
+wrenream(dev: ref Device)
+{
+ if(RBUFSIZE % 512)
+ panic(sys->sprint("kfs: bad buffersize(%d): restart a multiple of 512", RBUFSIZE));
+ if(RBUFSIZE > MAXBUFSIZE)
+ panic(sys->sprint("kfs: bad buffersize(%d): must be at most %d", RBUFSIZE, MAXBUFSIZE));
+ sys->print("kfs: reaming the file system using %d byte blocks\n", RBUFSIZE);
+ buf := array[RBUFSIZE] of {* => byte 0};
+ buf[256:] = sys->aprint("%s%d\n", wmagic, RBUFSIZE);
+ if(sys->seek(dev.fd, big 0, 0) < big 0 || sys->write(dev.fd, buf, len buf) != len buf)
+ panic("can't ream disk");
+}
+
+wreninit(dev: ref Device): int
+{
+ (ok, nil) := sys->fstat(dev.fd);
+ if(ok < 0)
+ return 0;
+ buf := array[MAXBUFSIZE] of byte;
+ sys->seek(dev.fd, big 0, 0);
+ n := sys->read(dev.fd, buf, len buf);
+ if(n < len buf)
+ return 0;
+ badmagic = 0;
+ RBUFSIZE = 1024;
+ if(string buf[256:256+len wmagic] != wmagic){
+ badmagic = 1;
+ return 0;
+ }
+ RBUFSIZE = int string buf[256+len wmagic:256+len wmagic+12];
+ if(RBUFSIZE % 512)
+ error("bad block size");
+ return 1;
+}
+
+wrenread(fd: ref Sys->FD, addr: int, a: array of byte): int
+{
+ return sys->pread(fd, a, len a, big addr * big RBUFSIZE) != len a;
+}
+
+wrenwrite(fd: ref Sys->FD, addr: int, a: array of byte): int
+{
+ return sys->pwrite(fd, a, len a, big addr * big RBUFSIZE) != len a;
+}
+
+wrentag(buf: array of byte, tag: int, qpath: int): int
+{
+ t := Tag.unpack(buf[BUFSIZE:]);
+ return t.tag != tag || (qpath&~QPDIR) != t.path;
+}
+
+wrencheck(fd: ref Sys->FD): int
+{
+ if(badmagic)
+ return 1;
+ buf := array[RBUFSIZE] of byte;
+ if(wrenread(fd, SUPERADDR, buf) || wrentag(buf, Tsuper, QPSUPER) ||
+ wrenread(fd, ROOTADDR, buf) || wrentag(buf, Tdir, QPROOT))
+ return 1;
+ d0 := Dentry.unpack(buf);
+ if(d0.mode & DALLOC)
+ return 0;
+ return 1;
+}
+
+wrensize(dev: ref Device): int
+{
+ (ok, d) := sys->fstat(dev.fd);
+ if(ok < 0)
+ return -1;
+ return int (d.length / big RBUFSIZE);
+}
+
+checkname9p2(s: string): int
+{
+ for(i := 0; i < len s; i++)
+ if(s[i] <= 8r40)
+ return 0;
+ return styx->utflen(s);
+}
+
+isro(d: ref Device): int
+{
+ return d == nil || d.ronly;
+}
+
+tlocks: list of ref Tlock;
+
+tlocked(f: ref File, d: ref Dentry): ref Tlock
+{
+ tim := now();
+ path := int d.qid.path;
+ t1: ref Tlock;
+ for(l := tlocks; l != nil; l = tl l){
+ t := hd l;
+ if(t.qpath == path && t.time >= tim && t.dev == f.fs)
+ return nil; # it's locked
+ if(t.file == nil || t1 == nil && t.time < tim)
+ t1 = t;
+ }
+ t := t1;
+ if(t == nil)
+ t = ref Tlock;
+ t.dev = f.fs;
+ t.qpath = path;
+ t.time = tim + TLOCK;
+ tlocks = t :: tlocks;
+ return t;
+}
+
+mkqid(path: int, vers: int, mode: int): Qid
+{
+ qid: Qid;
+
+ qid.path = big (path & ~QPDIR);
+ qid.vers = vers;
+ qid.qtype = 0;
+ if(mode & DDIR)
+ qid.qtype |= QTDIR;
+ if(mode & DAPND)
+ qid.qtype |= QTAPPEND;
+ if(mode & DLOCK)
+ qid.qtype |= QTEXCL;
+ return qid;
+}
+
+dir9p2(d: ref Dentry): Sys->Dir
+{
+ dir: Sys->Dir;
+
+ dir.name = d.name;
+ dir.uid = uidtostr(d.uid);
+ dir.gid = uidtostr(d.gid);
+ dir.muid = uidtostr(d.muid);
+ dir.qid = d.qid;
+ dir.mode = d.mode & 8r777;
+ if(d.mode & DDIR)
+ dir.mode |= DMDIR;
+ if(d.mode & DAPND)
+ dir.mode |= DMAPPEND;
+ if(d.mode & DLOCK)
+ dir.mode |= DMEXCL;
+ dir.atime = d.atime;
+ dir.mtime = d.mtime;
+ dir.length = big d.size;
+ dir.dtype = 0;
+ dir.dev = 0;
+ return dir;
+}
+
+rootream(dev: ref Device, addr: int)
+{
+ p := Iobuf.get(dev, addr, Bmod|Bimm);
+ p.iobuf[0:] = emptyblock;
+ p.settag(Tdir, QPROOT);
+ d := Dentry.get(p, 0);
+ d.name = "/";
+ d.uid = -1;
+ d.gid = -1;
+ d.mode = DALLOC | DDIR |
+ ((DREAD|DWRITE|DEXEC) << 6) |
+ ((DREAD|DWRITE|DEXEC) << 3) |
+ ((DREAD|DWRITE|DEXEC) << 0);
+ d.qid.path = big QPROOT;
+ d.qid.vers = 0;
+ d.qid.qtype = QTDIR;
+ d.atime = now();
+ d.mtime = d.atime;
+ d.change(~0);
+ d.access(FREAD|FWRITE, -1);
+ d.update();
+ p.put();
+}
+
+superream(dev: ref Device, addr: int)
+{
+ fsize := wrensize(dev);
+ if(fsize <= 0)
+ panic("file system device size");
+ p := Iobuf.get(dev, addr, Bmod|Bimm);
+ p.iobuf[0:] = emptyblock;
+ p.settag(Tsuper, QPSUPER);
+ sb := ref Superb;
+ sb.iob = p;
+ sb.fstart = 1;
+ sb.fsize = fsize;
+ sb.qidgen = 10;
+ sb.tfree = 0;
+ sb.fsok = 0;
+ sb.fbuf = p.iobuf[Super1size:];
+ put4(sb.fbuf, 0, 1); # nfree = 1
+ for(i := fsize-1; i>=addr+2; i--)
+ addfree(dev, i, sb);
+ sb.put();
+}
+
+eprint(s: string)
+{
+ sys->print("kfs: %s\n", s);
+}
+
+#
+# /adm/users
+#
+# uid:user:leader:members[,...]
+
+User: adt {
+ uid: int;
+ name: string;
+ leader: int;
+ mem: list of int;
+};
+
+users: list of ref User;
+
+admusers := array[] of {
+ (-1, "adm", "adm"),
+ (None, "none", "adm"),
+ (Noworld, "noworld", nil),
+ (10000, "sys", nil),
+ (10001, "upas", "upas"),
+ (10002, "bootes", "bootes"),
+ (10006, "inferno", nil),
+};
+
+userinit()
+{
+ if(!cmd_users() && users == nil){
+ cprint("initializing minimal user table");
+ defaultusers();
+ }
+ writegroup = strtouid("write");
+}
+
+cmd_users(): int
+{
+ if(kopen(FID1, FID2, array[] of {"adm", "users"}, OREAD) != nil)
+ return 0;
+ buf: array of byte;
+ for(off := 0;;){
+ (a, e) := kread(FID2, off, Styx->MAXFDATA);
+ if(e != nil){
+ cprint("/adm/users read error: "+e);
+ return 0;
+ }
+ if(len a == 0)
+ break;
+ off += len a;
+ if(buf != nil){
+ c := array[len buf + len a] of byte;
+ if(buf != nil)
+ c[0:] = buf;
+ c[len buf:] = a;
+ buf = c;
+ }else
+ buf = a;
+ }
+ kclose(FID2);
+
+ # (uid:name:lead:mem,...\n)+
+ (nl, lines) := sys->tokenize(string buf, "\n");
+ if(nl == 0){
+ cprint("empty /adm/users");
+ return 0;
+ }
+ oldusers := users;
+ users = nil;
+
+ # first pass: enter id:name
+ for(l := lines; l != nil; l = tl l){
+ uid, name, r: string;
+ s := hd l;
+ if(s == "" || s[0] == '#')
+ continue;
+ (uid, r) = field(s, ':');
+ (name, r) = field(r, ':');
+ if(uid == nil || name == nil || string int uid != uid){
+ cprint("invalid /adm/users line: "+hd l);
+ users = oldusers;
+ return 0;
+ }
+ adduser(int uid, name, nil, nil);
+ }
+
+ # second pass: groups and leaders
+ for(l = lines; l != nil; l = tl l){
+ s := hd l;
+ if(s == "" || s[0] == '#')
+ continue;
+ name, lead, mem, r: string;
+ (nil, r) = field(s, ':'); # skip id
+ (name, r) = field(r, ':');
+ (lead, mem) = field(r, ':');
+ (nil, mems) := sys->tokenize(mem, ",\n");
+ if(name == nil || lead == nil && mems == nil)
+ continue;
+ u := finduname(name);
+ if(lead != nil){
+ lu := strtouid(lead);
+ if(lu != None)
+ u.leader = lu;
+ else if(lead != nil)
+ u.leader = u.uid; # mimic kfs not fs
+ }
+ mids: list of int = nil;
+ for(; mems != nil; mems = tl mems){
+ lu := strtouid(hd mems);
+ if(lu != None)
+ mids = lu :: mids;
+ }
+ u.mem = mids;
+ }
+
+ if(debug)
+ for(x := users; x != nil; x = tl x){
+ u := hd x;
+ sys->print("%d : %q : %d :", u.uid, u.name, u.leader);
+ for(y := u.mem; y != nil; y = tl y)
+ sys->print(" %d", hd y);
+ sys->print("\n");
+ }
+ return 1;
+}
+
+field(s: string, c: int): (string, string)
+{
+ for(i := 0; i < len s; i++)
+ if(s[i] == c)
+ return (s[0:i], s[i+1:]);
+ return (s, nil);
+}
+
+defaultusers()
+{
+ for(i := 0; i < len admusers; i++){
+ (id, name, leader) := admusers[i];
+ adduser(id, name, leader, nil);
+ }
+}
+
+finduname(s: string): ref User
+{
+ for(l := users; l != nil; l = tl l){
+ u := hd l;
+ if(u.name == s)
+ return u;
+ }
+ return nil;
+}
+
+uidtostr(id: int): string
+{
+ if(id == None)
+ return "none";
+ for(l := users; l != nil; l = tl l){
+ u := hd l;
+ if(u.uid == id)
+ return u.name;
+ }
+ return sys->sprint("#%d", id);
+}
+
+leadgroup(ui: int, gi: int): int
+{
+ for(l := users; l != nil; l = tl l){
+ u := hd l;
+ if(u.uid == gi){
+ if(u.leader == ui)
+ return 1;
+ if(u.leader == 0)
+ return ingroup(ui, gi);
+ return 0;
+ }
+ }
+ return 0;
+}
+
+strtouid(s: string): int
+{
+ if(s == "none")
+ return None;
+ u := finduname(s);
+ if(u != nil)
+ return u.uid;
+ return 0;
+}
+
+ingroup(uid: int, gid: int): int
+{
+ if(uid == gid)
+ return 1;
+ for(l := users; l != nil; l = tl l){
+ u := hd l;
+ if(u.uid == gid){
+ for(m := u.mem; m != nil; m = tl m)
+ if(hd m == uid)
+ return 1;
+ return 0;
+ }
+ }
+ return 0;
+}
+
+baduname(s: string): int
+{
+ n := checkname9p2(s);
+ if(n == 0 || n+1 > NAMELEN || s == "." || s == ".."){
+ sys->print("kfs: illegal user name %q\n", s);
+ return 1;
+ }
+ return 0;
+}
+
+adduser(id: int, name: string, leader: string, mem: list of string)
+{
+ if(baduname(name))
+ return;
+ for(l := users; l != nil; l = tl l){
+ u := hd l;
+ if(u.uid == id){
+ sys->print("kfs: duplicate user ID %d (name %q)\n", id, u.name);
+ return;
+ }else if(u.name == name){
+ sys->print("kfs: duplicate user name %q (id %d)\n", name, u.uid);
+ return;
+ }
+ }
+ if(name == leader)
+ lid := id;
+ else if(leader == nil)
+ lid = 0;
+ else if(!baduname(leader))
+ lid = strtouid(leader);
+ else
+ return;
+ memid: list of int;
+ for(; mem != nil; mem = tl mem){
+ if(baduname(hd mem))
+ return;
+ x := strtouid(hd mem);
+ if(x != 0)
+ memid = x :: memid;
+ }
+ u := ref User(id, name, lid, memid);
+ users = u :: users;
+}
+
+Lock.new(): ref Lock
+{
+ return ref Lock(chan[1] of int);
+}
+
+Lock.lock(l: self ref Lock)
+{
+ l.c <-= 1;
+}
+
+Lock.canlock(l: self ref Lock): int
+{
+ alt{
+ l.c <-= 1 =>
+ return 1;
+ * =>
+ return 0;
+ }
+}
+
+Lock.unlock(l: self ref Lock)
+{
+ <-l.c;
+}
+
+#
+# kfs check, could be a separate module if that seemed important
+#
+
+MAXDEPTH: con 100;
+MAXNAME: con 4000;
+
+Map: adt {
+ lo, hi: int;
+ bits: array of byte;
+ nbad: int;
+ ndup: int;
+ nmark: int;
+
+ new: fn(lo, hi: int): ref Map;
+ isset: fn(b: self ref Map, a: int): int;
+ mark: fn(b: self ref Map, a: int): string;
+};
+
+Check: adt {
+ dev: ref Device;
+
+ amap: ref Map;
+ qmap: ref Map;
+
+ name: string;
+ nfiles: int;
+ maxq: int;
+
+ mod: int;
+ flags: int;
+ oldblock: int;
+
+ depth: int;
+ maxdepth: int;
+
+ check: fn(c: self ref Check);
+ touch: fn(c: self ref Check, a: int): int;
+ checkdir: fn(c: self ref Check, a: int, qpath: int): int;
+ checkindir: fn(c: self ref Check, a: int, d: ref Dentry, qpath: int): int;
+ maked: fn(c: self ref Check, a: int, s: int, qpath: int): ref Dentry;
+ modd: fn(c: self ref Check, a: int, s: int, d: ref Dentry);
+ fsck: fn(c: self ref Check, d: ref Dentry): int;
+ xread: fn(c: self ref Check, a: int, qpath: int);
+ xtag: fn(c: self ref Check, a: int, tag: int, qpath: int): ref Iobuf;
+ ckfreelist: fn(c: self ref Check, sb: ref Superb);
+ mkfreelist: fn(c: self ref Check, sb: ref Superb);
+ amark: fn(c: self ref Check, a: int): int;
+ fmark: fn(c: self ref Check, a: int): int;
+ missing: fn(c: self ref Check, sb: ref Superb);
+ qmark: fn(c: self ref Check, q: int);
+};
+
+check(dev: ref Device, flag: int)
+{
+ #mainlock.wlock();
+ #mainlock.wunlock();
+ c := ref Check;
+ c.dev = dev;
+ c.nfiles = 0;
+ c.maxq = 0;
+ c.mod = 0;
+ c.flags = flag;
+ c.oldblock = 0;
+ c.depth = 0;
+ c.maxdepth = 0;
+ c.check();
+}
+
+checkflags(s: string): int
+{
+ f := 0;
+ for(i := 0; i < len s; i++)
+ case s[i] {
+ 'r' => f |= Crdall;
+ 't' => f |= Ctag;
+ 'P' => f |= Cpfile;
+ 'p' => f |= Cpdir;
+ 'f' => f |= Cfree;
+ 'c' => f |= Cream;
+ 'd' => f |= Cbad;
+ 'w' => f |= Ctouch;
+ 'q' => f |= Cquiet;
+ 'v' => ; # old verbose flag; ignored
+ * => return -1;
+ }
+ return f;
+}
+
+Check.check(c: self ref Check)
+{
+ sbaddr := SUPERADDR;
+ p := c.xtag(sbaddr, Tsuper, QPSUPER);
+ if(p == nil){
+ cprint(sys->sprint("bad superblock"));
+ return;
+ }
+ sb := Superb.unpack(p.iobuf);
+ sb.iob = p;
+
+ fstart := sb.fstart;
+ if(fstart != 1){
+ cprint(sys->sprint("invalid superblock"));
+ return;
+ }
+ fsize := sb.fsize;
+ if(fsize < fstart || fsize > wrensize(c.dev)){
+ cprint(sys->sprint("invalid size in superblock"));
+ return;
+ }
+ c.amap = Map.new(fstart, fsize);
+
+ nqid := sb.qidgen+100; # not as much of a botch
+ if(nqid > 1024*1024*8)
+ nqid = 1024*1024*8;
+ if(nqid < 64*1024)
+ nqid = 64*1024;
+ c.qmap = Map.new(0, nqid);
+
+ c.mod = 0;
+ c.depth = 0;
+ c.maxdepth = 0;
+
+ if(c.amark(sbaddr))
+ {}
+
+ if(!(c.flags & Cquiet))
+ cprint(sys->sprint("checking file system: %s", "main"));
+ c.nfiles = 0;
+ c.maxq = 0;
+
+ d := c.maked(ROOTADDR, 0, QPROOT);
+ if(d != nil){
+ if(c.amark(ROOTADDR))
+ {}
+ if(c.fsck(d))
+ c.modd(ROOTADDR, 0, d);
+ if(--c.depth != 0)
+ cprint("depth not zero on return");
+ }
+ if(sb.qidgen < c.maxq)
+ cprint(sys->sprint("qid generator low path=%d maxq=%d", sb.qidgen, c.maxq));
+
+ nqbad := c.qmap.nbad + c.qmap.ndup;
+ c.qmap = nil; # could use to implement resequence
+
+ ndup := c.amap.ndup;
+ nused := c.amap.nmark;
+
+ c.amap.ndup = c.amap.nmark = 0; # reset for free list counts
+ if(c.flags & Cfree){
+ c.name = "free list";
+ c.mkfreelist(sb);
+ sb.qidgen = c.maxq;
+ p.settag(Tsuper, QPNONE);
+ }else
+ c.ckfreelist(sb);
+
+ nbad := c.amap.nbad;
+ nfdup := c.amap.ndup;
+ nfree := c.amap.nmark;
+ # leave amap for missing, below
+
+ if(c.mod){
+ cprint("file system was modified");
+ p.settag(Tsuper, QPNONE);
+ }
+
+ if(!(c.flags & Cquiet)){
+ cprint(sys->sprint("%8d files", c.nfiles));
+ cprint(sys->sprint("%8d blocks in the file system", fsize-fstart));
+ cprint(sys->sprint("%8d used blocks", nused));
+ cprint(sys->sprint("%8d free blocks", sb.tfree));
+ }
+ if(!(c.flags & Cfree)){
+ if(nfree != sb.tfree)
+ cprint(sys->sprint("%8d free blocks found", nfree));
+ if(nfdup)
+ cprint(sys->sprint("%8d blocks duplicated in the free list", nfdup));
+ if(fsize-fstart-nused-nfree)
+ cprint(sys->sprint("%8d missing blocks", fsize-fstart-nused-nfree));
+ }
+ if(ndup)
+ cprint(sys->sprint("%8d address duplications", ndup));
+ if(nbad)
+ cprint(sys->sprint("%8d bad block addresses", nbad));
+ if(nqbad)
+ cprint(sys->sprint("%8d bad qids", nqbad));
+ if(!(c.flags & Cquiet))
+ cprint(sys->sprint("%8d maximum qid path", c.maxq));
+ c.missing(sb);
+
+ sb.put();
+}
+
+Check.touch(c: self ref Check, a: int): int
+{
+ if((c.flags&Ctouch) && a){
+ p := Iobuf.get(c.dev, a, Bread|Bmod);
+ if(p != nil)
+ p.put();
+ return 1;
+ }
+ return 0;
+}
+
+Check.checkdir(c: self ref Check, a: int, qpath: int): int
+{
+ ns := len c.name;
+ dmod := c.touch(a);
+ for(i:=0; i<DIRPERBUF; i++){
+ nd := c.maked(a, i, qpath);
+ if(nd == nil)
+ break;
+ if(c.fsck(nd)){
+ c.modd(a, i, nd);
+ dmod++;
+ }
+ c.depth--;
+ c.name = c.name[0:ns];
+ }
+ c.name = c.name[0:ns];
+ return dmod;
+}
+
+Check.checkindir(c: self ref Check, a: int, d: ref Dentry, qpath: int): int
+{
+ dmod := c.touch(a);
+ p := c.xtag(a, Tind1, qpath);
+ if(p == nil)
+ return dmod;
+ for(i:=0; i<INDPERBUF; i++){
+ a = get4(p.iobuf, i*4);
+ if(a == 0)
+ continue;
+ if(c.amark(a)){
+ if(c.flags & Cbad){
+ put4(p.iobuf, i*4, 0);
+ p.flags |= Bmod;
+ }
+ continue;
+ }
+ if(d.mode & DDIR)
+ dmod += c.checkdir(a, qpath);
+ else if(c.flags & Crdall)
+ c.xread(a, qpath);
+ }
+ p.put();
+ return dmod;
+}
+
+Check.fsck(c: self ref Check, d: ref Dentry): int
+{
+ p: ref Iobuf;
+ i: int;
+ a, qpath: int;
+
+ if(++c.depth >= c.maxdepth){
+ c.maxdepth = c.depth;
+ if(c.maxdepth >= MAXDEPTH){
+ cprint(sys->sprint("max depth exceeded: %s", c.name));
+ return 0;
+ }
+ }
+ dmod := 0;
+ if(!(d.mode & DALLOC))
+ return 0;
+ c.nfiles++;
+
+ ns := len c.name;
+ i = styx->utflen(d.name);
+ if(i >= NAMELEN){
+ d.name[NAMELEN-1] = 0; # TO DO: not quite right
+ cprint(sys->sprint("%q.name (%q) not terminated", c.name, d.name));
+ return 0;
+ }
+ ns += i;
+ if(ns >= MAXNAME){
+ cprint(sys->sprint("%q.name (%q) name too large", c.name, d.name));
+ return 0;
+ }
+ c.name += d.name;
+
+ if(d.mode & DDIR){
+ if(ns > 1)
+ c.name += "/";
+ if(c.flags & Cpdir)
+ cprint(sys->sprint("%s", c.name));
+ } else if(c.flags & Cpfile)
+ cprint(sys->sprint("%s", c.name));
+
+ qpath = int d.qid.path & ~QPDIR;
+ c.qmark(qpath);
+ if(qpath > c.maxq)
+ c.maxq = qpath;
+ for(i=0; i<NDBLOCK; i++){
+ a = get4(d.buf, Odblock+i*4);
+ if(a == 0)
+ continue;
+ if(c.amark(a)){
+ put4(d.buf, Odblock+i*4, 0);
+ dmod++;
+ continue;
+ }
+ if(d.mode & DDIR)
+ dmod += c.checkdir(a, qpath);
+ else if(c.flags & Crdall)
+ c.xread(a, qpath);
+ }
+ a = get4(d.buf, Oiblock);
+ if(a){
+ if(c.amark(a)){
+ put4(d.buf, Oiblock, 0);
+ dmod++;
+ }
+ else
+ dmod += c.checkindir(a, d, qpath);
+ }
+
+ a = get4(d.buf, Odiblock);
+ if(a && c.amark(a)){
+ put4(d.buf, Odiblock, 0);
+ return dmod + 1;
+ }
+ dmod += c.touch(a);
+ p = c.xtag(a, Tind2, qpath);
+ if(p != nil){
+ for(i=0; i<INDPERBUF; i++){
+ a = get4(p.iobuf, i*4);
+ if(a == 0)
+ continue;
+ if(c.amark(a)){
+ if(c.flags & Cbad){
+ put4(p.iobuf, i*4, 0);
+ p.flags |= Bmod;
+ }
+ continue;
+ }
+ dmod += c.checkindir(a, d, qpath);
+ }
+ p.put();
+ }
+ return dmod;
+}
+
+Check.ckfreelist(c: self ref Check, sb: ref Superb)
+{
+ c.name = "free list";
+ cprint(sys->sprint("check %s", c.name));
+ fb := sb.fbuf;
+ a := SUPERADDR;
+ p: ref Iobuf;
+ lo := 0;
+ hi := 0;
+ for(;;){
+ n := get4(fb, 0); # nfree
+ if(n < 0 || n > FEPERBUF){
+ cprint(sys->sprint("check: nfree bad %d", a));
+ break;
+ }
+ for(i:=1; i<n; i++){
+ a = get4(fb, 4+i*4); # free[i]
+ if(a && !c.fmark(a)){
+ if(!lo || lo > a)
+ lo = a;
+ if(!hi || hi < a)
+ hi = a;
+ }
+ }
+ a = get4(fb, 4); # free[0]
+ if(a == 0)
+ break;
+ if(c.fmark(a))
+ break;
+ if(!lo || lo > a)
+ lo = a;
+ if(!hi || hi < a)
+ hi = a;
+ if(p != nil)
+ p.put();
+ p = c.xtag(a, Tfree, QPNONE);
+ if(p == nil)
+ break;
+ fb = p.iobuf;
+ }
+ if(p != nil)
+ p.put();
+ cprint(sys->sprint("lo = %d; hi = %d", lo, hi));
+}
+
+#
+# make freelist from scratch
+#
+Check.mkfreelist(c: self ref Check, sb: ref Superb)
+{
+ sb.fbuf[0:] = emptyblock[0:(FEPERBUF+1)*4];
+ sb.tfree = 0;
+ put4(sb.fbuf, 0, 1); # nfree = 1
+ for(a:=sb.fsize-sb.fstart-1; a >= 0; a--){
+ i := a>>3;
+ if(i < 0 || i >= len c.amap.bits)
+ continue;
+ b := byte (1 << (a&7));
+ if((c.amap.bits[i] & b) != byte 0)
+ continue;
+ addfree(c.dev, sb.fstart+a, sb);
+ c.amap.bits[i] |= b;
+ }
+ sb.iob.flags |= Bmod;
+}
+
+#
+# makes a copy of a Dentry's representation on disc so that
+# the rest of the much larger iobuf can be freed.
+#
+Check.maked(c: self ref Check, a: int, s: int, qpath: int): ref Dentry
+{
+ p := c.xtag(a, Tdir, qpath);
+ if(p == nil)
+ return nil;
+ d := Dentry.get(p, s);
+ if(d == nil)
+ return nil;
+ copy := array[len d.buf] of byte;
+ copy[0:] = d.buf;
+ d.put();
+ d.buf = copy;
+ return d;
+}
+
+Check.modd(c: self ref Check, a: int, s: int, d1: ref Dentry)
+{
+ if(!(c.flags & Cbad))
+ return;
+ p := Iobuf.get(c.dev, a, Bread);
+ d := Dentry.get(p, s);
+ if(d == nil){
+ if(p != nil)
+ p.put();
+ return;
+ }
+ d.buf[0:] = d1.buf;
+ p.flags |= Bmod;
+ p.put();
+}
+
+Check.xread(c: self ref Check, a: int, qpath: int)
+{
+ p := c.xtag(a, Tfile, qpath);
+ if(p != nil)
+ p.put();
+}
+
+Check.xtag(c: self ref Check, a: int, tag: int, qpath: int): ref Iobuf
+{
+ if(a == 0)
+ return nil;
+ p := Iobuf.get(c.dev, a, Bread);
+ if(p == nil){
+ cprint(sys->sprint("check: \"%s\": xtag: p null", c.name));
+ if(c.flags & (Cream|Ctag)){
+ p = Iobuf.get(c.dev, a, Bmod);
+ if(p != nil){
+ p.iobuf[0:] = emptyblock;
+ p.settag(tag, qpath);
+ c.mod++;
+ return p;
+ }
+ }
+ return nil;
+ }
+ if(p.checktag(tag, qpath)){
+ cprint(sys->sprint("check: \"%s\": xtag: checktag", c.name));
+ if(c.flags & Cream)
+ p.iobuf[0:] = emptyblock;
+ if(c.flags & (Cream|Ctag)){
+ p.settag(tag, qpath);
+ c.mod++;
+ }
+ return p;
+ }
+ return p;
+}
+
+Check.amark(c: self ref Check, a: int): int
+{
+ e := c.amap.mark(a);
+ if(e != nil){
+ cprint(sys->sprint("check: \"%s\": %s %d", c.name, e, a));
+ return e != "dup"; # don't clear dup blocks because rm might repair
+ }
+ return 0;
+}
+
+Check.fmark(c: self ref Check,a: int): int
+{
+ e := c.amap.mark(a);
+ if(e != nil){
+ cprint(sys->sprint("check: \"%s\": %s %d", c.name, e, a));
+ return 1;
+ }
+ return 0;
+}
+
+Check.missing(c: self ref Check, sb: ref Superb)
+{
+ n := 0;
+ for(a:=sb.fsize-sb.fstart-1; a>=0; a--){
+ i := a>>3;
+ b := byte (1 << (a&7));
+ if((c.amap.bits[i] & b) == byte 0){
+ cprint(sys->sprint("missing: %d", sb.fstart+a));
+ n++;
+ }
+ if(n > 10){
+ cprint(sys->sprint(" ..."));
+ break;
+ }
+ }
+}
+
+Check.qmark(c: self ref Check, qpath: int)
+{
+ e := c.qmap.mark(qpath);
+ if(e != nil){
+ if(c.qmap.nbad+c.qmap.ndup < 20)
+ cprint(sys->sprint("check: \"%s\": qid %s 0x%ux", c.name, e, qpath));
+ }
+}
+
+Map.new(lo, hi: int): ref Map
+{
+ m := ref Map;
+ n := (hi-lo+7)>>3;
+ m.bits = array[n] of {* => byte 0};
+ m.lo = lo;
+ m.hi = hi;
+ m.nbad = 0;
+ m.ndup = 0;
+ m.nmark = 0;
+ return m;
+}
+
+Map.isset(m: self ref Map, i: int): int
+{
+ if(i < m.lo || i >= m.hi)
+ return -1; # hard to say
+ i -= m.lo;
+ return (m.bits[i>>3] & byte (1<<(i&7))) != byte 0;
+}
+
+Map.mark(m: self ref Map, i: int): string
+{
+ if(i < m.lo || i >= m.hi){
+ m.nbad++;
+ return "out of range";
+ }
+ i -= m.lo;
+ b := byte (1 << (i&7));
+ i >>= 3;
+ if((m.bits[i] & b) != byte 0){
+ m.ndup++;
+ return "dup";
+ }
+ m.bits[i] |= b;
+ m.nmark++;
+ return nil;
+}
+
+cprint(s: string)
+{
+ if(consoleout != nil)
+ consoleout <-= s+"\n";
+ else
+ eprint(s);
+}
diff --git a/appl/cmd/disk/kfscmd.b b/appl/cmd/disk/kfscmd.b
new file mode 100644
index 00000000..e1b023a9
--- /dev/null
+++ b/appl/cmd/disk/kfscmd.b
@@ -0,0 +1,53 @@
+implement Kfscmd;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+include "arg.m";
+
+Kfscmd: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ err(sys->sprint("can't load %s: %r", Arg->PATH));
+
+ cfs := "main";
+ arg->init(args);
+ arg->setusage("disk/kfscmd [-n fsname] cmd ...");
+ while((c := arg->opt()) != 0)
+ case c {
+ 'n' =>
+ cfs = arg->earg();
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ ctlf := "/chan/kfs."+cfs+".cmd";
+ ctl := sys->open(ctlf, Sys->ORDWR);
+ if(ctl == nil)
+ err(sys->sprint("can't open %s: %r", ctlf));
+ for(; args != nil; args = tl args){
+ if(sys->fprint(ctl, "%s", hd args) > 0){
+ buf := array[1024] of byte;
+ while((n := sys->read(ctl, buf, len buf)) > 0)
+ sys->write(sys->fildes(1), buf, n);
+ }else
+ err(sys->sprint("%q: %r", hd args));
+ }
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "kfscmd: %s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/disk/mbr.b b/appl/cmd/disk/mbr.b
new file mode 100644
index 00000000..9d51c945
--- /dev/null
+++ b/appl/cmd/disk/mbr.b
@@ -0,0 +1,134 @@
+implement Mbr;
+
+#
+# install new master boot record boot code on PC disk.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "disks.m";
+ disks: Disks;
+ Disk, PCpart, Toffset: import disks;
+
+include "arg.m";
+
+Mbr: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+
+
+#
+# Default boot block prints an error message and reboots.
+#
+ndefmbr := Toffset;
+defmbr := array[512] of {
+ byte 16rEB, byte 16r3C, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00,
+ byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00,
+16r03E => byte 16rFA, byte 16rFC, byte 16r8C, byte 16rC8, byte 16r8E, byte 16rD8, byte 16r8E, byte 16rD0,
+ byte 16rBC, byte 16r00, byte 16r7C, byte 16rBE, byte 16r77, byte 16r7C, byte 16rE8, byte 16r19,
+ byte 16r00, byte 16r33, byte 16rC0, byte 16rCD, byte 16r16, byte 16rBB, byte 16r40, byte 16r00,
+ byte 16r8E, byte 16rC3, byte 16rBB, byte 16r72, byte 16r00, byte 16rB8, byte 16r34, byte 16r12,
+ byte 16r26, byte 16r89, byte 16r07, byte 16rEA, byte 16r00, byte 16r00, byte 16rFF, byte 16rFF,
+ byte 16rEB, byte 16rD6, byte 16rAC, byte 16r0A, byte 16rC0, byte 16r74, byte 16r09, byte 16rB4,
+ byte 16r0E, byte 16rBB, byte 16r07, byte 16r00, byte 16rCD, byte 16r10, byte 16rEB, byte 16rF2,
+ byte 16rC3, byte 'N', byte 'o', byte 't', byte ' ', byte 'a', byte ' ', byte 'b',
+ byte 'o', byte 'o', byte 't', byte 'a', byte 'b', byte 'l', byte 'e', byte ' ',
+ byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'o', byte 'r', byte ' ',
+ byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'e', byte 'r', byte 'r',
+ byte 'o', byte 'r', byte '\r', byte '\n', byte 'P', byte 'r', byte 'e', byte 's',
+ byte 's', byte ' ', byte 'a', byte 'l', byte 'm', byte 'o', byte 's', byte 't',
+ byte ' ', byte 'a', byte 'n', byte 'y', byte ' ', byte 'k', byte 'e', byte 'y',
+ byte ' ', byte 't', byte 'o', byte ' ', byte 'r', byte 'e', byte 'b', byte 'o',
+ byte 'o', byte 't', byte '.', byte '.', byte '.', byte 16r00, byte 16r00, byte 16r00,
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ flag9 := 0;
+ mbrfile: string;
+ sys = load Sys Sys->PATH;
+ disks = load Disks Disks->PATH;
+
+ sys->pctl(Sys->FORKFD, nil);
+ disks->init();
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("disk/mbr [-m mbrfile] disk");
+ while((o := arg->opt()) != 0)
+ case o {
+ '9' =>
+ flag9 = 1;
+ 'm' =>
+ mbrfile = arg->earg();
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(len args != 1)
+ arg->usage();
+ arg = nil;
+
+ disk := Disk.open(hd args, Sys->ORDWR, 0);
+ if(disk == nil)
+ fatal(sys->sprint("opendisk %s: %r", hd args));
+
+ if(disk.dtype == "floppy")
+ fatal(sys->sprint("will not install mbr on floppy"));
+ if(disk.secsize != 512)
+ fatal(sys->sprint("secsize %d invalid: must be 512", disk.secsize));
+
+ secsize := disk.secsize;
+ mbr := array[secsize*disk.s] of {* => byte 0};
+
+ #
+ # Start with initial sector from disk.
+ #
+ if(sys->seek(disk.fd, big 0, 0) < big 0)
+ fatal(sys->sprint("seek to boot sector: %r\n"));
+ if(sys->read(disk.fd, mbr, secsize) != secsize)
+ fatal(sys->sprint("reading boot sector: %r"));
+
+ nmbr: int;
+ if(mbrfile == nil){
+ nmbr = ndefmbr;
+ mbr[0:] = defmbr;
+ } else {
+ buf := array[secsize*(disk.s+1)] of {* => byte 0};
+ if((sysfd := sys->open(mbrfile, Sys->OREAD)) == nil)
+ fatal(sys->sprint("open %s: %r", mbrfile));
+ if((nmbr = sys->read(sysfd, buf, secsize*(disk.s+1))) < 0)
+ fatal(sys->sprint("read %s: %r", mbrfile));
+ if(nmbr > secsize*disk.s)
+ fatal(sys->sprint("master boot record too large %d > %d", nmbr, secsize*disk.s));
+ if(nmbr < secsize)
+ nmbr = secsize;
+ sysfd = nil;
+ buf[Toffset:] = mbr[Toffset:secsize];
+ mbr[0:] = buf[0:nmbr];
+ }
+
+ if(flag9){
+ for(i := Toffset; i < secsize; i++)
+ mbr[i] = byte 0;
+ mbr[Toffset:] = PCpart(0, Disks->Type9, big 0, big disk.s, disk.secs-big disk.s).bytes(disk);
+ }
+ mbr[secsize-2] = byte Disks->Magic0;
+ mbr[secsize-1] = byte Disks->Magic1;
+ nmbr = (nmbr+secsize-1)&~(secsize-1);
+ if(sys->seek(disk.wfd, big 0, 0) < big 0)
+ fatal(sys->sprint("seek to MBR sector: %r\n"));
+ if(sys->write(disk.wfd, mbr, nmbr) != nmbr)
+ fatal(sys->sprint("writing MBR: %r"));
+}
+
+fatal(s: string)
+{
+ sys->fprint(sys->fildes(2), "disk/mbr: %s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/disk/mkext.b b/appl/cmd/disk/mkext.b
new file mode 100644
index 00000000..fc13f2fe
--- /dev/null
+++ b/appl/cmd/disk/mkext.b
@@ -0,0 +1,377 @@
+implement Mkext;
+
+include "sys.m";
+ sys: Sys;
+ Dir, sprint, fprint: import sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+ arg: Arg;
+
+Mkext: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+LEN: con Sys->ATOMICIO;
+NFLDS: con 6; # filename, modes, uid, gid, mtime, bytes
+
+bin: ref Iobuf;
+uflag := 0;
+tflag := 0;
+hflag := 0;
+vflag := 0;
+fflag := 0;
+qflag := 1;
+stderr: ref Sys->FD;
+bout: ref Iobuf;
+argv0 := "mkext";
+
+usage()
+{
+ fprint(stderr, "Usage: mkext [-h] [-u] [-v] [-f] [-t] [-q] [-d dest-fs] [file ...]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ error(sys->sprint("cannot load %s: %r\n", Bufio->PATH));
+
+ str = load String String->PATH;
+ if(str == nil)
+ error(sys->sprint("cannot load %s: %r\n", String->PATH));
+
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ error(sys->sprint("cannot load %s: %r\n", Arg->PATH));
+
+ destdir := "";
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'd' =>
+ destdir = arg->arg();
+ if(destdir == nil)
+ error("destination directory name missing");
+ 'f' =>
+ fflag = 1;
+
+ 'h' =>
+ hflag = 1;
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if(bout == nil)
+ error(sys->sprint("can't access standard output: %r"));
+ 'u' =>
+ uflag = 1;
+ 't' =>
+ tflag = 1;
+ 'v' =>
+ vflag = 1;
+ 'q' =>
+ qflag = 0;
+ * =>
+ usage();
+ }
+ args = arg->argv();
+
+ bin = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ if(bin == nil)
+ error(sys->sprint("can't access standard input: %r"));
+ while((p := bin.gets('\n')) != nil){
+ if(p == "end of archive\n"){
+ fprint(stderr, "done\n");
+ quit(nil);
+ }
+ fields: list of string;
+ nf: int;
+ if(qflag){
+ fields = str->unquoted(p);
+ nf = len fields;
+ }else
+ (nf, fields) = sys->tokenize(p, " \t\n");
+ if(nf != NFLDS){
+ warn("too few fields in file header");
+ continue;
+ }
+ name := hd fields;
+ fields = tl fields;
+ (mode, nil) := str->toint(hd fields, 8);
+ fields = tl fields;
+ uid := hd fields;
+ fields = tl fields;
+ gid := hd fields;
+ fields = tl fields;
+ (mtime, nil) := str->toint(hd fields, 10);
+ fields = tl fields;
+ (bytes, nil) := str->tobig(hd fields, 10);
+ if(args != nil){
+ if(!selected(name, args)){
+ if(bytes != big 0)
+ seekpast(bytes);
+ continue;
+ }
+ mkdirs(destdir, name);
+ }
+ name = destdir+name;
+ if(hflag){
+ bout.puts(sys->sprint("%s %s %s %s %ud %bd\n",
+ quoted(name), octal(mode), uid, gid, mtime, bytes));
+ if(bytes != big 0)
+ seekpast(bytes);
+ continue;
+ }
+ if(mode & Sys->DMDIR)
+ mkdir(name, mode, mtime, uid, gid);
+ else
+ extract(name, mode, mtime, uid, gid, bytes);
+ }
+ fprint(stderr, "premature end of archive\n");
+ quit("eof");
+}
+
+quit(s: string)
+{
+ if(bout != nil)
+ bout.flush();
+ if(s != nil)
+ raise "fail: "+s;
+ exit;
+}
+
+fileprefix(prefix, s: string): int
+{
+ n := len prefix;
+ m := len s;
+ if(n > m || !str->prefix(prefix, s))
+ return 0;
+ if(m > n && s[n] != '/')
+ return 0;
+ return 1;
+}
+
+selected(s: string, args: list of string): int
+{
+ for(; args != nil; args = tl args)
+ if(fileprefix(hd args, s))
+ return 1;
+ return 0;
+}
+
+mkdirs(basedir, name: string)
+{
+ (nil, names) := sys->tokenize(name, "/");
+ while(names != nil) {
+ #sys->print("mkdir %s\n", basedir);
+ create(basedir, Sys->OREAD, 8r775|Sys->DMDIR);
+
+ if(tl names == nil)
+ break;
+ basedir = basedir + "/" + hd names;
+ names = tl names;
+ }
+}
+
+mkdir(name: string, mode: int, mtime: int, uid: string, gid: string)
+{
+ d: Dir;
+ i: int;
+
+ fd := create(name, Sys->OREAD, mode);
+ if(fd == nil){
+ (i, d) = sys->stat(name);
+ if(i < 0 || !(d.mode & Sys->DMDIR)){
+ warn(sys->sprint("can't make directory %s: %r", name));
+ return;
+ }
+ }else{
+ (i, d) = sys->fstat(fd);
+ if(i < 0)
+ warn(sys->sprint("can't stat %s: %r", name));
+ fd = nil;
+ }
+
+ d = sys->nulldir;
+ (nil, p) := str->splitr(name, "/");
+ if(p == nil)
+ p = name;
+ d.name = p;
+ if(tflag)
+ d.mtime = mtime;
+ if(uflag){
+ d.uid = uid;
+ d.gid = gid;
+ d.mtime = mtime;
+ }
+ d.mode = mode;
+ if(sys->wstat(name, d) < 0)
+ warn(sys->sprint("can't set modes for %s: %r", name));
+ if(uflag){
+ (i, d) = sys->stat(name);
+ if(i < 0)
+ warn(sys->sprint("can't reread modes for %s: %r", name));
+ if(d.mtime != mtime)
+ warn(sys->sprint("%s: time mismatch %ud %ud\n", name, mtime, d.mtime));
+ if(uid != d.uid)
+ warn(sys->sprint("%s: uid mismatch %s %s", name, uid, d.uid));
+ if(gid != d.gid)
+ warn(sys->sprint("%s: gid mismatch %s %s", name, gid, d.gid));
+ }
+}
+
+extract(name: string, mode: int, mtime: int, uid: string, gid: string, bytes: big)
+{
+ n: int;
+
+ if(vflag)
+ sys->print("x %s %bd bytes\n", name, bytes);
+
+ sfd := create(name, Sys->OWRITE, mode);
+ if(sfd == nil) {
+ if(!fflag || sys->remove(name) == -1 ||
+ (sfd = create(name, Sys->OWRITE, mode)) == nil) {
+ warn(sys->sprint("can't make file %s: %r", name));
+ seekpast(bytes);
+ return;
+ }
+ }
+ b := bufio->fopen(sfd, Bufio->OWRITE);
+ if (b == nil) {
+ warn(sys->sprint("can't open file %s for bufio : %r", name));
+ seekpast(bytes);
+ return;
+ }
+ buf := array [LEN] of byte;
+ for(tot := big 0; tot < bytes; tot += big n){
+ n = len buf;
+ if(tot + big n > bytes)
+ n = int(bytes - tot);
+ n = bin.read(buf, n);
+ if(n <= 0)
+ error(sys->sprint("premature eof reading %s", name));
+ if(b.write(buf, n) != n)
+ warn(sys->sprint("error writing %s: %r", name));
+ }
+
+ (i, nil) := sys->fstat(b.fd);
+ if(i < 0)
+ warn(sys->sprint("can't stat %s: %r", name));
+ d := sys->nulldir;
+ (nil, p) := str->splitr(name, "/");
+ if(p == nil)
+ p = name;
+ d.name = p;
+ if(tflag || uflag)
+ d.mtime = mtime;
+ if(uflag){
+ d.uid = uid;
+ d.gid = gid;
+ }
+ d.mode = mode;
+ if(b.flush() == Bufio->ERROR)
+ warn(sys->sprint("error writing %s: %r", name));
+ if(sys->fwstat(b.fd, d) < 0)
+ warn(sys->sprint("can't set modes for %s: %r", name));
+ if(uflag){
+ (i, d) = sys->fstat(b.fd);
+ if(i < 0)
+ warn(sys->sprint("can't reread modes for %s: %r", name));
+ if(d.mtime != mtime)
+ warn(sys->sprint("%s: time mismatch %ud %ud\n", name, mtime, d.mtime));
+ if(d.uid != uid)
+ warn(sys->sprint("%s: uid mismatch %s %s", name, uid, d.uid));
+ if(d.gid != gid)
+ warn(sys->sprint("%s: gid mismatch %s %s", name, gid, d.gid));
+ }
+ b.close();
+}
+
+seekpast(bytes: big)
+{
+ n: int;
+
+ buf := array [LEN] of byte;
+ for(tot := big 0; tot < bytes; tot += big n){
+ n = len buf;
+ if(tot + big n > bytes)
+ n = int(bytes - tot);
+ n = bin.read(buf, n);
+ if(n <= 0)
+ error("premature eof");
+ }
+}
+
+error(s: string)
+{
+ fprint(stderr, "%s: %s\n", argv0, s);
+ quit("error");
+}
+
+warn(s: string)
+{
+ fprint(stderr, "%s: %s\n", argv0, s);
+}
+
+octal(i: int): string
+{
+ s := "";
+ do {
+ t: string;
+ t[0] = '0' + (i&7);
+ s = t+s;
+ } while((i = (i>>3)&~(7<<29)) != 0);
+ return s;
+}
+
+parent(name : string) : string
+{
+ slash := -1;
+ for (i := 0; i < len name; i++)
+ if (name[i] == '/')
+ slash = i;
+ if (slash > 0)
+ return name[0:slash];
+ return "/";
+}
+
+create(name : string, rw : int, mode : int) : ref Sys->FD
+{
+ fd := sys->create(name, rw, mode);
+ if (fd == nil) {
+ p := parent(name);
+ (ok, d) := sys->stat(p);
+ if (ok < 0)
+ return nil;
+ omode := d.mode;
+ d = sys->nulldir;
+ d.mode = omode | 8r222; # ensure parent is writable
+ if(sys->wstat(p, d) < 0) {
+ warn(sys->sprint("can't set modes for %s: %r", p));
+ return nil;
+ }
+ fd = sys->create(name, rw, mode);
+ d.mode = omode;
+ sys->wstat(p, d);
+ }
+ return fd;
+}
+
+quoted(s: string): string
+{
+ if(qflag)
+ for(i:=0; i<len s; i++)
+ if((c := s[i]) == ' ' || c == '\t' || c == '\n' || c == '\'')
+ return str->quoted(s :: nil);
+ return s;
+}
diff --git a/appl/cmd/disk/mkfile b/appl/cmd/disk/mkfile
new file mode 100644
index 00000000..46b7f067
--- /dev/null
+++ b/appl/cmd/disk/mkfile
@@ -0,0 +1,25 @@
+<../../../mkconfig
+
+DIRS=\
+ prep\
+
+TARG=\
+ kfs.dis\
+ mbr.dis\
+ mkext.dis\
+ mkfs.dis\
+ kfscmd.dis\
+ format.dis\
+ ftl.dis\
+
+SYSMODULES=\
+ arg.m\
+ sys.m\
+ draw.m\
+ bufio.m\
+ string.m\
+
+DISBIN=$ROOT/dis/disk
+
+<$ROOT/mkfiles/mkdis
+<$ROOT/mkfiles/mksubdirs
diff --git a/appl/cmd/disk/mkfs.b b/appl/cmd/disk/mkfs.b
new file mode 100644
index 00000000..8b07aa8f
--- /dev/null
+++ b/appl/cmd/disk/mkfs.b
@@ -0,0 +1,778 @@
+implement Mkfs;
+
+include "sys.m";
+ sys: Sys;
+ Dir, sprint, fprint: import sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+ arg: Arg;
+
+Mkfs: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+LEN: con Sys->ATOMICIO;
+HUNKS: con 128;
+
+Kfs, Fs, Archive: con iota; # types of destination file sytems
+
+File: adt {
+ new: string;
+ elem: string;
+ old: string;
+ uid: string;
+ gid: string;
+ mode: int;
+};
+
+b: ref Iobuf;
+bout: ref Iobuf; # stdout when writing archive
+newfile: string;
+oldfile: string;
+proto: string;
+cputype: string;
+users: string;
+oldroot: string;
+newroot: string;
+prog := "mkfs";
+lineno := 0;
+buf: array of byte;
+zbuf: array of byte;
+buflen := 1024-8;
+indent: int;
+verb: int;
+modes: int;
+ream: int;
+debug: int;
+xflag: int;
+qflag := 1;
+sfd: ref Sys->FD;
+fskind: int; # Kfs, Fs, Archive
+user: string;
+stderr: ref Sys->FD;
+usrid, grpid : string;
+setuid: int;
+
+usage()
+{
+ fprint(stderr, "usage: %s [-apqrvx] [-d root] [-n kfsname] [-s src-fs] [-u userfile] [-z n] proto ...\n", prog);
+ quit("usage");
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ str = load String String->PATH;
+ arg = load Arg Arg->PATH;
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil);
+
+ stderr = sys->fildes(2);
+ if(arg == nil)
+ error(sys->sprint("can't load %s: %r", Arg->PATH));
+
+ user = getuser();
+ if(user == nil)
+ user = "none";
+ name := "";
+ file := ref File;
+ file.new = "";
+ file.old = nil;
+ file.mode = 0;
+ oldroot = "";
+ newroot = "/n/kfs";
+ users = nil;
+ fskind = Kfs; # i suspect Inferno default should be different
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'a' =>
+ fskind = Archive;
+ newroot = "";
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if(bout == nil)
+ error(sys->sprint("can't open standard output for archive: %r"));
+ 'd' =>
+ fskind = Fs;
+ newroot = reqarg("destination directory (-d)");
+ 'D' =>
+ debug = 1;
+ 'n' =>
+ name = reqarg("kfs instance name (-n)");
+ 'p' =>
+ modes = 1;
+ 'q' =>
+ qflag = 0;
+ 'r' =>
+ ream = 1;
+ 's' =>
+ oldroot = reqarg("source directory (-d)");
+ 'u' =>
+ users = reqarg("/adm/users file (-u)");
+ 'v' =>
+ verb = 1;
+ 'x' =>
+ xflag = 1;
+ 'z' =>
+ (buflen, nil) = str->toint(reqarg("buffer length (-z)"), 10);
+ buflen -= 8; # qid.path and tag at end of each kfs block
+ 'U' =>
+ usrid = reqarg("user name (-U)");
+ 'G' =>
+ grpid = reqarg("group name (-G)");
+ 'S' =>
+ setuid = 1;
+ * =>
+ usage();
+ }
+
+ args = arg->argv();
+ if(args == nil)
+ usage();
+
+ buf = array [buflen] of byte;
+ zbuf = array [buflen] of { * => byte 0 };
+
+ mountkfs(name);
+ kfscmd("allow");
+ proto = "users";
+ setusers();
+ cputype = getenv("cputype");
+ if(cputype == nil)
+ cputype = "dis";
+
+ errs := 0;
+ for(; args != nil; args = tl args){
+ proto = hd args;
+ fprint(stderr, "processing %s\n", proto);
+
+ b = bufio->open(proto, Sys->OREAD);
+ if(b == nil){
+ fprint(stderr, "%s: can't open %s: %r: skipping\n", prog, proto);
+ errs++;
+ continue;
+ }
+
+ lineno = 0;
+ indent = 0;
+ mkfs(file, -1);
+ b.close();
+ }
+ fprint(stderr, "file system made\n");
+ kfscmd("disallow");
+ kfscmd("sync");
+ if(errs)
+ quit("skipped protos");
+ if(fskind == Archive){
+ bout.puts("end of archive\n");
+ if(bout.flush() == Bufio->ERROR)
+ error(sys->sprint("write error: %r"));
+ }
+}
+
+quit(why: string)
+{
+ if(bout != nil)
+ bout.flush();
+ if(why != nil)
+ raise "fail:"+why;
+ exit;
+}
+
+reqarg(what: string): string
+{
+ if((o := arg->arg()) == nil){
+ sys->fprint(stderr, "%s: missing %s\n", prog, what);
+ quit("usage");
+ }
+ return o;
+}
+
+mkfs(me: ref File, level: int)
+{
+ (child, fp) := getfile(me);
+ if(child == nil)
+ return;
+ if(child.elem == "+" || child.elem == "*" || child.elem == "%"){
+ rec := child.elem[0] == '+';
+ filesonly := child.elem[0] == '%';
+ child.new = me.new;
+ setnames(child);
+ mktree(child, rec, filesonly);
+ (child, fp) = getfile(me);
+ }
+ while(child != nil && indent > level){
+ if(mkfile(child))
+ mkfs(child, indent);
+ (child, fp) = getfile(me);
+ }
+ if(child != nil){
+ b.seek(fp, 0);
+ lineno--;
+ }
+}
+
+mktree(me: ref File, rec: int, filesonly: int)
+{
+ fd := sys->open(oldfile, Sys->OREAD);
+ if(fd == nil){
+ warn(sys->sprint("can't open %s: %r", oldfile));
+ return;
+ }
+
+ child := ref *me;
+ r := ref Rec(nil, 0);
+ for(;;){
+ (n, d) := sys->dirread(fd);
+ if(n <= 0)
+ break;
+ for(i := 0; i < n; i++)
+ if (!recall(d[i].name, r)) {
+ if(filesonly && d[i].mode & Sys->DMDIR)
+ continue;
+ child.new = mkpath(me.new, d[i].name);
+ if(me.old != nil)
+ child.old = mkpath(me.old, d[i].name);
+ child.elem = d[i].name;
+ setnames(child);
+ if(copyfile(child, ref d[i], 1) && rec)
+ mktree(child, rec, filesonly);
+ }
+ }
+}
+
+# Recall namespace fix
+# -- remove duplicates (could use Readdir->init(,Readdir->COMPACT))
+# obc
+
+Rec: adt
+{
+ ad: array of string;
+ l: int;
+};
+
+AL : con HUNKS;
+recall(e : string, r : ref Rec) : int
+{
+ if (r.ad == nil) r.ad = array[AL] of string;
+ # double array
+ if (r.l >= len r.ad) {
+ nar := array[2*(len r.ad)] of string;
+ nar[0:] = r.ad;
+ r.ad = nar;
+ }
+ for(i := 0; i < r.l; i++)
+ if (r.ad[i] == e) return 1;
+ r.ad[r.l++] = e;
+ return 0;
+}
+
+mkfile(f: ref File): int
+{
+ (i, dir) := sys->stat(oldfile);
+ if(i < 0){
+ warn(sys->sprint("can't stat file %s: %r", oldfile));
+ skipdir();
+ return 0;
+ }
+ return copyfile(f, ref dir, 0);
+}
+
+copyfile(f: ref File, d: ref Dir, permonly: int): int
+{
+ mode: int;
+
+ if(xflag && bout != nil){
+ bout.puts(sys->sprint("%s\t%d\t%bd\n", quoted(f.new), d.mtime, d.length));
+ return (d.mode & Sys->DMDIR) != 0;
+ }
+ d.name = f.elem;
+ if(d.dtype != 'M' && d.dtype != 'U'){ # hmm... Indeed!
+ d.uid = "inferno";
+ d.gid = "inferno";
+ mode = (d.mode >> 6) & 7;
+ d.mode |= mode | (mode << 3);
+ }
+ if(f.uid != "-")
+ d.uid = f.uid;
+ if(f.gid != "-")
+ d.gid = f.gid;
+ if(fskind == Fs && !setuid){ # new system: set to nil
+ d.uid = user;
+ d.gid = user;
+ }
+ if (usrid != nil)
+ d.uid = usrid;
+ if (grpid != nil)
+ d.gid = grpid;
+ if(f.mode != ~0){
+ if(permonly)
+ d.mode = (d.mode & ~8r666) | (f.mode & 8r666);
+ else if((d.mode&Sys->DMDIR) != (f.mode&Sys->DMDIR))
+ warn(sys->sprint("inconsistent mode for %s", f.new));
+ else
+ d.mode = f.mode;
+ }
+ if(!uptodate(d, newfile)){
+ if(d.mode & Sys->DMDIR)
+ mkdir(d);
+ else {
+ if(verb)
+ fprint(stderr, "%s\n", f.new);
+ copy(d);
+ }
+ }else if(modes){
+ nd := sys->nulldir;
+ nd.mode = d.mode;
+ nd.mtime = d.mtime;
+ nd.gid = d.gid;
+ if(sys->wstat(newfile, nd) < 0)
+ warn(sys->sprint("can't set modes for %s: %r", f.new));
+ # do the uid separately since different file systems object
+ nd = sys->nulldir;
+ nd.uid = d.uid;
+ sys->wstat(newfile, nd);
+ }
+ return (d.mode & Sys->DMDIR) != 0;
+}
+
+
+# check if file to is up to date with
+# respect to the file represented by df
+
+uptodate(df: ref Dir, newf: string): int
+{
+ if(fskind == Archive || ream)
+ return 0;
+ (i, dt) := sys->stat(newf);
+ if(i < 0)
+ return 0;
+ return dt.mtime >= df.mtime;
+}
+
+copy(d: ref Dir)
+{
+ t: ref Sys->FD;
+ n: int;
+
+ f := sys->open(oldfile, Sys->OREAD);
+ if(f == nil){
+ warn(sys->sprint("can't open %s: %r", oldfile));
+ return;
+ }
+ t = nil;
+ if(fskind == Archive)
+ arch(d);
+ else{
+ (dname, fname) := str->splitr(newfile, "/");
+ if(fname == nil)
+ error(sys->sprint("internal temporary file error (%s)", dname));
+ cptmp := dname+"__mkfstmp";
+ t = sys->create(cptmp, Sys->OWRITE, 8r666);
+ if(t == nil){
+ warn(sys->sprint("can't create %s: %r", newfile));
+ return;
+ }
+ }
+
+ for(tot := big 0;; tot += big n){
+ n = sys->read(f, buf, buflen);
+ if(n < 0){
+ warn(sys->sprint("can't read %s: %r", oldfile));
+ break;
+ }
+ if(n == 0)
+ break;
+ if(fskind == Archive){
+ if(bout.write(buf, n) != n)
+ error(sys->sprint("write error: %r"));
+ }else if(buf[0:buflen] == zbuf[0:buflen]){
+ if(sys->seek(t, big buflen, 1) < big 0)
+ error(sys->sprint("can't write zeros to %s: %r", newfile));
+ }else if(sys->write(t, buf, n) < n)
+ error(sys->sprint("can't write %s: %r", newfile));
+ }
+ f = nil;
+ if(tot != d.length){
+ warn(sys->sprint("wrong number bytes written to %s (was %bd should be %bd)",
+ newfile, tot, d.length));
+ if(fskind == Archive){
+ warn("seeking to proper position");
+ bout.seek(d.length - tot, 1);
+ }
+ }
+ if(fskind == Archive)
+ return;
+ sys->remove(newfile);
+ nd := sys->nulldir;
+ nd.name = d.name;
+ nd.mode = d.mode;
+ nd.mtime = d.mtime;
+ if(sys->fwstat(t, nd) < 0)
+ error(sys->sprint("can't move tmp file to %s: %r", newfile));
+ nd = sys->nulldir;
+ nd.gid = d.gid;
+ if(sys->fwstat(t, nd) < 0)
+ warn(sys->sprint("can't set group id of %s to %s: %r", newfile, d.gid));
+ nd.gid = nil;
+ nd.uid = d.uid;
+ sys->fwstat(t, nd);
+}
+
+mkdir(d: ref Dir)
+{
+ if(fskind == Archive){
+ arch(d);
+ return;
+ }
+ fd := sys->create(newfile, Sys->OREAD, d.mode);
+ nd := sys->nulldir;
+ nd.mode = d.mode;
+ nd.gid = d.gid;
+ nd.mtime = d.mtime;
+ if(fd == nil){
+ (i, d1) := sys->stat(newfile);
+ if(i < 0 || !(d1.mode & Sys->DMDIR))
+ error(sys->sprint("can't create %s", newfile));
+ if(sys->wstat(newfile, nd) < 0)
+ warn(sys->sprint("can't set modes for %s: %r", newfile));
+ nd = sys->nulldir;
+ nd.uid = d.uid;
+ sys->wstat(newfile, nd);
+ return;
+ }
+ if(sys->fwstat(fd, nd) < 0)
+ warn(sys->sprint("can't set modes for %s: %r", newfile));
+ nd = sys->nulldir;
+ nd.uid = d.uid;
+ sys->fwstat(fd, nd);
+}
+
+arch(d: ref Dir)
+{
+ bout.puts(sys->sprint("%s %s %s %s %ud %bd\n",
+ quoted(newfile), octal(d.mode), d.uid, d.gid, d.mtime, d.length));
+}
+
+mkpath(prefix, elem: string): string
+{
+ return sys->sprint("%s/%s", prefix, elem);
+}
+
+setnames(f: ref File)
+{
+ newfile = newroot+f.new;
+ if(f.old != nil){
+ if(f.old[0] == '/')
+ oldfile = oldroot+f.old;
+ else
+ oldfile = f.old;
+ }else
+ oldfile = oldroot+f.new;
+}
+
+#
+# skip all files in the proto that
+# could be in the current dir
+#
+skipdir()
+{
+ if(indent < 0)
+ return;
+ level := indent;
+ for(;;){
+ indent = 0;
+ fp := b.offset();
+ p := b.gets('\n');
+ lineno++;
+ if(p == nil){
+ indent = -1;
+ return;
+ }
+ for(j := 0; (c := p[j++]) != '\n';)
+ if(c == ' ')
+ indent++;
+ else if(c == '\t')
+ indent += 8;
+ else
+ break;
+ if(indent <= level){
+ b.seek(fp, 0);
+ lineno--;
+ return;
+ }
+ }
+}
+
+getfile(old: ref File): (ref File, big)
+{
+ f: ref File;
+ p, elem: string;
+ c: int;
+
+ if(indent < 0)
+ return (nil, big 0);
+ fp := b.offset();
+ do {
+ indent = 0;
+ p = b.gets('\n');
+ lineno++;
+ if(p == nil){
+ indent = -1;
+ return (nil, big 0);
+ }
+ for(; (c = p[0]) != '\n'; p = p[1:])
+ if(c == ' ')
+ indent++;
+ else if(c == '\t')
+ indent += 8;
+ else
+ break;
+ } while(c == '\n' || c == '#');
+ f = ref File;
+ (elem, p) = getname(p);
+ if(debug)
+ fprint(stderr, "getfile: %s root %s\n", elem, old.new);
+ f.new = mkpath(old.new, elem);
+ (nil, f.elem) = str->splitr(f.new, "/");
+ if(f.elem == nil)
+ error(sys->sprint("can't find file name component of %s", f.new));
+ (f.mode, p) = getmode(p);
+ (f.uid, p) = getname(p);
+ if(f.uid == nil)
+ f.uid = "-";
+ (f.gid, p) = getname(p);
+ if(f.gid == nil)
+ f.gid = "-";
+ f.old = getpath(p);
+ if(f.old == "-")
+ f.old = nil;
+ setnames(f);
+
+ if(debug)
+ printfile(f);
+
+ return (f, fp);
+}
+
+getpath(p: string): string
+{
+ for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:])
+ ;
+ for(n := 0; (c = p[n]) != '\n' && c != ' ' && c != '\t'; n++)
+ ;
+ return p[0:n];
+}
+
+getname(p: string): (string, string)
+{
+ for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:])
+ ;
+ i := 0;
+ s := "";
+ quoted := 0;
+ for(; (c = p[0]) != '\n' && (c != ' ' && c != '\t' || quoted); p = p[1:]){
+ if(quoted && c == '\'' && p[1] == '\'')
+ p = p[1:];
+ else if(c == '\'' && qflag){
+ quoted = !quoted;
+ continue;
+ }
+ s[i++] = c;
+ }
+ if(len s > 0 && s[0] == '$'){
+ s = getenv(s[1:]);
+ if(s == nil)
+ error(sys->sprint("can't read environment variable %s", s));
+ }
+ return (s, p);
+}
+
+getenv(s: string): string
+{
+ if(s == "user")
+ return getuser();
+ return readfile("/env/"+s);
+}
+
+getuser(): string
+{
+ return readfile("/dev/user");
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if(fd != nil){
+ a := array[256] of byte;
+ n := sys->read(fd, a, len a);
+ if(n > 0)
+ return string a[0:n];
+ }
+ return nil;
+}
+
+getmode(p: string): (int, string)
+{
+ s: string;
+
+ (s, p) = getname(p);
+ if(s == nil || s == "-")
+ return (~0, p);
+ os := s;
+ m := 0;
+ if(s[0] == 'd'){
+ m |= Sys->DMDIR;
+ s = s[1:];
+ }
+ if(s[0] == 'a'){
+ m |= Sys->DMAPPEND;
+ s = s[1:];
+ }
+ if(s[0] == 'l'){
+ m |= Sys->DMEXCL;
+ s = s[1:];
+ }
+
+ for(i:=0; i<len s || i < 3; i++)
+ if(i >= len s || !(s[i]>='0' && s[i]<='7')){
+ warn(sys->sprint("bad mode specification %s", os));
+ return (~0, p);
+ }
+ (v, nil) := str->toint(s, 8);
+ return (m|v, p);
+}
+
+quoted(s: string): string
+{
+ if(qflag)
+ return sys->sprint("%q", s);
+ return s;
+}
+
+setusers()
+{
+ if(fskind != Kfs)
+ return;
+ file := ref File;
+ m := modes;
+ modes = 1;
+ file.uid = "adm";
+ file.gid = "adm";
+ file.mode = Sys->DMDIR|8r775;
+ file.new = "/adm";
+ file.elem = "adm";
+ file.old = nil;
+ setnames(file);
+ mkfile(file);
+ file.new = "/adm/users";
+ file.old = users;
+ file.elem = "users";
+ file.mode = 8r664;
+ setnames(file);
+ mkfile(file);
+ kfscmd("user");
+ mkfile(file);
+ file.mode = Sys->DMDIR|8r775;
+ file.new = "/adm";
+ file.old = "/adm";
+ file.elem = "adm";
+ setnames(file);
+ mkfile(file);
+ modes = m;
+}
+
+# this isn't right for the current #K
+mountkfs(name: string)
+{
+ kname: string;
+
+ if(fskind != Kfs)
+ return;
+ if(name != nil)
+ kname = sys->sprint("/srv/kfs.%s", name);
+ else
+ kname = "/srv/kfs";
+ fd := sys->open(kname, Sys->ORDWR);
+ if(fd == nil){
+ fprint(stderr, "%s: can't open %s: %r\n", prog, kname);
+ quit("open kfs");
+ }
+ if(sys->mount(fd, nil, "/n/kfs", Sys->MREPL|Sys->MCREATE, "") < 0){
+ fprint(stderr, "%s: can't mount kfs on /n/kfs: %r\n", prog);
+ quit("mount kfs");
+ }
+ kname += ".cmd";
+ sfd = sys->open(kname, Sys->ORDWR);
+ if(sfd == nil){
+ fprint(stderr, "%s: can't open %s: %r\n", prog, kname);
+ quit("open kfscmd");
+ }
+}
+
+kfscmd(cmd: string)
+{
+ if(fskind != Kfs || sfd == nil)
+ return;
+ a := array of byte cmd;
+ if(sys->write(sfd, a, len a) != len a){
+ fprint(stderr, "%s: error writing %s: %r", prog, cmd);
+ return;
+ }
+ for(;;){
+ reply := array[4*1024] of byte;
+ n := sys->read(sfd, reply, len reply);
+ if(n <= 0)
+ return;
+ s := string reply[0:n];
+ if(s == "done" || s == "success")
+ return;
+ if(s == "unknown command"){
+ fprint(stderr, "%s: command %s not recognized\n", prog, cmd);
+ return;
+ }
+ }
+}
+
+error(s: string)
+{
+ fprint(stderr, "%s: %s: %d: %s\n", prog, proto, lineno, s);
+ kfscmd("disallow");
+ kfscmd("sync");
+ quit("error");
+}
+
+warn(s: string)
+{
+ fprint(stderr, "%s: %s: %d: %s\n", prog, proto, lineno, s);
+}
+
+printfile(f: ref File)
+{
+ if(f.old != nil)
+ fprint(stderr, "%s from %s %s %s %s\n", f.new, f.old, f.uid, f.gid, octal(f.mode));
+ else
+ fprint(stderr, "%s %s %s %s\n", f.new, f.uid, f.gid, octal(f.mode));
+}
+
+octal(i: int): string
+{
+ s := "";
+ do {
+ t: string;
+ t[0] = '0' + (i&7);
+ s = t+s;
+ } while((i = (i>>3)&~(7<<29)) != 0);
+ return s;
+}
diff --git a/appl/cmd/disk/prep/calc.tab.b b/appl/cmd/disk/prep/calc.tab.b
new file mode 100644
index 00000000..25f81487
--- /dev/null
+++ b/appl/cmd/disk/prep/calc.tab.b
@@ -0,0 +1,454 @@
+implement Calc;
+
+#line 2 "calc.y"
+#
+# from Plan 9. subject to the Lucent Public License 1.02
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+ NUM,
+ DOT,
+ DOLLAR,
+ ADD,
+ SUB,
+ MUL,
+ DIV,
+ FRAC,
+ NEG: con iota;
+
+Exp: adt {
+ ty: int;
+ n: big;
+ e1, e2: cyclic ref Exp;
+};
+
+YYSTYPE: adt {
+ e: ref Exp;
+};
+yyexp: ref Exp;
+
+YYLEX: adt {
+ s: string;
+ n: int;
+ lval: YYSTYPE;
+ lex: fn(l: self ref YYLEX): int;
+ error: fn(l: self ref YYLEX, msg: string);
+};
+Calc: module {
+
+ parseexpr: fn(s: string, a, b, c: big): (big, string);
+ init: fn(nil: ref Draw->Context, nil: list of string);
+NUMBER: con 57346;
+UNARYMINUS: con 57347;
+
+};
+YYEOFCODE: con 1;
+YYERRCODE: con 2;
+YYMAXDEPTH: con 200;
+
+#line 68 "calc.y"
+
+
+mkNUM(x: big): ref Exp
+{
+ return ref Exp(NUM, x, nil, nil);
+}
+
+mkOP(ty: int, e1: ref Exp, e2: ref Exp): ref Exp
+{
+ return ref Exp(ty, big 0, e1, e2);
+}
+
+dot, size, dollar: big;
+
+YYLEX.lex(l: self ref YYLEX): int
+{
+ while(l.n < len l.s && isspace(l.s[l.n]))
+ l.n++;
+
+ if(l.n == len l.s)
+ return -1;
+
+ if(isdigit(l.s[l.n])){
+ for(o := l.n; o < len l.s && isdigit(l.s[o]); o++)
+ ;
+ l.lval.e = mkNUM(big l.s[l.n:o]);
+ l.n = o;
+ return NUMBER;
+ }
+
+ return l.s[l.n++];
+}
+
+isdigit(c: int): int
+{
+ return c >= '0' && c <= '9';
+}
+
+isspace(c: int): int
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f';
+}
+
+YYLEX.error(nil: self ref YYLEX, s: string)
+{
+ raise s;
+}
+
+eval(e: ref Exp): big
+{
+ case e.ty {
+ NUM =>
+ return e.n;
+ DOT =>
+ return dot;
+ DOLLAR =>
+ return dollar;
+ ADD =>
+ return eval(e.e1)+eval(e.e2);
+ SUB =>
+ return eval(e.e1)-eval(e.e2);
+ MUL =>
+ return eval(e.e1)*eval(e.e2);
+ DIV =>
+ i := eval(e.e2);
+ if(i == big 0)
+ raise "division by zero";
+ return eval(e.e1)/i;
+ FRAC =>
+ return (size*eval(e.e1))/big 100;
+ NEG =>
+ return -eval(e.e1);
+ * =>
+ raise "invalid operator";
+ }
+}
+
+parseexpr(s: string, xdot: big, xdollar: big, xsize: big): (big, string)
+{
+ dot = xdot;
+ size = xsize;
+ dollar = xdollar;
+ l := ref YYLEX(s, 0, YYSTYPE(nil));
+ {
+ yyparse(l);
+ if(yyexp == nil)
+ return (big 0, "nil yylval?");
+ return (eval(yyexp), nil);
+ }exception e{
+ "*" =>
+ return (big 0, e);
+ }
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ while((args = tl args) != nil){
+ (r, e) := parseexpr(hd args, big 1000, big 1000000, big 1000000);
+ if(e != nil)
+ sys->print("%s\n", e);
+ else
+ sys->print("%bd\n", r);
+ }
+}
+
+yyexca := array[] of {-1, 1,
+ 1, -1,
+ -2, 0,
+};
+YYNPROD: con 12;
+YYPRIVATE: con 57344;
+yytoknames: array of string;
+yystates: array of string;
+yydebug: con 0;
+YYLAST: con 30;
+yyact := array[] of {
+ 8, 9, 10, 11, 3, 12, 7, 2, 12, 19,
+ 1, 4, 5, 6, 13, 14, 15, 16, 17, 18,
+ 8, 9, 10, 11, 0, 12, 10, 11, 0, 12,
+};
+yypact := array[] of {
+ 0,-1000, 15,-1000,-1000,-1000, 0, 0, 0, 0,
+ 0, 0,-1000, -5,-1000, 19, 19, -2, -2,-1000,
+};
+yypgo := array[] of {
+ 0, 7, 10,
+};
+yyr1 := array[] of {
+ 0, 2, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1,
+};
+yyr2 := array[] of {
+ 0, 1, 1, 1, 1, 3, 3, 3, 3, 3,
+ 2, 2,
+};
+yychk := array[] of {
+-1000, -2, -1, 4, 11, 12, 13, 6, 5, 6,
+ 7, 8, 10, -1, -1, -1, -1, -1, -1, 14,
+};
+yydef := array[] of {
+ 0, -2, 1, 2, 3, 4, 0, 0, 0, 0,
+ 0, 0, 10, 0, 11, 6, 7, 8, 9, 5,
+};
+yytok1 := array[] of {
+ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 12, 10, 3, 3,
+ 13, 14, 7, 5, 3, 6, 11, 8,
+};
+yytok2 := array[] of {
+ 2, 3, 4, 9,
+};
+yytok3 := array[] of {
+ 0
+};
+
+YYSys: module
+{
+ FD: adt
+ {
+ fd: int;
+ };
+ fildes: fn(fd: int): ref FD;
+ fprint: fn(fd: ref FD, s: string, *): int;
+};
+
+yysys: YYSys;
+yystderr: ref YYSys->FD;
+
+YYFLAG: con -1000;
+
+# parser for yacc output
+
+yytokname(yyc: int): string
+{
+ if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil)
+ return yytoknames[yyc-1];
+ return "<"+string yyc+">";
+}
+
+yystatname(yys: int): string
+{
+ if(yys >= 0 && yys < len yystates && yystates[yys] != nil)
+ return yystates[yys];
+ return "<"+string yys+">\n";
+}
+
+yylex1(yylex: ref YYLEX): int
+{
+ c : int;
+ yychar := yylex.lex();
+ if(yychar <= 0)
+ c = yytok1[0];
+ else if(yychar < len yytok1)
+ c = yytok1[yychar];
+ else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2)
+ c = yytok2[yychar-YYPRIVATE];
+ else{
+ n := len yytok3;
+ c = 0;
+ for(i := 0; i < n; i+=2) {
+ if(yytok3[i+0] == yychar) {
+ c = yytok3[i+1];
+ break;
+ }
+ }
+ if(c == 0)
+ c = yytok2[1]; # unknown char
+ }
+ if(yydebug >= 3)
+ yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c));
+ return c;
+}
+
+YYS: adt
+{
+ yyv: YYSTYPE;
+ yys: int;
+};
+
+yyparse(yylex: ref YYLEX): int
+{
+ if(yydebug >= 1 && yysys == nil) {
+ yysys = load YYSys "$Sys";
+ yystderr = yysys->fildes(2);
+ }
+
+ yys := array[YYMAXDEPTH] of YYS;
+
+ yyval: YYSTYPE;
+ yystate := 0;
+ yychar := -1;
+ yynerrs := 0; # number of errors
+ yyerrflag := 0; # error recovery flag
+ yyp := -1;
+ yyn := 0;
+
+yystack:
+ for(;;){
+ # put a state and value onto the stack
+ if(yydebug >= 4)
+ yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+ yyp++;
+ if(yyp >= len yys)
+ yys = (array[len yys * 2] of YYS)[0:] = yys;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = yyval;
+
+ for(;;){
+ yyn = yypact[yystate];
+ if(yyn > YYFLAG) { # simple state
+ if(yychar < 0)
+ yychar = yylex1(yylex);
+ yyn += yychar;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yyn = yyact[yyn];
+ if(yychk[yyn] == yychar) { # valid shift
+ yychar = -1;
+ yyp++;
+ if(yyp >= len yys)
+ yys = (array[len yys * 2] of YYS)[0:] = yys;
+ yystate = yyn;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = yylex.lval;
+ if(yyerrflag > 0)
+ yyerrflag--;
+ if(yydebug >= 4)
+ yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+ continue;
+ }
+ }
+ }
+
+ # default state action
+ yyn = yydef[yystate];
+ if(yyn == -2) {
+ if(yychar < 0)
+ yychar = yylex1(yylex);
+
+ # look through exception table
+ for(yyxi:=0;; yyxi+=2)
+ if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate)
+ break;
+ for(yyxi += 2;; yyxi += 2) {
+ yyn = yyexca[yyxi];
+ if(yyn < 0 || yyn == yychar)
+ break;
+ }
+ yyn = yyexca[yyxi+1];
+ if(yyn < 0){
+ yyn = 0;
+ break yystack;
+ }
+ }
+
+ if(yyn != 0)
+ break;
+
+ # error ... attempt to resume parsing
+ if(yyerrflag == 0) { # brand new error
+ yylex.error("syntax error");
+ yynerrs++;
+ if(yydebug >= 1) {
+ yysys->fprint(yystderr, "%s", yystatname(yystate));
+ yysys->fprint(yystderr, "saw %s\n", yytokname(yychar));
+ }
+ }
+
+ if(yyerrflag != 3) { # incompletely recovered error ... try again
+ yyerrflag = 3;
+
+ # find a state where "error" is a legal shift action
+ while(yyp >= 0) {
+ yyn = yypact[yys[yyp].yys] + YYERRCODE;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yystate = yyact[yyn]; # simulate a shift of "error"
+ if(yychk[yystate] == YYERRCODE)
+ continue yystack;
+ }
+
+ # the current yyp has no shift onn "error", pop stack
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n",
+ yys[yyp].yys, yys[yyp-1].yys );
+ yyp--;
+ }
+ # there is no state on the stack with an error shift ... abort
+ yyn = 1;
+ break yystack;
+ }
+
+ # no shift yet; clobber input char
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar));
+ if(yychar == YYEOFCODE) {
+ yyn = 1;
+ break yystack;
+ }
+ yychar = -1;
+ # try again in the same state
+ }
+
+ # reduction by production yyn
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+ yypt := yyp;
+ yyp -= yyr2[yyn];
+# yyval = yys[yyp+1].yyv;
+ yym := yyn;
+
+ # consult goto table to find next state
+ yyn = yyr1[yyn];
+ yyg := yypgo[yyn];
+ yyj := yyg + yys[yyp].yys + 1;
+
+ if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+ yystate = yyact[yyg];
+ case yym {
+
+1=>
+#line 54 "calc.y"
+{ yyexp = yys[yypt-0].yyv.e; return 0; }
+2=>
+yyval.e = yys[yyp+1].yyv.e;
+3=>
+#line 57 "calc.y"
+{ yyval.e = mkOP(DOT, nil, nil); }
+4=>
+#line 58 "calc.y"
+{ yyval.e = mkOP(DOLLAR, nil, nil); }
+5=>
+#line 59 "calc.y"
+{ yyval.e = yys[yypt-1].yyv.e; }
+6=>
+#line 60 "calc.y"
+{ yyval.e = mkOP(ADD, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); }
+7=>
+#line 61 "calc.y"
+{ yyval.e = mkOP(SUB, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); }
+8=>
+#line 62 "calc.y"
+{ yyval.e = mkOP(MUL, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); }
+9=>
+#line 63 "calc.y"
+{ yyval.e = mkOP(DIV, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); }
+10=>
+#line 64 "calc.y"
+{ yyval.e = mkOP(FRAC, yys[yypt-1].yyv.e, nil); }
+11=>
+#line 65 "calc.y"
+{ yyval.e = mkOP(NEG, yys[yypt-0].yyv.e, nil); }
+ }
+ }
+
+ return yyn;
+}
diff --git a/appl/cmd/disk/prep/calc.tab.m b/appl/cmd/disk/prep/calc.tab.m
new file mode 100644
index 00000000..fa531c74
--- /dev/null
+++ b/appl/cmd/disk/prep/calc.tab.m
@@ -0,0 +1,7 @@
+Calc: module {
+
+ parseexpr: fn(s: string, a, b, c: big): (big, string);
+ init: fn(nil: ref Draw->Context, nil: list of string);
+NUMBER: con 57346;
+UNARYMINUS: con 57347;
+};
diff --git a/appl/cmd/disk/prep/calc.y b/appl/cmd/disk/prep/calc.y
new file mode 100644
index 00000000..7ce56049
--- /dev/null
+++ b/appl/cmd/disk/prep/calc.y
@@ -0,0 +1,174 @@
+%{
+#
+# from Plan 9. subject to the Lucent Public License 1.02
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+ NUM,
+ DOT,
+ DOLLAR,
+ ADD,
+ SUB,
+ MUL,
+ DIV,
+ FRAC,
+ NEG: con iota;
+
+Exp: adt {
+ ty: int;
+ n: big;
+ e1, e2: cyclic ref Exp;
+};
+
+YYSTYPE: adt {
+ e: ref Exp;
+};
+yyexp: ref Exp;
+
+YYLEX: adt {
+ s: string;
+ n: int;
+ lval: YYSTYPE;
+ lex: fn(l: self ref YYLEX): int;
+ error: fn(l: self ref YYLEX, msg: string);
+};
+%}
+%module Calc
+{
+ parseexpr: fn(s: string, a, b, c: big): (big, string);
+ init: fn(nil: ref Draw->Context, nil: list of string);
+}
+
+%token <e> NUMBER
+
+%type <e> expr
+
+%left '+' '-'
+%left '*' '/'
+%left UNARYMINUS '%'
+%%
+top: expr { yyexp = $1; return 0; }
+
+expr: NUMBER
+ | '.' { $$ = mkOP(DOT, nil, nil); }
+ | '$' { $$ = mkOP(DOLLAR, nil, nil); }
+ | '(' expr ')' { $$ = $2; }
+ | expr '+' expr { $$ = mkOP(ADD, $1, $3); }
+ | expr '-' expr { $$ = mkOP(SUB, $1, $3); }
+ | expr '*' expr { $$ = mkOP(MUL, $1, $3); }
+ | expr '/' expr { $$ = mkOP(DIV, $1, $3); }
+ | expr '%' { $$ = mkOP(FRAC, $1, nil); }
+ | '-' expr %prec UNARYMINUS { $$ = mkOP(NEG, $2, nil); }
+ ;
+
+%%
+
+mkNUM(x: big): ref Exp
+{
+ return ref Exp(NUM, x, nil, nil);
+}
+
+mkOP(ty: int, e1: ref Exp, e2: ref Exp): ref Exp
+{
+ return ref Exp(ty, big 0, e1, e2);
+}
+
+dot, size, dollar: big;
+
+YYLEX.lex(l: self ref YYLEX): int
+{
+ while(l.n < len l.s && isspace(l.s[l.n]))
+ l.n++;
+
+ if(l.n == len l.s)
+ return -1;
+
+ if(isdigit(l.s[l.n])){
+ for(o := l.n; o < len l.s && isdigit(l.s[o]); o++)
+ ;
+ l.lval.e = mkNUM(big l.s[l.n:o]);
+ l.n = o;
+ return NUMBER;
+ }
+
+ return l.s[l.n++];
+}
+
+isdigit(c: int): int
+{
+ return c >= '0' && c <= '9';
+}
+
+isspace(c: int): int
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f';
+}
+
+YYLEX.error(nil: self ref YYLEX, s: string)
+{
+ raise s;
+}
+
+eval(e: ref Exp): big
+{
+ case e.ty {
+ NUM =>
+ return e.n;
+ DOT =>
+ return dot;
+ DOLLAR =>
+ return dollar;
+ ADD =>
+ return eval(e.e1)+eval(e.e2);
+ SUB =>
+ return eval(e.e1)-eval(e.e2);
+ MUL =>
+ return eval(e.e1)*eval(e.e2);
+ DIV =>
+ i := eval(e.e2);
+ if(i == big 0)
+ raise "division by zero";
+ return eval(e.e1)/i;
+ FRAC =>
+ return (size*eval(e.e1))/big 100;
+ NEG =>
+ return -eval(e.e1);
+ * =>
+ raise "invalid operator";
+ }
+}
+
+parseexpr(s: string, xdot: big, xdollar: big, xsize: big): (big, string)
+{
+ dot = xdot;
+ size = xsize;
+ dollar = xdollar;
+ l := ref YYLEX(s, 0, YYSTYPE(nil));
+ {
+ yyparse(l);
+ if(yyexp == nil)
+ return (big 0, "nil yylval?");
+ return (eval(yyexp), nil);
+ }exception e{
+ "*" =>
+ return (big 0, e);
+ }
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ while((args = tl args) != nil){
+ (r, e) := parseexpr(hd args, big 1000, big 1000000, big 1000000);
+ if(e != nil)
+ sys->print("%s\n", e);
+ else
+ sys->print("%bd\n", r);
+ }
+}
+
diff --git a/appl/cmd/disk/prep/fdisk.b b/appl/cmd/disk/prep/fdisk.b
new file mode 100644
index 00000000..00ecbb36
--- /dev/null
+++ b/appl/cmd/disk/prep/fdisk.b
@@ -0,0 +1,925 @@
+implement Fdisk;
+
+#
+# fdisk - edit dos disk partition table
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "disks.m";
+ disks: Disks;
+ Disk, PCpart: import disks;
+ NTentry, Toffset, TentrySize: import Disks;
+ Magic0, Magic1: import Disks;
+ readn: import disks;
+
+include "pedit.m";
+ pedit: Pedit;
+ Edit, Part: import pedit;
+
+include "arg.m";
+
+Fdisk: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Mpart: con 64;
+
+blank := 0;
+dowrite := 0;
+file := 0;
+rdonly := 0;
+doauto := 0;
+mbroffset := big 0;
+printflag := 0;
+printchs := 0;
+sec2cyl := big 0;
+written := 0;
+
+edit: ref Edit;
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ disks = load Disks Disks->PATH;
+ pedit = load Pedit Pedit->PATH;
+
+ sys->pctl(Sys->FORKFD, nil);
+ disks->init();
+ pedit->init();
+
+ edit = Edit.mk("cylinder");
+
+ edit.add = cmdadd;
+ edit.del = cmddel;
+ edit.okname = cmdokname;
+ edit.ext = cmdext;
+ edit.help = cmdhelp;
+ edit.sum = cmdsum;
+ edit.write = cmdwrite;
+ edit.printctl = cmdprintctl;
+
+ stderr = sys->fildes(2);
+
+ secsize := 0;
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("disk/fdisk [-abfprvw] [-s sectorsize] /dev/sdC0/data");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' =>
+ doauto++;
+ 'b' =>
+ blank++;
+ 'f' =>
+ file++;
+ 'p' =>
+ printflag++;
+ 'r' =>
+ rdonly++;
+ 's' =>
+ secsize = int arg->earg();
+ 'v' =>
+ printchs++;
+ 'w' =>
+ dowrite++;
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(len args != 1)
+ arg->usage();
+ arg = nil;
+
+ mode := Sys->ORDWR;
+ if(rdonly)
+ mode = Sys->OREAD;
+ edit.disk = Disk.open(hd args, mode, file);
+ if(edit.disk == nil) {
+ sys->fprint(stderr, "cannot open disk: %r\n");
+ exits("opendisk");
+ }
+
+ if(secsize != 0) {
+ edit.disk.secsize = secsize;
+ edit.disk.secs = edit.disk.size / big secsize;
+ }
+
+ sec2cyl = big (edit.disk.h * edit.disk.s);
+ edit.end = edit.disk.secs / sec2cyl;
+
+ findmbr(edit);
+
+ if(blank)
+ blankpart(edit);
+ else
+ rdpart(edit, big 0, big 0);
+
+ if(doauto)
+ autopart(edit);
+
+ {
+ if(dowrite)
+ edit.runcmd("w");
+
+ if(printflag)
+ edit.runcmd("P");
+
+ if(dowrite || printflag)
+ exits(nil);
+
+ sys->fprint(stderr, "cylinder = %bd bytes\n", sec2cyl*big edit.disk.secsize);
+ edit.runcmd("p");
+ for(;;) {
+ sys->fprint(stderr, ">>> ");
+ edit.runcmd(edit.getline());
+ }
+ }exception e{
+ "*" =>
+ sys->fprint(stderr, "fdisk: exception %q\n", e);
+ if(written)
+ recover(edit);
+ }
+}
+
+Active: con 16r80; # partition is active
+Primary: con 16r01; # internal flag
+
+TypeBB: con 16rFF;
+
+TypeEMPTY: con 16r00;
+TypeFAT12: con 16r01;
+TypeXENIX: con 16r02; # root
+TypeXENIXUSR: con 16r03; # usr
+TypeFAT16: con 16r04;
+TypeEXTENDED: con 16r05;
+TypeFATHUGE: con 16r06;
+TypeHPFS: con 16r07;
+TypeAIXBOOT: con 16r08;
+TypeAIXDATA: con 16r09;
+TypeOS2BOOT: con 16r0A; # OS/2 Boot Manager
+TypeFAT32: con 16r0B; # FAT 32
+TypeFAT32LBA: con 16r0C; # FAT 32 needing LBA support
+TypeEXTHUGE: con 16r0F; # FAT 32 extended partition
+TypeUNFORMATTED: con 16r16; # unformatted primary partition (OS/2 FDISK)?
+TypeHPFS2: con 16r17;
+TypeIBMRecovery: con 16r1C; # really hidden fat
+TypeCPM0: con 16r52;
+TypeDMDDO: con 16r54; # Disk Manager Dynamic Disk Overlay
+TypeGB: con 16r56; # ????
+TypeSPEEDSTOR: con 16r61;
+TypeSYSV386: con 16r63; # also HURD?
+TypeNETWARE: con 16r64;
+TypePCIX: con 16r75;
+TypeMINIX13: con 16r80; # Minix v1.3 and below
+TypeMINIX: con 16r81; # Minix v1.5+
+TypeLINUXSWAP: con 16r82;
+TypeLINUX: con 16r83;
+TypeLINUXEXT: con 16r85;
+TypeAMOEBA: con 16r93;
+TypeAMOEBABB: con 16r94;
+TypeBSD386: con 16rA5;
+TypeBSDI: con 16rB7;
+TypeBSDISWAP: con 16rB8;
+TypeOTHER: con 16rDA;
+TypeCPM: con 16rDB;
+TypeDellRecovery: con 16rDE;
+TypeSPEEDSTOR12: con 16rE1;
+TypeSPEEDSTOR16: con 16rE4;
+TypeLANSTEP: con 16rFE;
+
+Type9: con Disks->Type9;
+
+TableSize: con TentrySize*NTentry;
+Omagic: con TableSize;
+
+Type: adt {
+ desc: string;
+ name: string;
+};
+
+Dospart: adt {
+ p: ref Part;
+ pc: ref PCpart;
+ primary: int;
+ lba: big; # absolute address
+ size: big;
+};
+
+Recover: adt {
+ table: array of byte; # [TableSize+2] copy of table and magic
+ lba: big; # where it came from
+};
+
+types: array of Type = array[256] of {
+ TypeEMPTY => ( "EMPTY", "" ),
+ TypeFAT12 => ( "FAT12", "dos" ),
+ TypeFAT16 => ( "FAT16", "dos" ),
+ TypeFAT32 => ( "FAT32", "dos" ),
+ TypeFAT32LBA => ( "FAT32LBA", "dos" ),
+ TypeEXTHUGE => ( "EXTHUGE", "" ),
+ TypeIBMRecovery => ( "IBMRECOVERY", "ibm" ),
+ TypeEXTENDED => ( "EXTENDED", "" ),
+ TypeFATHUGE => ( "FATHUGE", "dos" ),
+ TypeBB => ( "BB", "bb" ),
+
+ TypeXENIX => ( "XENIX", "xenix" ),
+ TypeXENIXUSR => ( "XENIX USR", "xenixusr" ),
+ TypeHPFS => ( "HPFS", "ntfs" ),
+ TypeAIXBOOT => ( "AIXBOOT", "aixboot" ),
+ TypeAIXDATA => ( "AIXDATA", "aixdata" ),
+ TypeOS2BOOT => ( "OS/2BOOT", "os2boot" ),
+ TypeUNFORMATTED => ( "UNFORMATTED", "" ),
+ TypeHPFS2 => ( "HPFS2", "hpfs2" ),
+ TypeCPM0 => ( "CPM0", "cpm0" ),
+ TypeDMDDO => ( "DMDDO", "dmdd0" ),
+ TypeGB => ( "GB", "gb" ),
+ TypeSPEEDSTOR => ( "SPEEDSTOR", "speedstor" ),
+ TypeSYSV386 => ( "SYSV386", "sysv386" ),
+ TypeNETWARE => ( "NETWARE", "netware" ),
+ TypePCIX => ( "PCIX", "pcix" ),
+ TypeMINIX13 => ( "MINIXV1.3", "minix13" ),
+ TypeMINIX => ( "MINIXV1.5", "minix15" ),
+ TypeLINUXSWAP => ( "LINUXSWAP", "linuxswap" ),
+ TypeLINUX => ( "LINUX", "linux" ),
+ TypeLINUXEXT => ( "LINUXEXTENDED", "" ),
+ TypeAMOEBA => ( "AMOEBA", "amoeba" ),
+ TypeAMOEBABB => ( "AMOEBABB", "amoebaboot" ),
+ TypeBSD386 => ( "BSD386", "bsd386" ),
+ TypeBSDI => ( "BSDI", "bsdi" ),
+ TypeBSDISWAP => ( "BSDISWAP", "bsdiswap" ),
+ TypeOTHER => ( "OTHER", "other" ),
+ TypeCPM => ( "CPM", "cpm" ),
+ TypeDellRecovery => ( "DELLRECOVERY", "dell" ),
+ TypeSPEEDSTOR12 => ( "SPEEDSTOR12", "speedstor" ),
+ TypeSPEEDSTOR16 => ( "SPEEDSTOR16", "speedstor" ),
+ TypeLANSTEP => ( "LANSTEP", "lanstep" ),
+
+ Type9 => ( "PLAN9", "plan9" ),
+
+ * => (nil, nil),
+};
+
+dosparts: list of ref Dospart;
+
+tag2part(p: ref Part): ref Dospart
+{
+ for(l := dosparts; l != nil; l = tl l)
+ if((hd l).p.tag == p.tag)
+ return hd l;
+ raise "tag2part: cannot happen";
+}
+
+typestr0(ptype: int): string
+{
+ if(ptype < 0 || ptype >= len types || types[ptype].desc == nil)
+ return sys->sprint("type %d", ptype);
+ return types[ptype].desc;
+}
+
+gettable(disk: ref Disk, addr: big, mbr: int): array of byte
+{
+ table := array[TableSize+2] of {* => byte 0};
+ diskread(disk, table, len table, addr, Toffset);
+ if(mbr){
+ # the informal specs say all must have this but apparently not, only mbr
+ if(int table[Omagic] != Magic0 || int table[Omagic+1] != Magic1)
+ sysfatal("did not find master boot record");
+ }
+ return table;
+}
+
+diskread(disk: ref Disk, data: array of byte, ndata: int, sec: big, off: int)
+{
+ a := sec*big disk.secsize + big off;
+ if(sys->seek(disk.fd, a, 0) != a)
+ sysfatal(sys->sprint("diskread seek %bud.%ud: %r", sec, off));
+ if(readn(disk.fd, data, ndata) != ndata)
+ sysfatal(sys->sprint("diskread %ud at %bud.%ud: %r", ndata, sec, off));
+}
+
+puttable(disk: ref Disk, table: array of byte, sec: big): int
+{
+ return diskwrite(disk, table, len table, sec, Toffset);
+}
+
+diskwrite(disk: ref Disk, data: array of byte, ndata: int, sec: big, off: int): int
+{
+ written = 1;
+ a := sec*big disk.secsize + big off;
+ if(sys->seek(disk.wfd, a, 0) != a ||
+ sys->write(disk.wfd, data, ndata) != ndata){
+ sys->fprint(stderr, "write %d bytes at %bud.%ud failed: %r\n", ndata, sec, off);
+ return -1;
+ }
+ return 0;
+}
+
+partgen := 0;
+parttag := 0;
+
+mkpart(name: string, primary: int, lba: big, size: big, pcpart: ref PCpart): ref Dospart
+{
+ p := ref Dospart;
+ if(name == nil){
+ if(primary)
+ c := 'p';
+ else
+ c = 's';
+ name = sys->sprint("%c%d", c, ++partgen);
+ }
+
+ if(pcpart != nil)
+ p.pc = pcpart;
+ else
+ p.pc = ref PCpart(0, 0, big 0, big 0, big 0);
+
+ p.primary = primary;
+ p.p = ref Part; # TO DO
+ p.p.name = name;
+ p.p.start = lba/sec2cyl;
+ p.p.end = (lba+size)/sec2cyl;
+ p.p.ctlstart = lba;
+ p.p.ctlend = lba+size;
+ p.p.tag = ++parttag;
+ p.lba = lba; # absolute lba
+ p.size = size;
+ dosparts = p :: dosparts;
+ return p;
+}
+
+#
+# Recovery takes care of remembering what the various tables
+# looked like when we started, attempting to restore them when
+# we are finished.
+#
+rtabs: list of ref Recover;
+
+addrecover(t: array of byte, lba: big)
+{
+ tc := array[TableSize+2] of byte;
+ tc[0:] = t[0:len tc];
+ rtabs = ref Recover(tc, lba) :: rtabs;
+}
+
+recover(edit: ref Edit)
+{
+ err := 0;
+ for(rl := rtabs; rl != nil; rl = tl rl){
+ r := hd rl;
+ if(puttable(edit.disk, r.table, r.lba) < 0)
+ err = 1;
+ }
+ if(err) {
+ sys->fprint(stderr, "warning: some writes failed during restoration of old partition tables\n");
+ exits("inconsistent");
+ } else
+ sys->fprint(stderr, "restored old partition tables\n");
+
+ ctlfd := edit.disk.ctlfd;
+ if(ctlfd != nil){
+ offset := edit.disk.offset;
+ for(i:=0; i<len edit.part; i++)
+ if(edit.part[i].ctlname != nil && sys->fprint(ctlfd, "delpart %s", edit.part[i].ctlname)<0)
+ sys->fprint(stderr, "delpart failed: %s: %r", edit.part[i].ctlname);
+ for(i=0; i<len edit.ctlpart; i++)
+ if(edit.part[i].name != nil && sys->fprint(ctlfd, "delpart %s", edit.ctlpart[i].name)<0)
+ sys->fprint(stderr, "delpart failed: %s: %r", edit.ctlpart[i].name);
+ for(i=0; i<len edit.ctlpart; i++){
+ if(sys->fprint(ctlfd, "part %s %bd %bd", edit.ctlpart[i].name,
+ edit.ctlpart[i].start+offset, edit.ctlpart[i].end+offset) < 0){
+ sys->fprint(stderr, "restored disk partition table but not kernel; reboot\n");
+ exits("inconsistent");
+ }
+ }
+ }
+ exits("restored");
+}
+
+#
+# Read the partition table (including extended partition tables)
+# from the disk into the part array.
+#
+rdpart(edit: ref Edit, lba: big, xbase: big)
+{
+ if(xbase == big 0)
+ xbase = lba; # extended partition in mbr sets the base
+
+ table := gettable(edit.disk, mbroffset+lba, lba == big 0);
+ addrecover(table, mbroffset+lba);
+
+ for(tp := 0; tp<TableSize; tp += TentrySize){
+ dp := PCpart.extract(table[tp:], edit.disk);
+ case dp.ptype {
+ TypeEMPTY =>
+ ;
+ TypeEXTENDED or
+ TypeEXTHUGE or
+ TypeLINUXEXT =>
+ rdpart(edit, xbase+dp.offset, xbase);
+ * =>
+ p := mkpart(nil, lba==big 0, lba+dp.offset, dp.size, ref dp);
+ if((err := edit.addpart(p.p)) != nil)
+ sys->fprint(stderr, "error adding partition: %s\n", err);
+ }
+ }
+}
+
+blankpart(edit: ref Edit)
+{
+ edit.changed = 1;
+}
+
+findmbr(edit: ref Edit)
+{
+ table := gettable(edit.disk, big 0, 1);
+ for(tp := 0; tp < TableSize; tp += TentrySize){
+ p := PCpart.extract(table[tp:], edit.disk);
+ if(p.ptype == TypeDMDDO)
+ mbroffset = big edit.disk.s;
+ }
+}
+
+haveroom(edit: ref Edit, primary: int, start: big): int
+{
+ if(primary) {
+ #
+ # must be open primary slot.
+ # primary slots are taken by primary partitions
+ # and runs of secondary partitions.
+ #
+ n := 0;
+ lastsec := 0;
+ for(i:=0; i<len edit.part; i++) {
+ p := tag2part(edit.part[i]);
+ if(p.primary){
+ n++;
+ lastsec = 0;
+ }else if(!lastsec){
+ n++;
+ lastsec = 1;
+ }
+ }
+ return n<4;
+ }
+
+ #
+ # secondary partitions can be inserted between two primary
+ # partitions only if there is an empty primary slot.
+ # otherwise, we can put a new secondary partition next
+ # to a secondary partition no problem.
+ #
+ n := 0;
+ for(i:=0; i<len edit.part; i++){
+ p := tag2part(edit.part[i]);
+ if(p.primary)
+ n++;
+ pend := p.p.end;
+ q: ref Dospart;
+ qstart: big;
+ if(i+1<len edit.part){
+ q = tag2part(edit.part[i+1]);
+ qstart = q.p.start;
+ }else{
+ qstart = edit.end;
+ q = nil;
+ }
+ if(start < pend || start >= qstart)
+ continue;
+ # we go between these two
+ if(p.primary==0 || (q != nil && q.primary==0))
+ return 1;
+ }
+ # not next to a secondary, need a new primary
+ return n<4;
+}
+
+autopart(edit: ref Edit)
+{
+ for(i:=0; i<len edit.part; i++)
+ if(tag2part(edit.part[i]).pc.ptype == Type9)
+ return;
+
+ # look for the biggest gap in which we can put a primary partition
+ start := big 0;
+ bigsize := big 0;
+ bigstart := big 0;
+ for(i=0; i<len edit.part; i++) {
+ p := tag2part(edit.part[i]);
+ if(p.p.start > start && p.p.start - start > bigsize && haveroom(edit, 1, start)) {
+ bigsize = p.p.start - start;
+ bigstart = start;
+ }
+ start = p.p.end;
+ }
+
+ if(edit.end - start > bigsize && haveroom(edit, 1, start)) {
+ bigsize = edit.end - start;
+ bigstart = start;
+ }
+ if(bigsize < big 1) {
+ sys->fprint(stderr, "couldn't find space or partition slot for plan 9 partition\n");
+ return;
+ }
+
+ # set new partition active only if no others are
+ active := Active;
+ for(i=0; i<len edit.part; i++){
+ p := tag2part(edit.part[i]);
+ if(p.primary && p.pc.active & Active)
+ active = 0;
+ }
+
+ # add new plan 9 partition
+ bigsize *= sec2cyl;
+ bigstart *= sec2cyl;
+ if(bigstart == big 0) {
+ bigstart += big edit.disk.s;
+ bigsize -= big edit.disk.s;
+ }
+ p := mkpart(nil, 1, bigstart, bigsize, nil);
+ p.p.changed = 1;
+ p.pc.active = active;
+ p.pc.ptype = Type9;
+ edit.changed = 1;
+ if((err := edit.addpart(p.p)) != nil){
+ sys->fprint(stderr, "error adding plan9 partition: %s\n", err);
+ return;
+ }
+}
+
+namelist: list of string;
+
+plan9print(part: ref Dospart, fd: ref Sys->FD)
+{
+ vname := types[part.pc.ptype].name;
+ if(vname==nil) {
+ part.p.ctlname = "";
+ return;
+ }
+
+ start := mbroffset+part.lba;
+ end := start+part.size;
+
+ # avoid names like plan90
+ i := len vname - 1;
+ if(isdigit(vname[i]))
+ sep := ".";
+ else
+ sep = "";
+
+ i = 0;
+ name := sys->sprint("%s", vname);
+ ok: int;
+ do {
+ ok = 1;
+ for(nl := namelist; nl != nil; nl = tl nl)
+ if(name == hd nl) {
+ i++;
+ name = sys->sprint("%s%s%d", vname, sep, i);
+ ok = 0;
+ }
+ } while(ok == 0);
+
+ namelist = name :: namelist;
+ part.p.ctlname = name;
+
+ if(fd != nil)
+ sys->print("part %s %bd %bd\n", name, start, end);
+}
+
+cmdprintctl(edit: ref Edit, ctlfd: ref Sys->FD)
+{
+ namelist = nil;
+ for(i:=0; i<len edit.part; i++)
+ plan9print(tag2part(edit.part[i]), nil);
+ edit.ctldiff(ctlfd);
+}
+
+cmdokname(nil: ref Edit, name: string): string
+{
+ if(name[0] != 'p' && name[0] != 's' || len name < 2)
+ return "name must be pN or sN";
+ for(i := 1; i < len name; i++)
+ if(!isdigit(name[i]))
+ return "name must be pN or sN";
+
+ return nil;
+}
+
+KB: con big 1024;
+MB: con KB*KB;
+GB: con KB*MB;
+
+cmdsum(edit: ref Edit, vp: ref Part, a, b: big)
+{
+ if(vp != nil)
+ p := tag2part(vp);
+
+ qual: string;
+ if(p != nil && p.p.changed)
+ qual += "'";
+ else
+ qual += " ";
+ if(p != nil && p.pc.active&Active)
+ qual += "*";
+ else
+ qual += " ";
+
+ if(p != nil)
+ name := p.p.name;
+ else
+ name = "empty";
+ if(p != nil)
+ ty := " "+typestr0(p.pc.ptype);
+ else
+ ty = "";
+
+ sz := (b-a)*big edit.disk.secsize*sec2cyl;
+ suf := "B";
+ div := big 1;
+ if(sz >= big 1*GB){
+ suf = "GB";
+ div = GB;
+ }else if(sz >= big 1*MB){
+ suf = "MB";
+ div = MB;
+ }else if(sz >= big 1*KB){
+ suf = "KB";
+ div = KB;
+ }
+
+ if(div == big 1)
+ sys->print("%s %-12s %*bd %-*bd (%bd cylinders, %bd %s)%s\n", qual, name,
+ edit.disk.width, a, edit.disk.width, b, b-a, sz, suf, ty);
+ else
+ sys->print("%s %-12s %*bd %-*bd (%bd cylinders, %bd.%.2d %s)%s\n", qual, name,
+ edit.disk.width, a, edit.disk.width, b, b-a,
+ sz/div, int(((sz%div)*big 100)/div), suf, ty);
+}
+
+cmdadd(edit: ref Edit, name: string, start: big, end: big): string
+{
+ if(!haveroom(edit, name[0]=='p', start))
+ return "no room for partition";
+ start *= sec2cyl;
+ end *= sec2cyl;
+ if(start == big 0 || name[0] != 'p')
+ start += big edit.disk.s;
+ p := mkpart(name, name[0]=='p', start, end-start, nil);
+ p.p.changed = 1;
+ p.pc.ptype = Type9;
+ return edit.addpart(p.p);
+}
+
+cmddel(edit: ref Edit, p: ref Part): string
+{
+ return edit.delpart(p);
+}
+
+cmdwrite(edit: ref Edit): string
+{
+ wrpart(edit);
+ return nil;
+}
+
+help: con
+ "A name - set partition active\n"+
+ "P - sys->print table in ctl format\n"+
+ "R - restore disk back to initial configuration and exit\n"+
+ "e - show empty dos partitions\n"+
+ "t name [type] - set partition type\n";
+
+cmdhelp(nil: ref Edit): string
+{
+ sys->print("%s\n", help);
+ return nil;
+}
+
+cmdactive(edit: ref Edit, f: array of string): string
+{
+ if(len f != 2)
+ return "args";
+
+ if(f[1][0] != 'p')
+ return "cannot set secondary partition active";
+
+ if((p := tag2part(edit.findpart(f[1]))) == nil)
+ return "unknown partition";
+
+ for(i:=0; i<len edit.part; i++) {
+ ip := tag2part(edit.part[i]);
+ if(ip.pc.active & Active) {
+ ip.pc.active &= ~Active;
+ ip.p.changed = 1;
+ edit.changed = 1;
+ }
+ }
+
+ if((p.pc.active & Active) == 0) {
+ p.pc.active |= Active;
+ p.p.changed = 1;
+ edit.changed = 1;
+ }
+
+ return nil;
+}
+
+strupr(s: string): string
+{
+ for(i := 0; i < len s; i++)
+ if(s[i] >= 'a' && s[i] <= 'z')
+ s[i] += 'A' - 'a';
+ return s;
+}
+
+dumplist()
+{
+ n := 0;
+ for(i:=0; i<len types; i++) {
+ if(types[i].desc != nil) {
+ sys->print("%-16s", types[i].desc);
+ if(n++%4 == 3)
+ sys->print("\n");
+ }
+ }
+ if(n%4)
+ sys->print("\n");
+}
+
+cmdtype(edit: ref Edit, f: array of string): string
+{
+ if(len f < 2)
+ return "args";
+
+ if((p := tag2part(edit.findpart(f[1]))) == nil)
+ return "unknown partition";
+
+ q: string;
+ if(len f == 2) {
+ for(;;) {
+ sys->fprint(stderr, "new partition type [? for list]: ");
+ q = edit.getline();
+ if(q[0] == '?')
+ dumplist();
+ else
+ break;
+ }
+ } else
+ q = f[2];
+
+ q = strupr(q);
+ for(i:=0; i<len types; i++)
+ if(types[i].desc != nil && types[i].desc == q)
+ break;
+ if(i < len types && p.pc.ptype != i) {
+ p.pc.ptype = i;
+ p.p.changed = 1;
+ edit.changed = 1;
+ }
+ return nil;
+}
+
+cmdext(edit: ref Edit, f: array of string): string
+{
+ case f[0][0] {
+ 'A' =>
+ return cmdactive(edit, f);
+ 't' =>
+ return cmdtype(edit, f);
+ 'R' =>
+ recover(edit);
+ return nil;
+ * =>
+ return "unknown command";
+ }
+}
+
+wrextend(edit: ref Edit, i: int, xbase: big, startlba: big): (int, big)
+{
+ if(i == len edit.part){
+ endlba := edit.disk.secs;
+ if(startlba < endlba)
+ wrzerotab(edit.disk, mbroffset+startlba);
+ return (i, endlba);
+ }
+
+ p := tag2part(edit.part[i]);
+ if(p.primary){
+ endlba := p.p.start*sec2cyl;
+ if(startlba < endlba)
+ wrzerotab(edit.disk, mbroffset+startlba);
+ return (i, endlba);
+ }
+
+ disk := edit.disk;
+ table := gettable(disk, mbroffset+startlba, 0);
+
+ (ni, endlba) := wrextend(edit, i+1, xbase, p.p.end*sec2cyl);
+
+ tp := wrtentry(disk, table[0:], p.pc.active, p.pc.ptype, startlba, startlba+big disk.s, p.p.end*sec2cyl);
+ if(p.p.end*sec2cyl != endlba)
+ tp += wrtentry(disk, table[tp:], 0, TypeEXTENDED, xbase, p.p.end*sec2cyl, endlba);
+
+ for(; tp<TableSize; tp++)
+ table[tp] = byte 0;
+
+ table[Omagic] = byte Magic0;
+ table[Omagic+1] = byte Magic1;
+
+ if(puttable(edit.disk, table, mbroffset+startlba) < 0)
+ recover(edit);
+ return (ni, endlba);
+}
+
+wrzerotab(disk: ref Disk, addr: big)
+{
+ table := array[TableSize+2] of {Omagic => byte Magic0, Omagic+1 => byte Magic1, * => byte 0};
+ if(puttable(disk, table, addr) < 0)
+ recover(edit);
+}
+
+wrpart(edit: ref Edit)
+{
+ disk := edit.disk;
+
+ table := gettable(disk, mbroffset, 0);
+
+ tp := 0;
+ for(i:=0; i<len edit.part && tp<TableSize; ) {
+ p := tag2part(edit.part[i]);
+ if(p.p.start == big 0)
+ s := big disk.s;
+ else
+ s = p.p.start*sec2cyl;
+ if(p.primary) {
+ tp += wrtentry(disk, table[tp:], p.pc.active, p.pc.ptype, big 0, s, p.p.end*sec2cyl);
+ i++;
+ }else{
+ (ni, endlba) := wrextend(edit, i, p.p.start*sec2cyl, p.p.start*sec2cyl);
+ if(endlba >= big 1024*sec2cyl)
+ t := TypeEXTHUGE;
+ else
+ t = TypeEXTENDED;
+ tp += wrtentry(disk, table[tp:], 0, t, big 0, s, endlba);
+ i = ni;
+ }
+ }
+ for(; tp<TableSize; tp++)
+ table[tp] = byte 0;
+
+ if(i != len edit.part)
+ raise "wrpart: cannot happen #1";
+
+ if(puttable(disk, table, mbroffset) < 0)
+ recover(edit);
+
+ # bring parts up to date
+ namelist = nil;
+ for(i=0; i<len edit.part; i++)
+ plan9print(tag2part(edit.part[i]), nil);
+
+ if(edit.ctldiff(disk.ctlfd) < 0)
+ sys->fprint(stderr, "?warning: partitions could not be updated in devsd\n");
+}
+
+isdigit(c: int): int
+{
+ return c >= '0' && c <= '9';
+}
+
+sysfatal(s: string)
+{
+ sys->fprint(stderr, "fdisk: %s\n", s);
+ raise "fail:error";
+}
+
+exits(s: string)
+{
+ if(s != nil)
+ raise "fail:"+s;
+ exit;
+}
+
+assert(i: int)
+{
+ if(!i)
+ raise "assertion failed";
+}
+
+wrtentry(disk: ref Disk, entry: array of byte, active: int, ptype: int, xbase: big, lba: big, end: big): int
+{
+ pc: PCpart;
+ pc.active = active;
+ pc.ptype = ptype;
+ pc.base = xbase;
+ pc.offset = lba-xbase;
+ pc.size = end-lba;
+ entry[0:] = pc.bytes(disk);
+ return TentrySize;
+}
diff --git a/appl/cmd/disk/prep/mkfile b/appl/cmd/disk/prep/mkfile
new file mode 100644
index 00000000..714c26f8
--- /dev/null
+++ b/appl/cmd/disk/prep/mkfile
@@ -0,0 +1,26 @@
+<../../../../mkconfig
+
+TARG=\
+ fdisk.dis\
+ pedit.dis\
+ prep.dis\
+ calc.tab.dis\
+
+MODULES=\
+ pedit.m\
+
+SYSMODULES=\
+ arg.m\
+ sys.m\
+ draw.m\
+ disks.m\
+ bufio.m\
+ string.m\
+
+DISBIN=$ROOT/dis/disk
+
+<$ROOT/mkfiles/mkdis
+
+# calc
+calc.tab.b:
+ yacc -s calc -d calc.y
diff --git a/appl/cmd/disk/prep/pedit.b b/appl/cmd/disk/prep/pedit.b
new file mode 100644
index 00000000..f55bcaff
--- /dev/null
+++ b/appl/cmd/disk/prep/pedit.b
@@ -0,0 +1,504 @@
+implement Pedit;
+
+#
+# disk partition editor
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "disks.m";
+ disks: Disks;
+ Disk: import disks;
+ readn: import disks;
+
+include "draw.m";
+include "calc.tab.m";
+ calc: Calc;
+
+include "pedit.m";
+
+Cmd: adt {
+ c: int;
+ f: ref fn(e: ref Edit, a: array of string): string;
+};
+
+cmds: array of Cmd;
+
+bin: ref Iobuf;
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ calc = load Calc "/dis/disk/calc.tab.dis";
+ bufio = load Bufio Bufio->PATH;
+ disks = load Disks Disks->PATH;
+ disks->init();
+
+ bin = bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ cmds = array[] of {
+ ('.', editdot),
+ ('a', editadd),
+ ('d', editdel),
+ ('?', edithelp),
+ ('h', edithelp),
+ ('P', editctlprint),
+ ('p', editprint),
+ ('w', editwrite),
+ ('q', editquit),
+ };
+}
+
+Edit.mk(unit: string): ref Edit
+{
+ e := ref Edit;
+ e.unit = unit;
+ e.dot = big 0;
+ e.end = big 0;
+ e.changed = 0;
+ e.warned = 0;
+ e.lastcmd = 0;
+ return e;
+}
+
+Edit.getline(edit: self ref Edit): string
+{
+ p := bin.gets('\n');
+ if(p == nil){
+ if(edit.changed)
+ sys->fprint(sys->fildes(2), "?warning: changes not written\n");
+ exit;
+ }
+ for(i := 0; i < len p; i++)
+ if(!isspace(p[i]))
+ break;
+ if(i)
+ return p[i:];
+ return p;
+}
+
+Edit.findpart(edit: self ref Edit, name: string): ref Part
+{
+ for(i:=0; i<len edit.part; i++)
+ if(edit.part[i].name == name)
+ return edit.part[i];
+ return nil;
+}
+
+okname(edit: ref Edit, name: string): string
+{
+ if(name[0] == '\0')
+ return "partition has no name";
+
+ for(i:=0; i<len edit.part; i++) {
+ if(name == edit.part[i].name)
+ return sys->sprint("already have partition with name '%s'", name);
+ }
+ return nil;
+}
+
+Edit.addpart(edit: self ref Edit, p: ref Part): string
+{
+ if((err := okname(edit, p.name)) != nil)
+ return err;
+
+ for(i:=0; i<len edit.part; i++) {
+ if(p.start < edit.part[i].end && edit.part[i].start < p.end) {
+ msg := sys->sprint("\"%s\" %bd-%bd overlaps with \"%s\" %bd-%bd",
+ p.name, p.start, p.end,
+ edit.part[i].name, edit.part[i].start, edit.part[i].end);
+ # return msg;
+ }
+ }
+
+ if(len edit.part >= Maxpart)
+ return "too many partitions";
+
+ pa := array[i+1] of ref Part;
+ pa[0:] = edit.part;
+ edit.part = pa;
+
+ edit.part[i] = p;
+ for(; i > 0 && p.start < edit.part[i-1].start; i--) {
+ edit.part[i] = edit.part[i-1];
+ edit.part[i-1] = p;
+ }
+
+ if(p.changed)
+ edit.changed = 1;
+ return nil;
+}
+
+Edit.delpart(edit: self ref Edit, p: ref Part): string
+{
+ n := len edit.part;
+ for(i:=0; i<n; i++)
+ if(edit.part[i] == p)
+ break;
+ if(i >= n)
+ raise "internal error: Part not found";
+ n--;
+ pa := array[n] of ref Part;
+ if(n){
+ pa[0:] = edit.part[0:i];
+ if(i != n)
+ pa[i:] = edit.part[i+1:];
+ }
+ edit.part = pa;
+ edit.changed = 1;
+ return nil;
+}
+
+editdot(edit: ref Edit, argv: array of string): string
+{
+ if(len argv == 1) {
+ sys->print("\t. %bd\n", edit.dot);
+ return nil;
+ }
+
+ if(len argv > 2)
+ return "args";
+
+ (ndot, err) := calc->parseexpr(argv[1], edit.dot, edit.end, edit.end);
+ if(err != nil)
+ return err;
+
+ edit.dot = ndot;
+ return nil;
+}
+
+editadd(edit: ref Edit, argv: array of string): string
+{
+ if(len argv < 2)
+ return "args";
+
+ name := argv[1];
+ if((err := okname(edit, name)) != nil || edit.okname != nil && (err = edit.okname(edit, name)) != nil)
+ return err;
+
+ if(len argv >= 3)
+ q := argv[2];
+ else {
+ sys->fprint(sys->fildes(2), "start %s: ", edit.unit);
+ q = edit.getline();
+ }
+ start: big;
+ (start, err) = calc->parseexpr(q, edit.dot, edit.end, edit.end);
+ if(err != nil)
+ return err;
+
+ if(start < big 0 || start >= edit.end)
+ return "start out of range";
+
+ for(i:=0; i < len edit.part; i++) {
+ if(edit.part[i].start <= start && start < edit.part[i].end)
+ return sys->sprint("start %s in partition '%s'", edit.unit, edit.part[i].name);
+ }
+
+ maxend := edit.end;
+ for(i=0; i < len edit.part; i++)
+ if(start < edit.part[i].start && edit.part[i].start < maxend)
+ maxend = edit.part[i].start;
+
+ if(len argv >= 4)
+ q = argv[3];
+ else {
+ sys->fprint(sys->fildes(2), "end [%bd..%bd] ", start, maxend);
+ q = edit.getline();
+ }
+ end: big;
+ (end, err) = calc->parseexpr(q, edit.dot, maxend, edit.end);
+ if(err != nil)
+ return err;
+
+ if(start == end)
+ return "size zero partition";
+
+ if(end <= start || end > maxend)
+ return "end out of range";
+
+ if(len argv > 4)
+ return "args";
+
+ if((err = edit.add(edit, name, start, end)) != nil)
+ return err;
+
+ edit.dot = end;
+ return nil;
+}
+
+editdel(edit: ref Edit, argv: array of string): string
+{
+ if(len argv != 2)
+ return "args";
+
+ if((p := edit.findpart(argv[1])) == nil)
+ return "no such partition";
+
+ return edit.del(edit, p);
+}
+
+helptext :=
+ ". [newdot] - display or set value of dot\n"+
+ "a name [start [end]] - add partition\n"+
+ "d name - delete partition\n"+
+ "h - sys->print help message\n"+
+ "p - sys->print partition table\n"+
+ "P - sys->print commands to update sd(3) device\n"+
+ "w - write partition table\n"+
+ "q - quit\n";
+
+edithelp(edit: ref Edit, nil: array of string): string
+{
+ sys->print("%s", helptext);
+ if(edit.help != nil)
+ return edit.help(edit);
+ return nil;
+}
+
+editprint(edit: ref Edit, argv: array of string): string
+{
+ if(len argv != 1)
+ return "args";
+
+ lastend := big 0;
+ part := edit.part;
+ for(i:=0; i<len edit.part; i++) {
+ if(lastend < part[i].start)
+ edit.sum(edit, nil, lastend, part[i].start);
+ edit.sum(edit, part[i], part[i].start, part[i].end);
+ lastend = part[i].end;
+ }
+ if(lastend < edit.end)
+ edit.sum(edit, nil, lastend, edit.end);
+ return nil;
+}
+
+editwrite(edit: ref Edit, argv: array of string): string
+{
+ if(len argv != 1)
+ return "args";
+
+ if(edit.disk.rdonly)
+ return "read only";
+
+ err := edit.write(edit);
+ if(err != nil)
+ return err;
+ for(i:=0; i<len edit.part; i++)
+ edit.part[i].changed = 0;
+ edit.changed = 0;
+ return nil;
+}
+
+editquit(edit: ref Edit, argv: array of string): string
+{
+ if(len argv != 1) {
+ edit.warned = 0;
+ return "args";
+ }
+
+ if(edit.changed && (!edit.warned || edit.lastcmd != 'q')) {
+ edit.warned = 1;
+ return "changes unwritten";
+ }
+
+ exit;
+}
+
+editctlprint(edit: ref Edit, argv: array of string): string
+{
+ if(len argv != 1)
+ return "args";
+
+ if(edit.printctl != nil)
+ edit.printctl(edit, sys->fildes(1));
+ else
+ edit.ctldiff(sys->fildes(1));
+ return nil;
+}
+
+Edit.runcmd(edit: self ref Edit, cmd: string)
+{
+ (nf, fl) := sys->tokenize(cmd, " \t\n\r");
+ if(nf < 1)
+ return;
+ f := array[nf] of string;
+ for(nf = 0; fl != nil; fl = tl fl)
+ f[nf++] = hd fl;
+ if(len f[0] != 1) {
+ sys->fprint(sys->fildes(2), "?\n");
+ return;
+ }
+
+ err := "";
+ for(i:=0; i<len cmds; i++) {
+ if(cmds[i].c == f[0][0]) {
+ op := cmds[i].f;
+ err = op(edit, f);
+ break;
+ }
+ }
+ if(i == len cmds){
+ if(edit.ext != nil)
+ err = edit.ext(edit, f);
+ else
+ err = "unknown command";
+ }
+ if(err != nil)
+ sys->fprint(sys->fildes(2), "?%s\n", err);
+ edit.lastcmd = f[0][0];
+}
+
+isspace(c: int): int
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+ctlmkpart(name: string, start: big, end: big, changed: int): ref Part
+{
+ p := ref Part;
+ p.name = name;
+ p.ctlname = name;
+ p.start = start;
+ p.end = end;
+ p.ctlstart = big 0;
+ p.ctlend = big 0;
+ p.changed = changed;
+ return p;
+}
+
+rdctlpart(edit: ref Edit)
+{
+ disk := edit.disk;
+ edit.ctlpart = array[0] of ref Part;
+ sys->seek(disk.ctlfd, big 0, 0);
+ buf := array[4096] of byte;
+ if(readn(disk.ctlfd, buf, len buf) <= 0)
+ return;
+ for(i := 0; i < len buf; i++)
+ if(buf[i] == byte 0)
+ break;
+
+ (nline, lines) := sys->tokenize(string buf[0:i], "\n\r");
+ edit.ctlpart = array[nline] of ref Part; # upper bound
+ npart := 0;
+ for(i=0; i<nline; i++){
+ line := hd lines;
+ lines = tl lines;
+ if(len line < 5 || line[0:5] != "part ")
+ continue;
+
+ (nf, f) := sys->tokenize(line, " \t");
+ if(nf != 4 || hd f != "part")
+ break;
+
+ a := big hd tl tl f;
+ b := big hd tl tl tl f;
+
+ if(a >= b)
+ break;
+
+ # only gather partitions contained in the disk partition we are editing
+ if(a < disk.offset || disk.offset+disk.secs < b)
+ continue;
+
+ a -= disk.offset;
+ b -= disk.offset;
+
+ # the partition we are editing does not count
+ if(hd tl f == disk.part)
+ continue;
+
+ edit.ctlpart[npart++] = ctlmkpart(hd tl f, a, b, 0);
+ }
+ if(npart != len edit.ctlpart)
+ edit.ctlpart = edit.ctlpart[0:npart];
+}
+
+ctlstart(p: ref Part): big
+{
+ if(p.ctlstart != big 0)
+ return p.ctlstart;
+ return p.start;
+}
+
+ctlend(p: ref Part): big
+{
+ if(p.ctlend != big 0)
+ return p.ctlend;
+ return p.end;
+}
+
+areequiv(p: ref Part, q: ref Part): int
+{
+ if(p.ctlname == nil || q.ctlname == nil)
+ return 0;
+ return p.ctlname == q.ctlname &&
+ ctlstart(p) == ctlstart(q) && ctlend(p) == ctlend(q);
+}
+
+unchange(edit: ref Edit, p: ref Part)
+{
+ for(i:=0; i<len edit.ctlpart; i++) {
+ q := edit.ctlpart[i];
+ if(p.start <= q.start && q.end <= p.end)
+ q.changed = 0;
+ }
+ if(p.changed)
+ raise "internal error: Part unchanged";
+}
+
+Edit.ctldiff(edit: self ref Edit, ctlfd: ref Sys->FD): int
+{
+ rdctlpart(edit);
+
+ # everything is bogus until we prove otherwise
+ for(i:=0; i<len edit.ctlpart; i++)
+ edit.ctlpart[i].changed = 1;
+
+ #
+ # partitions with same info have not changed,
+ # and neither have partitions inside them.
+ #
+ for(i=0; i<len edit.ctlpart; i++)
+ for(j:=0; j<len edit.part; j++)
+ if(areequiv(edit.ctlpart[i], edit.part[j])) {
+ unchange(edit, edit.ctlpart[i]);
+ break;
+ }
+
+ waserr := 0;
+ #
+ # delete all the changed partitions except data (we'll add them back if necessary)
+ #
+ for(i=0; i<len edit.ctlpart; i++) {
+ p := edit.ctlpart[i];
+ if(p.changed)
+ if(sys->fprint(ctlfd, "delpart %s\n", p.ctlname)<0) {
+ sys->fprint(sys->fildes(2), "delpart failed: %s: %r\n", p.ctlname);
+ waserr = -1;
+ }
+ }
+
+ #
+ # add all the partitions from the real list;
+ # this is okay since adding a partition with
+ # information identical to what is there is a no-op.
+ #
+ offset := edit.disk.offset;
+ for(i=0; i<len edit.part; i++) {
+ p := edit.part[i];
+ if(p.ctlname != nil) {
+ if(sys->fprint(ctlfd, "part %s %bd %bd\n", p.ctlname, offset+ctlstart(p), offset+ctlend(p)) < 0) {
+ sys->fprint(sys->fildes(2), "adding part failed: %s: %r\n", p.ctlname);
+ waserr = -1;
+ }
+ }
+ }
+ return waserr;
+}
diff --git a/appl/cmd/disk/prep/pedit.m b/appl/cmd/disk/prep/pedit.m
new file mode 100644
index 00000000..2b0d142d
--- /dev/null
+++ b/appl/cmd/disk/prep/pedit.m
@@ -0,0 +1,53 @@
+Pedit: module
+{
+ PATH: con "/dis/disk/pedit.dis";
+
+ Part: adt {
+ name: string;
+ ctlname: string;
+ start: big;
+ end: big;
+ ctlstart: big;
+ ctlend: big;
+ changed: int;
+ tag: int;
+ };
+
+ Maxpart: con 32;
+
+ Edit: adt {
+ disk: ref Disks->Disk;
+
+ ctlpart: array of ref Part;
+ part: array of ref Part;
+
+ # to do: replace by channels
+ add: ref fn(e: ref Edit, s: string, a, b: big): string;
+ del: ref fn(e: ref Edit, p: ref Part): string;
+ ext: ref fn(e: ref Edit, f: array of string): string;
+ help: ref fn(e: ref Edit): string;
+ okname: ref fn(e: ref Edit, s: string): string;
+ sum: ref fn(e: ref Edit, p: ref Part, a, b: big);
+ write: ref fn(e: ref Edit): string;
+ printctl: ref fn(e: ref Edit, x: ref Sys->FD);
+
+ unit: string;
+ dot: big;
+ end: big;
+
+ # do not use fields below this line
+ changed: int;
+ warned: int;
+ lastcmd: int;
+
+ mk: fn(unit: string): ref Edit;
+ getline: fn(e: self ref Edit): string;
+ runcmd: fn(e: self ref Edit, c: string);
+ findpart: fn(e: self ref Edit, n: string): ref Part;
+ addpart: fn(e: self ref Edit, p: ref Part): string;
+ delpart: fn(e: self ref Edit, p: ref Part): string;
+ ctldiff: fn(e: self ref Edit, ctlfd: ref Sys->FD): int;
+ };
+
+ init: fn();
+};
diff --git a/appl/cmd/disk/prep/prep.b b/appl/cmd/disk/prep/prep.b
new file mode 100644
index 00000000..fa4c60a1
--- /dev/null
+++ b/appl/cmd/disk/prep/prep.b
@@ -0,0 +1,509 @@
+implement Prep;
+
+#
+# prepare plan 9/inferno disk partition
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "disks.m";
+ disks: Disks;
+ Disk: import disks;
+ readn: import disks;
+
+include "pedit.m";
+ pedit: Pedit;
+ Edit, Part: import pedit;
+
+include "arg.m";
+
+Prep: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+blank := 0;
+file := 0;
+doauto := 0;
+printflag := 0;
+opart: array of ref Part;
+secbuf: array of byte;
+osecbuf: array of byte;
+zeroes: array of byte;
+rdonly := 0;
+dowrite := 0;
+
+Prepedit: type Edit[string];
+
+edit: ref Edit;
+
+Auto: adt
+{
+ name: string;
+ min: big;
+ max: big;
+ weight: int;
+ alloc: int;
+ size: big;
+};
+
+KB: con big 1024;
+MB: con KB*KB;
+GB: con KB*MB;
+
+#
+# Order matters -- this is the layout order on disk.
+#
+auto: array of Auto = array[] of {
+ ("9fat", big 10*MB, big 100*MB, 10, 0, big 0),
+ ("nvram", big 512, big 512, 1, 0, big 0),
+ ("fscfg", big 512, big 512, 1, 0, big 0),
+ ("fs", big 200*MB, big 0, 10, 0, big 0),
+ ("fossil", big 200*MB, big 0, 4, 0, big 0),
+ ("arenas", big 500*MB, big 0, 20, 0, big 0),
+ ("isect", big 25*MB, big 0, 1, 0, big 0),
+ ("other", big 200*MB, big 0, 4, 0, big 0),
+ ("swap", big 100*MB, big 512*MB, 1, 0, big 0),
+ ("cache", big 50*MB, big 1*GB, 2, 0, big 0),
+};
+
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ disks = load Disks Disks->PATH;
+ pedit = load Pedit Pedit->PATH;
+
+ sys->pctl(Sys->FORKFD, nil);
+ disks->init();
+ pedit->init();
+
+ edit = Edit.mk("sector");
+
+ edit.add = cmdadd;
+ edit.del = cmddel;
+ edit.okname = cmdokname;
+ edit.sum = cmdsum;
+ edit.write = cmdwrite;
+
+ stderr = sys->fildes(2);
+ secsize := 0;
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("disk/prep [-bfprw] [-a partname]... [-s sectorsize] /dev/sdC0/plan9");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' =>
+ p := arg->earg();
+ for(i:=0; i<len auto; i++){
+ if(p == auto[i].name){
+ if(auto[i].alloc){
+ sys->fprint(stderr, "you said -a %s more than once.\n", p);
+ arg->usage();
+ }
+ auto[i].alloc = 1;
+ break;
+ }
+ }
+ if(i == len auto){
+ sys->fprint(stderr, "don't know how to create automatic partition %s\n", p);
+ arg->usage();
+ }
+ doauto = 1;
+ 'b' =>
+ blank++;
+ 'f' =>
+ file++;
+ 'p' =>
+ printflag++;
+ rdonly++;
+ 'r' =>
+ rdonly++;
+ 's' =>
+ secsize = int arg->earg();
+ 'w' =>
+ dowrite++;
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(len args != 1)
+ arg->usage();
+ arg = nil;
+
+ mode := Sys->ORDWR;
+ if(rdonly)
+ mode = Sys->OREAD;
+ disk := Disk.open(hd args, mode, file);
+ if(disk == nil) {
+ sys->fprint(stderr, "cannot open disk: %r\n");
+ exits("opendisk");
+ }
+
+ if(secsize != 0) {
+ disk.secsize = secsize;
+ disk.secs = disk.size / big secsize;
+ }
+ edit.end = disk.secs;
+
+ checkfat(disk);
+
+ secbuf = array[disk.secsize+1] of byte;
+ osecbuf = array[disk.secsize+1] of byte;
+ zeroes = array[disk.secsize+1] of {* => byte 0};
+ edit.disk = disk;
+
+ if(blank == 0)
+ rdpart(edit);
+
+ # save old partition table
+ opart = array[len edit.part] of ref Part;
+ opart[0:] = edit.part;
+
+ if(printflag) {
+ edit.runcmd("P");
+ exits(nil);
+ }
+
+ if(doauto)
+ autopart(edit);
+
+ if(dowrite) {
+ edit.runcmd("w");
+ exits(nil);
+ }
+
+ edit.runcmd("p");
+ for(;;) {
+ sys->fprint(stderr, ">>> ");
+ edit.runcmd(edit.getline());
+ }
+}
+
+cmdsum(edit: ref Edit, p: ref Part, a: big, b: big)
+{
+ c := ' ';
+ name := "empty";
+ if(p != nil){
+ if(p.changed)
+ c = '\'';
+ name = p.name;
+ }
+
+ sz := (b-a)*big edit.disk.secsize;
+ suf := "B ";
+ div := big 1;
+ if(sz >= big 1*GB){
+ suf = "GB";
+ div = GB;
+ }else if(sz >= big 1*MB){
+ suf = "MB";
+ div = MB;
+ }else if(sz >= big 1*KB){
+ suf = "KB";
+ div = KB;
+ }
+
+ if(div == big 1)
+ sys->print("%c %-12s %*bd %-*bd (%bd sectors, %bd %s)\n", c, name,
+ edit.disk.width, a, edit.disk.width, b, b-a, sz, suf);
+ else
+ sys->print("%c %-12s %*bd %-*bd (%bd sectors, %bd.%.2d %s)\n", c, name,
+ edit.disk.width, a, edit.disk.width, b, b-a,
+ sz/div, int (((sz%div)*big 100)/div), suf);
+}
+
+cmdadd(edit: ref Edit, name: string, start: big, end: big): string
+{
+ if(start < big 2 && name == "9fat")
+ return "overlaps with the pbs and/or the partition table";
+
+ return edit.addpart(mkpart(name, start, end, 1));
+}
+
+cmddel(edit: ref Edit, p: ref Part): string
+{
+ return edit.delpart(p);
+}
+
+cmdwrite(edit: ref Edit): string
+{
+ wrpart(edit);
+ return nil;
+}
+
+isfrog := array[256] of {
+ byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # NUL
+ byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # BKS
+ byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # DLE
+ byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # CAN
+ ' ' => byte 1,
+ '/' => byte 1,
+ 16r7f=> byte 1,
+ * => byte 0
+};
+
+cmdokname(nil: ref Edit, elem: string): string
+{
+ for(i := 0; i < len elem; i++)
+ if(int isfrog[elem[i]])
+ return "bad character in name";
+ return nil;
+}
+
+mkpart(name: string, start: big, end: big, changed: int): ref Part
+{
+ p := ref Part;
+ p.name = name;
+ p.ctlname = name;
+ p.start = start;
+ p.end = end;
+ p.changed = changed;
+ p.ctlstart = big 0;
+ p.ctlend = big 0;
+ return p;
+}
+
+# plan9 partition table is first sector of the disk
+
+rdpart(edit: ref Edit)
+{
+ disk := edit.disk;
+ sys->seek(disk.fd, big disk.secsize, 0);
+ if(readn(disk.fd, osecbuf, disk.secsize) != disk.secsize)
+ return;
+ osecbuf[disk.secsize] = byte 0;
+ secbuf[0:] = osecbuf;
+
+ for(i := 0; i < disk.secsize; i++)
+ if(secbuf[i] == byte 0)
+ break;
+
+ tab := string secbuf[0:i];
+ if(len tab < 4 || tab[0:4] != "part"){
+ sys->fprint(stderr, "no plan9 partition table found\n");
+ return;
+ }
+
+ waserr := 0;
+ (nline, lines) := sys->tokenize(tab, "\n");
+ for(i=0; i<nline; i++){
+ line := hd lines;
+ lines = tl lines;
+ if(len line < 4 || line[0:4] != "part"){
+ waserr = 1;
+ continue;
+ }
+
+ (nf, f) := sys->tokenize(line, " \t\r");
+ if(nf != 4 || hd f != "part"){
+ waserr = 1;
+ continue;
+ }
+
+ a := big hd tl tl f;
+ b := big hd tl tl tl f;
+ if(a >= b){
+ waserr = 1;
+ continue;
+ }
+
+ if((err := edit.addpart(mkpart(hd tl f, a, b, 0))) != nil) {
+ sys->fprint(stderr, "?%s: not continuing\n", err);
+ exits("partition");
+ }
+ }
+ if(waserr)
+ sys->fprint(stderr, "syntax error reading partition\n");
+}
+
+min(a, b: big): big
+{
+ if(a < b)
+ return a;
+ return b;
+}
+
+autopart(edit: ref Edit)
+{
+ if(len edit.part > 0) {
+ if(doauto)
+ sys->fprint(stderr, "partitions already exist; not repartitioning\n");
+ return;
+ }
+
+ secs := edit.disk.secs;
+ secsize := big edit.disk.secsize;
+ for(;;){
+ # compute total weights
+ totw := 0;
+ for(i:=0; i<len auto; i++){
+ if(auto[i].alloc==0 || auto[i].size != big 0)
+ continue;
+ totw += auto[i].weight;
+ }
+ if(totw == 0)
+ break;
+
+ if(secs <= big 0){
+ sys->fprint(stderr, "ran out of disk space during autopartition.\n");
+ return;
+ }
+
+ # assign any minimums for small disks
+ futz := 0;
+ for(i=0; i<len auto; i++){
+ if(auto[i].alloc==0 || auto[i].size != big 0)
+ continue;
+ s := (secs*big auto[i].weight)/big totw;
+ if(s < big auto[i].min/secsize){
+ auto[i].size = big auto[i].min/secsize;
+ secs -= auto[i].size;
+ futz = 1;
+ break;
+ }
+ }
+ if(futz)
+ continue;
+
+ # assign any maximums for big disks
+ futz = 0;
+ for(i=0; i<len auto; i++){
+ if(auto[i].alloc==0 || auto[i].size != big 0)
+ continue;
+ s := (secs*big auto[i].weight)/big totw;
+ if(auto[i].max != big 0 && s > auto[i].max/secsize){
+ auto[i].size = auto[i].max/secsize;
+ secs -= auto[i].size;
+ futz = 1;
+ break;
+ }
+ }
+ if(futz)
+ continue;
+
+ # finally, assign partition sizes according to weights
+ for(i=0; i<len auto; i++){
+ if(auto[i].alloc==0 || auto[i].size != big 0)
+ continue;
+ s := (secs*big auto[i].weight)/big totw;
+ auto[i].size = s;
+
+ # use entire disk even in face of rounding errors
+ secs -= auto[i].size;
+ totw -= auto[i].weight;
+ }
+ }
+
+ for(i:=0; i<len auto; i++)
+ if(auto[i].alloc)
+ sys->print("%s %bud\n", auto[i].name, auto[i].size);
+
+ s := big 0;
+ for(i=0; i<len auto; i++){
+ if(auto[i].alloc == 0)
+ continue;
+ if((err := edit.addpart(mkpart(auto[i].name, s, s+auto[i].size, 1))) != nil)
+ sys->fprint(stderr, "addpart %s: %s\n", auto[i].name, err);
+ s += auto[i].size;
+ }
+}
+
+restore(edit: ref Edit, ctlfd: ref Sys->FD)
+{
+ offset := edit.disk.offset;
+ sys->fprint(stderr, "attempting to restore partitions to previous state\n");
+ if(sys->seek(edit.disk.wfd, big edit.disk.secsize, 0) != big 0){
+ sys->fprint(stderr, "cannot restore: error seeking on disk: %r\n");
+ exits("inconsistent");
+ }
+
+ if(sys->write(edit.disk.wfd, osecbuf, edit.disk.secsize) != edit.disk.secsize){
+ sys->fprint(stderr, "cannot restore: couldn't write old partition table to disk: %r\n");
+ exits("inconsistent");
+ }
+
+ if(ctlfd != nil){
+ for(i:=0; i<len edit.part; i++)
+ sys->fprint(ctlfd, "delpart %s", edit.part[i].name);
+ for(i=0; i<len opart; i++){
+ if(sys->fprint(ctlfd, "part %s %bd %bd", opart[i].name, opart[i].start+offset, opart[i].end+offset) < 0){
+ sys->fprint(stderr, "restored disk partition table but not kernel table; reboot\n");
+ exits("inconsistent");
+ }
+ }
+ }
+ exits("restored");
+}
+
+wrpart(edit: ref Edit)
+{
+ disk := edit.disk;
+
+ secbuf[0:] = zeroes;
+ n := 0;
+ for(i:=0; i<len edit.part; i++){
+ a := sys->aprint("part %s %bd %bd\n",
+ edit.part[i].name, edit.part[i].start, edit.part[i].end);
+ if(n + len a > disk.secsize){
+ sys->fprint(stderr, "partition table bigger than sector (%d bytes)\n", disk.secsize);
+ exits("overflow");
+ }
+ secbuf[n:] = a;
+ n += len a;
+ }
+
+ if(sys->seek(disk.wfd, big disk.secsize, 0) != big disk.secsize){
+ sys->fprint(stderr, "error seeking to %d on disk: %r\n", disk.secsize);
+ exits("seek");
+ }
+
+ if(sys->write(disk.wfd, secbuf, disk.secsize) != disk.secsize){
+ sys->fprint(stderr, "error writing partition table to disk: %r\n");
+ restore(edit, nil);
+ }
+
+ if(edit.ctldiff(disk.ctlfd) < 0)
+ sys->fprint(stderr, "?warning: partitions could not be updated in devsd\n");
+}
+
+#
+# Look for a boot sector in sector 1, as would be
+# the case if editing /dev/sdC0/data when that
+# was really a bootable disk.
+#
+checkfat(disk: ref Disk)
+{
+ buf := array[32] of byte;
+
+ if(sys->seek(disk.fd, big disk.secsize, 0) != big disk.secsize ||
+ sys->read(disk.fd, buf, len buf) < len buf)
+ return;
+
+ if(buf[0] != byte 16rEB || buf[1] != byte 16r3C || buf[2] != byte 16r90)
+ return;
+
+ sys->fprint(stderr,
+ "there's a fat partition where the\n"+
+ "plan9 partition table would go.\n"+
+ "if you really want to overwrite it, zero\n"+
+ "the second sector of the disk and try again\n");
+
+ exits("fat partition");
+}
+
+exits(s: string)
+{
+ if(s != nil)
+ raise "fail:"+s;
+ exit;
+}
diff --git a/appl/cmd/dossrv.b b/appl/cmd/dossrv.b
new file mode 100644
index 00000000..aefe7948
--- /dev/null
+++ b/appl/cmd/dossrv.b
@@ -0,0 +1,3432 @@
+implement Dossrv;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+
+Dossrv: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+ system: fn(ctxt: ref Draw->Context, args: list of string): string;
+};
+
+arg0 := "dossrv";
+
+deffile: string;
+pflag := 0;
+debug := 0;
+
+usage(iscmd: int): string
+{
+ sys->fprint(sys->fildes(2), "usage: %s [-v] [-s] [-F] [-c] [-S secpertrack] [-f devicefile] [-m mountpoint]\n", arg0);
+ if(iscmd)
+ raise "fail:usage";
+ return "usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ e := init2(nil, args, 1);
+ if(e != nil){
+ sys->fprint(sys->fildes(2), "%s: %s\n", arg0, e);
+ raise "fail:error";
+ }
+}
+
+system(nil: ref Draw->Context, args: list of string): string
+{
+ e := init2(nil, args, 0);
+ if(e != nil)
+ sys->fprint(sys->fildes(2), "%s: %s\n", arg0, e);
+ return e;
+}
+
+nomod(s: string): string
+{
+ return sys->sprint("can't load %s: %r", s);
+}
+
+init2(nil: ref Draw->Context, args: list of string, iscmd: int): string
+{
+ sys = load Sys Sys->PATH;
+
+ pipefd := array[2] of ref Sys->FD;
+
+ srvfile := "/n/dos";
+ deffile = ""; # no default, for safety
+ sectors := 0;
+ stdin := 0;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ return nomod(Arg->PATH);
+ arg->init(args);
+ arg0 = arg->progname();
+ while((o := arg->opt()) != 0) {
+ case o {
+ 'v' =>
+ if(debug & STYX_MESS)
+ debug |= VERBOSE;
+ debug |= STYX_MESS;
+ 'F' =>
+ debug |= FAT_INFO;
+ 'c' =>
+ debug |= CLUSTER_INFO;
+ iodebug = 1;
+ 'S' =>
+ s := arg->arg();
+ if(s != nil && s[0]>='0' && s[0]<='9')
+ sectors = int s;
+ else
+ return usage(iscmd);
+ 's' =>
+ stdin = 1;
+ 'f' =>
+ deffile = arg->arg();
+ if(deffile == nil)
+ return usage(iscmd);
+ 'm' =>
+ srvfile = arg->arg();
+ if(srvfile == nil)
+ return usage(iscmd);
+ 'p' =>
+ pflag++;
+ * =>
+ return usage(iscmd);
+ }
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(deffile == "" || !stdin && srvfile == "")
+ return usage(iscmd);
+
+ styx = load Styx Styx->PATH;
+ if(styx == nil)
+ return nomod(Styx->PATH);
+ styx->init();
+
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ return nomod(Daytime->PATH);
+
+ iotrackinit(sectors);
+
+ if(!stdin) {
+ if(sys->pipe(pipefd) < 0)
+ return sys->sprint("can't create pipe: %r");
+ }else{
+ pipefd[0] = nil;
+ pipefd[1] = sys->fildes(1);
+ }
+
+ dossetup();
+
+ spawn dossrv(pipefd[1]);
+
+ if(!stdin) {
+ if(sys->mount(pipefd[0], nil, srvfile, sys->MREPL|sys->MCREATE, deffile) < 0)
+ return sys->sprint("mount %s: %r", srvfile);
+ }
+
+ return nil;
+}
+
+#
+# Styx server
+#
+
+ Enevermind,
+ Eformat,
+ Eio,
+ Enomem,
+ Enonexist,
+ Enotdir,
+ Enofid,
+ Efidopen,
+ Efidinuse,
+ Eexist,
+ Eperm,
+ Enofilsys,
+ Eauth,
+ Econtig,
+ Efull,
+ Eopen,
+ Ephase: con iota;
+
+errmsg := array[] of {
+ Enevermind => "never mind",
+ Eformat => "unknown format",
+ Eio => "I/O error",
+ Enomem => "server out of memory",
+ Enonexist => "file does not exist",
+ Enotdir => "not a directory",
+ Enofid => "no such fid",
+ Efidopen => "fid already open",
+ Efidinuse => "fid in use",
+ Eexist => "file exists",
+ Eperm => "permission denied",
+ Enofilsys => "no file system device specified",
+ Eauth => "authentication failed",
+ Econtig => "out of contiguous disk space",
+ Efull => "file system full",
+ Eopen => "invalid open mode",
+ Ephase => "phase error -- directory entry not found",
+};
+
+e(n: int): ref Rmsg.Error
+{
+ if(n < 0 || n >= len errmsg)
+ return ref Rmsg.Error(0, "it's thermal problems");
+ return ref Rmsg.Error(0, errmsg[n]);
+}
+
+dossrv(rfd: ref Sys->FD)
+{
+ sys->pctl(Sys->NEWFD, rfd.fd :: 2 :: nil);
+ rfd = sys->fildes(rfd.fd);
+ data := array[Styx->MAXRPC] of byte;
+ while((t := Tmsg.read(rfd, 0)) != nil){
+ if(debug & STYX_MESS)
+ chat(sys->sprint("%s...", t.text()));
+
+ r: ref Rmsg;
+ pick m := t {
+ Readerror =>
+ panic(sys->sprint("mount read error: %s", m.error));
+ Version =>
+ r = rversion(m);
+ Auth =>
+ r = rauth(m);
+ Flush =>
+ r = rflush(m);
+ Attach =>
+ r = rattach(m);
+ Walk =>
+ r = rwalk(m);
+ Open =>
+ r = ropen(m);
+ Create =>
+ r = rcreate(m);
+ Read =>
+ r = rread(m);
+ Write =>
+ r = rwrite(m);
+ Clunk =>
+ r = rclunk(m);
+ Remove =>
+ r = rremove(m);
+ Stat =>
+ r = rstat(m);
+ Wstat =>
+ r = rwstat(m);
+ * =>
+ panic("Styx mtype");
+ }
+ pick m := r {
+ Error =>
+ r.tag = t.tag;
+ }
+ rbuf := r.pack();
+ if(rbuf == nil)
+ panic("Rmsg.pack");
+ if(debug & STYX_MESS)
+ chat(sys->sprint("%s\n", r.text()));
+ if(styx->write(rfd, rbuf, len rbuf) != len rbuf)
+ panic("mount write");
+ }
+
+ if(debug & STYX_MESS)
+ chat("server EOF\n");
+}
+
+rversion(t: ref Tmsg.Version): ref Rmsg
+{
+ (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION);
+ return ref Rmsg.Version(t.tag, msize, version);
+}
+
+rauth(t: ref Tmsg.Auth): ref Rmsg
+{
+ return ref Rmsg.Error(t.tag, "authentication not required");
+}
+
+rflush(t: ref Tmsg.Flush): ref Rmsg
+{
+ return ref Rmsg.Flush(t.tag);
+}
+
+rattach(t: ref Tmsg.Attach): ref Rmsg
+{
+ root := xfile(t.fid, Clean);
+ if(root == nil)
+ return e(Eio);
+ if(t.aname == nil)
+ t.aname = deffile;
+ (xf, ec) := getxfs(t.aname);
+ root.xf = xf;
+ if(xf == nil) {
+ if(root!=nil)
+ xfile(t.fid, Clunk);
+ return ref Rmsg.Error(t.tag, ec);
+ }
+ if(xf.fmt == 0 && dosfs(xf) < 0){
+ if(root!=nil)
+ xfile(t.fid, Clunk);
+ return e(Eformat);
+ }
+
+ root.qid = Sys->Qid(big 0, 0, Sys->QTDIR);
+ root.xf.rootqid = root.qid;
+ return ref Rmsg.Attach(t.tag, root.qid);
+}
+
+clone(ofl: ref Xfile, newfid: int): ref Xfile
+{
+ nfl := xfile(newfid, Clean);
+ next := nfl.next;
+ *nfl = *ofl;
+ nfl.ptr = nil;
+ nfl.next = next;
+ nfl.fid = newfid;
+ refxfs(nfl.xf, 1);
+ if(ofl.ptr != nil){
+ dp := ref *ofl.ptr;
+ dp.p = nil;
+ dp.d = nil;
+ nfl.ptr = dp;
+ }
+ return nfl;
+}
+
+walk1(f: ref Xfile, name: string): ref Rmsg.Error
+{
+ if((f.qid.qtype & Sys->QTDIR) == 0){
+ if(debug)
+ chat(sys->sprint("qid.path=0x%bx...", f.qid.path));
+ return e(Enotdir);
+ }
+
+ if(name == ".") # can't happen
+ return nil;
+
+ if(name== "..") {
+ if(f.qid.path == f.xf.rootqid.path) {
+ if (debug)
+ chat("walkup from root...");
+ return nil;
+ }
+ (r,dp) := walkup(f);
+ if(r < 0)
+ return e(Enonexist);
+
+ f.ptr = dp;
+ if(dp.addr == 0) {
+ f.qid.path = f.xf.rootqid.path;
+ f.qid.qtype = Sys->QTFILE;
+ } else {
+ f.qid.path = QIDPATH(dp);
+ f.qid.qtype = Sys->QTDIR;
+ }
+ } else {
+ if(getfile(f) < 0)
+ return e(Enonexist);
+ (r,dp) := searchdir(f, name, 0,1);
+ putfile(f);
+ if(r < 0)
+ return e(Enonexist);
+
+ f.ptr = dp;
+ f.qid.path = QIDPATH(dp);
+ f.qid.qtype = Sys->QTFILE;
+ if(dp.addr == 0)
+ f.qid.path = f.xf.rootqid.path;
+ else {
+ d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
+ if((int d.attr & DDIR) != 0)
+ f.qid.qtype = Sys->QTDIR;
+ }
+ putfile(f);
+ }
+ return nil;
+}
+
+rwalk(t: ref Tmsg.Walk): ref Rmsg
+{
+ f := xfile(t.fid, Asis);
+ if(f==nil) {
+ if(debug)
+ chat("no xfile...");
+ return e(Enofid);
+ }
+ nf: ref Xfile;
+ if(t.newfid != t.fid)
+ f = nf = clone(f, t.newfid);
+ qids: array of Sys->Qid;
+ if(len t.names > 0){
+ savedqid := f.qid;
+ savedptr := f.ptr;
+ qids = array[len t.names] of Sys->Qid;
+ for(i := 0; i < len t.names; i++){
+ e := walk1(f, t.names[i]);
+ if(e != nil){
+ f.qid = savedqid;
+ f.ptr = savedptr;
+ if(nf != nil)
+ xfile(t.newfid, Clunk);
+ if(i == 0)
+ return e;
+ return ref Rmsg.Walk(t.tag, qids[0:i]);
+ }
+ qids[i] = f.qid;
+ }
+ }
+ return ref Rmsg.Walk(t.tag, qids);
+}
+
+ropen(t: ref Tmsg.Open): ref Rmsg
+{
+ attr: int;
+
+ omode := 0;
+ f := xfile(t.fid, Asis);
+ if(f == nil)
+ return e(Enofid);
+ if((f.flags&Omodes) != 0)
+ return e(Efidopen);
+
+ dp := f.ptr;
+ if(dp.paddr && (t.mode & Styx->ORCLOSE) != 0) {
+ # check on parent directory of file to be deleted
+ p := getsect(f.xf, dp.paddr);
+ if(p == nil)
+ return e(Eio);
+ # 11 is the attr byte offset in a FAT directory entry
+ attr = int p.iobuf[dp.poffset+11];
+ putsect(p);
+ if((attr & int DRONLY) != 0)
+ return e(Eperm);
+ omode |= Orclose;
+ } else if(t.mode & Styx->ORCLOSE)
+ omode |= Orclose;
+
+ if(getfile(f) < 0)
+ return e(Enonexist);
+
+ if(dp.addr != 0) {
+ d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
+ attr = int d.attr;
+ } else
+ attr = int DDIR;
+
+ case t.mode & 7 {
+ Styx->OREAD or
+ Styx->OEXEC =>
+ omode |= Oread;
+ Styx->ORDWR =>
+ omode |= Oread;
+ omode |= Owrite;
+ if(attr & int (DRONLY|DDIR)) {
+ putfile(f);
+ return e(Eperm);
+ }
+ Styx->OWRITE =>
+ omode |= Owrite;
+ if(attr & int (DRONLY|DDIR)) {
+ putfile(f);
+ return e(Eperm);
+ }
+ * =>
+ putfile(f);
+ return e(Eopen);
+ }
+
+ if(t.mode & Styx->OTRUNC) {
+ if((attr & int DDIR)!=0 || (attr & int DRONLY) != 0) {
+ putfile(f);
+ return e(Eperm);
+ }
+
+ if(truncfile(f) < 0) {
+ putfile(f);
+ return e(Eio);
+ }
+ }
+
+ f.flags |= omode;
+ putfile(f);
+ return ref Rmsg.Open(t.tag, f.qid, Styx->MAXFDATA);
+}
+
+mkdentry(xf: ref Xfs, ndp: ref Dosptr, name: string, sname: string, islong: int, nattr: byte, start: array of byte, length: array of byte): int
+{
+ ndp.p = getsect(xf, ndp.addr);
+ if(ndp.p == nil)
+ return Eio;
+ if(islong && (r := putlongname(xf, ndp, name, sname)) < 0){
+ putsect(ndp.p);
+ if(r == -2)
+ return Efull;
+ return Eio;
+ }
+
+ nd := ref Dosdir(". "," ",byte 0,array[10] of { * => byte 0},
+ array[2] of { * => byte 0}, array[2] of { * => byte 0},
+ array[2] of { * => byte 0},array[4] of { * => byte 0});
+
+ nd.attr = nattr;
+ puttime(nd);
+ nd.start[0: ] = start[0: 2];
+ nd.length[0: ] = length[0: 4];
+
+ if(islong)
+ putname(sname[0:8]+"."+sname[8:11], nd);
+ else
+ putname(name, nd);
+ ndp.p.iobuf[ndp.offset: ] = Dosdir.Dd2arr(nd);
+ ndp.p.flags |= BMOD;
+ return 0;
+}
+
+rcreate(t: ref Tmsg.Create): ref Rmsg
+{
+ bp: ref Dosbpb;
+ omode:=0;
+ start:=0;
+ sname := "";
+ islong :=0;
+
+ f := xfile(t.fid, Asis);
+ if(f == nil)
+ return e(Enofid);
+ if((f.flags&Omodes) != 0)
+ return e(Efidopen);
+ if(getfile(f)<0)
+ return e(Eio);
+
+ pdp := f.ptr;
+ if(pdp.addr != 0)
+ pd := Dosdir.arr2Dd(pdp.p.iobuf[pdp.offset:pdp.offset+DOSDIRSIZE]);
+ else
+ pd = nil;
+
+ if(pd != nil)
+ attr := int pd.attr;
+ else
+ attr = DDIR;
+
+ if(!(attr & DDIR) || (attr & DRONLY)) {
+ putfile(f);
+ return e(Eperm);
+ }
+
+ if(t.mode & Styx->ORCLOSE)
+ omode |= Orclose;
+
+ case (t.mode & 7) {
+ Styx->OREAD or
+ Styx->OEXEC =>
+ omode |= Oread;
+ Styx->OWRITE or
+ Styx->ORDWR =>
+ if ((t.mode & 7) == Styx->ORDWR)
+ omode |= Oread;
+ omode |= Owrite;
+ if(t.perm & Sys->DMDIR){
+ putfile(f);
+ return e(Eperm);
+ }
+ * =>
+ putfile(f);
+ return e(Eopen);
+ }
+
+ if(t.name=="." || t.name=="..") {
+ putfile(f);
+ return e(Eperm);
+ }
+
+ (r,ndp) := searchdir(f, t.name, 1, 1);
+ if(r < 0) {
+ putfile(f);
+ if(r == -2)
+ return e(Efull);
+ return e(Eexist);
+ }
+
+ nds := name2de(t.name);
+ if(nds > 0) {
+ # long file name, find "new" short name
+ i := 1;
+ for(;;) {
+ sname = long2short(t.name, i);
+ (r1, tmpdp) := searchdir(f, sname, 0, 0);
+ if(r1 < 0)
+ break;
+ putsect(tmpdp.p);
+ i++;
+ }
+ islong = 1;
+ }
+
+ # allocate first cluster, if making directory
+ if(t.perm & Sys->DMDIR) {
+ bp = f.xf.ptr;
+ start = falloc(f.xf);
+ if(start <= 0) {
+ putfile(f);
+ return e(Efull);
+ }
+ }
+
+ # now we're committed
+ if(pd != nil) {
+ puttime(pd);
+ pdp.p.flags |= BMOD;
+ }
+
+ f.ptr = ndp;
+ ndp.p = getsect(f.xf, ndp.addr);
+ if(ndp.p == nil ||
+ islong && putlongname(f.xf, ndp, t.name, sname) < 0){
+ putsect(pdp.p);
+ if(ndp.p != nil)
+ putsect(ndp.p);
+ return e(Eio);
+ }
+
+ nd := ref Dosdir(". "," ",byte 0,array[10] of { * => byte 0},
+ array[2] of { * => byte 0}, array[2] of { * => byte 0},
+ array[2] of { * => byte 0},array[4] of { * => byte 0});
+
+ if((t.perm & 8r222) == 0)
+ nd.attr |= byte DRONLY;
+
+ puttime(nd);
+ nd.start[0] = byte start;
+ nd.start[1] = byte (start>>8);
+
+ if(islong)
+ putname(sname[0:8]+"."+sname[8:11], nd);
+ else
+ putname(t.name, nd);
+
+ f.qid.path = QIDPATH(ndp);
+ if(t.perm & Sys->DMDIR) {
+ nd.attr |= byte DDIR;
+ f.qid.qtype |= Sys->QTDIR;
+ xp := getsect(f.xf, bp.dataaddr+(start-2)*bp.clustsize);
+ if(xp == nil) {
+ if(ndp.p!=nil)
+ putfile(f);
+ putsect(pdp.p);
+ return e(Eio);
+ }
+ xd := ref *nd;
+ xd.name = ". ";
+ xd.ext = " ";
+ xp.iobuf[0:] = Dosdir.Dd2arr(xd);
+ if(pd!=nil)
+ xd = ref *pd;
+ else{
+ xd = ref Dosdir(".. "," ",byte 0,
+ array[10] of { * => byte 0},
+ array[2] of { * => byte 0},
+ array[2] of { * => byte 0},
+ array[2] of { * => byte 0},
+ array[4] of { * => byte 0});
+
+ puttime(xd);
+ xd.attr = byte DDIR;
+ }
+ xd.name=".. ";
+ xd.ext=" ";
+ xp.iobuf[DOSDIRSIZE:] = Dosdir.Dd2arr(xd);
+ xp.flags |= BMOD;
+ putsect(xp);
+ }else
+ f.qid.qtype = Sys->QTFILE;
+
+ ndp.p.flags |= BMOD;
+ tmp := Dosdir.Dd2arr(nd);
+ ndp.p.iobuf[ndp.offset:]= tmp;
+ putfile(f);
+ putsect(pdp.p);
+
+ f.flags |= omode;
+ return ref Rmsg.Create(t.tag, f.qid, Styx->MAXFDATA);
+}
+
+rread(t: ref Tmsg.Read): ref Rmsg
+{
+ r: int;
+ data: array of byte;
+
+ if(((f:=xfile(t.fid, Asis))==nil) ||
+ (f.flags&Oread == 0))
+ return e(Eio);
+
+ if((f.qid.qtype & Sys->QTDIR) != 0) {
+ if(getfile(f) < 0)
+ return e(Eio);
+ (r, data) = readdir(f, int t.offset, t.count);
+ } else {
+ if(getfile(f) < 0)
+ return e(Eio);
+ (r,data) = readfile(f, int t.offset, t.count);
+ }
+ putfile(f);
+
+ if(r < 0)
+ return e(Eio);
+ return ref Rmsg.Read(t.tag, data[0:r]);
+}
+
+rwrite(t: ref Tmsg.Write): ref Rmsg
+{
+ if(((f:=xfile(t.fid, Asis))==nil) ||
+ !(f.flags&Owrite))
+ return e(Eio);
+ if(getfile(f) < 0)
+ return e(Eio);
+ r := writefile(f, t.data, int t.offset, len t.data);
+ putfile(f);
+ if(r < 0){
+ if(r == -2)
+ return e(Efull);
+ return e(Eio);
+ }
+ return ref Rmsg.Write(t.tag, r);
+}
+
+rclunk(t: ref Tmsg.Clunk): ref Rmsg
+{
+ xfile(t.fid, Clunk);
+ sync();
+ return ref Rmsg.Clunk(t.tag);
+}
+
+doremove(f: ref Xfs, dp: ref Dosptr)
+{
+ dp.p.iobuf[dp.offset] = byte DOSEMPTY;
+ dp.p.flags |= BMOD;
+ for(prevdo := dp.offset-DOSDIRSIZE; prevdo >= 0; prevdo-=DOSDIRSIZE){
+ if (dp.p.iobuf[prevdo+11] != byte DLONG)
+ break;
+ dp.p.iobuf[prevdo] = byte DOSEMPTY;
+ }
+
+ if (prevdo <= 0 && dp.prevaddr != -1){
+ p := getsect(f,dp.prevaddr);
+ for(prevdo = f.ptr.sectsize-DOSDIRSIZE; prevdo >= 0; prevdo-=DOSDIRSIZE) {
+ if(p.iobuf[prevdo+11] != byte DLONG)
+ break;
+ p.iobuf[prevdo] = byte DOSEMPTY;
+ p.flags |= BMOD;
+ }
+ putsect(p);
+ }
+}
+
+rremove(t: ref Tmsg.Remove): ref Rmsg
+{
+ f := xfile(t.fid, Asis);
+ if(f == nil)
+ return e(Enofid);
+
+ if(!f.ptr.addr) {
+ if(debug)
+ chat("root...");
+ xfile(t.fid, Clunk);
+ sync();
+ return e(Eperm);
+ }
+
+ # check on parent directory of file to be deleted
+ parp := getsect(f.xf, f.ptr.paddr);
+ if(parp == nil) {
+ xfile(t.fid, Clunk);
+ sync();
+ return e(Eio);
+ }
+
+ pard := Dosdir.arr2Dd(parp.iobuf[f.ptr.poffset:f.ptr.poffset+DOSDIRSIZE]);
+ if(f.ptr.paddr && (int pard.attr & DRONLY)) {
+ if(debug)
+ chat("parent read-only...");
+ putsect(parp);
+ xfile(t.fid, Clunk);
+ sync();
+ return e(Eperm);
+ }
+
+ if(getfile(f) < 0){
+ if(debug)
+ chat("getfile failed...");
+ putsect(parp);
+ xfile(t.fid, Clunk);
+ sync();
+ return e(Eio);
+ }
+
+ dattr := int f.ptr.p.iobuf[f.ptr.offset+11];
+ if(dattr & DDIR && emptydir(f) < 0){
+ if(debug)
+ chat("non-empty dir...");
+ putfile(f);
+ putsect(parp);
+ xfile(t.fid, Clunk);
+ sync();
+ return e(Eperm);
+ }
+ if(f.ptr.paddr == 0 && dattr&DRONLY) {
+ if(debug)
+ chat("read-only file in root directory...");
+ putfile(f);
+ putsect(parp);
+ xfile(t.fid, Clunk);
+ sync();
+ return e(Eperm);
+ }
+
+ doremove(f.xf, f.ptr);
+
+ if(f.ptr.paddr) {
+ puttime(pard);
+ parp.flags |= BMOD;
+ }
+
+ parp.iobuf[f.ptr.poffset:] = Dosdir.Dd2arr(pard);
+ putsect(parp);
+ err := 0;
+ if(truncfile(f) < 0)
+ err = Eio;
+
+ putfile(f);
+ xfile(t.fid, Clunk);
+ sync();
+ if(err)
+ return e(err);
+ return ref Rmsg.Remove(t.tag);
+}
+
+rstat(t: ref Tmsg.Stat): ref Rmsg
+{
+ f := xfile(t.fid, Asis);
+ if(f == nil)
+ return e(Enofid);
+ if(getfile(f) < 0)
+ return e(Eio);
+ dir := dostat(f);
+ putfile(f);
+ return ref Rmsg.Stat(t.tag, *dir);
+}
+
+dostat(f: ref Xfile): ref Sys->Dir
+{
+ islong :=0;
+ prevdo: int;
+ longnamebuf:="";
+
+ # get file info.
+ dir := getdir(f.ptr.p.iobuf[f.ptr.offset:f.ptr.offset+DOSDIRSIZE],
+ f.ptr.addr, f.ptr.offset);
+ # get previous entry
+ if(f.ptr.prevaddr == -1) {
+ # maybe extended, but will never cross sector boundary...
+ # short filename at beginning of sector..
+ if(f.ptr.offset!=0) {
+ for(prevdo = f.ptr.offset-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE) {
+ prevdattr := f.ptr.p.iobuf[prevdo+11];
+ if(prevdattr != byte DLONG)
+ break;
+ islong = 1;
+ longnamebuf += getnamesect(f.ptr.p.iobuf[prevdo:prevdo+DOSDIRSIZE]);
+ }
+ }
+ } else {
+ # extended and will cross sector boundary.
+ for(prevdo = f.ptr.offset-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE) {
+ prevdattr := f.ptr.p.iobuf[prevdo+11];
+ if(prevdattr != byte DLONG)
+ break;
+ islong = 1;
+ longnamebuf += getnamesect(f.ptr.p.iobuf[prevdo:prevdo+DOSDIRSIZE]);
+ }
+ if (prevdo < 0) {
+ p := getsect(f.xf,f.ptr.prevaddr);
+ for(prevdo = f.xf.ptr.sectsize-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE){
+ prevdattr := p.iobuf[prevdo+11];
+ if(prevdattr != byte DLONG)
+ break;
+ islong = 1;
+ longnamebuf += getnamesect(p.iobuf[prevdo:prevdo+DOSDIRSIZE]);
+ }
+ putsect(p);
+ }
+ }
+ if(islong)
+ dir.name = longnamebuf;
+ return dir;
+}
+
+nameok(elem: string): int
+{
+ isfrog := array[256] of {
+ # NUL
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ # BKS
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ # DLE
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ # CAN
+ 1, 1, 1, 1, 1, 1, 1, 1,
+# ' ' => 1,
+ '/' => 1, 16r7f => 1, * => 0
+ };
+
+ for(i:=0; i < len elem; i++) {
+ if(isfrog[elem[i]])
+ return -1;
+ }
+ return 0;
+}
+
+rwstat(t: ref Tmsg.Wstat): ref Rmsg
+{
+ f := xfile(t.fid, Asis);
+ if(f == nil)
+ return e(Enofid);
+
+ if(getfile(f) < 0)
+ return e(Eio);
+
+ dp := f.ptr;
+
+ if(dp.addr == 0){ # root
+ putfile(f);
+ return e(Eperm);
+ }
+
+ changes := 0;
+ dir := dostat(f);
+ wdir := ref t.stat;
+
+ if(dir.uid != wdir.uid || dir.gid != wdir.gid){
+ putfile(f);
+ return e(Eperm);
+ }
+
+ if(dir.mtime != wdir.mtime || ((dir.mode^wdir.mode) & 8r777))
+ changes = 1;
+
+ if((wdir.mode & 7) != ((wdir.mode >> 3) & 7)
+ || (wdir.mode & 7) != ((wdir.mode >> 6) & 7)){
+ putfile(f);
+ return e(Eperm);
+ }
+
+ if(dir.name != wdir.name){
+ # temporarily disable this
+ # g.errno = Eperm;
+ # putfile(f);
+ # return;
+
+ #
+ # grab parent directory of file to be changed and check for write perm
+ # rename also disallowed for read-only files in root directory
+ #
+ parp := getsect(f.xf, dp.paddr);
+ if(parp == nil){
+ putfile(f);
+ return e(Eio);
+ }
+ # pard := Dosdir.arr2Dd(parp.iobuf[dp.poffset: dp.poffset+DOSDIRSIZE]);
+ pardattr := int parp.iobuf[dp.poffset+11];
+ dpd := Dosdir.arr2Dd(dp.p.iobuf[dp.offset: dp.offset+DOSDIRSIZE]);
+ if(dp.paddr != 0 && int pardattr & DRONLY
+ || dp.paddr == 0 && int dpd.attr & DRONLY){
+ putsect(parp);
+ putfile(f);
+ return e(Eperm);
+ }
+
+ #
+ # retrieve info from old entry
+ #
+ oaddr := dp.addr;
+ ooffset := dp.offset;
+ d := dpd;
+ od := *d;
+ # start := getstart(f.xf, d);
+ start := d.start;
+ length := d.length;
+ attr := d.attr;
+
+ #
+ # temporarily release file to allow other directory ops:
+ # walk to parent, validate new name
+ # then remove old entry
+ #
+ putfile(f);
+ pf := ref *f;
+ pdp := ref Dosptr(dp.paddr, dp.poffset, 0, 0, 0, 0, -1, -1, parp, nil);
+ # if(pdp.addr != 0)
+ # pdpd := Dosdir.arr2Dd(parp.iobuf[pdp.offset: pdp.offset+DOSDIRSIZE]);
+ # else
+ # pdpd = nil;
+ pf.ptr = pdp;
+ if(wdir.name == "." || wdir.name == ".."){
+ putsect(parp);
+ return e(Eperm);
+ }
+ islong := 0;
+ sname := "";
+ nds := name2de(wdir.name);
+ if(nds > 0) {
+ # long file name, find "new" short name
+ i := 1;
+ for(;;) {
+ sname = long2short(wdir.name, i);
+ (r1, tmpdp) := searchdir(f, sname, 0, 0);
+ if(r1 < 0)
+ break;
+ putsect(tmpdp.p);
+ i++;
+ }
+ islong = 1;
+ }else{
+ (b, e) := dosname(wdir.name);
+ sname = b+e;
+ }
+ # (r, ndp) := searchdir(pf, wdir.name, 1, 1);
+ # if(r < 0){
+ # putsect(parp);
+ # g.errno = Eperm;
+ # return;
+ # }
+ if(getfile(f) < 0){
+ putsect(parp);
+ return e(Eio);
+ }
+ doremove(f.xf, dp);
+ putfile(f);
+
+ #
+ # search for dir entry again, since we may be able to use the old slot,
+ # and we need to set up the naddr field if a long name spans the block.
+ # create new entry.
+ #
+ r := 0;
+ (r, dp) = searchdir(pf, sname, 1, islong);
+ if(r < 0){
+ putsect(parp);
+ return e(Ephase);
+ }
+ if((r = mkdentry(pf.xf, dp, wdir.name, sname, islong, attr, start, length)) != 0){
+ putsect(parp);
+ return e(r);
+ }
+ putsect(parp);
+
+ #
+ # relocate up other fids to the same file, if it moved
+ #
+ f.ptr = dp;
+ f.qid.path = QIDPATH(dp);
+ if(oaddr != dp.addr || ooffset != dp.offset)
+ dosptrreloc(f, dp, oaddr, ooffset);
+ changes = 1;
+ # f = nil;
+ }
+
+ if(changes){
+ d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
+ putdir(d, wdir);
+ dp.p.iobuf[dp.offset: ] = Dosdir.Dd2arr(d);
+ dp.p.flags |= BMOD;
+ }
+ if(f != nil)
+ putfile(f);
+ sync();
+ return ref Rmsg.Wstat(t.tag);
+}
+
+#
+# FAT file system format
+#
+
+Dospart: adt {
+ active: byte;
+ hstart: byte;
+ cylstart: array of byte;
+ typ: byte;
+ hend: byte;
+ cylend: array of byte;
+ start: array of byte;
+ length: array of byte;
+};
+
+Dosboot: adt {
+ arr2Db: fn(arr: array of byte): ref Dosboot;
+ magic: array of byte;
+ version: array of byte;
+ sectsize: array of byte;
+ clustsize: byte;
+ nresrv: array of byte;
+ nfats: byte;
+ rootsize: array of byte;
+ volsize: array of byte;
+ mediadesc: byte;
+ fatsize: array of byte;
+ trksize: array of byte;
+ nheads: array of byte;
+ nhidden: array of byte;
+ bigvolsize: array of byte;
+ driveno: byte;
+ bootsig: byte;
+ volid: array of byte;
+ label: array of byte;
+};
+
+Dosbpb: adt {
+ sectsize: int; # in bytes
+ clustsize: int; # in sectors
+ nresrv: int; # sectors
+ nfats: int; # usually 2
+ rootsize: int; # number of entries
+ volsize: int; # in sectors
+ mediadesc: int;
+ fatsize: int; # in sectors
+ fatclusters: int;
+ fatbits: int; # 12 or 16
+ fataddr: int; #big; # sector number
+ rootaddr: int; #big;
+ dataaddr: int; #big;
+ freeptr: int; #big; # next free cluster candidate
+};
+
+Dosdir: adt {
+ Dd2arr: fn(d: ref Dosdir): array of byte;
+ arr2Dd: fn(arr: array of byte): ref Dosdir;
+ name: string;
+ ext: string;
+ attr: byte;
+ reserved: array of byte;
+ time: array of byte;
+ date: array of byte;
+ start: array of byte;
+ length: array of byte;
+};
+
+Dosptr: adt {
+ addr: int; # of file's directory entry
+ offset: int;
+ paddr: int; # of parent's directory entry
+ poffset: int;
+ iclust: int; # ordinal within file
+ clust: int;
+ prevaddr: int;
+ naddr: int;
+ p: ref Iosect;
+ d: ref Dosdir;
+};
+
+Asis, Clean, Clunk: con iota;
+
+FAT12: con byte 16r01;
+FAT16: con byte 16r04;
+FATHUGE: con byte 16r06;
+DMDDO: con 16r54;
+DRONLY: con 16r01;
+DHIDDEN: con 16r02;
+DSYSTEM: con 16r04;
+DVLABEL: con 16r08;
+DDIR: con 16r10;
+DARCH: con 16r20;
+DLONG: con DRONLY | DHIDDEN | DSYSTEM | DVLABEL;
+DMLONG: con DLONG | DDIR | DARCH;
+
+DOSDIRSIZE: con 32;
+DOSEMPTY: con 16rE5;
+DOSRUNES: con 13;
+
+FATRESRV: con 2;
+
+Oread: con 1;
+Owrite: con 2;
+Orclose: con 4;
+Omodes: con 3;
+
+VERBOSE, STYX_MESS, FAT_INFO, CLUSTER_INFO: con (1 << iota);
+
+nowt, nowt1: int;
+tzoff: int;
+
+#
+# because we map all incoming short names from all upper to all lower case,
+# and FAT cannot store mixed case names in short name form,
+# we'll declare upper case as unacceptable to decide whether a long name
+# is needed on output. thus, long names are always written in the case
+# in the system call, and are always read back as written; short names
+# are produced by the common case of writing all lower case letters
+#
+isdos := array[256] of {
+ 'a' to 'z' => 1, 'A' to 'Z' => 0, '0' to '9' => 1,
+ ' ' => 1, '$' => 1, '%' => 1, '"' => 1, '-' => 1, '_' => 1, '@' => 1,
+ '~' => 1, '`' => 1, '!' => 1, '(' => 1, ')' => 1, '{' => 1, '}' => 1, '^' => 1,
+ '#' => 1, '&' => 1,
+ * => 0
+};
+
+dossetup()
+{
+ nowt = daytime->now();
+ nowt1 = sys->millisec();
+ tzoff = daytime->local(0).tzoff;
+}
+
+# make xf into a Dos file system... or die trying to.
+dosfs(xf: ref Xfs): int
+{
+ mbroffset := 0;
+ i: int;
+ p: ref Iosect;
+
+Dmddo:
+ for(;;) {
+ for(i=2; i>0; i--) {
+ p = getsect(xf, 0);
+ if(p == nil)
+ return -1;
+
+ if((mbroffset == 0) && (p.iobuf[0] == byte 16re9))
+ break;
+
+ # Check if the jump displacement (magic[1]) is too
+ # short for a FAT. DOS 4.0 MBR has a displacement of 8.
+ if(p.iobuf[0] == byte 16reb &&
+ p.iobuf[2] == byte 16r90 &&
+ p.iobuf[1] != byte 16r08)
+ break;
+
+ if(i < 2 ||
+ p.iobuf[16r1fe] != byte 16r55 ||
+ p.iobuf[16r1ff] != byte 16raa) {
+ i = 0;
+ break;
+ }
+
+ dp := 16r1be;
+ for(j:=4; j>0; j--) {
+ if(debug) {
+ chat(sys->sprint("16r%2.2ux (%d,%d) 16r%2.2ux (%d,%d) %d %d...",
+ int p.iobuf[dp], int p.iobuf[dp+1],
+ bytes2short(p.iobuf[dp+2: dp+4]),
+ int p.iobuf[dp+4], int p.iobuf[dp+5],
+ bytes2short(p.iobuf[dp+6: dp+8]),
+ bytes2int(p.iobuf[dp+8: dp+12]),
+ bytes2int(p.iobuf[dp+12:dp+16])));
+ }
+
+ # Check for a disc-manager partition in the MBR.
+ # Real MBR is at lba 63. Unfortunately it starts
+ # with 16rE9, hence the check above against magic.
+ if(int p.iobuf[dp+4] == DMDDO) {
+ mbroffset = 63*Sectorsize;
+ putsect(p);
+ purgebuf(xf);
+ xf.offset += mbroffset;
+ break Dmddo;
+ }
+
+ # Make sure it really is the right type, other
+ # filesystems can look like a FAT
+ # (e.g. OS/2 BOOT MANAGER).
+ if(p.iobuf[dp+4] == FAT12 ||
+ p.iobuf[dp+4] == FAT16 ||
+ p.iobuf[dp+4] == FATHUGE)
+ break;
+ dp+=16;
+ }
+
+ if(j <= 0) {
+ if(debug)
+ chat("no active partition...");
+ putsect(p);
+ return -1;
+ }
+
+ offset := bytes2int(p.iobuf[dp+8:dp+12])* Sectorsize;
+ putsect(p);
+ purgebuf(xf);
+ xf.offset = mbroffset+offset;
+ }
+ break;
+ }
+ if(i <= 0) {
+ if(debug)
+ chat("bad magic...");
+ putsect(p);
+ return -1;
+ }
+
+ b := Dosboot.arr2Db(p.iobuf);
+ if(debug & FAT_INFO)
+ bootdump(b);
+
+ bp := ref Dosbpb;
+ xf.ptr = bp;
+ xf.fmt = 1;
+
+ bp.sectsize = bytes2short(b.sectsize);
+ bp.clustsize = int b.clustsize;
+ bp.nresrv = bytes2short(b.nresrv);
+ bp.nfats = int b.nfats;
+ bp.rootsize = bytes2short(b.rootsize);
+ bp.volsize = bytes2short(b.volsize);
+ if(bp.volsize == 0)
+ bp.volsize = bytes2int(b.bigvolsize);
+ bp.mediadesc = int b.mediadesc;
+ bp.fatsize = bytes2short(b.fatsize);
+
+ bp.fataddr = int bp.nresrv;
+ bp.rootaddr = bp.fataddr + bp.nfats*bp.fatsize;
+ i = bp.rootsize*DOSDIRSIZE + bp.sectsize-1;
+ i /= bp.sectsize;
+ bp.dataaddr = bp.rootaddr + i;
+ bp.fatclusters = FATRESRV+(bp.volsize - bp.dataaddr)/bp.clustsize;
+ if(bp.fatclusters < 4087)
+ bp.fatbits = 12;
+ else
+ bp.fatbits = 16;
+ bp.freeptr = 2;
+ if(debug & FAT_INFO){
+ chat(sys->sprint("fatbits=%d (%d clusters)...",
+ bp.fatbits, bp.fatclusters));
+ for(i=0; i< int b.nfats; i++)
+ chat(sys->sprint("fat %d: %d...",
+ i, bp.fataddr+i*bp.fatsize));
+ chat(sys->sprint("root: %d...", bp.rootaddr));
+ chat(sys->sprint("data: %d...", bp.dataaddr));
+ }
+ putsect(p);
+ return 0;
+}
+
+QIDPATH(dp: ref Dosptr): big
+{
+ return big (dp.addr*(Sectorsize/DOSDIRSIZE) + dp.offset/DOSDIRSIZE);
+}
+
+isroot(addr: int): int
+{
+ return addr == 0;
+}
+
+getfile(f: ref Xfile): int
+{
+ dp := f.ptr;
+ if(dp.p!=nil)
+ panic("getfile");
+ if(dp.addr < 0)
+ panic("getfile address");
+ p := getsect(f.xf, dp.addr);
+ if(p == nil)
+ return -1;
+
+ dp.d = nil;
+ if(!isroot(dp.addr)) {
+ if(f.qid.path != QIDPATH(dp)){
+ if(debug) {
+ chat(sys->sprint("qid mismatch f=0x%x d=0x%x...",
+ int f.qid.path, int QIDPATH(dp)));
+ }
+ putsect(p);
+ return -1;
+ }
+ # dp.d = Dosdir.arr2Dd(p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
+ }
+ dp.p = p;
+ return 0;
+}
+
+putfile(f: ref Xfile)
+{
+ dp := f.ptr;
+ if(dp.p==nil)
+ panic("putfile");
+ putsect(dp.p);
+ dp.p = nil;
+ dp.d = nil;
+}
+
+getstart(nil: ref Xfs, d: ref Dosdir): int
+{
+ start := bytes2short(d.start);
+# if(xf.isfat32)
+# start |= bytes2short(d.hstart)<<16;
+ return start;
+}
+
+putstart(nil: ref Xfs, d: ref Dosdir, start: int)
+{
+ d.start[0] = byte start;
+ d.start[1] = byte (start>>8);
+# if(xf.isfat32){
+# d.hstart[0] = start>>16;
+# d.hstart[1] = start>>24;
+# }
+}
+
+#
+# return the disk cluster for the iclust cluster in f
+#
+fileclust(f: ref Xfile, iclust: int, cflag: int): int
+{
+
+ bp := f.xf.ptr;
+ dp := f.ptr;
+ if(isroot(dp.addr))
+ return -1; # root directory for old FAT format does not start on a cluster boundary
+ d := dp.d;
+ if(d == nil){
+ if(dp.p == nil)
+ panic("fileclust");
+ d = Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
+ }
+ next := 0;
+ start := getstart(f.xf, d);
+ if(start == 0) {
+ if(!cflag)
+ return -1;
+ start = falloc(f.xf);
+ if(start <= 0)
+ return -1;
+ puttime(d);
+ putstart(f.xf, d, start);
+ dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d);
+ dp.p.flags |= BMOD;
+ dp.clust = 0;
+ }
+
+ clust, nskip: int;
+ if(dp.clust == 0 || iclust < dp.iclust) {
+ clust = start;
+ nskip = iclust;
+ } else {
+ clust = dp.clust;
+ nskip = iclust - dp.iclust;
+ }
+
+ if(debug & CLUSTER_INFO && nskip > 0)
+ chat(sys->sprint("clust %d, skip %d...", clust, nskip));
+
+ if(clust <= 0)
+ return -1;
+
+ if(nskip > 0) {
+ while(--nskip >= 0) {
+ next = getfat(f.xf, clust);
+ if(debug & CLUSTER_INFO)
+ chat(sys->sprint(".%d", next));
+ if(next <= 0){
+ if(!cflag)
+ break;
+ next = falloc(f.xf);
+ if(next <= 0)
+ return -1;
+ putfat(f.xf, clust, next);
+ }
+ clust = next;
+ }
+ if(next <= 0)
+ return -1;
+ dp.clust = clust;
+ dp.iclust = iclust;
+ }
+ if(debug & CLUSTER_INFO)
+ chat(sys->sprint(" clust(%d)=0x%x...", iclust, clust));
+ return clust;
+}
+
+#
+# return the disk sector for the isect disk sector in f,
+# allocating space if necessary and cflag is set
+#
+fileaddr(f: ref Xfile, isect: int, cflag: int): int
+{
+ bp := f.xf.ptr;
+ dp := f.ptr;
+ if(isroot(dp.addr)) {
+ if(isect*bp.sectsize >= bp.rootsize*DOSDIRSIZE)
+ return -1;
+ return bp.rootaddr + isect;
+ }
+ clust := fileclust(f, isect/bp.clustsize, cflag);
+ if(clust < 0)
+ return -1;
+ return clust2sect(bp, clust) + isect%bp.clustsize;
+}
+
+#
+# look for a directory entry matching name
+# always searches for long names which match a short name
+#
+# if creating (cflag is set), set address of available slot and allocate next cluster if necessary
+#
+searchdir(f: ref Xfile, name: string, cflag: int, lflag: int): (int, ref Dosptr)
+{
+ xf := f.xf;
+ bp := xf.ptr;
+ addr1 := -1;
+ addr2 := -1;
+ prevaddr1 := -1;
+ o1 := 0;
+ dp := ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil); # prevaddr and naddr are -1
+ dp.paddr = f.ptr.addr;
+ dp.poffset = f.ptr.offset;
+ islong :=0;
+ buf := "";
+
+ need := 1;
+ if(lflag && cflag)
+ need += name2de(name);
+ if(!lflag) {
+ name = name[0:8]+"."+name[8:11];
+ i := len name -1;
+ while(i >= 0 && (name[i]==' ' || name[i] == '.'))
+ i--;
+ name = name[0:i+1];
+ }
+
+ addr := -1;
+ prevaddr: int;
+ have := 0;
+ for(isect:=0;; isect++) {
+ prevaddr = addr;
+ addr = fileaddr(f, isect, cflag);
+ if(addr < 0)
+ break;
+ p := getsect(xf, addr);
+ if(p == nil)
+ break;
+ for(o:=0; o<bp.sectsize; o+=DOSDIRSIZE) {
+ dattr := int p.iobuf[o+11];
+ dname0 := p.iobuf[o];
+ if(dname0 == byte 16r00) {
+ if(debug)
+ chat("end dir(0)...");
+ putsect(p);
+ if(!cflag)
+ return (-1, nil);
+
+ #
+ # addr1 and o1 are the start of the dirs
+ # addr2 is the optional second cluster used if the long name
+ # entry does not fit within the addr1 cluster
+ # have tells us the number of contiguous free dirs
+ # starting at addr1.o1; need is the number needed to hold the long name
+ #
+ if(addr1 < 0){
+ addr1 = addr;
+ prevaddr1 = prevaddr;
+ o1 = o;
+ }
+ nleft := (bp.sectsize-o)/DOSDIRSIZE;
+ if(addr2 < 0 && nleft+have < need){
+ addr2 = fileaddr(f, isect+1, cflag);
+ if(addr2 < 0){
+ if(debug)
+ chat("end dir(2)...");
+ return (-2, nil);
+ }
+ }else if(addr2 < 0)
+ addr2 = addr;
+ if(addr2 == addr1)
+ addr2 = -1;
+ if(debug)
+ chat(sys->sprint("allocate addr1=%d,%d addr2=%d for %s nleft=%d have=%d need=%d", addr1, o1, addr2, name, nleft, have, need));
+ dp.addr = addr1;
+ dp.offset = o1;
+ dp.prevaddr = prevaddr1;
+ dp.naddr = addr2;
+ return (0, dp);
+ }
+
+ if(dname0 == byte DOSEMPTY) {
+ if(debug)
+ chat("empty...");
+ have++;
+ if(addr1 == -1){
+ addr1 = addr;
+ o1 = o;
+ prevaddr1 = prevaddr;
+ }
+ if(addr2 == -1 && have >= need)
+ addr2 = addr;
+ continue;
+ }
+ have = 0;
+ if(addr2 == -1)
+ addr1 = -1;
+
+ if(0 && lflag && debug)
+ dirdump(p.iobuf[o:o+DOSDIRSIZE],addr,o);
+
+ if((dattr & DMLONG) == DLONG) {
+ if(!islong)
+ buf = "";
+ islong = 1;
+ buf = getnamesect(p.iobuf[o:o+DOSDIRSIZE]) + buf; # getnamesect should return sum
+ continue;
+ }
+ if(dattr & DVLABEL) {
+ islong = 0;
+ continue;
+ }
+
+ if(!islong || !lflag)
+ buf = getname(p.iobuf[o:o+DOSDIRSIZE]);
+ islong = 0;
+
+ if(debug)
+ chat(sys->sprint("cmp: [%s] [%s]", buf, name));
+ if(mystrcmp(buf, name) != 0) {
+ buf="";
+ continue;
+ }
+ if(debug)
+ chat("found\n");
+
+ if(cflag) {
+ putsect(p);
+ return (-1,nil);
+ }
+
+ dp.addr = addr;
+ dp.prevaddr = prevaddr;
+ dp.offset = o;
+ dp.p = p;
+ #dp.d = Dosdir.arr2Dd(p.iobuf[o:o+DOSDIRSIZE]);
+ return (0, dp);
+ }
+ putsect(p);
+ }
+ if(debug)
+ chat("end dir(1)...");
+ if(!cflag)
+ return (-1, nil);
+ #
+ # end of root directory or end of non-root directory on cluster boundary
+ #
+ if(addr1 < 0){
+ addr1 = fileaddr(f, isect, 1);
+ if(addr1 < 0)
+ return (-2, nil);
+ prevaddr1 = prevaddr;
+ o1 = 0;
+ }else{
+ if(addr2 < 0 && have < need){
+ addr2 = fileaddr(f, isect, 1);
+ if(addr2 < 0)
+ return (-2, nil);
+ }
+ }
+ if(addr2 == addr1)
+ addr2 = -1;
+ dp.addr = addr1;
+ dp.offset = o1;
+ dp.prevaddr = prevaddr1;
+ dp.naddr = addr2;
+ return (0, dp);
+}
+
+emptydir(f: ref Xfile): int
+{
+ for(isect:=0;; isect++) {
+ addr := fileaddr(f, isect, 0);
+ if(addr < 0)
+ break;
+
+ p := getsect(f.xf, addr);
+ if(p == nil)
+ return -1;
+
+ for(o:=0; o<f.xf.ptr.sectsize; o+=DOSDIRSIZE) {
+ dname0 := p.iobuf[o];
+ dattr := int p.iobuf[o+11];
+
+ if(dname0 == byte 16r00) {
+ putsect(p);
+ return 0;
+ }
+
+ if(dname0 == byte DOSEMPTY || dname0 == byte '.')
+ continue;
+
+ if(dattr & DVLABEL)
+ continue; # ignore any long name entries: it's empty if there are no short ones
+
+ putsect(p);
+ return -1;
+ }
+ putsect(p);
+ }
+ return 0;
+}
+
+readdir(f:ref Xfile, offset: int, count: int): (int, array of byte)
+{
+ xf := f.xf;
+ bp := xf.ptr;
+ rcnt := 0;
+ buf := array[Styx->MAXFDATA] of byte;
+ islong :=0;
+ longnamebuf:="";
+
+ if(count <= 0)
+ return (0, nil);
+
+Read:
+ for(isect:=0;; isect++) {
+ addr := fileaddr(f, isect, 0);
+ if(addr < 0)
+ break;
+ p := getsect(xf, addr);
+ if(p == nil)
+ return (-1,nil);
+
+ for(o:=0; o<bp.sectsize; o+=DOSDIRSIZE) {
+ dname0 := int p.iobuf[o];
+ dattr := int p.iobuf[o+11];
+
+ if(dname0 == 16r00) {
+ putsect(p);
+ break Read;
+ }
+
+ if(dname0 == DOSEMPTY)
+ continue;
+
+ if(dname0 == '.') {
+ dname1 := int p.iobuf[o+1];
+ if(dname1 == ' ' || dname1 == 0)
+ continue;
+ dname2 := int p.iobuf[o+2];
+ if(dname1 == '.' &&
+ (dname2 == ' ' || dname2 == 0))
+ continue;
+ }
+
+ if((dattr & DMLONG) == DLONG) {
+ if(!islong)
+ longnamebuf = "";
+ longnamebuf = getnamesect(p.iobuf[o:o+DOSDIRSIZE]) + longnamebuf;
+ islong = 1;
+ continue;
+ }
+ if(dattr & DVLABEL) {
+ islong = 0;
+ continue;
+ }
+
+ dir := getdir(p.iobuf[o:o+DOSDIRSIZE], addr, o);
+ if(islong) {
+ dir.name = longnamebuf;
+ longnamebuf = "";
+ islong = 0;
+ }
+ d := styx->packdir(*dir);
+ if(offset > 0) {
+ offset -= len d;
+ islong = 0;
+ continue;
+ }
+ if(rcnt+len d > count){
+ putsect(p);
+ break Read;
+ }
+ buf[rcnt:] = d;
+ rcnt += len d;
+ if(rcnt >= count) {
+ putsect(p);
+ break Read;
+ }
+ }
+ putsect(p);
+ }
+
+ return (rcnt, buf[0:rcnt]);
+}
+
+walkup(f: ref Xfile): (int, ref Dosptr)
+{
+ bp := f.xf.ptr;
+ dp := f.ptr;
+ o: int;
+ ndp:= ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil);
+ ndp.addr = dp.paddr;
+ ndp.offset = dp.poffset;
+
+ if(debug)
+ chat(sys->sprint("walkup: paddr=0x%x...", dp.paddr));
+
+ if(dp.paddr == 0)
+ return (0,ndp);
+
+ p := getsect(f.xf, dp.paddr);
+ if(p == nil)
+ return (-1,nil);
+
+ if(debug)
+ dirdump(p.iobuf[dp.poffset:dp.poffset+DOSDIRSIZE],dp.paddr,dp.poffset);
+
+ xd := Dosdir.arr2Dd(p.iobuf[dp.poffset:dp.poffset+DOSDIRSIZE]);
+ start := getstart(f.xf, xd);
+ if(debug & CLUSTER_INFO)
+ if(debug)
+ chat(sys->sprint("start=0x%x...", start));
+ putsect(p);
+ if(start == 0)
+ return (-1,nil);
+
+ #
+ # check that parent's . points to itself
+ #
+ p = getsect(f.xf, bp.dataaddr + (start-2)*bp.clustsize);
+ if(p == nil)
+ return (-1,nil);
+
+ if(debug)
+ dirdump(p.iobuf,0,0);
+
+ xd = Dosdir.arr2Dd(p.iobuf);
+ if(p.iobuf[0]!= byte '.' ||
+ p.iobuf[1]!= byte ' ' ||
+ start != getstart(f.xf, xd)) {
+ if(p!=nil)
+ putsect(p);
+ return (-1,nil);
+ }
+
+ if(debug)
+ dirdump(p.iobuf[DOSDIRSIZE:],0,0);
+
+ #
+ # parent's .. is the next entry, and has start of parent's parent
+ #
+ xd = Dosdir.arr2Dd(p.iobuf[DOSDIRSIZE:]);
+ if(p.iobuf[32] != byte '.' || p.iobuf[33] != byte '.') {
+ if(p != nil)
+ putsect(p);
+ return (-1,nil);
+ }
+
+ #
+ # we're done if parent is root
+ #
+ pstart := getstart(f.xf, xd);
+ putsect(p);
+ if(pstart == 0)
+ return (0, ndp);
+
+ #
+ # check that parent's . points to itself
+ #
+ p = getsect(f.xf, clust2sect(bp, pstart));
+ if(p == nil) {
+ if(debug)
+ chat(sys->sprint("getsect %d failed\n", pstart));
+ return (-1,nil);
+ }
+ if(debug)
+ dirdump(p.iobuf,0,0);
+ xd = Dosdir.arr2Dd(p.iobuf);
+ if(p.iobuf[0]!= byte '.' ||
+ p.iobuf[1]!=byte ' ' ||
+ pstart!=getstart(f.xf, xd)) {
+ if(p != nil)
+ putsect(p);
+ return (-1,nil);
+ }
+
+ #
+ # parent's parent's .. is the next entry, and has start of parent's parent's parent
+ #
+ if(debug)
+ dirdump(p.iobuf[DOSDIRSIZE:],0,0);
+
+ xd = Dosdir.arr2Dd(p.iobuf[DOSDIRSIZE:]);
+ if(xd.name[0] != '.' || xd.name[1] != '.') {
+ if(p != nil)
+ putsect(p);
+ return (-1,nil);
+ }
+ ppstart :=getstart(f.xf, xd);
+ putsect(p);
+
+ #
+ # open parent's parent's parent, and walk through it until parent's paretn is found
+ # need this to find parent's parent's addr and offset
+ #
+ ppclust := ppstart;
+ # TO DO: FAT32
+ if(ppclust != 0)
+ k := clust2sect(bp, ppclust);
+ else
+ k = bp.rootaddr;
+ p = getsect(f.xf, k);
+ if(p == nil) {
+ if(debug)
+ chat(sys->sprint("getsect %d failed\n", k));
+ return (-1,nil);
+ }
+
+ if(debug)
+ dirdump(p.iobuf,0,0);
+
+ if(ppstart) {
+ xd = Dosdir.arr2Dd(p.iobuf);
+ if(p.iobuf[0]!= byte '.' ||
+ p.iobuf[1]!= byte ' ' ||
+ ppstart!=getstart(f.xf, xd)) {
+ if(p!=nil)
+ putsect(p);
+ return (-1,nil);
+ }
+ }
+
+ for(so:=1; ;so++) {
+ for(o=0; o<bp.sectsize; o+=DOSDIRSIZE) {
+ xdname0 := p.iobuf[o];
+ if(xdname0 == byte 16r00) {
+ if(debug)
+ chat("end dir\n");
+ if(p != nil)
+ putsect(p);
+ return (-1,nil);
+ }
+
+ if(xdname0 == byte DOSEMPTY)
+ continue;
+
+ #xd = Dosdir.arr2Dd(p.iobuf[o:o+DOSDIRSIZE]);
+ xdstart:= p.iobuf[o+26:o+28]; # TO DO: getstart
+ if(bytes2short(xdstart) == pstart) {
+ putsect(p);
+ ndp.paddr = k;
+ ndp.poffset = o;
+ return (0,ndp);
+ }
+ }
+ if(ppclust) {
+ if(so%bp.clustsize == 0) {
+ ppstart = getfat(f.xf, ppstart);
+ if(ppstart < 0){
+ if(debug)
+ chat(sys->sprint("getfat %d fail\n",
+ ppstart));
+ if(p != nil)
+ putsect(p);
+ return (-1,nil);
+ }
+ }
+ k = clust2sect(bp, ppclust) +
+ so%bp.clustsize;
+ }
+ else {
+ if(so*bp.sectsize >= bp.rootsize*DOSDIRSIZE) {
+ if(p != nil)
+ putsect(p);
+ return (-1,nil);
+ }
+ k = bp.rootaddr + so;
+ }
+ putsect(p);
+ p = getsect(f.xf, k);
+ if(p == nil) {
+ if(debug)
+ chat(sys->sprint("getsect %d failed\n", k));
+ return (-1,nil);
+ }
+ }
+ putsect(p);
+ ndp.paddr = k;
+ ndp.poffset = o;
+ return (0,ndp);
+}
+
+readfile(f: ref Xfile, offset: int, count: int): (int, array of byte)
+{
+ xf := f.xf;
+ bp := xf.ptr;
+ dp := f.ptr;
+
+ length := bytes2int(dp.p.iobuf[dp.offset+28:dp.offset+32]);
+ rcnt := 0;
+ if(offset >= length)
+ return (0,nil);
+ buf := array[Styx->MAXFDATA] of byte;
+ if(offset+count >= length)
+ count = length - offset;
+ isect := offset/bp.sectsize;
+ o := offset%bp.sectsize;
+ while(count > 0) {
+ addr := fileaddr(f, isect++, 0);
+ if(addr < 0)
+ break;
+ c := bp.sectsize - o;
+ if(c > count)
+ c = count;
+ p := getsect(xf, addr);
+ if(p == nil)
+ return (-1, nil);
+ buf[rcnt:] = p.iobuf[o:o+c];
+ putsect(p);
+ count -= c;
+ rcnt += c;
+ o = 0;
+ }
+ return (rcnt, buf[0:rcnt]);
+}
+
+writefile(f: ref Xfile, buf: array of byte, offset,count: int): int
+{
+ xf := f.xf;
+ bp := xf.ptr;
+ dp := f.ptr;
+ addr := 0;
+ c: int;
+ rcnt := 0;
+ p: ref Iosect;
+
+ d := dp.d;
+ if(d == nil)
+ d = Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
+ isect := offset/bp.sectsize;
+
+ o := offset%bp.sectsize;
+ while(count > 0) {
+ addr = fileaddr(f, isect++, 1);
+ if(addr < 0)
+ break;
+ c = bp.sectsize - o;
+ if(c > count)
+ c = count;
+ if(c == bp.sectsize){
+ p = getosect(xf, addr);
+ if(p == nil)
+ return -1;
+ p.flags = 0;
+ }else{
+ p = getsect(xf, addr);
+ if(p == nil)
+ return -1;
+ }
+ p.iobuf[o:] = buf[rcnt:rcnt+c];
+ p.flags |= BMOD;
+ putsect(p);
+ count -= c;
+ rcnt += c;
+ o = 0;
+ }
+ if(rcnt <= 0 && addr < 0)
+ return -2;
+ length := 0;
+ dlen := bytes2int(d.length);
+ if(rcnt > 0)
+ length = offset+rcnt;
+ else if(dp.addr && dp.clust) {
+ c = bp.clustsize*bp.sectsize;
+ if(dp.iclust > (dlen+c-1)/c)
+ length = c*dp.iclust;
+ }
+ if(length > dlen) {
+ d.length[0] = byte length;
+ d.length[1] = byte (length>>8);
+ d.length[2] = byte (length>>16);
+ d.length[3] = byte (length>>24);
+ }
+ puttime(d);
+ dp.p.flags |= BMOD;
+ dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d);
+ return rcnt;
+}
+
+truncfile(f: ref Xfile): int
+{
+ xf := f.xf;
+ bp := xf.ptr;
+ dp := f.ptr;
+ d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
+
+ clust := getstart(f.xf, d);
+ putstart(f.xf, d, 0);
+ while(clust > 0) {
+ next := getfat(xf, clust);
+ putfat(xf, clust, 0);
+ clust = next;
+ }
+
+ d.length[0] = byte 0;
+ d.length[1] = byte 0;
+ d.length[2] = byte 0;
+ d.length[3] = byte 0;
+
+ dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d);
+ dp.iclust = 0;
+ dp.clust = 0;
+ dp.p.flags |= BMOD;
+
+ return 0;
+}
+
+getdir(arr: array of byte, addr,offset: int) :ref Sys->Dir
+{
+ dp := ref Sys->Dir;
+
+ if(arr == nil || addr == 0) {
+ dp.name = "";
+ dp.qid.path = big 0;
+ dp.qid.qtype = Sys->QTDIR;
+ dp.length = big 0;
+ dp.mode = Sys->DMDIR|8r777;
+ }
+ else {
+ dp.name = getname(arr);
+ for(i:=0; i < len dp.name; i++)
+ if(dp.name[i]>='A' && dp.name[i]<='Z')
+ dp.name[i] = dp.name[i]-'A'+'a';
+
+ # dp.qid.path = bytes2short(d.start);
+ dp.qid.path = big (addr*(Sectorsize/DOSDIRSIZE) + offset/DOSDIRSIZE);
+ dattr := int arr[11];
+
+ if(dattr & DRONLY)
+ dp.mode = 8r444;
+ else
+ dp.mode = 8r666;
+
+ dp.atime = gtime(arr);
+ dp.mtime = dp.atime;
+ if(dattr & DDIR) {
+ dp.length = big 0;
+ dp.qid.qtype |= Styx->QTDIR;
+ dp.mode |= Sys->DMDIR|8r111;
+ }
+ else
+ dp.length = big bytes2int(arr[28:32]);
+
+ if(dattr & DSYSTEM){
+ dp.mode |= Styx->DMEXCL;
+ dp.qid.qtype |= Styx->QTEXCL;
+ }
+ }
+
+ dp.qid.vers = 0;
+ dp.dtype = 0;
+ dp.dev = 0;
+ dp.uid = "dos";
+ dp.gid = "srv";
+
+ return dp;
+}
+
+putdir(d: ref Dosdir, dp: ref Sys->Dir)
+{
+ if(dp.mode & 2)
+ d.attr &= byte ~DRONLY;
+ else
+ d.attr |= byte DRONLY;
+
+ if(dp.mode & Styx->DMEXCL)
+ d.attr |= byte DSYSTEM;
+ else
+ d.attr &= byte ~DSYSTEM;
+ xputtime(d, dp.mtime);
+}
+
+getname(arr: array of byte): string
+{
+ p: string;
+ for(i:=0; i<8; i++) {
+ c := int arr[i];
+ if(c == 0 || c == ' ')
+ break;
+ if(i == 0 && c == 16r05)
+ c = 16re5;
+ p[len p] = c;
+ }
+ for(i=8; i<11; i++) {
+ c := int arr[i];
+ if(c == 0 || c == ' ')
+ break;
+ if(i == 8)
+ p[len p] = '.';
+ p[len p] = c;
+ }
+
+ return p;
+}
+
+dosname(p: string): (string, string)
+{
+ name := " ";
+ for(i := 0; i < len p && i < 8; i++) {
+ c := p[i];
+ if(c >= 'a' && c <= 'z')
+ c += 'A'-'a';
+ else if(c == '.')
+ break;
+ name[i] = c;
+ }
+ ext := " ";
+ for(j := len p - 1; j >= i; j--) {
+ if(p[j] == '.') {
+ q := 0;
+ for(j++; j < len p && q < 3; j++) {
+ c := p[j];
+ if(c >= 'a' && c <= 'z')
+ c += 'A'-'a';
+ ext[q++] = c;
+ }
+ break;
+ }
+ }
+ return (name, ext);
+}
+
+putname(p: string, d: ref Dosdir)
+{
+ if ((int d.attr & DLONG) == DLONG)
+ panic("putname of long name");
+ (d.name, d.ext) = dosname(p);
+}
+
+mystrcmp(s1, s2: string): int
+{
+ n := len s1;
+ if(n != len s2)
+ return 1;
+
+ for(i := 0; i < n; i++) {
+ c := s1[i];
+ if(c >= 'A' && c <= 'Z')
+ c -= 'A'-'a';
+ d := s2[i];
+ if(d >= 'A' && d <= 'Z')
+ d -= 'A'-'a';
+ if(c != d)
+ return 1;
+ }
+ return 0;
+}
+
+#
+# return the length of a long name in directory
+# entries or zero if it's normal dos
+#
+name2de(p: string): int
+{
+ ext := 0;
+ name := 0;
+
+ for(end := len p; --end >= 0 && p[end] != '.';)
+ ext++;
+
+ if(end > 0) {
+ name = end;
+ for(i := 0; i < end; i++) {
+ if(p[i] == '.')
+ return (len p+DOSRUNES-1)/DOSRUNES;
+ }
+ }
+ else {
+ name = ext;
+ ext = 0;
+ }
+
+ if(name <= 8 && ext <= 3 && isvalidname(p))
+ return 0;
+
+ return (len p+DOSRUNES-1)/DOSRUNES;
+}
+
+isvalidname(s: string): int
+{
+ dot := 0;
+ for(i := 0; i < len s; i++)
+ if(s[i] == '.') {
+ if(++dot > 1 || i == len s-1)
+ return 0;
+ } else if(s[i] > len isdos || isdos[s[i]] == 0)
+ return 0;
+ return 1;
+}
+
+getnamesect(arr: array of byte): string
+{
+ s: string;
+ c: int;
+
+ for(i := 1; i < 11; i += 2) {
+ c = int arr[i] | (int arr[i+1] << 8);
+ if(c == 0)
+ return s;
+ s[len s] = c;
+ }
+ for(i = 14; i < 26; i += 2) {
+ c = int arr[i] | (int arr[i+1] << 8);
+ if(c == 0)
+ return s;
+ s[len s] = c;
+ }
+ for(i = 28; i < 32; i += 2) {
+ c = int arr[i] | (int arr[i+1] << 8);
+ if(c == 0)
+ return s;
+ s[len s] = c;
+ }
+ return s;
+}
+
+# takes a long filename and converts to a short dos name, with a tag number.
+long2short(src: string,val: int): string
+{
+ dst :=" ";
+ skip:=0;
+ xskip:=0;
+ ext:=len src-1;
+ while(ext>=0 && src[ext]!='.')
+ ext--;
+
+ if (ext < 0)
+ ext=len src -1;
+
+ # convert name eliding periods
+ j:=0;
+ for(name := 0; name < ext && j<8; name++){
+ c := src[name];
+ if(c!='.' && c!=' ' && c!='\t') {
+ if(c>='a' && c<='z')
+ dst[j++] = c-'a'+'A';
+ else
+ dst[j++] = c;
+ }
+ else
+ skip++;
+ }
+
+ # convert extension
+ j=8;
+ for(xname := ext+1; xname < len src && j<11; xname++) {
+ c := src[xname];
+ if(c!=' ' && c!='\t'){
+ if (c>='a' && c<='z')
+ dst[j++] = c-'a'+'A';
+ else
+ dst[j++] = c;
+ }else
+ xskip++;
+ }
+
+ # add tag number
+ j =1;
+ for(i:=val; i > 0; i/=10)
+ j++;
+
+ if (8-j<name)
+ name = 8-j;
+ else
+ name -= skip;
+
+ dst[name]='~';
+ for(; val > 0; val /= 10)
+ dst[name+ --j] = (val%10)+'0';
+
+ if(debug)
+ chat(sys->sprint("returning dst [%s] src [%s]\n",dst,src));
+
+ return dst;
+}
+
+getfat(xf: ref Xfs, n: int): int
+{
+ bp := xf.ptr;
+ k := 0;
+
+ if(n < 2 || n >= bp.fatclusters)
+ return -1;
+ fb := bp.fatbits;
+ k = (fb*n) >> 3;
+ if(k < 0 || k >= bp.fatsize*bp.sectsize)
+ panic("getfat");
+
+ sect := k/bp.sectsize + bp.fataddr;
+ o := k%bp.sectsize;
+ p := getsect(xf, sect);
+ if(p == nil)
+ return -1;
+ k = int p.iobuf[o++];
+ if(o >= bp.sectsize) {
+ putsect(p);
+ p = getsect(xf, sect+1);
+ if(p == nil)
+ return -1;
+ o = 0;
+ }
+ k |= int p.iobuf[o++]<<8;
+ if(fb == 32){
+ # fat32 is really fat28
+ k |= int p.iobuf[o++] << 16;
+ k |= (int p.iobuf[o] & 16r0F) << 24;
+ fb = 28;
+ }
+ putsect(p);
+ if(fb == 12) {
+ if(n&1)
+ k >>= 4;
+ else
+ k &= 16rfff;
+ }
+
+ if(debug & FAT_INFO)
+ chat(sys->sprint("fat(0x%x)=0x%x...", n, k));
+
+ #
+ # check for out of range
+ #
+ if(k >= (1<<fb) - 8)
+ return -1;
+ return k;
+}
+
+putfat(xf: ref Xfs, n, val: int)
+{
+ bp := xf.ptr;
+ if(n < 2 || n >= bp.fatclusters)
+ panic(sys->sprint("putfat n=%d", n));
+ k := (bp.fatbits*n) >> 3;
+ if(k >= bp.fatsize*bp.sectsize)
+ panic("putfat");
+ sect := k/bp.sectsize + bp.fataddr;
+ for(; sect<bp.rootaddr; sect+=bp.fatsize) {
+ o := k%bp.sectsize;
+ p := getsect(xf, sect);
+ if(p == nil)
+ continue;
+ case bp.fatbits {
+ 12 =>
+ if(n&1) {
+ p.iobuf[o] &= byte 16r0f;
+ p.iobuf[o++] |= byte (val<<4);
+ if(o >= bp.sectsize) {
+ p.flags |= BMOD;
+ putsect(p);
+ p = getsect(xf, sect+1);
+ if(p == nil)
+ continue;
+ o = 0;
+ }
+ p.iobuf[o] = byte (val>>4);
+ }
+ else {
+ p.iobuf[o++] = byte val;
+ if(o >= bp.sectsize) {
+ p.flags |= BMOD;
+ putsect(p);
+ p = getsect(xf, sect+1);
+ if(p == nil)
+ continue;
+ o = 0;
+ }
+ p.iobuf[o] &= byte 16rf0;
+ p.iobuf[o] |= byte ((val>>8)&16r0f);
+ }
+ 16 =>
+ p.iobuf[o++] = byte val;
+ p.iobuf[o] = byte (val>>8);
+ 32 => # fat32 is really fat28
+ p.iobuf[o++] = byte val;
+ p.iobuf[o++] = byte (val>>8);
+ p.iobuf[o++] = byte (val>>16);
+ p.iobuf[o] = byte ((int p.iobuf[o] & 16rF0) | ((val>>24) & 16r0F));
+ * =>
+ panic("putfat fatbits");
+ }
+
+ p.flags |= BMOD;
+ putsect(p);
+ }
+}
+
+falloc(xf: ref Xfs): int
+{
+ bp := xf.ptr;
+ n := bp.freeptr;
+ for(;;) {
+ if(getfat(xf, n) == 0)
+ break;
+ if(++n >= bp.fatclusters)
+ n = FATRESRV;
+ if(n == bp.freeptr)
+ return 0;
+ }
+ bp.freeptr = n+1;
+ if(bp.freeptr >= bp.fatclusters)
+ bp.freeptr = FATRESRV;
+ putfat(xf, n, int 16rffffffff);
+ k := clust2sect(bp, n);
+ for(i:=0; i<bp.clustsize; i++) {
+ p := getosect(xf, k+i);
+ if(p == nil)
+ return -1;
+ for(j:=0; j<len p.iobuf; j++)
+ p.iobuf[j] = byte 0;
+ p.flags = BMOD;
+ putsect(p);
+ }
+ return n;
+}
+
+clust2sect(bp: ref Dosbpb, clust: int): int
+{
+ return bp.dataaddr + (clust - FATRESRV)*bp.clustsize;
+}
+
+sect2clust(bp: ref Dosbpb, sect: int): int
+{
+ c := (sect - bp.dataaddr) / bp.clustsize + FATRESRV;
+ # assert(sect == clust2sect(bp, c));
+ return c;
+}
+
+bootdump(b: ref Dosboot)
+{
+ chat(sys->sprint("magic: 0x%2.2x 0x%2.2x 0x%2.2x\n",
+ int b.magic[0], int b.magic[1], int b.magic[2]));
+ chat(sys->sprint("version: \"%8.8s\"\n", string b.version));
+ chat(sys->sprint("sectsize: %d\n", bytes2short(b.sectsize)));
+ chat(sys->sprint("allocsize: %d\n", int b.clustsize));
+ chat(sys->sprint("nresrv: %d\n", bytes2short(b.nresrv)));
+ chat(sys->sprint("nfats: %d\n", int b.nfats));
+ chat(sys->sprint("rootsize: %d\n", bytes2short(b.rootsize)));
+ chat(sys->sprint("volsize: %d\n", bytes2short(b.volsize)));
+ chat(sys->sprint("mediadesc: 0x%2.2x\n", int b.mediadesc));
+ chat(sys->sprint("fatsize: %d\n", bytes2short(b.fatsize)));
+ chat(sys->sprint("trksize: %d\n", bytes2short(b.trksize)));
+ chat(sys->sprint("nheads: %d\n", bytes2short(b.nheads)));
+ chat(sys->sprint("nhidden: %d\n", bytes2int(b.nhidden)));
+ chat(sys->sprint("bigvolsize: %d\n", bytes2int(b.bigvolsize)));
+ chat(sys->sprint("driveno: %d\n", int b.driveno));
+ chat(sys->sprint("bootsig: 0x%2.2x\n", int b.bootsig));
+ chat(sys->sprint("volid: 0x%8.8x\n", bytes2int(b.volid)));
+ chat(sys->sprint("label: \"%11.11s\"\n", string b.label));
+}
+
+xputtime(d: ref Dosdir, s: int)
+{
+ if(s == 0)
+ t := daytime->local((sys->millisec() - nowt1)/1000 + nowt);
+ else
+ t = daytime->local(s);
+ x := (t.hour<<11) | (t.min<<5) | (t.sec>>1);
+ d.time[0] = byte x;
+ d.time[1] = byte (x>>8);
+ x = ((t.year-80)<<9) | ((t.mon+1)<<5) | t.mday;
+ d.date[0] = byte x;
+ d.date[1] = byte (x>>8);
+}
+
+puttime(d: ref Dosdir)
+{
+ xputtime(d, 0);
+}
+
+gtime(a: array of byte): int
+{
+ tm := ref Daytime->Tm;
+ i := bytes2short(a[22:24]); # dos time
+ tm.hour = i >> 11;
+ tm.min = (i>>5) & 63;
+ tm.sec = (i & 31) << 1;
+ i = bytes2short(a[24:26]); # dos date
+ tm.year = 80 + (i>>9);
+ tm.mon = ((i>>5) & 15) - 1;
+ tm.mday = i & 31;
+ tm.tzoff = tzoff; # DOS time is local time
+ return daytime->tm2epoch(tm);
+}
+
+dirdump(arr: array of byte, addr, offset: int)
+{
+ if(!debug)
+ return;
+ attrchar:= "rhsvda67";
+ d := Dosdir.arr2Dd(arr);
+ buf := sys->sprint("\"%.8s.%.3s\" ", d.name, d.ext);
+ p_i:=7;
+
+ for(i := 16r80; i != 0; i >>= 1) {
+ if((d.attr & byte i) == byte i)
+ ch := attrchar[p_i];
+ else
+ ch = '-';
+ buf += sys->sprint("%c", ch);
+ p_i--;
+ }
+
+ i = bytes2short(d.time);
+ buf += sys->sprint(" %2.2d:%2.2d:%2.2d", i>>11, (i>>5)&63, (i&31)<<1);
+ i = bytes2short(d.date);
+ buf += sys->sprint(" %2.2d.%2.2d.%2.2d", 80+(i>>9), (i>>5)&15, i&31);
+ buf += sys->sprint(" %d %d", bytes2short(d.start), bytes2short(d.length));
+ buf += sys->sprint(" %d %d\n",addr,offset);
+ chat(buf);
+}
+
+putnamesect(longname: string, curslot: int, first: int, sum: int, a: array of byte)
+{
+ for(i := 0; i < DOSDIRSIZE; i++)
+ a[i] = byte 16rFF;
+ if(first)
+ a[0] = byte (16r40 | curslot);
+ else
+ a[0] = byte curslot;
+ a[11] = byte DLONG;
+ a[12] = byte 0;
+ a[13] = byte sum;
+ a[26] = byte 0;
+ a[27] = byte 0;
+ # a[1:1+10] = characters 1 to 5
+ n := len longname;
+ j := (curslot-1)*DOSRUNES;
+ for(i = 1; i < 1+10; i += 2){
+ c := 0;
+ if(j < n)
+ c = longname[j++];
+ a[i] = byte c;
+ a[i+1] = byte (c >> 8);
+ if(c == 0)
+ return;
+ }
+ # a[14:14+12] = characters 6 to 11
+ for(i = 14; i < 14+12; i += 2){
+ c := 0;
+ if(j < n)
+ c = longname[j++];
+ a[i] = byte c;
+ a[i+1] = byte (c >> 8);
+ if(c == 0)
+ return;
+ }
+ # a[28:28+4] characters 12 to 13
+ for(i = 28; i < 28+4; i += 2){
+ c := 0;
+ if(j < n)
+ c = longname[j++];
+ a[i] = byte c;
+ a[i+1] = byte (c>>8);
+ if(c == 0)
+ return;
+ }
+}
+
+putlongname(xf: ref Xfs, ndp: ref Dosptr, name: string, sname: string): int
+{
+ bp := xf.ptr;
+ first := 1;
+ sum := aliassum(sname);
+ for(nds := (len name+DOSRUNES-1)/DOSRUNES; nds > 0; nds--) {
+ putnamesect(name, nds, first, sum, ndp.p.iobuf[ndp.offset:]);
+ first = 0;
+ ndp.offset += DOSDIRSIZE;
+ if(ndp.offset == bp.sectsize) {
+ if(debug)
+ chat(sys->sprint("long name %s entry %d/%d crossing sector, addr=%d, naddr=%d", name, nds, (len name+DOSRUNES-1)/DOSRUNES, ndp.addr, ndp.naddr));
+ ndp.p.flags |= BMOD;
+ putsect(ndp.p);
+ ndp.p = nil;
+ ndp.d = nil;
+
+ # switch to the next cluster for the next long entry or the subsequent normal dir. entry
+ # naddr must be set up correctly by searchdir because we'll need one or the other
+
+ ndp.prevaddr = ndp.addr;
+ ndp.addr = ndp.naddr;
+ ndp.naddr = -1;
+ if(ndp.addr < 0)
+ return -1;
+ ndp.p = getsect(xf, ndp.addr);
+ if(ndp.p == nil)
+ return -1;
+ ndp.offset = 0;
+ }
+ }
+ return 0;
+}
+
+bytes2int(a: array of byte): int
+{
+ return (((((int a[3] << 8) | int a[2]) << 8) | int a[1]) << 8) | int a[0];
+}
+
+bytes2short(a: array of byte): int
+{
+ return (int a[1] << 8) | int a[0];
+}
+
+chat(s: string)
+{
+ if(debug)
+ sys->fprint(sys->fildes(2), "%s", s);
+}
+
+panic(s: string)
+{
+ sys->fprint(sys->fildes(2), "dosfs: panic: %s\n", s);
+ if(pflag)
+ <-chan of int; # hang here
+ raise "fail:panic";
+}
+
+Dosboot.arr2Db(arr: array of byte): ref Dosboot
+{
+ db := ref Dosboot;
+ db.magic = arr[0:3];
+ db.version = arr[3:11];
+ db.sectsize = arr[11:13];
+ db.clustsize = arr[13];
+ db.nresrv = arr[14:16];
+ db.nfats = arr[16];
+ db.rootsize = arr[17:19];
+ db.volsize = arr[19:21];
+ db.mediadesc = arr[21];
+ db.fatsize = arr[22:24];
+ db.trksize = arr[24:26];
+ db.nheads = arr[26:28];
+ db.nhidden = arr[28:32];
+ db.bigvolsize = arr[32:36];
+ db.driveno = arr[36];
+ db.bootsig = arr[38];
+ db.volid = arr[39:43];
+ db.label = arr[43:54];
+ return db;
+}
+
+Dosdir.arr2Dd(arr: array of byte): ref Dosdir
+{
+ dir := ref Dosdir;
+ for(i := 0; i < 8; i++)
+ dir.name[len dir.name] = int arr[i];
+ for(; i < 11; i++)
+ dir.ext[len dir.ext] = int arr[i];
+ dir.attr = arr[11];
+ dir.reserved = arr[12:22];
+ dir.time = arr[22:24];
+ dir.date = arr[24:26];
+ dir.start = arr[26:28];
+ dir.length = arr[28:32];
+ return dir;
+}
+
+Dosdir.Dd2arr(d: ref Dosdir): array of byte
+{
+ a := array[32] of byte;
+ i:=0;
+ for(j := 0; j < len d.name; j++)
+ a[i++] = byte d.name[j];
+ for(; j<8; j++)
+ a[i++]= byte 0;
+ for(j=0; j<len d.ext; j++)
+ a[i++] = byte d.ext[j];
+ for(; j<3; j++)
+ a[i++]= byte 0;
+ a[i++] = d.attr;
+ for(j=0; j<10; j++)
+ a[i++] = d.reserved[j];
+ for(j=0; j<2; j++)
+ a[i++] = d.time[j];
+ for(j=0; j<2; j++)
+ a[i++] = d.date[j];
+ for(j=0; j<2; j++)
+ a[i++] = d.start[j];
+ for(j=0; j<4; j++)
+ a[i++] = d.length[j];
+ return a;
+}
+
+#
+# checksum of short name for use in long name directory entries
+# assumes sname is already padded correctly to 8+3
+#
+aliassum(sname: string): int
+{
+ i := 0;
+ for(sum:=0; i<11; i++)
+ sum = (((sum&1)<<7)|((sum&16rfe)>>1))+sname[i];
+ return sum;
+}
+
+#
+# track i/o
+#
+
+# An Xfs represents the root of an external file system, anchored
+# to the server and the client
+Xfs: adt {
+ next:cyclic ref Xfs;
+ name: string; # of file containing external f.s.
+ qid: Sys->Qid; # of file containing external f.s.
+ refn: int; # attach count
+ rootqid: Sys->Qid; # of inferno constructed root directory
+ dev: ref Sys->FD; # FD of the file containing external f.s.
+ fmt: int; # successfully read format
+ offset: int; # offset in sectors to file system
+ ptr: ref Dosbpb;
+};
+
+# An Xfile represents the mapping of fid's & qid's to the server.
+Xfile: adt {
+ next: cyclic ref Xfile; # in hash bucket
+ client: int;
+ fid: int;
+ flags: int;
+ qid: Sys->Qid;
+ xf: ref Xfs;
+ ptr: ref Dosptr;
+};
+
+Iosect: adt
+{
+ next: cyclic ref Iosect;
+ flags: int;
+ t: cyclic ref Iotrack;
+ iobuf: array of byte;
+};
+
+Iotrack: adt
+{
+ flags: int;
+ xf: ref Xfs;
+ addr: int;
+ next: cyclic ref Iotrack; # in lru list
+ prev: cyclic ref Iotrack;
+ hnext: cyclic ref Iotrack; # in hash list
+ hprev: cyclic ref Iotrack;
+ refn: int;
+ tp: cyclic ref Track;
+};
+
+Track: adt
+{
+ create: fn(): ref Track;
+ p: cyclic array of ref Iosect;
+ buf: array of byte;
+};
+
+BMOD: con 1<<0;
+BIMM: con 1<<1;
+BSTALE: con 1<<2;
+
+HIOB: con 31; # a prime
+NIOBUF: con 20;
+
+Sectorsize: con 512;
+Sect2trk: con 9; # default
+
+hiob := array[HIOB+1] of ref Iotrack; # hash buckets + lru list
+iobuf := array[NIOBUF] of ref Iotrack; # the real ones
+freelist: ref Iosect;
+sect2trk := Sect2trk;
+trksize := Sect2trk*Sectorsize;
+
+FIDMOD: con 127; # prime
+xhead: ref Xfs;
+client: int;
+
+xfiles := array[FIDMOD] of ref Xfile;
+iodebug := 0;
+
+iotrackinit(sectors: int)
+{
+ if(sectors <= 0)
+ sectors = 9;
+ sect2trk = sectors;
+ trksize = sect2trk*Sectorsize;
+
+ freelist = nil;
+
+ for(i := 0;i < FIDMOD; i++)
+ xfiles[i] = ref Xfile(nil,0,0,0,Sys->Qid(big 0,0,0),nil,nil);
+
+ for(i = 0; i <= HIOB; i++)
+ hiob[i] = ref Iotrack;
+
+ for(i = 0; i < HIOB; i++) {
+ hiob[i].hprev = hiob[i];
+ hiob[i].hnext = hiob[i];
+ hiob[i].refn = 0;
+ hiob[i].addr = 0;
+ }
+ hiob[i].prev = hiob[i];
+ hiob[i].next = hiob[i];
+ hiob[i].refn = 0;
+ hiob[i].addr = 0;
+
+ for(i=0;i<NIOBUF;i++)
+ iobuf[i] = ref Iotrack;
+
+ for(i=0; i<NIOBUF; i++) {
+ iobuf[i].hprev = iobuf[i].hnext = iobuf[i];
+ iobuf[i].prev = iobuf[i].next = iobuf[i];
+ iobuf[i].refn=iobuf[i].addr=0;
+ iobuf[i].flags = 0;
+ if(hiob[HIOB].next != iobuf[i]) {
+ iobuf[i].prev.next = iobuf[i].next;
+ iobuf[i].next.prev = iobuf[i].prev;
+ iobuf[i].next = hiob[HIOB].next;
+ iobuf[i].prev = hiob[HIOB];
+ hiob[HIOB].next.prev = iobuf[i];
+ hiob[HIOB].next = iobuf[i];
+ }
+ iobuf[i].tp = Track.create();
+ }
+}
+
+Track.create(): ref Track
+{
+ t := ref Track;
+ t.p = array[sect2trk] of ref Iosect;
+ t.buf = array[trksize] of byte;
+ return t;
+}
+
+getsect(xf: ref Xfs, addr: int): ref Iosect
+{
+ return getiosect(xf, addr, 1);
+}
+
+getosect(xf: ref Xfs, addr: int): ref Iosect
+{
+ return getiosect(xf, addr, 0);
+}
+
+# get the sector corresponding to the address addr.
+getiosect(xf: ref Xfs, addr , rflag: int): ref Iosect
+{
+ # offset from beginning of track.
+ toff := addr % sect2trk;
+
+ # address of beginning of track.
+ taddr := addr - toff;
+ t := getiotrack(xf, taddr);
+
+ if(rflag && t.flags&BSTALE) {
+ if(tread(t) < 0)
+ return nil;
+
+ t.flags &= ~BSTALE;
+ }
+
+ t.refn++;
+ if(t.tp.p[toff] == nil) {
+ p := newsect();
+ t.tp.p[toff] = p;
+ p.flags = t.flags&BSTALE;
+ p.t = t;
+ p.iobuf = t.tp.buf[toff*Sectorsize:(toff+1)*Sectorsize];
+ }
+ return t.tp.p[toff];
+}
+
+putsect(p: ref Iosect)
+{
+ t: ref Iotrack;
+
+ t = p.t;
+ t.flags |= p.flags;
+ p.flags = 0;
+ t.refn--;
+ if(t.refn < 0)
+ panic("putsect: refcount");
+
+ if(t.flags & BIMM) {
+ if(t.flags & BMOD)
+ twrite(t);
+ t.flags &= ~(BMOD|BIMM);
+ }
+}
+
+# get the track corresponding to addr
+# (which is the address of the beginning of a track
+getiotrack(xf: ref Xfs, addr: int): ref Iotrack
+{
+ p: ref Iotrack;
+ mp := hiob[HIOB];
+
+ if(iodebug)
+ chat(sys->sprint("iotrack %d,%d...", xf.dev.fd, addr));
+
+ # find bucket in hash table.
+ h := (xf.dev.fd<<24) ^ addr;
+ if(h < 0)
+ h = ~h;
+ h %= HIOB;
+ hp := hiob[h];
+
+ out: for(;;){
+ loop: for(;;) {
+ # look for it in the active list
+ for(p = hp.hnext; p != hp; p=p.hnext) {
+ if(p.addr != addr || p.xf != xf)
+ continue;
+ if(p.addr == addr && p.xf == xf) {
+ break out;
+ }
+ continue loop;
+ }
+
+ # not found
+ # take oldest unref'd entry
+ for(p = mp.prev; p != mp; p=p.prev)
+ if(p.refn == 0 )
+ break;
+ if(p == mp) {
+ if(iodebug)
+ chat("iotrack all ref'd\n");
+ continue loop;
+ }
+
+ if((p.flags & BMOD)!= 0) {
+ twrite(p);
+ p.flags &= ~(BMOD|BIMM);
+ continue loop;
+ }
+ purgetrack(p);
+ p.addr = addr;
+ p.xf = xf;
+ p.flags = BSTALE;
+ break out;
+ }
+ }
+
+ if(hp.hnext != p) {
+ p.hprev.hnext = p.hnext;
+ p.hnext.hprev = p.hprev;
+ p.hnext = hp.hnext;
+ p.hprev = hp;
+ hp.hnext.hprev = p;
+ hp.hnext = p;
+ }
+ if(mp.next != p) {
+ p.prev.next = p.next;
+ p.next.prev = p.prev;
+ p.next = mp.next;
+ p.prev = mp;
+ mp.next.prev = p;
+ mp.next = p;
+ }
+ return p;
+}
+
+purgetrack(t: ref Iotrack)
+{
+ refn := sect2trk;
+ for(i := 0; i < sect2trk; i++) {
+ if(t.tp.p[i] == nil) {
+ --refn;
+ continue;
+ }
+ freesect(t.tp.p[i]);
+ --refn;
+ t.tp.p[i]=nil;
+ }
+ if(t.refn != refn)
+ panic("purgetrack");
+ if(refn!=0)
+ panic("refn not 0");
+}
+
+twrite(t: ref Iotrack): int
+{
+ if(iodebug)
+ chat(sys->sprint("[twrite %d...", t.addr));
+
+ if((t.flags & BSTALE)!= 0) {
+ refn:=0;
+ for(i:=0; i<sect2trk; i++)
+ if(t.tp.p[i]!=nil)
+ ++refn;
+
+ if(refn < sect2trk) {
+ if(tread(t) < 0) {
+ if (iodebug)
+ chat("error]");
+ return -1;
+ }
+ }
+ else
+ t.flags &= ~BSTALE;
+ }
+
+ if(devwrite(t.xf, t.addr, t.tp.buf) < 0) {
+ if(iodebug)
+ chat("error]");
+ return -1;
+ }
+
+ if(iodebug)
+ chat(" done]");
+
+ return 0;
+}
+
+tread(t: ref Iotrack): int
+{
+ refn := 0;
+ rval: int;
+
+ for(i := 0; i < sect2trk; i++)
+ if(t.tp.p[i] != nil)
+ ++refn;
+
+ if(iodebug)
+ chat(sys->sprint("[tread %d...", t.addr));
+
+ tbuf := t.tp.buf;
+ if(refn != 0)
+ tbuf = array[trksize] of byte;
+
+ rval = devread(t.xf, t.addr, tbuf);
+ if(rval < 0) {
+ if(iodebug)
+ chat("error]");
+ return -1;
+ }
+
+ if(refn != 0) {
+ for(i=0; i < sect2trk; i++) {
+ if(t.tp.p[i] == nil) {
+ t.tp.buf[i*Sectorsize:]=tbuf[i*Sectorsize:(i+1)*Sectorsize];
+ if(iodebug)
+ chat(sys->sprint("%d ", i));
+ }
+ }
+ }
+
+ if(iodebug)
+ chat("done]");
+
+ t.flags &= ~BSTALE;
+ return 0;
+}
+
+purgebuf(xf: ref Xfs)
+{
+ for(p := 0; p < NIOBUF; p++) {
+ if(iobuf[p].xf != xf)
+ continue;
+ if(iobuf[p].xf == xf) {
+ if((iobuf[p].flags & BMOD) != 0)
+ twrite(iobuf[p]);
+
+ iobuf[p].flags = BSTALE;
+ purgetrack(iobuf[p]);
+ }
+ }
+}
+
+sync()
+{
+ for(p := 0; p < NIOBUF; p++) {
+ if(!(iobuf[p].flags & BMOD))
+ continue;
+
+ if(iobuf[p].flags & BMOD){
+ twrite(iobuf[p]);
+ iobuf[p].flags &= ~(BMOD|BIMM);
+ }
+ }
+}
+
+
+newsect(): ref Iosect
+{
+ if((p := freelist)!=nil) {
+ freelist = p.next;
+ p.next = nil;
+ } else
+ p = ref Iosect(nil, 0, nil,nil);
+
+ return p;
+}
+
+freesect(p: ref Iosect)
+{
+ p.next = freelist;
+ freelist = p;
+}
+
+
+# devio from here
+deverror(name: string, xf: ref Xfs, addr,n,nret: int): int
+{
+ if(nret < 0) {
+ if(iodebug)
+ chat(sys->sprint("%s errstr=\"%r\"...", name));
+ xf.dev = nil;
+ return -1;
+ }
+ if(iodebug)
+ chat(sys->sprint("dev %d sector %d, %s: %d, should be %d\n",
+ xf.dev.fd, addr, name, nret, n));
+
+ panic(name);
+ return -1;
+}
+
+devread(xf: ref Xfs, addr: int, buf: array of byte): int
+{
+ if(xf.dev==nil)
+ return -1;
+
+ sys->seek(xf.dev, big (xf.offset+addr*Sectorsize), sys->SEEKSTART);
+ nread := sys->read(xf.dev, buf, trksize);
+ if(nread != trksize)
+ return deverror("read", xf, addr, trksize, nread);
+
+ return 0;
+}
+
+devwrite(xf: ref Xfs, addr: int, buf: array of byte): int
+{
+ if(xf.dev == nil)
+ return -1;
+
+ sys->seek(xf.dev, big (xf.offset+addr*Sectorsize), 0);
+ nwrite := sys->write(xf.dev, buf, trksize);
+ if(nwrite != trksize)
+ return deverror("write", xf, addr, trksize , nwrite);
+
+ return 0;
+}
+
+devcheck(xf: ref Xfs): int
+{
+ buf := array[Sectorsize] of byte;
+
+ if(xf.dev == nil)
+ return -1;
+
+ sys->seek(xf.dev, big 0, sys->SEEKSTART);
+ if(sys->read(xf.dev, buf, Sectorsize) != Sectorsize){
+ xf.dev = nil;
+ return -1;
+ }
+
+ return 0;
+}
+
+# setup and return the Xfs associated with "name"
+
+getxfs(name: string): (ref Xfs, string)
+{
+ if(name == nil)
+ return (nil, "no file system device specified");
+
+
+ # If the name passed is of the form 'name:offset' then
+ # offset is used to prime xf->offset. This allows accessing
+ # a FAT-based filesystem anywhere within a partition.
+ # Typical use would be to mount a filesystem in the presence
+ # of a boot manager programm at the beginning of the disc.
+
+ offset := 0;
+ for(i := 0;i < len name; i++)
+ if(name[i]==':')
+ break;
+
+ if(i < len name) {
+ offset = int name[i+1:];
+ if(offset < 0)
+ return (nil, "invalid device offset to file system");
+ offset *= Sectorsize;
+ name = name[0:i];
+ }
+
+ fd := sys->open(name, Sys->ORDWR);
+ if(fd == nil) {
+ if(iodebug)
+ chat(sys->sprint("getxfs: open(%s) failed: %r\n", name));
+ return (nil, sys->sprint("can't open %s: %r", name));
+ }
+
+ (rval,dir) := sys->fstat(fd);
+ if(rval < 0)
+ return (nil, sys->sprint("can't stat %s: %r", name));
+
+ # lock down the list of xf's.
+ fxf: ref Xfs;
+ for(xf := xhead; xf != nil; xf = xf.next) {
+ if(xf.refn == 0) {
+ if(fxf == nil)
+ fxf = xf;
+ continue;
+ }
+ if(xf.qid.path != dir.qid.path || xf.qid.vers != dir.qid.vers)
+ continue;
+
+ if(xf.name!= name || xf.dev == nil)
+ continue;
+
+ if(devcheck(xf) < 0) # look for media change
+ continue;
+
+ if(offset && xf.offset != offset)
+ continue;
+
+ if(iodebug)
+ chat(sys->sprint("incref \"%s\", dev=%d...",
+ xf.name, xf.dev.fd));
+
+ ++xf.refn;
+ return (xf, nil);
+ }
+
+ # this xf doesn't exist, make a new one and stick it on the list.
+ if(fxf == nil){
+ fxf = ref Xfs;
+ fxf.next = xhead;
+ xhead = fxf;
+ }
+
+ if(iodebug)
+ chat(sys->sprint("alloc \"%s\", dev=%d...", name, fd.fd));
+
+ fxf.name = name;
+ fxf.refn = 1;
+ fxf.qid = dir.qid;
+ fxf.dev = fd;
+ fxf.fmt = 0;
+ fxf.offset = offset;
+ return (fxf, nil);
+}
+
+refxfs(xf: ref Xfs, delta: int)
+{
+ xf.refn += delta;
+ if(xf.refn == 0) {
+ if (iodebug)
+ chat(sys->sprint("free \"%s\", dev=%d...",
+ xf.name, xf.dev.fd));
+
+ purgebuf(xf);
+ if(xf.dev !=nil)
+ xf.dev = nil;
+ }
+}
+
+xfile(fid, flag: int): ref Xfile
+{
+ pf: ref Xfile;
+
+ # find hashed file list in LRU? table.
+ k := (fid^client)%FIDMOD;
+
+ # find if this fid is in the hashed file list.
+ f:=xfiles[k];
+ for(pf = nil; f != nil; f = f.next) {
+ if(f.fid == fid && f.client == client)
+ break;
+ pf=f;
+ }
+
+ # move this fid to the front of the list if it was further down.
+ if(f != nil && pf != nil){
+ pf.next = f.next;
+ f.next = xfiles[k];
+ xfiles[k] = f;
+ }
+
+ case flag {
+ * =>
+ panic("xfile");
+ Asis =>
+ if(f != nil && f.xf != nil && f.xf.dev == nil)
+ return nil;
+ return f;
+ Clean =>
+ break;
+ Clunk =>
+ if(f != nil) {
+ xfiles[k] = f.next;
+ clean(f);
+ }
+ return nil;
+ }
+
+ # clean it up ..
+ if(f != nil)
+ return clean(f);
+
+ # f wasn't found in the hashtable, make a new one and add it
+ f = ref Xfile;
+ f.next = xfiles[k];
+ xfiles[k] = f;
+ # sort out the fid, etc.
+ f.fid = fid;
+ f.client = client;
+ f.flags = 0;
+ f.qid = Sys->Qid(big 0, 0, Styx->QTFILE);
+ f.xf = nil;
+ f.ptr = ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil);
+ return f;
+}
+
+clean(f: ref Xfile): ref Xfile
+{
+ f.ptr = nil;
+ if(f.xf != nil) {
+ refxfs(f.xf, -1);
+ f.xf = nil;
+ }
+ f.flags = 0;
+ f.qid = Sys->Qid(big 0, 0, 0);
+ return f;
+}
+
+#
+# the file at <addr, offset> has moved
+# relocate the dos entries of all fids in the same file
+#
+dosptrreloc(f: ref Xfile, dp: ref Dosptr, addr: int, offset: int)
+{
+ i: int;
+ p: ref Xfile;
+ xdp: ref Dosptr;
+
+ for(i=0; i < FIDMOD; i++){
+ for(p = xfiles[i]; p != nil; p = p.next){
+ xdp = p.ptr;
+ if(p != f && p.xf == f.xf
+ && xdp != nil && xdp.addr == addr && xdp.offset == offset){
+ *xdp = *dp;
+ xdp.p = nil;
+ # xdp.d = nil;
+ p.qid.path = big QIDPATH(xdp);
+ }
+ }
+ }
+}
diff --git a/appl/cmd/du.b b/appl/cmd/du.b
new file mode 100644
index 00000000..45b8ee1b
--- /dev/null
+++ b/appl/cmd/du.b
@@ -0,0 +1,163 @@
+implement Du;
+
+include "sys.m";
+ sys: Sys;
+ sprint: import sys;
+include "draw.m";
+include "string.m";
+ strmod: String;
+include "readdir.m";
+ readdir: Readdir;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "arg.m";
+
+aflag := 0; # all files, not just directories
+nflag := 0; # names only (but see -t); implies -a
+sflag := 0; # summary of top level names
+tflag := 0; # use modification time, not size; netlib format if -n also given
+uflag := 0; # use last use (access) time, not size
+blocksize := big 1024; # quantise length to this block size (still displayed in kb)
+bout: ref Iobuf;
+
+Du: module
+{
+ init: fn(nil: ref Draw->Context, arg: list of string);
+};
+
+kb(b: big): big
+{
+ return (((b + blocksize - big 1)/blocksize)*blocksize)/big 1024;
+}
+
+report(name: string, mtime: int, atime: int, l: big, chksum: int)
+{
+ t := mtime;
+ if(uflag)
+ t = atime;
+ if(nflag){
+ if(tflag)
+ bout.puts(sprint("%q %ud %bd %d\n", name, t, l, chksum));
+ else
+ bout.puts(sprint("%q\n", name));
+ }else{
+ if(tflag)
+ bout.puts(sprint("%ud %q\n", t, name));
+ else
+ bout.puts(sprint("%-4bd %q\n", kb(l), name));
+ }
+}
+
+# Avoid loops in tangled namespaces.
+NCACHE: con 1024; # must be power of two
+cache := array[NCACHE] of list of ref sys->Dir;
+
+seen(dir: ref sys->Dir): int
+{
+ h := int dir.qid.path & (NCACHE-1);
+ for(c := cache[h]; c!=nil; c = tl c){
+ t := hd c;
+ if(dir.qid.path==t.qid.path && dir.dtype==t.dtype && dir.dev==t.dev)
+ return 1;
+ }
+ cache[h] = dir :: cache[h];
+ return 0;
+}
+
+dir(dirname: string): big
+{
+ prefix := dirname+"/";
+ if(dirname==".")
+ prefix = nil;
+ sum := big 0;
+ (de, nde) := readdir->init(dirname, readdir->NAME);
+ if(nde < 0)
+ warn("can't read", dirname);
+ for(i := 0; i < nde; i++) {
+ s := prefix+de[i].name;
+ if(de[i].mode & Sys->DMDIR){
+ if(!seen(de[i])){ # arguably should apply to files as well
+ size := dir(s);
+ sum += size;
+ if(!sflag && !nflag)
+ report(s, de[i].mtime, de[i].atime, size, 0);
+ }
+ }else{
+ l := de[i].length;
+ sum += l;
+ if(aflag)
+ report(s, de[i].mtime, de[i].atime, l, 0);
+ }
+ }
+ return sum;
+}
+
+du(name: string)
+{
+ (rc, d) := sys->stat(name);
+ if(rc < 0){
+ warn("can't stat", name);
+ return;
+ }
+ if(d.mode & Sys->DMDIR){
+ d.length = dir(name);
+ if(nflag && !sflag)
+ return;
+ }
+ report(name, d.mtime, d.atime, d.length, 0);
+}
+
+warn(why: string, f: string)
+{
+ sys->fprint(sys->fildes(2), "du: %s %q: %r\n", why, f);
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ strmod = load String String->PATH;
+ readdir = load Readdir Readdir->PATH;
+ arg := load Arg Arg->PATH;
+ if(arg == nil || bufio==nil || arg==nil || readdir==nil || readdir==nil){
+ sys->fprint(sys->fildes(2), "du: load Error: %r\n");
+ raise "fail:can't load";
+ }
+ sys->pctl(Sys->FORKFD, nil);
+ bout = bufio->fopen(sys->fildes(1), bufio->OWRITE);
+ arg->init(args);
+ arg->setusage("du [-anstu] [-b bsize] [file ...]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' =>
+ aflag = 1;
+ 'b' =>
+ s := arg->earg();
+ blocksize = big s;
+ if(len s > 0 && s[len s-1] == 'k')
+ blocksize *= big 1024;
+ if(blocksize <= big 0)
+ blocksize = big 1;
+ 'n' =>
+ nflag = 1;
+ aflag = 1;
+ 's' =>
+ sflag = 1;
+ 't' =>
+ tflag = 1;
+ 'u' =>
+ uflag = 1;
+ tflag = 1;
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(args==nil)
+ args = "." :: nil;
+ for(; args!=nil; args = tl args)
+ du(hd args);
+ bout.close();
+}
diff --git a/appl/cmd/echo.b b/appl/cmd/echo.b
new file mode 100644
index 00000000..e47b7ed0
--- /dev/null
+++ b/appl/cmd/echo.b
@@ -0,0 +1,36 @@
+implement Echo;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+Echo: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if(args != nil)
+ args = tl args;
+ addnl := 1;
+ if(args != nil && (hd args == "-n" || hd args == "--")) {
+ if(hd args == "-n")
+ addnl = 0;
+ args = tl args;
+ }
+ s := "";
+ if(args != nil) {
+ s = hd args;
+ while((args = tl args) != nil)
+ s += " " + hd args;
+ }
+ if(addnl)
+ s[len s] = '\n';
+ a := array of byte s;
+ if(sys->write(sys->fildes(1), a, len a) < 0){
+ sys->fprint(sys->fildes(2), "echo: write error: %r\n");
+ raise "fail:write error";
+ }
+}
diff --git a/appl/cmd/ed.b b/appl/cmd/ed.b
new file mode 100644
index 00000000..e374ce4e
--- /dev/null
+++ b/appl/cmd/ed.b
@@ -0,0 +1,1588 @@
+#
+# Editor
+#
+
+implement Editor;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "regex.m";
+ regex: Regex;
+ Re: import regex;
+include "sh.m";
+ sh: Sh;
+
+Editor: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+FNSIZE: con 128; # file name
+LBSIZE: con 4096; # max line size
+BLKSIZE: con 4096; # block size in temp file
+NBLK: con 8191; # max size of temp file
+ESIZE: con 256; # max size of reg exp
+GBSIZE: con 256; # max size of global command
+MAXSUB: con 9; # max number of sub reg exp
+ESCFLG: con 16rFFFF; # escape Rune - user defined code
+EOF: con -1;
+BytesPerRune: con 2;
+RunesPerBlock: con BLKSIZE / BytesPerRune;
+
+APPEND_GETTTY, APPEND_GETSUB, APPEND_GETCOPY, APPEND_GETFILE: con iota;
+
+Subexp: adt {
+ rsp, rep: int;
+};
+
+Globp: adt {
+ s: string;
+ isnil: int;
+};
+
+addr1: int;
+addr2: int;
+anymarks: int;
+col: int;
+count: int;
+dol: int;
+dot: int;
+fchange: int;
+file: string;
+genbuf := array[LBSIZE] of int;
+given: int;
+globp: Globp;
+iblock: int;
+ichanged: int;
+io: ref Sys->FD;
+iobuf: ref Iobuf;
+lastc: int;
+line := array [70] of byte;
+linebp := -1;
+linebuf := array [LBSIZE] of int;
+listf: int;
+listn: int;
+loc1: int;
+loc2: int;
+names := array [26] of int;
+oblock: int;
+oflag: int;
+pattern: Re;
+peekc: int;
+pflag: int;
+rescuing: int;
+rhsbuf := array [LBSIZE/2] of int;
+savedfile: string;
+subnewa: int;
+subolda: int;
+subexp: array of Subexp;
+tfname: string;
+tline: int;
+waiting: int;
+wrapp: int;
+zero: array of int;
+drawctxt: ref Draw->Context;
+
+Q: con "";
+T: con "TMP";
+WRERR: con "WRITE ERROR";
+bpagesize := 20;
+hex: con "0123456789abcdef";
+linp: int;
+nlall := 128;
+tfile: ref Sys->FD;
+vflag := 1;
+
+debug(s: string)
+{
+ sys->print("%s", s);
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ drawctxt = ctxt;
+
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) {
+ sys->fprint(sys->fildes(2), "can't load %s\n", Bufio->PATH);
+ return;
+ }
+ regex = load Regex Regex->PATH;
+ if (regex == nil) {
+ sys->fprint(sys->fildes(2), "can't load %s\n", Regex->PATH);
+ return;
+ }
+
+# notify(notifyf);
+
+ if (args != nil)
+ args = tl args;
+
+ if (args != nil && hd args == "-o") {
+ oflag = 1;
+ vflag = 0;
+ args = tl args;
+ }
+
+ if (args != nil && hd args == "-") {
+ vflag = 0;
+ args = tl args;
+ }
+
+ if (oflag) {
+ savedfile = "/fd/1";
+ globp = ("a", 0);
+ } else if (args != nil) {
+ savedfile = hd args;
+ globp = ("r", 0);
+ }
+ else
+ globp = (nil, 1);
+ zero = array [nlall + 5] of int;
+ tfname = mktemp("/tmp/eXXXXX");
+# debug(sys->sprint("tfname %s\n", tfname));
+ _init();
+ for(;;){
+ {
+ commands();
+ quit();
+ }exception{
+ "savej" =>
+ ;
+ }
+ }
+}
+
+casee(c: int)
+{
+ setnoaddr();
+ if(vflag && fchange) {
+ fchange = 0;
+ error(Q);
+ }
+ filename(c);
+ _init();
+ addr2 = 0;
+ caseread();
+}
+
+casep()
+{
+ newline();
+ printcom();
+}
+
+caseq()
+{
+ setnoaddr();
+ newline();
+ quit();
+}
+
+caseread()
+{
+#debug("caseread " + file);
+ if((io=sys->open(file, Sys->OREAD)) == nil) {
+ lastc = '\n';
+ error(file);
+ }
+ iobuf = bufio->fopen(io, Sys->OREAD);
+ setwide();
+ squeeze(0);
+ c := 0 != dol;
+ append(APPEND_GETFILE, addr2);
+ exfile(Sys->OREAD);
+
+ fchange = c;
+}
+
+commands()
+{
+ a1: int;
+ c, temp: int;
+ lastsep: int;
+
+ for(;;) {
+ if(pflag) {
+ pflag = 0;
+ addr1 = addr2 = dot;
+ printcom();
+ }
+ c = '\n';
+ for(addr1 = -1;;) {
+ lastsep = c;
+ a1 = address();
+ c = getchr();
+ if(c != ',' && c != ';')
+ break;
+ if(lastsep == ',')
+ error(Q);
+ if(a1 < 0) {
+ a1 = 1;
+ if(a1 > dol)
+ a1--;
+ }
+ addr1 = a1;
+ if(c == ';')
+ dot = a1;
+ }
+ if(lastsep != '\n' && a1 < 0)
+ a1 = dol;
+ if((addr2=a1) < 0) {
+ given = 0;
+ addr2 = dot;
+ } else
+ given = 1;
+ if(addr1 < 0)
+ addr1 = addr2;
+#debug(sys->sprint("%d,%d %c\n", addr1, addr2, c));
+ case c {
+ 'a' =>
+ add(0);
+ continue;
+
+ 'b' =>
+ nonzero();
+ browse();
+ continue;
+
+ 'c' =>
+ nonzero();
+ newline();
+ rdelete(addr1, addr2);
+ append(APPEND_GETTTY, addr1-1);
+ continue;
+
+ 'd' =>
+ nonzero();
+ newline();
+ rdelete(addr1, addr2);
+ continue;
+
+ 'E' =>
+ fchange = 0;
+ c = 'e';
+ casee(c);
+ continue;
+
+ 'e' =>
+ casee(c);
+ continue;
+
+ 'f' =>
+ setnoaddr();
+ filename(c);
+ putst(savedfile);
+ continue;
+
+ 'g' =>
+ global(1);
+ continue;
+
+ 'i' =>
+ add(-1);
+ continue;
+
+ 'j' =>
+ if(!given)
+ addr2++;
+ newline();
+ join();
+ continue;
+
+ 'k' =>
+ nonzero();
+ c = getchr();
+ if(c < 'a' || c > 'z')
+ error(Q);
+ newline();
+ names[c-'a'] = zero[addr2] & ~16r1;
+ anymarks |= 16r1;
+ continue;
+
+ 'm' =>
+ move(0);
+ continue;
+
+ 'n' =>
+ listn++;
+ newline();
+ printcom();
+ continue;
+
+ '\n' =>
+ if(a1 < 0) {
+ a1 = dot+1;
+ addr2 = a1;
+ addr1 = a1;
+ }
+ if(lastsep==';')
+ addr1 = a1;
+ printcom();
+ continue;
+
+ 'l' =>
+ listf++;
+ casep();
+ continue;
+
+ 'p' or 'P' =>
+ casep();
+ continue;
+
+ 'Q' =>
+ fchange = 0;
+ caseq();
+ continue;
+
+ 'q' =>
+ caseq();
+ continue;
+
+ 'r' =>
+ filename(c);
+ caseread();
+ continue;
+
+ 's' =>
+ nonzero();
+ substitute(!globp.isnil);
+ continue;
+
+ 't' =>
+ move(1);
+ continue;
+
+ 'u' =>
+ nonzero();
+ newline();
+ if((zero[addr2]&~8r01) != subnewa)
+ error(Q);
+ zero[addr2] = subolda;
+ dot = addr2;
+ continue;
+
+ 'v' =>
+ global(0);
+ continue;
+
+ 'W' or 'w' =>
+ if (c == 'W')
+ wrapp++;
+ setwide();
+ squeeze(dol>0);
+ temp = getchr();
+ if(temp != 'q' && temp != 'Q') {
+ peekc = temp;
+ temp = 0;
+ }
+ filename(c);
+ if(!wrapp ||
+ ((io = sys->open(file, Sys->OWRITE)) == nil) ||
+ ((sys->seek(io, big 0, Sys->SEEKEND)) < big 0))
+ if((io = sys->create(file, Sys->OWRITE, 8r0666)) == nil)
+ error(file);
+ iobuf = bufio->fopen(io, Sys->OWRITE);
+ wrapp = 0;
+ if(dol > 0)
+ putfile();
+ exfile(Sys->OWRITE);
+ if(addr1<=1 && addr2==dol)
+ fchange = 0;
+ if(temp == 'Q')
+ fchange = 0;
+ if(temp)
+ quit();
+ continue;
+
+ '=' =>
+ setwide();
+ squeeze(0);
+ newline();
+ count = addr2 - 0;
+ putd();
+ putchr('\n');
+ continue;
+
+ '!' =>
+ callunix();
+ continue;
+
+ EOF =>
+ return;
+
+ }
+ error(Q);
+ }
+}
+
+printcom()
+{
+ a1: int;
+
+ nonzero();
+ a1 = addr1;
+ do {
+ if(listn) {
+ count = a1-0;
+ putd();
+ putchr('\t');
+ }
+ putshst(getline(zero[a1++]));
+ } while(a1 <= addr2);
+ dot = addr2;
+ listf = 0;
+ listn = 0;
+ pflag = 0;
+}
+
+
+address(): int
+{
+ sign, a, opcnt, nextopand, b, c: int;
+
+ nextopand = -1;
+ sign = 1;
+ opcnt = 0;
+ a = dot;
+ do {
+ do {
+ c = getchr();
+ } while(c == ' ' || c == '\t');
+ if(c >= '0' && c <= '9') {
+ peekc = c;
+ if(!opcnt)
+ a = 0;
+ a += sign*getnum();
+ } else
+ case c {
+ '$' or '.' =>
+ if (c == '$')
+ a = dol;
+ if(opcnt)
+ error(Q);
+
+ '\'' =>
+ c = getchr();
+ if(opcnt || c < 'a' || c > 'z')
+ error(Q);
+ a = 0;
+ do {
+ a++;
+ } while(a <= dol && names[c-'a'] != (zero[a] & ~8r01));
+
+ '?' or '/' =>
+ if (c == '?')
+ sign = -sign;
+ compile(c);
+ b = a;
+ for(;;) {
+ a += sign;
+ if(a <= 0)
+ a = dol;
+ if(a > dol)
+ a = 0;
+ if(match(a))
+ break;
+ if(a == b)
+ error(Q);
+ }
+ break;
+
+ * =>
+ if(nextopand == opcnt) {
+ a += sign;
+ if(a < 0 || dol < a)
+ continue; # error(Q);
+ }
+ if(c != '+' && c != '-' && c != '^') {
+ peekc = c;
+ if(opcnt == 0)
+ a = -1;
+ return a;
+ }
+ sign = 1;
+ if(c != '+')
+ sign = -sign;
+ nextopand = ++opcnt;
+ continue;
+ }
+ sign = 1;
+ opcnt++;
+ } while(0 <= a && a <= dol);
+ error(Q);
+ return -1;
+}
+
+getnum(): int
+{
+ r, c: int;
+
+ r = 0;
+ for(;;) {
+ c = getchr();
+ if(c < '0' || c > '9')
+ break;
+ r = r*10 + (c-'0');
+ }
+ peekc = c;
+ return r;
+}
+
+setwide()
+{
+ if(!given) {
+ addr1 = 0 + (dol>0);
+ addr2 = dol;
+ }
+}
+
+setnoaddr()
+{
+ if(given)
+ error(Q);
+}
+
+nonzero()
+{
+ squeeze(1);
+}
+
+squeeze(i: int)
+{
+ if(addr1 < 0+i || addr2 > dol || addr1 > addr2)
+ error(Q);
+}
+
+newline()
+{
+ c: int;
+
+ c = getchr();
+ if(c == '\n' || c == EOF)
+ return;
+ if(c == 'p' || c == 'l' || c == 'n') {
+ pflag++;
+ if(c == 'l')
+ listf++;
+ else
+ if(c == 'n')
+ listn++;
+ c = getchr();
+ if(c == '\n')
+ return;
+ }
+ error(Q);
+}
+
+filename(comm: int)
+{
+ rune: int;
+ c: int;
+
+ count = 0;
+ c = getchr();
+ if(c == '\n' || c == EOF) {
+ if(savedfile == nil && comm != 'f')
+ error(Q);
+ file = savedfile;
+ return;
+ }
+ if(c != ' ')
+ error(Q);
+ while((c=getchr()) == ' ')
+ ;
+ if(c == '\n')
+ error(Q);
+ file = nil;
+ do {
+ if(c == ' ' || c == EOF)
+ error(Q);
+ rune = c;
+ file[len file] = c;
+ } while((c=getchr()) != '\n');
+ if(savedfile == nil || comm == 'e' || comm == 'f')
+ savedfile = file;
+}
+
+exfile(om: int)
+{
+
+ if(om == Sys->OWRITE)
+ if(iobuf.flush() < 0)
+ error(Q);
+ iobuf.close();
+ iobuf = nil;
+ io = nil;
+ if(vflag) {
+ putd();
+ putchr('\n');
+ }
+}
+
+error1(s: string)
+{
+ c: int;
+
+ wrapp = 0;
+ listf = 0;
+ listn = 0;
+ count = 0;
+ sys->seek(sys->fildes(0), big 0, Sys->SEEKEND); # what does this do?
+ pflag = 0;
+ if(!globp.isnil)
+ lastc = '\n';
+ globp = (nil, 1);
+ peekc = lastc;
+ if(lastc)
+ for(;;) {
+ c = getchr();
+ if(c == '\n' || c == EOF)
+ break;
+ }
+ if(io != nil)
+ io = nil;
+ putchr('?');
+ putst(s);
+}
+
+error(s: string)
+{
+ error1(s);
+ raise "savej";
+}
+
+rescue()
+{
+ rescuing = 1;
+ if(dol > 0) {
+ addr1 = 0+1;
+ addr2 = dol;
+ io = sys->create("ed.hup", Sys->OWRITE, 8r0666);
+ if(io != nil){
+ iobuf = bufio->fopen(io, Sys->OWRITE);
+ putfile();
+ }
+ }
+ fchange = 0;
+ quit();
+}
+
+# void
+# notifyf(void *a, char *s)
+# {
+# if(strcmp(s, "interrupt") == 0){
+# if(rescuing || waiting)
+# noted(NCONT);
+# putchr(L'\n');
+# lastc = '\n';
+# error1(Q);
+# notejmp(a, savej, 0);
+# }
+# if(strcmp(s, "hangup") == 0){
+# if(rescuing)
+# noted(NDFLT);
+# rescue();
+# }
+# fprint(2, "ed: note: %s\n", s);
+# abort();
+# }
+
+getchr(): int
+{
+ s := array [Sys->UTFmax] of byte;
+ i: int;
+ r: int;
+ status: int;
+ if(lastc = peekc) {
+ peekc = 0;
+#debug(sys->sprint("getchr: peekc %c\n", lastc));
+ return lastc;
+ }
+ if(!globp.isnil) {
+ if (globp.s != nil) {
+ lastc = globp.s[0];
+ globp.s = globp.s[1:];
+#debug(sys->sprint("getchr: globp %c remaining %d\n", lastc, len globp.s));
+ return lastc;
+ }
+ globp = (nil, 1);
+#debug(sys->sprint("getchr: globp end\n"));
+ return EOF;
+ }
+#debug("globp nil\n");
+ for(i=0;;) {
+ if(sys->read(sys->fildes(0), s[i:], 1) <= 0)
+ return lastc = EOF;
+ i++;
+ (r, nil, status) = sys->byte2char(s, 0);
+ if (status > 0)
+ break;
+
+ }
+ lastc = r;
+ return lastc;
+}
+
+gety(): int
+{
+ c: int;
+ gf: int;
+ p: int;
+
+ p = 0;
+ gf = !globp.isnil;
+ for(;;) {
+ c = getchr();
+ if(c == '\n') {
+ linebuf[p] = 0;
+ return 0;
+ }
+ if(c == EOF) {
+ if(gf)
+ peekc = c;
+ return c;
+ }
+ if(c == 0)
+ continue;
+ linebuf[p++] = c;
+ if(p >= len linebuf)
+ error(Q);
+ }
+ return 0;
+}
+
+gettty(): int
+{
+ rc: int;
+
+ rc = gety();
+ if(rc)
+ return rc;
+ if(linebuf[0] == '.' && linebuf[1] == 0)
+ return EOF;
+ return 0;
+}
+
+getfile(): int
+{
+ c: int;
+ lp: int;
+
+ lp = 0;
+ do {
+ c = iobuf.getc();
+ if(c < 0) {
+ if(lp > 0) {
+ putst("'\\n' appended");
+ c = '\n';
+ } else
+ return EOF;
+ }
+ if(lp >= len linebuf) {
+ lastc = '\n';
+ error(Q);
+ }
+ linebuf[lp++] = c;
+ count++;
+ } while(c != '\n');
+ linebuf[lp - 1] = 0;
+#debug(sys->sprint("getline read %d\n", lp));
+ return 0;
+}
+
+putfile()
+{
+ a1: int;
+ lp: int;
+ c: int;
+
+ a1 = addr1;
+ do {
+ lp = getline(zero[a1++]);
+ for(;;) {
+ count++;
+ c = linebuf[lp++];
+ if(c == 0) {
+ if (iobuf.putc('\n') < 0)
+ error(Q);
+ break;
+ }
+ if (iobuf.putc(c) < 0)
+ error(Q);
+ }
+ } while(a1 <= addr2);
+ if(iobuf.flush() < 0)
+ error(Q);
+}
+
+append(f: int, a: int): int
+{
+ a1, a2, rdot, nline, _tl: int;
+ rv: int;
+
+ nline = 0;
+ dot = a;
+ for (;;) {
+ case f {
+ APPEND_GETTTY => rv = gettty();
+ APPEND_GETSUB => rv = getsub();
+ APPEND_GETCOPY => rv = getcopy();
+ APPEND_GETFILE => rv = getfile();
+ }
+ if (rv != 0)
+ break;
+ if(dol >= nlall) {
+ nlall += 512;
+ newzero := array [nlall + 5] of int;
+ if(newzero == nil) {
+ error("MEM?");
+ rescue();
+ }
+ newzero[0:] = zero;
+ zero = newzero;
+ }
+ _tl = putline();
+ nline++;
+ a1 = ++dol;
+ a2 = a1+1;
+ rdot = ++dot;
+ zero[rdot:] = zero[rdot - 1: a1];
+ zero[rdot] = _tl;
+ }
+#debug(sys->sprint("end of append - dot %d\n", dot));
+ return nline;
+}
+
+add(i: int)
+{
+ if(i && (given || dol > 0)) {
+ addr1--;
+ addr2--;
+ }
+ squeeze(0);
+ newline();
+ append(APPEND_GETTTY, addr2);
+}
+
+bformat, bnum: int;
+
+browse()
+{
+ forward, n: int;
+
+ forward = 1;
+ peekc = getchr();
+ if(peekc != '\n'){
+ if(peekc == '-' || peekc == '+') {
+ if(peekc == '-')
+ forward = 0;
+ getchr();
+ }
+ n = getnum();
+ if(n > 0)
+ bpagesize = n;
+ }
+ newline();
+ if(pflag) {
+ bformat = listf;
+ bnum = listn;
+ } else {
+ listf = bformat;
+ listn = bnum;
+ }
+ if(forward) {
+ addr1 = addr2;
+ addr2 += bpagesize;
+ if(addr2 > dol)
+ addr2 = dol;
+ } else {
+ addr1 = addr2-bpagesize;
+ if(addr1 <= 0)
+ addr1 = 0+1;
+ }
+ printcom();
+}
+
+callunix()
+{
+ buf: string;
+ c: int;
+
+ if (sh == nil)
+ sh = load Sh Sh->PATH;
+ if (sh == nil) {
+ putst("can't load shell");
+ return;
+ }
+ setnoaddr();
+ while((c=getchr()) != EOF && c != '\n')
+ buf[len buf] = c;
+ sh->system(drawctxt, buf);
+ if(vflag)
+ putst("!");
+}
+
+quit()
+{
+ if(vflag && fchange && dol!=0) {
+ fchange = 0;
+ error(Q);
+ }
+ sys->remove(tfname);
+ exit;
+}
+
+onquit(nil: int)
+{
+ quit();
+}
+
+rdelete(ad1, ad2: int)
+{
+ a1, a2, a3: int;
+
+ a1 = ad1;
+ a2 = ad2+1;
+ a3 = dol;
+ dol -= a2 - a1;
+ do {
+ zero[a1++] = zero[a2++];
+ } while (a2 <= a3);
+ a1 = ad1;
+ if(a1 > dol)
+ a1 = dol;
+ dot = a1;
+ fchange = 1;
+}
+
+gdelete()
+{
+ a1, a2, a3: int;
+
+ a3 = dol;
+ for(a1=0; (zero[a1]&8r01)==0; a1++)
+ if(a1>=a3)
+ return;
+ for(a2=a1+1; a2<=a3;) {
+ if(zero[a2] & 8r01) {
+ a2++;
+ dot = a1;
+ } else
+ zero[a1++] = zero[a2++];
+ }
+ dol = a1-1;
+ if(dot > dol)
+ dot = dol;
+ fchange = 1;
+}
+
+getline(_tl: int): int
+{
+ lp, bp: int;
+ nl: int;
+ block: array of int;
+#debug(sys->sprint("getline %d\n", _tl));
+ lp = 0;
+ (block, bp) = getblock(_tl, Sys->OREAD);
+ nl = len block - bp;
+ _tl &= ~(RunesPerBlock - 1);
+ while(linebuf[lp++] = block[bp++]) {
+ nl--;
+ if(nl == 0) {
+ (block, bp) = getblock(_tl += RunesPerBlock, Sys->OREAD);
+ nl = len block;
+ }
+ }
+ return 0;
+}
+
+putline(): int
+{
+ lp, bp: int;
+ nl, _tl: int;
+ block: array of int;
+ fchange = 1;
+ lp = 0;
+ _tl = tline;
+ (block, bp) = getblock(_tl, Sys->OWRITE);
+ nl = len block - bp;
+ _tl &= ~(RunesPerBlock-1); # _tl is now at the beginning of the block
+ while(block[bp] = linebuf[lp++]) {
+ if(block[bp++] == '\n') {
+ block[bp-1] = 0;
+ linebp = lp;
+ break;
+ }
+ nl--;
+ if(nl == 0) {
+ _tl += RunesPerBlock;
+ (block, bp) = getblock(_tl, Sys->OWRITE);
+ nl = len block;
+ }
+ }
+ nl = tline;
+ tline += ((lp) + 8r03) & 8r077776;
+ return nl;
+}
+
+tbuf := array [BLKSIZE] of byte;
+
+getrune(buf: array of byte): int
+{
+ return int buf[0] + (int buf[1] << 8);
+}
+
+putrune(buf: array of byte, v: int)
+{
+ buf[0] = byte (v);
+ buf[1] = byte (v >> 8);
+}
+
+blkio(b: int, buf: array of int, writefunc: int)
+{
+ sys->seek(tfile, big b * big BLKSIZE, Sys->SEEKSTART);
+ if (writefunc) {
+ # flatten buf into tbuf
+ for (x := 0; x < RunesPerBlock; x++)
+ putrune(tbuf[x * BytesPerRune:], buf[x]);
+ if (sys->write(tfile, tbuf, BLKSIZE) != len tbuf) {
+ error(T);
+ }
+ }
+ else {
+ if (sys->read(tfile, tbuf, len tbuf) != len tbuf) {
+ error(T);
+ }
+ for (x := 0; x < RunesPerBlock; x++)
+ buf[x] = getrune(tbuf[x * BytesPerRune:]);
+ }
+}
+
+ibuff := array [RunesPerBlock] of int;
+obuff := array [RunesPerBlock] of int;
+
+getblock(atl, iof: int): (array of int, int)
+{
+ bno, off: int;
+
+ bno = atl / RunesPerBlock;
+ off = (atl * BytesPerRune) & (BLKSIZE-1) & ~8r03;
+ if(bno >= NBLK) {
+ lastc = '\n';
+ error(T);
+ }
+ off /= BytesPerRune;
+ if(bno == iblock) {
+ ichanged |= iof;
+#debug(sys->sprint("getblock(%d, %d): returns ibuff offset %d\n", atl, iof, off));
+ return (ibuff, off);
+ }
+ if(bno == oblock) {
+#debug(sys->sprint("getblock(%d, %d): returns obuff offset %d\n", atl, iof, off));
+ return (obuff, off);
+ }
+ if(iof == Sys->OREAD) {
+ if(ichanged)
+ blkio(iblock, ibuff, 1);
+ ichanged = 0;
+ iblock = bno;
+ blkio(bno, ibuff, 0);
+#debug(sys->sprint("getblock(%d, %d): returns ibuff offset %d\n", atl, iof, off));
+ return (ibuff, off);
+ }
+ if(oblock >= 0)
+ blkio(oblock, obuff, 1);
+ oblock = bno;
+#debug(sys->sprint("getblock(%d, %d): returns offset %d\n", atl, iof, off));
+ return (obuff, off);
+}
+
+_init()
+{
+ markp: int;
+
+ tfile = nil;
+ tline = RunesPerBlock;
+ for(markp = 0; markp < len names; markp++)
+ names[markp] = 0;
+ subnewa = 0;
+ anymarks = 0;
+ iblock = -1;
+ oblock = -1;
+ ichanged = 0;
+ if((tfile = sys->create(tfname, Sys->ORDWR, 8r0600)) == nil){
+ error1(T);
+ exit;
+ }
+ dot = dol = 0;
+}
+
+global(k: int)
+{
+ globuf: string;
+ c, a1: int;
+
+ if(!globp.isnil)
+ error(Q);
+ setwide();
+ squeeze(dol > 0);
+ c = getchr();
+ if(c == '\n')
+ error(Q);
+ compile(c);
+ globuf = nil;
+ while((c=getchr()) != '\n') {
+ if(c == EOF)
+ error(Q);
+ if(c == '\\') {
+ c = getchr();
+ if(c != '\n')
+ globuf[len globuf] = '\\';
+ }
+ globuf[len globuf] = c;
+ }
+ if(globuf == nil)
+ globuf = "p";
+ globuf[len globuf] = '\n';
+ for(a1=0; a1<=dol; a1++) {
+ zero[a1] &= ~8r01;
+ if(a1 >= addr1 && a1 <= addr2 && match(a1) == k)
+ zero[a1] |= 8r01;
+ }
+
+ #
+ # Special case: g/.../d (avoid n^2 algorithm)
+
+ if(globuf[0] == 'd' && globuf[1] == '\n' && globuf[2] == 0) {
+ gdelete();
+ return;
+ }
+ for(a1=0; a1<=dol; a1++) {
+ if(zero[a1] & 8r01) {
+ zero[a1] &= ~8r01;
+ dot = a1;
+ globp = (globuf, 0);
+ commands();
+ a1 = 0;
+ }
+ }
+}
+
+join()
+{
+ gp, lp: int;
+ a1: int;
+
+ nonzero();
+ gp = 0;
+ for(a1=addr1; a1<=addr2; a1++) {
+ lp = getline(zero[a1]);
+ while(genbuf[gp] = linebuf[lp++])
+ if(gp++ >= LBSIZE-2)
+ error(Q);
+ }
+ lp = 0;
+ gp = 0;
+ while(linebuf[lp++] = genbuf[gp++])
+ ;
+ zero[addr1] = putline();
+ if(addr1 < addr2)
+ rdelete(addr1+1, addr2);
+ dot = addr1;
+}
+
+substitute(inglob: int)
+{
+ mp, a1, nl, gsubf, n: int;
+
+ n = getnum(); # OK even if n==0
+ gsubf = compsub();
+ for(a1 = addr1; a1 <= addr2; a1++) {
+ if(match(a1)){
+ m := n;
+
+ do {
+ span := loc2-loc1;
+
+ if(--m <= 0) {
+ dosub();
+ if(!gsubf)
+ break;
+ if(span == 0) { # null RE match
+ if(zero[loc2] == 0)
+ break;
+ loc2++;
+ }
+ }
+ } while(match(-1));
+ if(m <= 0) {
+ inglob |= 8r01;
+ subnewa = putline();
+ zero[a1] &= ~8r01;
+ if(anymarks) {
+ for(mp=0; mp<len names; mp++)
+ if(names[mp] == zero[a1])
+ names[mp] = subnewa;
+ }
+ subolda = zero[a1];
+ zero[a1] = subnewa;
+#debug(sys->sprint("append-getsub linebp = %d\n", linebp));
+ nl = append(APPEND_GETSUB, a1);
+ addr2 += nl;
+ }
+ }
+ }
+ if(inglob == 0)
+ error(Q);
+}
+
+compsub(): int
+{
+ seof, c: int;
+ p: int;
+
+ seof = getchr();
+ if(seof == '\n' || seof == ' ')
+ error(Q);
+ compile(seof);
+ p = 0;
+ for(;;) {
+ c = getchr();
+ if(c == '\\') {
+ c = getchr();
+ rhsbuf[p++] = ESCFLG;
+ if(p >= LBSIZE / 2)
+ error(Q);
+ } else
+ if(c == '\n' && (globp.isnil || globp.s == nil)) {
+ peekc = c;
+ pflag++;
+ break;
+ } else
+ if(c == seof)
+ break;
+ rhsbuf[p++] = c;
+ if(p >= LBSIZE / 2)
+ error(Q);
+ }
+ rhsbuf[p] = 0;
+ peekc = getchr();
+ if(peekc == 'g') {
+ peekc = 0;
+ newline();
+ return 1;
+ }
+ newline();
+ return 0;
+}
+
+getsub(): int
+{
+ p1, p2: int;
+
+ p1 = 0;
+ if((p2 = linebp) == -1)
+ return EOF;
+ while(linebuf[p1++] = linebuf[p2++])
+ ;
+ linebp = -1;
+ return 0;
+}
+
+dosub()
+{
+ lp, sp, rp: int;
+ c, n: int;
+
+# lp = linebuf;
+# sp = genbuf;
+# rp = rhsbuf;
+ lp = 0;
+ sp = 0;
+ rp = 0;
+ while(lp < loc1)
+ genbuf[sp++] = linebuf[lp++];
+ while(c = rhsbuf[rp++]) {
+ if(c == '&'){
+ sp = place(sp, loc1, loc2);
+ continue;
+ }
+ if(c == ESCFLG && (c = rhsbuf[rp++]) >= '1' && c < MAXSUB+'0') {
+ n = c-'0';
+ if(subexp != nil && subexp[n].rsp >= 0 && subexp[n].rep >= 0) {
+ sp = place(sp, subexp[n].rsp, subexp[n].rep);
+ continue;
+ }
+ error(Q);
+ }
+ genbuf[sp++] = c;
+ if(sp >= LBSIZE)
+ error(Q);
+ }
+ lp = loc2;
+ loc2 = sp;
+ while(genbuf[sp++] = linebuf[lp++])
+ if(sp >= LBSIZE)
+ error(Q);
+ linebuf[0:] = genbuf[0: sp];
+}
+
+place(sp: int, l1: int, l2: int): int
+{
+
+ while(l1 < l2) {
+ genbuf[sp++] = linebuf[l1++];
+ if(sp >= LBSIZE)
+ error(Q);
+ }
+ return sp;
+}
+
+move(cflag: int)
+{
+ _adt, ad1, ad2: int;
+
+ nonzero();
+ if((_adt = address()) < 0) # address() guarantees addr is in range
+ error(Q);
+ newline();
+ if(cflag) {
+ ad1 = dol;
+ append(APPEND_GETCOPY, ad1++);
+ ad2 = dol;
+ } else {
+ ad2 = addr2;
+ for(ad1 = addr1; ad1 <= ad2;)
+ zero[ad1++] &= ~8r01;
+ ad1 = addr1;
+ }
+ ad2++;
+ if(_adt<ad1) {
+ dot = _adt + (ad2-ad1);
+ if((++_adt)==ad1)
+ return;
+ reverse(_adt, ad1);
+ reverse(ad1, ad2);
+ reverse(_adt, ad2);
+ } else
+ if(_adt >= ad2) {
+ dot = _adt++;
+ reverse(ad1, ad2);
+ reverse(ad2, _adt);
+ reverse(ad1, _adt);
+ } else
+ error(Q);
+ fchange = 1;
+}
+
+reverse(a1, a2: int)
+{
+ t: int;
+
+ for(;;) {
+ t = zero[--a2];
+ if(a2 <= a1)
+ return;
+ zero[a2] = zero[a1];
+ zero[a1++] = t;
+ }
+}
+
+getcopy(): int
+{
+ if(addr1 > addr2)
+ return EOF;
+ getline(zero[addr1++]);
+ return 0;
+}
+
+compile(eof: int)
+{
+ c: int;
+
+ if((c = getchr()) == '\n') {
+ peekc = c;
+ c = eof;
+ }
+ if(c == eof) {
+ if(pattern == nil)
+ error(Q);
+ return;
+ }
+ pattern = nil;
+ program := "";
+ do {
+
+ if(c == '\\') {
+ program[len program] = '\\';
+ if((c = getchr()) == '\n') {
+ error(Q);
+ return;
+ }
+ }
+ program[len program] = c;
+ } while((c = getchr()) != eof && c != '\n');
+ if(c == '\n')
+ peekc = c;
+ diag: string;
+#debug("program " + program + "\n");
+ (pattern, diag) = regex->compile(program, 1);
+#if (diag != nil)
+# debug("diag " + diag + "\n");
+ if (diag != nil)
+ pattern = nil;
+}
+
+mkstring(a: array of int): string
+{
+ s: string;
+ for (x := 0; x < len a; x++) {
+ if (a[x] == 0)
+ break;
+ s[x] = a[x];
+ }
+ return s;
+}
+
+match(addr: int): int
+{
+ rsp: int;
+ if(pattern == nil)
+ return 0;
+ if(addr >= 0){
+ if(addr == 0)
+ return 0;
+ rsp = getline(zero[addr]);
+ } else
+ rsp = loc2;
+ s := mkstring(linebuf);
+ subexp = regex->executese(pattern, s, (rsp, len s), rsp == 0, 1);
+ if(subexp != nil) {
+ (loc1, loc2) = subexp[0];
+ return 1;
+ }
+ loc1 = loc2 = -1;
+ return 0;
+}
+
+putd()
+{
+ r: int;
+
+ r = count%10;
+ count /= 10;
+ if(count)
+ putd();
+ putchr(r + '0');
+}
+
+putst(s: string)
+{
+ col = 0;
+ for(x := 0; x < len s; x++)
+ putchr(s[x]);
+ putchr('\n');
+}
+
+putshst(sp: int)
+{
+ col = 0;
+ while(linebuf[sp]) {
+ putchr(linebuf[sp++]);
+ }
+ putchr('\n');
+}
+
+putchr(ac: int)
+{
+ lp: int;
+ c: int;
+ rune: int;
+ lp = linp;
+ c = ac;
+ if(listf) {
+ if(c == '\n') {
+ if(linp != 0 && line[linp - 1] == byte ' ') {
+ line[lp++] = byte '\\';
+ line[lp++] = byte 'n';
+ }
+ } else {
+ if(col > (72-6-2)) {
+ col = 8;
+ line[lp++] = byte '\\';
+ line[lp++] = byte '\n';
+ line[lp++] = byte '\t';
+ }
+ col++;
+ if(c=='\b' || c=='\t' || c=='\\') {
+ line[lp++] = byte '\\';
+ if(c == '\b')
+ c = 'b';
+ else
+ if(c == '\t')
+ c = 't';
+ col++;
+ } else
+ if(c<' ' || c>=8r0177) {
+ line[lp++] = byte '\\';
+ line[lp++] = byte 'x';
+ line[lp++] = byte hex[c>>12];
+ line[lp++] = byte hex[c>>8&16rF];
+ line[lp++] = byte hex[c>>4&16rF];
+ c = hex[c&16rF];
+ col += 5;
+ }
+ }
+ }
+
+ rune = c;
+ lp += sys->char2byte(rune, line, lp);
+
+ if(c == '\n' || lp >= len line - 5) {
+ linp = 0;
+ if (oflag)
+ sys->write(sys->fildes(2), line, lp);
+ else
+ sys->write(sys->fildes(1), line, lp);
+ return;
+ }
+ linp = lp;
+}
+
+stringfromint(i: int): string
+{
+ s: string;
+ s[0] = i;
+ return s;
+}
+
+mktemp(as: string): string
+{
+ pid: int;
+ s: string;
+
+ s = nil;
+ pid = sys->pctl(0, nil);
+ for (x := len as - 1; x >= 0; x--)
+ if (as[x] == 'X') {
+ s = stringfromint('0' + pid % 10) + s;
+ pid /= 10;
+ }
+ else
+ s = stringfromint(as[x]) + s;
+ s[len s] = 'a';
+ for (;;) {
+ (rv, nil) := sys->stat(s);
+ if (rv < 0)
+ break;
+ if (s[len s - 1] == 'z')
+ return "/";
+ s[len s - 1]++;
+ }
+ return s;
+}
diff --git a/appl/cmd/emuinit.b b/appl/cmd/emuinit.b
new file mode 100644
index 00000000..56b11521
--- /dev/null
+++ b/appl/cmd/emuinit.b
@@ -0,0 +1,110 @@
+implement Emuinit;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "arg.m";
+ arg: Arg;
+
+Emuinit: module
+{
+ init: fn();
+};
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ sys->bind("#e", "/env", sys->MREPL|sys->MCREATE); # if #e not configured, that's fine
+ args := getenv("emuargs");
+ arg = load Arg Arg->PATH;
+ if (arg == nil)
+ sys->fprint(sys->fildes(2), "emuinit: cannot load %s: %r\n", Arg->PATH);
+ else{
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'g' or 'c' or 'C' or 'm' or 'p' or 'f' or 'r' or 'd' =>
+ arg->arg();
+ }
+ args = arg->argv();
+ }
+ mod: Command;
+ (mod, args) = loadmod(args);
+ mod->init(nil, args);
+}
+
+loadmod(args: list of string): (Command, list of string)
+{
+ path := Command->PATH;
+ if(args != nil)
+ path = hd args;
+ else
+ args = "-l" :: nil; # add startup option
+
+ # try loading the module directly.
+ mod: Command;
+ if (path != nil && path[0] == '/')
+ mod = load Command path;
+ else {
+ mod = load Command "/dis/"+path;
+ if (mod == nil)
+ mod = load Command "/"+path;
+ }
+ if(mod != nil)
+ return (mod, args);
+
+ # if we can't load the module directly, try getting the shell to run it.
+ err := sys->sprint("%r");
+ mod = load Command Command->PATH;
+ if(mod == nil){
+ sys->fprint(sys->fildes(2), "emuinit: unable to load %s: %s\n", path, err);
+ raise "fail:error";
+ }
+ return (mod, "sh" :: "-c" :: "$*" :: args);
+}
+
+getenv(v: string): list of string
+{
+ fd := sys->open("#e/"+v, Sys->OREAD);
+ if (fd == nil)
+ return nil;
+ (ok, d) := sys->fstat(fd);
+ if(ok == -1)
+ return nil;
+ buf := array[int d.length] of byte;
+ n := sys->read(fd, buf, len buf);
+ if (n <= 0)
+ return nil;
+ return unquoted(string buf[0:n]);
+}
+
+unquoted(s: string): list of string
+{
+ args: list of string;
+ word: string;
+ inquote := 0;
+ for(j := len s; j > 0;){
+ c := s[j-1];
+ if(c == ' ' || c == '\t' || c == '\n'){
+ j--;
+ continue;
+ }
+ for(i := j-1; i >= 0 && ((c = s[i]) != ' ' && c != '\t' && c != '\n' || inquote); i--){ # collect word
+ if(c == '\''){
+ word = s[i+1:j] + word;
+ j = i;
+ if(!inquote || i == 0 || s[i-1] != '\'')
+ inquote = !inquote;
+ else
+ i--;
+ }
+ }
+ args = (s[i+1:j]+word) :: args;
+ word = nil;
+ j = i;
+ }
+ # if quotes were unbalanced, balance them and try again.
+ if(inquote)
+ return unquoted(s + "'");
+ return args;
+}
diff --git a/appl/cmd/env.b b/appl/cmd/env.b
new file mode 100644
index 00000000..e6fd8889
--- /dev/null
+++ b/appl/cmd/env.b
@@ -0,0 +1,53 @@
+implement Envcmd;
+
+#
+# Copyright © 2000 Vita Nuova Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "env.m";
+
+include "readdir.m";
+
+Envcmd: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stdout := sys->fildes(1);
+ if (tl argv != nil) {
+ sys->fprint(stderr(), "Usage: env\n");
+ raise "fail:usage";
+ }
+ env := load Env Env->PATH;
+ if(env == nil)
+ error(sys->sprint("can't load %s: %r", Env->PATH));
+ readdir := load Readdir Readdir->PATH;
+ if(readdir == nil)
+ error(sys->sprint("can't load %s: %r", Readdir->PATH));
+ (a, n) := readdir->init("/env",
+ Readdir->NONE | Readdir->COMPACT | Readdir->DESCENDING);
+ for(i := 0; i < len a; i++){
+ s := a[i].name+"="+env->getenv(a[i].name)+"\n";
+ b := array of byte s;
+ sys->write(stdout, b, len b);
+ }
+}
+
+error(s: string)
+{
+ sys->fprint(stderr(), "env: %s\n", s);
+ raise "fail:error";
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
diff --git a/appl/cmd/export.b b/appl/cmd/export.b
new file mode 100644
index 00000000..f6688fd3
--- /dev/null
+++ b/appl/cmd/export.b
@@ -0,0 +1,57 @@
+#
+# export current name space on a connection
+#
+
+implement Export;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+Export: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(stderr(), "Usage: export [-a] dir [connection]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ # usage: export dir [connection]
+ sys = load Sys Sys->PATH;
+ if(argv != nil)
+ argv = tl argv;
+ flag := Sys->EXPWAIT;
+ for(; argv != nil && len hd argv && (hd argv)[0] == '-'; argv = tl argv)
+ for(i := 1; i < len hd argv; i++)
+ case (hd argv)[i] {
+ 'a' =>
+ flag = Sys->EXPASYNC;
+ * =>
+ usage();
+ }
+ n := len argv;
+ if (n < 1 || n > 2)
+ usage();
+ fd: ref Sys->FD;
+ if (n == 2) {
+ if ((fd = sys->open(hd tl argv, Sys->ORDWR)) == nil) {
+ sys->fprint(stderr(), "export: can't open %s: %r\n", hd tl argv);
+ raise "fail:open";
+ }
+ } else
+ fd = sys->fildes(0);
+ if (sys->export(fd, hd argv, flag) < 0) {
+ sys->fprint(stderr(), "export: can't export: %r\n");
+ raise "fail:export";
+ }
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
diff --git a/appl/cmd/fc.b b/appl/cmd/fc.b
new file mode 100644
index 00000000..50393b14
--- /dev/null
+++ b/appl/cmd/fc.b
@@ -0,0 +1,612 @@
+implement Fc;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "math.m";
+ math: Math;
+include "string.m";
+ str: String;
+include "regex.m";
+ regex: Regex;
+
+Fc: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+
+UNARY, BINARY, SPECIAL: con iota;
+
+oSWAP, oDUP, oREP, oSUM, oPRNUM, oMULT,
+oPLUS, oMINUS, oDIV, oDIVIDE, oMOD, oSHIFTL, oSHIFTR,
+oAND, oOR, oXOR, oNOT, oUMINUS, oFACTORIAL,
+oPOW, oHYPOT, oATAN2, oJN, oYN, oSCALBN, oCOPYSIGN,
+oFDIM, oFMIN, oFMAX, oNEXTAFTER, oREMAINDER, oFMOD,
+oPOW10, oSQRT, oEXP, oEXPM1, oLOG, oLOG10, oLOG1P,
+oCOS, oCOSH, oSIN, oSINH, oTAN, oTANH, oACOS, oASIN, oACOSH,
+oASINH, oATAN, oATANH, oERF, oERFC,
+oJ0, oJ1, oY0, oY1, oILOGB, oFABS, oCEIL,
+oFLOOR, oFINITE, oISNAN, oRINT, oLGAMMA, oMODF,
+oDEG, oRAD: con iota;
+Op: adt {
+ name: string;
+ kind: int;
+ op: int;
+};
+
+ops := array[] of {
+Op
+("swap", SPECIAL, oSWAP),
+("dup", SPECIAL, oDUP),
+("rep", SPECIAL, oREP),
+("sum", SPECIAL, oSUM),
+("p", SPECIAL, oPRNUM),
+("x", BINARY, oMULT),
+("×", BINARY, oMULT),
+("pow", BINARY, oPOW),
+("xx", BINARY, oPOW),
+("+", BINARY, oPLUS),
+("-", BINARY, oMINUS),
+("/", BINARY, oDIVIDE),
+("div", BINARY, oDIV),
+("%", BINARY, oMOD),
+("shl", BINARY, oSHIFTL),
+("shr", BINARY, oSHIFTR),
+("and", BINARY, oAND),
+("or", BINARY, oOR),
+("⋀", BINARY, oAND),
+("⋁", BINARY, oOR),
+("xor", BINARY, oXOR),
+("not", UNARY, oNOT),
+("_", UNARY, oUMINUS),
+("factorial", UNARY, oFACTORIAL),
+("!", UNARY, oFACTORIAL),
+("pow", BINARY, oPOW),
+("hypot", BINARY, oHYPOT),
+("atan2", BINARY, oATAN2),
+("jn", BINARY, oJN),
+("yn", BINARY, oYN),
+("scalbn", BINARY, oSCALBN),
+("copysign", BINARY, oCOPYSIGN),
+("fdim", BINARY, oFDIM),
+("fmin", BINARY, oFMIN),
+("fmax", BINARY, oFMAX),
+("nextafter", BINARY, oNEXTAFTER),
+("remainder", BINARY, oREMAINDER),
+("fmod", BINARY, oFMOD),
+("pow10", UNARY, oPOW10),
+("sqrt", UNARY, oSQRT),
+("exp", UNARY, oEXP),
+("expm1", UNARY, oEXPM1),
+("log", UNARY, oLOG),
+("log10", UNARY, oLOG10),
+("log1p", UNARY, oLOG1P),
+("cos", UNARY, oCOS),
+("cosh", UNARY, oCOSH),
+("sin", UNARY, oSIN),
+("sinh", UNARY, oSINH),
+("tan", UNARY, oTAN),
+("tanh", UNARY, oTANH),
+("acos", UNARY, oACOS),
+("asin", UNARY, oASIN),
+("acosh", UNARY, oACOSH),
+("asinh", UNARY, oASINH),
+("atan", UNARY, oATAN),
+("atanh", UNARY, oATANH),
+("erf", UNARY, oERF),
+("erfc", UNARY, oERFC),
+("j0", UNARY, oJ0),
+("j1", UNARY, oJ1),
+("y0", UNARY, oY0),
+("y1", UNARY, oY1),
+("ilogb", UNARY, oILOGB),
+("fabs", UNARY, oFABS),
+("ceil", UNARY, oCEIL),
+("floor", UNARY, oFLOOR),
+("finite", UNARY, oFINITE),
+("isnan", UNARY, oISNAN),
+("rint", UNARY, oRINT),
+("rad", UNARY, oRAD),
+("deg", UNARY, oDEG),
+("lgamma", SPECIAL, oLGAMMA),
+("modf", SPECIAL, oMODF),
+};
+
+nHEX, nBINARY, nOCTAL, nRADIX1, nRADIX2, nREAL, nCHAR: con iota;
+pats0 := array[] of {
+nHEX => "-?0[xX][0-9a-fA-F]+",
+nBINARY => "-?0[bB][01]+",
+nOCTAL => "-?0[0-7]+",
+nRADIX1 => "-?[0-9][rR][0-8]+",
+nRADIX2 => "-?[0-3][0-9][rR][0-9a-zA-Z]+",
+nREAL => "-?(([0-9]+(\\.[0-9]+)?)|([0-9]*(\\.[0-9]+)))([eE]-?[0-9]+)?",
+nCHAR => "@.",
+};
+RADIX, ANNOTATE, CHAR: con 1 << (iota + 10);
+
+outbase := 10;
+pats: array of Regex->Re;
+stack: list of real;
+last_op: Op;
+stderr: ref Sys->FD;
+
+usage()
+{
+ sys->fprint(stderr,
+ "usage: fc [-xdbB] [-r radix] <postfix expression>\n" +
+ "option specifies output format:\n" +
+ "\t-d decimal (default)\n" +
+ "\t-x hex\n" +
+ "\t-o octal\n" +
+ "\t-b binary\n" +
+ "\t-B annotated binary\n" +
+ "\t-c character\n" +
+ "\t-r <radix> specified base in Limbo 99r9999 format\n" +
+ "operands are decimal(default), hex(0x), octal(0), binary(0b), radix(99r)\n");
+ sys->fprint(stderr, "operators are:\n");
+ for (i := 0; i < len ops; i++)
+ sys->fprint(stderr, "%s ", ops[i].name);
+ sys->fprint(stderr, "\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ math = load Math Math->PATH;
+ regex = load Regex Regex->PATH;
+ if (regex == nil) {
+ sys->fprint(stderr, "fc: cannot load %s: %r\n", Regex->PATH);
+ raise "fail:error";
+ }
+
+ initpats();
+
+ if (argv == nil || tl argv == nil)
+ return;
+ argv = tl argv;
+ a := hd argv;
+ if (len a > 1 && a[0] == '-' && number(a).t0 == 0) {
+ case a[1] {
+ 'd' =>
+ outbase = 10;
+ 'x' =>
+ outbase = 16;
+ 'o' =>
+ outbase = 8;
+ 'b' =>
+ outbase = 2;
+ 'c' =>
+ outbase = CHAR;
+ 'r' =>
+ r := 0;
+ if (len a > 2)
+ r = int a[2:];
+ else if (tl argv == nil)
+ usage();
+ else {
+ argv = tl argv;
+ r = int hd argv;
+ }
+ if (r < 2 || r > 36)
+ usage();
+ outbase = r | RADIX;
+ 'B' =>
+ outbase = 2 | ANNOTATE;
+ * =>
+ sys->fprint(stderr, "fc: unknown option -%c\n", a[1]);
+ usage();
+ }
+ argv = tl argv;
+ }
+
+ math->FPcontrol(0, Math->INVAL|Math->ZDIV|Math->OVFL|Math->UNFL|Math->INEX);
+
+ for (; argv != nil; argv = tl argv) {
+ (ok, x) := number(hd argv);
+ if (ok)
+ stack = x :: stack;
+ else {
+ op := find(hd argv);
+ exec(op);
+ last_op = op;
+ }
+ }
+
+ sp: list of real;
+ for (; stack != nil; stack = tl stack)
+ sp = hd stack :: sp;
+
+ # print stack bottom first
+ for (; sp != nil; sp = tl sp)
+ printnum(hd sp);
+}
+
+printnum(n: real)
+{
+ case outbase {
+ CHAR =>
+ sys->print("@%c\n", int n);
+ 2 =>
+ sys->print("%s\n", binary(big n));
+ 2 | ANNOTATE =>
+ sys->print("%s\n", annotatebinary(big n));
+ 8 =>
+ sys->print("%#bo\n", big n);
+ 10 =>
+ sys->print("%g\n", n);
+ 16 =>
+ sys->print("%#bx\n", big n);
+ * =>
+ if ((outbase & RADIX) == 0)
+ error("unknown output base " + string outbase);
+ sys->print("%s\n", big2string(big n, outbase & ~RADIX));
+ }
+}
+
+# convert to binary string, keeping multiples of 8 digits.
+binary(n: big): string
+{
+ s := "0b";
+ for (j := 7; j > 0; j--)
+ if ((n & (big 16rff << (j * 8))) != big 0)
+ break;
+ for (i := 63; i >= 0; i--)
+ if (i / 8 <= j)
+ s[len s] = (int (n >> i) & 1) + '0';
+ return s;
+}
+
+annotatebinary(n: big): string
+{
+ s := binary(n);
+ a := s + "\n ";
+ ndig := len s - 2;
+ for (i := ndig - 1; i >= 0; i--)
+ a[len a] = (i % 10) + '0';
+ if (ndig < 10)
+ return a;
+ a += "\n ";
+ for (i = ndig - 1; i >= 10; i--) {
+ if (i % 10 == 0)
+ a[len a] = (i / 10) + '0';
+ else
+ a[len a] = ' ';
+ }
+ return a;
+}
+
+find(name: string): Op
+{
+ # XXX could do binary search here if we weren't a lousy performer anyway
+ for (i := 0; i < len ops; i++)
+ if (name == ops[i].name)
+ break;
+ if (i == len ops)
+ error("invalid operator '" + name + "'");
+ return ops[i];
+}
+
+exec(op: Op)
+{
+ case op.kind {
+ UNARY =>
+ unaryop(op.name, op.op);
+ BINARY =>
+ binaryop(op.name, op.op);
+ SPECIAL =>
+ specialop(op.name, op.op);
+ }
+}
+
+unaryop(name: string, op: int)
+{
+ assure(1, name);
+ v := hd stack;
+ case op {
+ oNOT =>
+ v = real !(int v);
+ oUMINUS =>
+ v = -v;
+ oFACTORIAL =>
+ n := int v;
+ v = 1.0;
+ while (n > 0)
+ v *= real n--;
+ oPOW10 =>
+ v = math->pow10(int v);
+ oSQRT =>
+ v = math->sqrt(v);
+ oEXP =>
+ v = math->exp(v);
+ oEXPM1 =>
+ v = math->expm1(v);
+ oLOG =>
+ v = math->log(v);
+ oLOG10 =>
+ v = math->log10(v);
+ oLOG1P =>
+ v = math->log1p(v);
+ oCOS =>
+ v = math->cos(v);
+ oCOSH =>
+ v = math->cosh(v);
+ oSIN =>
+ v = math->sin(v);
+ oSINH =>
+ v = math->sinh(v);
+ oTAN =>
+ v = math->tan(v);
+ oTANH =>
+ v = math->tanh(v);
+ oACOS =>
+ v = math->acos(v);
+ oASIN =>
+ v = math->asin(v);
+ oACOSH =>
+ v = math->acosh(v);
+ oASINH =>
+ v = math->asinh(v);
+ oATAN =>
+ v = math->atan(v);
+ oATANH =>
+ v = math->atanh(v);
+ oERF =>
+ v = math->erf(v);
+ oERFC =>
+ v = math->erfc(v);
+ oJ0 =>
+ v = math->j0(v);
+ oJ1 =>
+ v = math->j1(v);
+ oY0 =>
+ v = math->y0(v);
+ oY1 =>
+ v = math->y1(v);
+ oILOGB =>
+ v = real math->ilogb(v);
+ oFABS =>
+ v = math->fabs(v);
+ oCEIL =>
+ v = math->ceil(v);
+ oFLOOR =>
+ v = math->floor(v);
+ oFINITE =>
+ v = real math->finite(v);
+ oISNAN =>
+ v = real math->isnan(v);
+ oRINT =>
+ v = math->rint(v);
+ oRAD =>
+ v = (v / 360.0) * 2.0 * Math->Pi;
+ oDEG =>
+ v = v / (2.0 * Math->Pi) * 360.0;
+ * =>
+ error("unknown unary operator '" + name + "'");
+ }
+ stack = v :: tl stack;
+}
+
+binaryop(name: string, op: int)
+{
+ assure(2, name);
+ v1 := hd stack;
+ v0 := hd tl stack;
+ case op {
+ oMULT =>
+ v0 = v0 * v1;
+ oPLUS =>
+ v0 = v0 + v1;
+ oMINUS =>
+ v0 = v0 - v1;
+ oDIVIDE =>
+ v0 = v0 / v1;
+ oDIV =>
+ v0 = real (big v0 / big v1);
+ oMOD =>
+ v0 = real (big v0 % big v1);
+ oSHIFTL =>
+ v0 = real (big v0 << int v1);
+ oSHIFTR =>
+ v0 = real (big v0 >> int v1);
+ oAND =>
+ v0 = real (big v0 & big v1);
+ oOR =>
+ v0 = real (big v0 | big v1);
+ oXOR =>
+ v0 = real (big v0 ^ big v1);
+ oPOW =>
+ v0 = math->pow(v0, v1);
+ oHYPOT =>
+ v0 = math->hypot(v0, v1);
+ oATAN2 =>
+ v0 = math->atan2(v0, v1);
+ oJN =>
+ v0 = math->jn(int v0, v1);
+ oYN =>
+ v0 = math->yn(int v0, v1);
+ oSCALBN =>
+ v0 = math->scalbn(v0, int v1);
+ oCOPYSIGN =>
+ v0 = math->copysign(v0, v1);
+ oFDIM =>
+ v0 = math->fdim(v0, v1);
+ oFMIN =>
+ v0 = math->fmin(v0, v1);
+ oFMAX =>
+ v0 = math->fmax(v0, v1);
+ oNEXTAFTER =>
+ v0 = math->nextafter(v0, v1);
+ oREMAINDER =>
+ v0 = math->remainder(v0, v1);
+ oFMOD =>
+ v0 = math->fmod(v0, v1);
+ * =>
+ error("unknown binary operator '" + name + "'");
+ }
+ stack = v0 :: tl tl stack;
+}
+
+specialop(name: string, op: int)
+{
+ case op {
+ oSWAP =>
+ assure(2, name);
+ stack = hd tl stack :: hd stack :: tl tl stack;
+ oDUP =>
+ assure(1, name);
+ stack = hd stack :: stack;
+ oREP =>
+ if (last_op.kind != BINARY)
+ error("invalid operator '" + last_op.name + "' for rep");
+ while (stack != nil && tl stack != nil)
+ exec(last_op);
+ oSUM =>
+ for (sum := 0.0; stack != nil; stack = tl stack)
+ sum += hd stack;
+ stack = sum :: nil;
+ oPRNUM =>
+ assure(1, name);
+ printnum(hd stack);
+ stack = tl stack;
+ oLGAMMA =>
+ assure(1, name);
+ (s, lg) := math->lgamma(hd stack);
+ stack = lg :: real s :: tl stack;
+ oMODF =>
+ assure(1, name);
+ (i, r) := math->modf(hd stack);
+ stack = r :: real i :: tl stack;
+ * =>
+ error("unknown operator '" + name + "'");
+ }
+}
+
+initpats()
+{
+ pats = array[len pats0] of Regex->Re;
+ for (i := 0; i < len pats0; i++) {
+ (re, e) := regex->compile("^" + pats0[i] + "$", 0);
+ if (re == nil) {
+ sys->fprint(stderr, "fc: bad number pattern '^%s$': %s\n", pats0[i], e);
+ raise "fail:error";
+ }
+ pats[i] = re;
+ }
+}
+
+number(s: string): (int, real)
+{
+ case s {
+ "pi" or
+ "π" =>
+ return (1, Math->Pi);
+ "e" =>
+ return (1, 2.71828182845904509);
+ "nan" or
+ "NaN" =>
+ return (1, Math->NaN);
+ "-nan" or
+ "-NaN" =>
+ return (1, -Math->NaN);
+ "infinity" or
+ "Infinity" or
+ "∞" =>
+ return (1, Math->Infinity);
+ "-infinity" or
+ "-Infinity" or
+ "-∞" =>
+ return (1, -Math->Infinity);
+ "eps" or
+ "macheps" =>
+ return (1, Math->MachEps);
+ }
+ for (i := 0; i < len pats; i++) {
+ if (regex->execute(pats[i], s) != nil)
+ break;
+ }
+ case i {
+ nHEX =>
+ return base(s, 2, 16);
+ nBINARY =>
+ return base(s, 2, 2);
+ nOCTAL =>
+ return base(s, 1, 8);
+ nRADIX1 =>
+ return base(s, 2, int s);
+ nRADIX2 =>
+ return base(s, 3, int s);
+ nREAL =>
+ return (1, real s);
+ nCHAR =>
+ return (1, real s[1]);
+ }
+ return (0, Math->NaN);
+}
+
+base(s: string, i: int, radix: int): (int, real)
+{
+ neg := s[0] == '-';
+ if (neg)
+ i++;
+ n := big 0;
+ if (radix == 10)
+ n = big s[i:];
+ else if (radix == 0 || radix > 36)
+ return (0, Math->NaN);
+ else {
+ for (; i < len s; i++) {
+ c := s[i];
+ if ('0' <= c && c <= '9')
+ n = (n * big radix) + big(c - '0');
+ else if ('a' <= c && c < 'a' + radix - 10)
+ n = (n * big radix) + big(c - 'a' + 10);
+ else if ('A' <= c && c < 'A' + radix - 10)
+ n = (n * big radix) + big(c - 'A' + 10);
+ else
+ return (0, Math->NaN);
+ }
+ }
+ if (neg)
+ n = -n;
+ return (1, real n);
+}
+
+# stolen from /appl/cmd/sh/expr.b
+big2string(n: big, radix: int): string
+{
+ if (neg := n < big 0) {
+ n = -n;
+ }
+ s := "";
+ do {
+ c: int;
+ d := int (n % big radix);
+ if (d < 10)
+ c = '0' + d;
+ else
+ c = 'a' + d - 10;
+ s[len s] = c;
+ n /= big radix;
+ } while (n > big 0);
+ t := s;
+ for (i := len s - 1; i >= 0; i--)
+ t[len s - 1 - i] = s[i];
+ if (radix != 10)
+ t = string radix + "r" + t;
+ if (neg)
+ return "-" + t;
+ return t;
+}
+
+error(e: string)
+{
+ sys->fprint(stderr, "fc: %s\n", e);
+ raise "fail:error";
+}
+
+assure(n: int, opname: string)
+{
+ if (len stack < n)
+ error("stack too small for op '" + opname + "'");
+}
diff --git a/appl/cmd/fcp.b b/appl/cmd/fcp.b
new file mode 100644
index 00000000..0bc520e4
--- /dev/null
+++ b/appl/cmd/fcp.b
@@ -0,0 +1,312 @@
+implement Fcp;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+include "readdir.m";
+ readdir: Readdir;
+
+Fcp: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+errors := 0;
+
+fdc: chan of (ref Sys->FD, ref Sys->FD);
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ arg := load Arg Arg->PATH;
+ if (arg == nil) {
+ sys->fprint(stderr, "fcp: cannot load %s: %r\n", Arg->PATH);
+ raise "fail:bad module";
+ }
+ recursive := 0;
+ nreaders := nwriters := 8;
+ arg->init(argv);
+ arg->setusage("\tfcp [-r] [-R nproc] [-W nproc] src target\n\tfcp [-r] [-R nproc] [-W nproc] src ... directory");
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'R' =>
+ nreaders = int arg->earg();
+ 'W' =>
+ nwriters = int arg->earg();
+ 'r' =>
+ recursive = 1;
+ * =>
+ arg->usage();
+ }
+ }
+ if(nreaders < 1 || nwriters < 1)
+ arg->usage();
+ if(nreaders > 1 || nwriters > 1){
+ fdc = chan of (ref Sys->FD, ref Sys->FD);
+ spawn mstream(fdc, Sys->ATOMICIO, nreaders, nwriters);
+ }
+ argv = arg->argv();
+ argc := len argv;
+ if (argc < 2)
+ arg->usage();
+ arg = nil;
+
+ dst: string;
+ for (t := argv; t != nil; t = tl t)
+ dst = hd t;
+
+ (ok, dir) := sys->stat(dst);
+ todir := (ok != -1 && (dir.mode & Sys->DMDIR));
+ if (argc > 2 && !todir) {
+ sys->fprint(stderr, "fcp: %s not a directory\n", dst);
+ raise "fail:error";
+ }
+ if (recursive)
+ cpdir(argv, dst);
+ else {
+ for (; tl argv != nil; argv = tl argv) {
+ if (todir)
+ cp(hd argv, dst, basename(hd argv));
+ else
+ cp(hd argv, dst, nil);
+ }
+ }
+ if(fdc != nil)
+ fdc <-= (nil, nil);
+ if (errors)
+ raise "fail:error";
+}
+
+basename(s: string): string
+{
+ for ((nil, ls) := sys->tokenize(s, "/"); ls != nil; ls = tl ls)
+ s = hd ls;
+ return s;
+}
+
+cp(src, dst: string, newname: string)
+{
+ ok: int;
+ ds, dd: Sys->Dir;
+
+ if (newname != nil)
+ dst += "/" + newname;
+ (ok, ds) = sys->stat(src);
+ if (ok < 0) {
+ warning(sys->sprint("%s: %r", src));
+ return;
+ }
+ if (ds.mode & Sys->DMDIR) {
+ warning(src + " is a directory");
+ return;
+ }
+ (ok, dd) = sys->stat(dst);
+ if (ok != -1 &&
+ ds.qid.path == dd.qid.path &&
+ ds.dev == dd.dev &&
+ ds.dtype == dd.dtype) {
+ warning(src + " and " + dst + " are the same file");
+ return;
+ }
+ sfd := sys->open(src, sys->OREAD);
+ if (sfd == nil) {
+ warning(sys->sprint("cannot open %s: %r", src));
+ return;
+ }
+ dfd := sys->create(dst, sys->OWRITE, ds.mode);
+ if (dfd == nil) {
+ warning(sys->sprint("cannot create %s: %r", dst));
+ return;
+ }
+ copy(sfd, dfd, src, dst);
+}
+
+mkdir(d: string, mode: int): int
+{
+ dfd := sys->create(d, sys->OREAD, sys->DMDIR | mode);
+ if (dfd == nil) {
+ warning(sys->sprint("cannot make directory %s: %r", d));
+ return -1;
+ }
+ return 0;
+}
+
+copy(sfd, dfd: ref Sys->FD, src, dst: string): int
+{
+ if(fdc != nil){
+ fdc <-= (sfd, dfd);
+ return 0;
+ }
+ buf := array[Sys->ATOMICIO] of byte;
+ for (;;) {
+ r := sys->read(sfd, buf, Sys->ATOMICIO);
+ if (r < 0) {
+ warning(sys->sprint("error reading %s: %r", src));
+ return -1;
+ }
+ if (r == 0)
+ return 0;
+ if (sys->write(dfd, buf, r) != r) {
+ warning(sys->sprint("error writing %s: %r", dst));
+ return -1;
+ }
+ }
+}
+
+cpdir(argv: list of string, dst: string)
+{
+ readdir = load Readdir Readdir->PATH;
+ if (readdir == nil) {
+ sys->fprint(stderr, "fcp: cannot load %s: %r\n", Readdir->PATH);
+ raise "fail:bad module";
+ }
+ cache = array[NCACHE] of list of ref Sys->Dir;
+ dexists := 0;
+ (ok, dd) := sys->stat(dst);
+ # destination file exists
+ if (ok != -1) {
+ if ((dd.mode & Sys->DMDIR) == 0) {
+ warning(dst + ": destination not a directory");
+ return;
+ }
+ dexists = 1;
+ }
+ for (; tl argv != nil; argv = tl argv) {
+ ds: Sys->Dir;
+ src := hd argv;
+ (ok, ds) = sys->stat(src);
+ if (ok < 0) {
+ warning(sys->sprint("can't stat %s: %r", src));
+ continue;
+ }
+ if ((ds.mode & Sys->DMDIR) == 0) {
+ cp(hd argv, dst, basename(hd argv));
+ } else if (dexists) {
+ if (ds.qid.path==dd.qid.path &&
+ ds.dev==dd.dev &&
+ ds.dtype==dd.dtype) {
+ warning("cannot copy " + src + " into itself");
+ continue;
+ }
+ copydir(src, dst + "/" + basename(src), ds.mode);
+ } else {
+ copydir(src, dst, ds.mode);
+ }
+ }
+}
+
+copydir(src, dst: string, srcmode: int)
+{
+ (ok, nil) := sys->stat(dst);
+ if (ok != -1) {
+ warning("cannot copy " + src + " onto another directory");
+ return;
+ }
+ tmode := srcmode | 8r777; # Fix for Nt
+ if (mkdir(dst, tmode) == -1)
+ return;
+ (entries, n) := readdir->init(src, Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ e := entries[i];
+ path := src + "/" + e.name;
+ if ((e.mode & Sys->DMDIR) == 0)
+ cp(path, dst, e.name);
+ else if (seen(e))
+ warning(path + ": directory loop found");
+ else
+ copydir(path, dst + "/" + e.name, e.mode);
+ }
+ chmod(dst, srcmode);
+}
+
+# Avoid loops in tangled namespaces. (from du.b)
+NCACHE: con 64; # must be power of two
+cache: array of list of ref sys->Dir;
+
+seen(dir: ref sys->Dir): int
+{
+ savlist := cache[int dir.qid.path&(NCACHE-1)];
+ for(c := savlist; c!=nil; c = tl c){
+ sav := hd c;
+ if(dir.qid.path==sav.qid.path &&
+ dir.dtype==sav.dtype && dir.dev==sav.dev)
+ return 1;
+ }
+ cache[int dir.qid.path&(NCACHE-1)] = dir :: savlist;
+ return 0;
+}
+
+warning(e: string)
+{
+ sys->fprint(stderr, "fcp: %s\n", e);
+ errors++;
+}
+
+chmod(s: string, mode: int): int
+{
+ (ok, d) := sys->stat(s);
+ if (ok < 0)
+ return -1;
+
+ if(d.mode == mode)
+ return 0;
+ d = sys->nulldir;
+ d.mode = mode;
+ if (sys->wstat(s, d) < 0) {
+ warning(sys->sprint("cannot wstat %s: %r", s));
+ return -1;
+ }
+ return 0;
+}
+
+mstream(fdc: chan of (ref Sys->FD, ref Sys->FD), bufsize: int, nin, nout: int)
+{
+ inc := chan of (ref Sys->FD, big, int, ref Sys->FD);
+ outc := chan of (ref Sys->FD, big, array of byte);
+ for(i := 0; i < nin; i++)
+ spawn readproc(inc, outc);
+ for(i = 0; i < nout; i++)
+ spawn writeproc(outc);
+ while(((src, dst) := <-fdc).t0 != nil){
+ (ok, stat) := sys->fstat(src);
+ if(ok == -1)
+ continue;
+ tot := stat.length;
+ o := big 0;
+ while((n := tot - o) > big 0){
+ if(n < big bufsize)
+ inc <-= (src, o, int n, dst);
+ else
+ inc <-= (src, o, bufsize, dst);
+ o += big bufsize;
+ }
+ }
+ for(i = 0; i < nin; i++)
+ inc <-= (nil, big 0, 0, nil);
+ for(i = 0; i < nout; i++)
+ outc <-= (nil, big 0, nil);
+}
+
+readproc(inc: chan of (ref Sys->FD, big, int, ref Sys->FD), outc: chan of (ref Sys->FD, big, array of byte))
+{
+ buf: array of byte;
+ while(((src, o, nb, dst) := <-inc).t0 != nil){
+ if(len buf < nb)
+ buf = array[nb*2] of byte;
+ n := sys->pread(src, buf, nb, o);
+ if(n > 0){
+ outc <-= (dst, o, buf[0:n]);
+ buf = buf[n:];
+ }
+ }
+}
+
+writeproc(outc: chan of (ref Sys->FD, big, array of byte))
+{
+ while(((dst, o, buf) := <-outc).t0 != nil)
+ sys->pwrite(dst, buf, len buf, o);
+}
diff --git a/appl/cmd/fmt.b b/appl/cmd/fmt.b
new file mode 100755
index 00000000..337f9fd2
--- /dev/null
+++ b/appl/cmd/fmt.b
@@ -0,0 +1,204 @@
+implement Fmt;
+
+#
+# Copyright © 2002 Lucent Technologies Inc.
+# based on the Plan 9 command; subject to the Lucent Public License 1.02
+# this Vita Nuova variant uses Limbo channels and processes to avoid accumulating words
+#
+
+#
+# block up paragraphs, possibly with indentation
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+
+Fmt: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+extraindent := 0; # how many spaces to indent all lines
+indent := 0; # current value of indent, before extra indent
+length := 70; # how many columns per output line
+join := 1; # can lines be joined?
+maxtab := 8;
+bout: ref Iobuf;
+
+Word: adt {
+ text: string;
+ indent: int;
+ bol: int;
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg := load Arg Arg->PATH;
+
+ arg->init(args);
+ arg->setusage("fmt [-j] [-i indent] [-l length] [file...]");
+ while((c := arg->opt()) != 0)
+ case(c){
+ 'i' =>
+ extraindent = int arg->earg();
+ 'j' =>
+ join = 0;
+ 'w' or 'l' =>
+ length = int arg->earg();
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(length <= extraindent){
+ sys->fprint(sys->fildes(2), "fmt: line length<=indentation\n");
+ raise "fail:length";
+ }
+ arg = nil;
+
+ err := "";
+ bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ if(args == nil){
+ bin := bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ fmt(bin);
+ }else
+ for(; args != nil; args = tl args){
+ bin := bufio->open(hd args, Bufio->OREAD);
+ if(bin == nil){
+ sys->fprint(sys->fildes(2), "fmt: can't open %s: %r\n", hd args);
+ err = "open";
+ }else{
+ fmt(bin);
+ if(tl args != nil)
+ bout.putc('\n');
+ }
+ }
+ bout.flush();
+ if(err != nil)
+ raise "fail:"+err;
+}
+
+fmt(f: ref Iobuf)
+{
+ words := chan of ref Word;
+ spawn parser(f, words);
+ printwords(words);
+}
+
+parser(f: ref Iobuf, words: chan of ref Word)
+{
+ while((s := f.gets('\n')) != nil){
+ if(s[len s-1] == '\n')
+ s = s[0:len s-1];
+ parseline(s, words);
+ }
+ words <-= nil;
+}
+
+parseline(line: string, words: chan of ref Word)
+{
+ ind: int;
+ (line, ind) = indentof(line);
+ indent = ind;
+ bol := 1;
+ for(i:=0; i < len line;){
+ # find next word
+ if(line[i] == ' ' || line[i] == '\t'){
+ i++;
+ continue;
+ }
+ # where does this word end?
+ for(l:=i; l < len line; l++)
+ if(line[l]==' ' || line[l]=='\t')
+ break;
+ words <-= ref Word(line[i:l], indent, bol);
+ bol = 0;
+ i = l;
+ }
+ if(bol)
+ words <-= ref Word("", -1, bol);
+}
+
+indentof(line: string): (string, int)
+{
+ ind := 0;
+ for(i:=0; i < len line; i++)
+ case line[i] {
+ ' ' =>
+ ind++;
+ '\t' =>
+ ind += maxtab;
+ ind -= ind%maxtab;
+ * =>
+ return (line, ind);
+ }
+ # plain white space doesn't change the indent
+ return (line, indent);
+}
+
+printwords(words: chan of ref Word)
+{
+ # one output line per loop
+ nw := <-words;
+ while((w := nw) != nil){
+ # if it's a blank line, print it
+ if(w.indent == -1){
+ bout.putc('\n');
+ nw = <-words;
+ continue;
+ }
+ # emit leading indent
+ col := extraindent+w.indent;
+ printindent(col);
+ # emit words until overflow; always emit at least one word
+ for(n:=0;; n++){
+ bout.puts(w.text);
+ col += len w.text;
+ if((nw = <-words) == nil)
+ break; # out of words
+ if(nw.indent != w.indent)
+ break; # indent change
+ nsp := nspaceafter(w.text);
+ if(col+nsp+len nw.text > extraindent+length)
+ break; # fold line
+ if(!join && nw.bol)
+ break;
+ for(j:=0; j<nsp; j++)
+ bout.putc(' '); # emit space; another word will follow
+ col += nsp;
+ w = nw;
+ }
+ bout.putc('\n');
+ }
+}
+
+printindent(w: int)
+{
+ while(w >= maxtab){
+ bout.putc('\t');
+ w -= maxtab;
+ }
+ while(--w >= 0)
+ bout.putc(' ');
+}
+
+# give extra space if word ends with punctuation
+nspaceafter(s: string): int
+{
+ if(len s < 2)
+ return 1;
+ if(len s < 4 && s[0] >= 'A' && s[0] <= 'Z')
+ return 1; # assume it's a title, not full stop
+ if((c := s[len s-1]) == '.' || c == '!' || c == '?')
+ return 2;
+ return 1;
+}
diff --git a/appl/cmd/fone.b b/appl/cmd/fone.b
new file mode 100644
index 00000000..51bbede6
--- /dev/null
+++ b/appl/cmd/fone.b
@@ -0,0 +1,560 @@
+implement fone;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+ stdout: ref Sys->FD;
+ logfd: ref Sys->FD;
+
+include "draw.m";
+ draw: Draw;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "sh.m";
+ smtp: Command;
+
+#include "keyring.m";
+
+include "daytime.m";
+ daytime: Daytime;
+
+TIMEGRAN: con 60000;
+debug := 0;
+logflag := 0;
+logfile := ""; # name of log file
+Nphones := 0; # number of telephone sets configured
+voicefile := ""; # name of serial port to DECTalk
+voice: ref sys->FD;
+mailhost := "";
+
+person: adt {
+ mailaddr: string;
+ name: string; # name pronounced by the voice
+ lineno: string; # 4 digit extension
+ time: string;
+ orignum: string; # originating number
+ origname: string; # originating name
+ state: int;
+ flags: int;
+};
+
+# states
+ONHOOK: con 0;
+RING: con 1;
+DISPLAY: con 2;
+OFFHOOK: con 3;
+
+# flags
+LOG: con 1;
+MAIL: con 2;
+ANNOUNCE: con 4;
+
+telset: adt {
+ devfile: string; # file name of interface to phone set
+ apprfile: string;
+ apprtime: int; # time appearance file is read
+ phonefd: ref sys->FD; # open FD for this set
+ numappr: int; # number of appearances on this set
+ people: array of person; # appearance data for this set
+ version: string; # telephone set version
+};
+
+phone:= array[4] of telset;
+
+months:= array[13] of { 0 => "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug","Sep", "Oct", "Nov", "Dec"};
+
+fone: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string) {
+
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ str = load String String->PATH;
+ daytime = load Daytime Daytime->PATH;
+ smtp = load Command "smtp.dis";
+# keyring := load Keyring Keyring->PATH;
+
+ stdout = sys->fildes(1);
+ logfd = stdout;
+ stderr = sys->fildes(2);
+ voicechan := chan of string;
+ timechan := chan of string;
+
+#
+# set up name space. According to tradition this is done
+# outside of the program. Needs to be here so debugging
+# is not so tedious.
+#
+ if (sys->pctl(sys->FORKNS, nil) < 0) {
+ sys->fprint(stderr, "pctl(FORKNS) failed: %r\n");
+ exit;
+ }
+ if (sys->bind("#t", "/dev", sys->MAFTER) < 0) {
+ sys->fprint(stderr, "bind #t failed: %r\n");
+ exit;
+ }
+ if (sys->bind("#p", "/prog", sys->MAFTER) < 0) {
+ sys->fprint(stderr, "bind #p failed: %r\n");
+ exit;
+ }
+
+ if (sys->bind("#C", "/", sys->MAFTER) < 0) {
+ sys->fprint(stderr, "bind #C failed: %r\n");
+ exit;
+ }
+
+ argv = tl argv;
+ while(argv != nil && len hd argv && (arg := hd argv)[0] == '-' && len arg > 1){
+ case arg[1] {
+ 'd' =>
+ debug = 1;
+ logflag = 1;
+ }
+ argv = tl argv;
+ }
+ configfile("fone.cfg");
+#
+# Sound Blaster using sbtalker and read
+#
+# voice = SBsetup();
+#
+# DECtalk using second serial port
+ voice = DTsetup(voicefile);
+
+ sys->fprint(voice, "hello.\r");
+
+
+ spawn timekeeper(timechan);
+ for (phoneid := 0; phoneid < Nphones; phoneid++)
+ spawn watchphone(phoneid, voicechan);
+ for (;;) alt {
+ mesg := <- voicechan =>
+ sys->fprint(voice, "%s", mesg);
+ tmesg := <- timechan =>
+ case tmesg {
+ "filecheck" =>
+ for (i:=0; i<Nphones; i++) {
+ (r, f) := sys->stat(phone[i].apprfile);
+ if (r < 0) {
+ sys->fprint(stderr, "cannot stat %s: %r\n", phone[i].apprfile);
+ continue;
+ }
+ if (f.mtime > phone[i].apprtime)
+ getcallapprinfo(i);
+ }
+ }
+ }
+}
+
+#
+# read in the configuration file which tells the program which
+# files and devices to use.
+#
+configfile(cfgname: string): int {
+ line, errstr: string;
+
+ cfgfd := sys->open(cfgname, sys->OREAD);
+ if (cfgfd == nil) {
+ sys->fprint(stderr, "open %s failed, %r\n", cfgname);
+ bye();
+ }
+ do {
+ (line, errstr) = getline(cfgfd);
+ if (errstr != nil) {
+ sys->fprint(stderr, "error reading config file: %r\n");
+ return -1;
+ }
+ if (line != nil) {
+ (i, t) := sys->tokenize(line, ": \t\r\n");
+ if ((hd t)[0] == '#') continue;
+ case hd t {
+ "logfile" =>
+ if (i < 2) {
+ sys->fprint(stderr, "no log file name found. %d\n", i);
+ sys->fprint(stderr, "logfile: log_file_name\n");
+ return -1;
+ }
+ t = tl t;
+ logfile = hd t;
+ if (logfile != nil) {
+ if ((logfd = sys->open(logfile, sys->OWRITE)) == nil) {
+ sys->fprint(stderr, "open log file %s failed\n", logfile);
+ continue;
+ }
+ logflag = 1;
+ }
+ "mailhost" =>
+ if (i < 2) {
+ sys->fprint(stderr, "no mailhost found.");
+ sys->fprint(stderr, "mailhost: host_name\n");
+ return -1;
+ }
+ t = tl t;
+ mailhost = hd t;
+ "voice" =>
+ if (i < 2) {
+ sys->fprint(stderr, "no log file name found.");
+ sys->fprint(stderr, "voice: serial_port\n");
+ return -1;
+ }
+ t = tl t;
+ voicefile = hd t;
+ "phone" =>
+ if (i < 3) {
+ sys->fprint(stderr, "not enough fields for phone attendance line\n");
+ sys->fprint(stderr, "attend: serial_port phone_appearance_file_name\n");
+ return -1;
+ }
+ t = tl t;
+ phonefile := hd t;
+ t = tl t;
+ apprfile := hd t;
+ phone[Nphones].devfile = phonefile;
+ phone[Nphones].apprfile = apprfile;
+ phone[Nphones].phonefd = sys->open(phonefile, sys->ORDWR);
+ if (phone[Nphones].phonefd == nil) {
+ sys->fprint(stderr, "open %s failed, %r\n", phonefile);
+ return -1;
+ }
+ (numappr, version) := phoneinit(Nphones);
+ if (numappr == 0) continue;
+ phone[Nphones].numappr = numappr;
+ phone[Nphones].people = array[numappr + 1] of person;
+ phone[Nphones].version = version;
+ if (debug) sys->fprint(stderr, "phone %d initialized\n", Nphones);
+ getcallapprinfo(Nphones);
+ ++Nphones;
+ * =>
+ sys->fprint(stderr, "bad keyword <%s> in configuration file\n", hd t);
+ return -1;
+ }
+ }
+ } while (line != nil);
+ return 0;
+}
+
+#
+#
+#
+timekeeper(tchan: chan of string) {
+ for(;;) {
+ sys->sleep(TIMEGRAN);
+ tchan <- = sys->sprint("filecheck");
+ }
+}
+
+#
+# monitor the status messages of the phone(s).
+# look for ring indications and subsequent display data to send
+# to users if they do not answer their phones.
+# If display data is received and the phone is not answered,
+# a mail message is sent.
+#
+watchphone(pindex: int, voicechan: chan of string) {
+ buf, errbuf: string;
+
+ do {
+ (buf, errbuf) = getline(phone[pindex].phonefd);
+ if (errbuf != nil) {
+ sys->fprint(stderr, "%s\n", errbuf);
+ return;
+ }
+ if (debug) sys->fprint(stderr, "phone %d: %s\n", pindex, buf);
+ (resultcode, info) := str->splitl(buf, ":");
+ if (resultcode == nil) continue;
+
+ # get rid of colon
+ info = info[1:];
+
+ (i, t) := sys->tokenize(info, ",");
+ appr := int hd t;
+ t = tl t;
+ --i;
+ case resultcode {
+ "RING" or "02" =>
+ if ((phone[pindex].people[appr].flags & ANNOUNCE))
+ voicechan <- = sys->sprint("phone call for, %s.\r", phone[pindex].people[appr].name);
+ phone[pindex].people[appr].state = RING;
+ phone[pindex].people[appr].time = "";
+ phone[pindex].people[appr].orignum = "";
+ phone[pindex].people[appr].origname = "";
+ "DISPLAY" or "06" =>
+ if (i <= 0) {
+ sys->fprint(stderr, "not enough args for DISPLAY result code\n");
+ continue;
+ }
+ displaydata := hd t;
+ (displaytype, s) := str->toint(displaydata[0:2], 16);
+ case displaytype {
+ 16r03 =>
+ # originating number
+ phone[pindex].people[appr].orignum = displaydata[2:];
+ 16r05 =>
+ # originating name
+ phone[pindex].people[appr].origname = displaydata[2:];
+ 16r0a =>
+ correct24hr: int;
+
+ # date and time
+ if (displaydata[13:15] == "pm")
+ correct24hr = 12;
+ else
+ correct24hr = 0;
+# hour := int displaydata[8:10] + correct24hr;
+ phone[pindex].people[appr].time = sys->sprint("%s %2d %2d:%.2d", months[int displaydata[2:4]], int displaydata[5:7], int displaydata[8:10] % 12 + correct24hr, int displaydata[11:13]);
+ phone[pindex].people[appr].state = DISPLAY;
+ if (logflag && (phone[pindex].people[appr].flags & LOG))
+ sys->fprint(logfd, "%s: x%s %s (%s)\n", phone[pindex].people[appr].time, phone[pindex].people[appr].lineno, phone[pindex].people[appr].orignum, phone[pindex].people[appr].origname);
+ }
+ "SIGNAL" or "13" =>
+ signalcode := hd t;
+ t = tl t;
+ --i;
+ case signalcode {
+ "4F" =>
+ if (i <= 0) {
+ if (phone[pindex].people[appr].state == DISPLAY) {
+ phone[pindex].people[appr].state = OFFHOOK;
+ }
+ continue;
+ }
+ causecode := hd t;
+ case causecode {
+ "10" =>
+ case phone[pindex].people[appr].state {
+ DISPLAY =>
+ if ((phone[pindex].people[appr].flags & MAIL) && phone[pindex].people[appr].mailaddr != "-") {
+ mailmesg := sys->sprint("From: phoneca\nTo: %s\nSubject: Phone call from %s\n\n from: %s\n phone: %s\n time: %s\n", phone[pindex].people[appr].mailaddr, phone[pindex].people[appr].orignum, phone[pindex].people[appr].origname, phone[pindex].people[appr].orignum, phone[pindex].people[appr].time);
+
+ spawn smtp->init(nil, "smtp" :: mailhost :: "phoneca" :: phone[pindex].people[appr].mailaddr :: mailmesg :: nil);
+ }
+ }
+ phone[pindex].people[appr].state = ONHOOK;
+ }
+ }
+ }
+ } while(errbuf == nil);
+}
+
+usage() {
+ sys->fprint(stderr, "usage: fone -d phone_dev\n");
+ bye();
+}
+
+#
+# wait for an OK from a particular phone, part of Hayes protocol
+OK(phonefd: ref sys->FD): int {
+ buf, err: string;
+
+ do {
+ (buf, err) = getline(phonefd);
+ if (err != nil) {
+ sys->fprint(stderr, "%s\n", err);
+ return(0);
+ }
+ if (debug) sys->fprint(stderr, "%s\n", buf);
+ } while (buf != "OK" && buf != "0");
+ return(1);
+}
+
+bye() {
+ exit;
+}
+
+phoneinit(pindex: int): (int, string) {
+ buf, err: string;
+ i: int;
+ t: list of string;
+
+ phonefd := phone[pindex].phonefd;
+# E0=echo OFF, V0=verbal return codes ON/OFF, &D0=ignore DTR transition
+ if (debug) sys->fprint(stderr, "initialize phone %d serial port...", pindex);
+ sys->fprint(phonefd, "ATE0V1&D0\r");
+ if (!OK(phonefd)) return (0, "cannot initialize phone");
+
+# &&I=init phone, I3=report phone type
+ if (debug) sys->fprint(stderr, "get phone version...");
+ sys->fprint(phonefd, "AT&&II3\r");
+ do {
+ (buf, err) = getline(phonefd);
+ if (err != nil) {
+ sys->fprint(stderr, "%s\n", err);
+ return (0, "cannot get phone version");
+ }
+ (i, t) = sys->tokenize(buf, " \n\r");
+ } while (i != 4 || hd t != "03-");
+ t = tl t;
+ if (!OK(phonefd)) return (0, "cannot get phone version");
+ version := hd t;
+ if (debug) sys->fprint(stderr, "version <%s>\n", version);
+ numappr := int version[2:4];
+
+# %A0=3 channel assigned to control voice
+ if (debug) sys->fprint(stderr, "control phone's voice channel...");
+ sys->fprint(phonefd, "AT%%A0=3\r");
+ if (!OK(phonefd)) return (0, "cannot control voice channel");
+ return (numappr, version);
+}
+
+#
+# get a line of text (up to a newline or carriage return)
+# throw away initial newlines or carriage returns
+#
+getline(fd: ref sys->FD): (string, string) {
+ c := array[1] of byte;
+ s := "";
+ i := 0;
+
+ loop: while(i < 4096) {
+ r := sys->read(fd, c, 1);
+ if(r < 0)
+ return (s, sys->sprint("%r"));
+ if(r == 0)
+ return (nil, nil);
+ case int c[0] {
+ '\r' or
+ '\n' =>
+ if(i != 0)
+ break loop;
+ * =>
+ s[i++] = int c[0];
+ }
+
+ }
+ return (s, nil);
+}
+#
+# read in names and mail addresses for appearances on each phone
+#
+getcallapprinfo(pindex: int) {
+ name : string;
+ filename := phone[pindex].apprfile;
+
+ if (debug) sys->fprint(stderr, "getting call appearance data from %s\n", filename);
+ who := bufio->open(filename, sys->OREAD);
+ if (who == nil) {
+ sys->fprint(stderr, "open %s failed, %r\n", filename);
+ bye();
+ }
+ phone[pindex].apprtime = daytime->now();
+ while ((s := who.gets('\n')) != nil) {
+ if ((array of byte(s))[0] == byte '#') continue;
+ (i, t) := sys->tokenize(s, " \t\n\r");
+ if(i < 5) {
+ sys->fprint(stderr, "Error in %s. The line was:\n%s\n", filename, s);
+ continue;
+ }
+ appr := int hd t;
+ t = tl t;
+ phone[pindex].people[appr].lineno = hd t;
+ t = tl t;
+ flags := hd t;
+ phone[pindex].people[appr].flags = 0;
+ for (n:=0; n<len flags; n++) {
+ case int (array of byte flags)[n] {
+ 'l' =>
+ phone[pindex].people[appr].flags |= LOG;
+ 'm' =>
+ phone[pindex].people[appr].flags |= MAIL;
+ 'a' =>
+ phone[pindex].people[appr].flags |= ANNOUNCE;
+ * =>
+ sys->fprint(stderr, "unknown flag %c\n", int (array of byte flags)[n]);
+ }
+ }
+ t = tl t;
+ phone[pindex].people[appr].mailaddr = hd t;
+ t = tl t;
+ name = "";
+ while(t != nil) {
+ name += " " + hd t;
+ t = tl t;
+ }
+ phone[pindex].people[appr].name = name;
+# if (debug) sys->fprint(stderr, "added user %s at %d\n", phone[pindex].people[appr].name, appr);
+ }
+}
+
+#
+# Setup connection to use READ.EXE command in SounBlaster software
+#
+SBsetup(): ref sys->FD {
+ cmd := sys->open("/cmd/clone", sys->ORDWR);
+ if (cmd == nil) {
+ sys->fprint(stderr, "open %s failed, %r\n", "/cmd/clone");
+ bye();
+ }
+ cmdno := array[32] of byte;
+ if ((n:=sys->read(cmd, cmdno, 32)) <= 0) {
+ sys->fprint(stderr, "read error: %r\n");
+ bye();
+ }
+ cmddirname := "/cmd/" + string cmdno[0:n];
+
+ if (debug) sys->fprint(stderr, "exec'ing command\n");
+ if ((n=sys->fprint(cmd, "exec command")) < 0) {
+ sys->fprint(stderr, "fprint of cmd failed:%r\n");
+ bye();
+ }
+
+ cmddata := sys->open(cmddirname + "/data", sys->ORDWR);
+ if (cmddata == nil) {
+ sys->fprint(stderr, "open %s:%r\n", cmddirname + "/data");
+ bye();
+ }
+
+ buf := array[128] of byte;
+# sys->fprint(stderr, "sending sbtalker\n");
+ if ((n=sys->fprint(cmddata, "sbtalker /dBLASTER\r")) < 0) {
+ sys->fprint(stderr, "fprint of cmddata failed:%r\n");
+ bye();
+ }
+ n = sys->read(cmddata, buf, 128);
+ if (n < 0) {
+ sys->fprint(stderr, "read /cmd/n/data failed:%r\n");
+ bye();
+ }
+ sys->fprint(stderr, "%*s\n", n, string buf[0:n]);
+
+# sys->fprint(stderr, "sending read\n");
+ if ((n=sys->fprint(cmddata, "read\r")) < 0) {
+ sys->fprint(stderr, "fprint of cmddata failed:%r\n");
+ bye();
+ }
+ n = sys->read(cmddata, buf, 128);
+ if (n < 0) {
+ sys->fprint(stderr, "read /cmd/n/data failed:%r\n");
+ bye();
+ }
+ sys->fprint(stderr, "%*s\n", n, string buf[0:n]);
+ return cmddata;
+}
+
+#
+# setup connection to DECTalk
+#
+DTsetup(voicedev: string): ref sys->FD {
+ voicel := sys->open(voicedev, sys->ORDWR);
+ if (voicel == nil) {
+ sys->fprint(stderr, "open %s failed, %r\n", voicedev);
+ bye();
+ }
+ voicectl := sys->open(voicedev+"ctl", sys->OWRITE);
+ if (voicectl == nil) {
+ sys->fprint(stderr, "open %s failed, %r\n", voicedev+"ctl");
+ bye();
+ }
+ if (sys->fprint(voicectl, "B1200") != 5) {
+ sys->fprint(stderr, "write %s failed, %r\n", voicedev+"ctl");
+ bye();
+ }
+ return voicel;
+}
diff --git a/appl/cmd/fortune.b b/appl/cmd/fortune.b
new file mode 100755
index 00000000..7368e992
--- /dev/null
+++ b/appl/cmd/fortune.b
@@ -0,0 +1,100 @@
+#
+# initially generated by c2l
+#
+
+implement Fortune;
+
+Fortune: module
+{
+ init: fn(nil: ref Draw->Context, argl: list of string);
+};
+
+include "sys.m";
+ sys: Sys;
+ Dir: import sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "rand.m";
+ rand: Rand;
+
+include "keyring.m";
+include "security.m";
+
+choice: string;
+findex := "/lib/games/fortunes.index";
+fortunes := "/lib/games/fortunes";
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ rand = load Rand Rand->PATH;
+
+ if(args != nil)
+ args = tl args;
+ if(args != nil)
+ filename := hd args;
+ else
+ filename = fortunes;
+ if((f := bufio->open(filename, Bufio->OREAD)) == nil){
+ sys->fprint(sys->fildes(2), "fortune: can't open %s: %r\n", filename);
+ raise "fail:open";
+ }
+ ix, nix: ref Sys->FD;
+ length := big 0;
+ if(args == nil){
+ ix = sys->open(findex, Sys->OREAD);
+ if(ix != nil){
+ (nil, ixbuf) := sys->fstat(ix);
+ (nil, fbuf) := sys->fstat(f.fd);
+ if(fbuf.mtime > ixbuf.mtime){
+ ix = nil;
+ nix = sys->create(findex, Sys->OWRITE, 8r666);
+ }else
+ length = ixbuf.length;
+ }else
+ nix = sys->create(findex, Sys->OWRITE, 8r666);
+ }
+ off := array[4] of byte;
+ if(ix != nil && length != big 0){
+ sys->seek(ix, ((big truerand() & ((big 1<<32)-big 1))%length) & ~big 3, 0);
+ sys->read(ix, off, 4);
+ f.seek(big (int off[0]|int off[1]<<8|int off[2]<<16|int off[3]<<24), 0);
+ choice = f.gets('\n');
+ if(choice == nil)
+ choice = "Misfortune!\n";
+ }else{
+ rand->init(truerand());
+ offs := 0;
+ g := bufio->fopen(ix, Bufio->ORDWR);
+ for(i := 1;; i++){
+ if(nix != nil)
+ offs = int f.offset();
+ p := f.gets('\n');
+ if(p == nil)
+ break;
+ if(nix != nil){
+ off[0] = byte offs;
+ off[1] = byte (offs>>8);
+ off[2] = byte (offs>>16);
+ off[3] = byte (offs>>24);
+ g.write(off, 4);
+ }
+ if(rand->rand(i) == 0)
+ choice = p;
+ }
+ g.flush();
+ }
+ sys->print("%s", choice);
+}
+
+truerand(): int
+{
+ random := load Random Random->PATH;
+ return random->randomint(Random->ReallyRandom);
+}
diff --git a/appl/cmd/freq.b b/appl/cmd/freq.b
new file mode 100755
index 00000000..4629da30
--- /dev/null
+++ b/appl/cmd/freq.b
@@ -0,0 +1,112 @@
+implement Freq;
+
+#
+# Copyright © 2002 Lucent Technologies Inc.
+# transliteration of the Plan 9 command; subject to the Lucent Public License 1.02
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+
+Freq: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+count := array[1<<16] of big;
+flag := 0;
+
+Fdec, Fhex, Foct, Fchar, Frune: con 1<<iota;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg := load Arg Arg->PATH;
+
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ * =>
+ sys->fprint(sys->fildes(2), "freq: unknown option %c\n", c);
+ raise "fail:usage";
+ 'd' =>
+ flag |= Fdec;
+ 'x' =>
+ flag |= Fhex;
+ 'o' =>
+ flag |= Foct;
+ 'c' =>
+ flag |= Fchar;
+ 'r' =>
+ flag |= Frune;
+ }
+ args = arg->argv();
+ arg = nil;
+
+ bout := bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if((flag&(Fdec|Fhex|Foct|Fchar)) == 0)
+ flag |= Fdec|Fhex|Foct|Fchar;
+ if(args == nil){
+ freq(sys->fildes(0), "-", bout);
+ exit;
+ }
+ for(; args != nil; args = tl args){
+ f := sys->open(hd args, Sys->OREAD);
+ if(f == nil){
+ sys->fprint(sys->fildes(2), "cannot open %s\n", hd args);
+ continue;
+ }
+ freq(f, hd args, bout);
+ f = nil;
+ }
+}
+
+freq(f: ref Sys->FD, s: string, bout: ref Iobuf)
+{
+ c: int;
+
+ bin := bufio->fopen(f, Sys->OREAD);
+ if(flag&Frune)
+ for(;;){
+ c = bin.getc();
+ if(c < 0)
+ break;
+ count[c]++;
+ }
+ else
+ for(;;){
+ c = bin.getb();
+ if(c < 0)
+ break;
+ count[c]++;
+ }
+ if(c != Bufio->EOF)
+ sys->fprint(sys->fildes(2), "freq: read error on %s: %r\n", s);
+ for(i := 0; i < (len count)/4; i++){
+ if(count[i] == big 0)
+ continue;
+ if(flag&Fdec)
+ bout.puts(sys->sprint("%3d ", i));
+ if(flag&Foct)
+ bout.puts(sys->sprint("%.3o ", i));
+ if(flag&Fhex)
+ bout.puts(sys->sprint("%.2x ", i));
+ if(flag&Fchar)
+ if(i <= 16r20 || i >= 16r7f && i < 16ra0 || i > 16rff && !(flag&Frune))
+ bout.puts("- ");
+ else
+ bout.puts(sys->sprint("%c ", i));
+ bout.puts(sys->sprint("%8bd\n", count[i]));
+ }
+ bout.flush();
+}
+
diff --git a/appl/cmd/fs.b b/appl/cmd/fs.b
new file mode 100644
index 00000000..0314b0bf
--- /dev/null
+++ b/appl/cmd/fs.b
@@ -0,0 +1,109 @@
+implement Fs;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "readdir.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Quit: import Fslib;
+
+# fs distribution:
+
+# {filter -d {not {match -r '\.(dis|sbl)$'}} {filter {path /module/fslib.m /module/bundle.m /module/unbundle.m /appl/cmd/fs.b /appl/cmd/fs /appl/lib/fslib.b} /}}
+
+Fs: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+badmod(path: string)
+{
+ sys->fprint(stderr(), "fs: cannot load %s: %r\n", path);
+ raise "fail:bad module";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ fslib->init();
+ argv = tl argv;
+
+ if(argv == nil)
+ usage();
+ report := Report.new();
+ s := hd argv;
+ if(tl argv == nil && s != nil && s[0] == '{' && s[len s - 1] == '}')
+ s = "void " + hd argv;
+ else {
+ s = "void {" + hd argv;
+ for(argv = tl argv; argv != nil; argv = tl argv){
+ a := hd argv;
+ if(a == nil || a[0] != '{') # }
+ s += sys->sprint(" %q", a);
+ else
+ s += " " + hd argv;
+ }
+ s += "}";
+ }
+ m := load Fsmodule "/dis/fs/eval.dis";
+ if(m == nil)
+ badmod("/dis/fs/eval.dis");
+ if(!fslib->typecompat("as", m->types())){
+ sys->fprint(stderr(), "fs: eval module implements incompatible type (usage: %s)\n",
+ fslib->cmdusage("eval", m->types()));
+ raise "fail:bad eval module";
+ }
+ m->init();
+ v := m->run(ctxt, report, nil, ref Value.S(s) :: nil);
+ fail: string;
+ if(v == nil)
+ fail = "error";
+ else{
+ sync := v.v().i;
+ sync <-= 1;
+ }
+ report.enable();
+ while((e := <-report.reportc) != nil)
+ sys->fprint(stderr(), "fs: %s\n", e);
+ if(fail != nil)
+ raise "fail:" +fail;
+}
+
+usage()
+{
+ fd := stderr();
+ sys->fprint(fd, "usage: fs expression\n");
+ sys->fprint(fd, "verbs are:\n");
+ if((readdir := load Readdir Readdir->PATH) == nil){
+ sys->fprint(fd, "fs: cannot load %s: %r\n", Readdir->PATH);
+ }else{
+ (a, nil) := readdir->init("/dis/fs", Readdir->NAME|Readdir->COMPACT);
+ for(i := 0; i < len a; i++){
+ f := a[i].name;
+ if(len f < 4 || f[len f - 4:] != ".dis")
+ continue;
+ m := load Fsmodule "/dis/fs/" + f;
+ if(m == nil)
+ sys->fprint(fd, "\t(%s: cannot load: %r)\n", f[0:len f - 4]);
+ else
+ sys->fprint(fd, "\t%s\n", fslib->cmdusage(f[0:len f - 4], m->types()));
+ }
+ }
+ sys->fprint(fd, "automatic conversions:\n");
+ sys->fprint(fd, "\tstring -> fs {walk string}\n");
+ sys->fprint(fd, "\tfs -> entries {entries fs}\n");
+ sys->fprint(fd, "\tstring -> gate {match string}\n");
+ sys->fprint(fd, "\tentries -> void {print entries}\n");
+ sys->fprint(fd, "\tcommand -> string {run command}\n");
+ raise "fail:usage";
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
diff --git a/appl/cmd/fs/and.b b/appl/cmd/fs/and.b
new file mode 100644
index 00000000..ff867409
--- /dev/null
+++ b/appl/cmd/fs/and.b
@@ -0,0 +1,65 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "pppp*";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ c := chan of Gatequery;
+ spawn andgate(c, args);
+ return ref Value.P(c);
+}
+
+andgate(c: Gatechan, args: list of ref Value)
+{
+ sub: list of Gatechan;
+ for(; args != nil; args = tl args)
+ sub = (hd args).p().i :: sub;
+ sub = rev(sub);
+ myreply := chan of int;
+ while(((d, reply) := <-c).t0.t0 != nil){
+ for(l := sub; l != nil; l = tl l){
+ (hd l) <-= (d, myreply);
+ if(<-myreply == 0)
+ break;
+ }
+ reply <-= l == nil;
+ }
+ for(; sub != nil; sub = tl sub)
+ hd sub <-= (Nilentry, nil);
+}
+
+rev[T](x: list of T): list of T
+{
+ l: list of T;
+ for(; x != nil; x = tl x)
+ l = hd x :: l;
+ return l;
+}
diff --git a/appl/cmd/fs/bundle.b b/appl/cmd/fs/bundle.b
new file mode 100644
index 00000000..a4b1cee5
--- /dev/null
+++ b/appl/cmd/fs/bundle.b
@@ -0,0 +1,195 @@
+implement Bundle;
+include "sys.m";
+ sys: Sys;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "readdir.m";
+ readdir: Readdir;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, report, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+include "bundle.m";
+
+# XXX if we can't open a directory, is it ever worth passing its metadata
+# through anyway?
+
+EOF: con "end of archive\n";
+
+types(): string
+{
+ return "vx";
+}
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: bundle: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ badmod(Readdir->PATH);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ badmod(Readdir->PATH);
+ bufio->fopen(nil, Sys->OREAD); # XXX no bufio->init!
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Readdir->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ return ref Value.V(
+ bundle(
+ report,
+ bufio->fopen(sys->fildes(1), Sys->OWRITE),
+ (hd args).x().i
+ )
+ );
+}
+
+bundle(r: ref Report, iob: ref Iobuf, c: Fschan): chan of int
+{
+ sync := chan of int;
+ spawn bundleproc(c, sync, iob, r.start("bundle"));
+ return sync;
+}
+
+bundleproc(c: Fschan, sync: chan of int, iob: ref Iobuf, errorc: chan of string)
+{
+ if(sync != nil && <-sync == 0){
+ (<-c).t1 <-= Quit;
+ quit(errorc);
+ }
+ (d, reply) := <-c;
+ if(d.dir == nil){
+ report(errorc, "no root directory");
+ endarchive(iob, errorc);
+ }
+ if(puts(iob, dir2header(d.dir), errorc) == -1){
+ reply <-= Quit;
+ quit(errorc);
+ }
+ reply <-= Down;
+ bundledir(d.dir.name, d, c, iob, errorc);
+ endarchive(iob, errorc);
+}
+
+endarchive(iob: ref Iobuf, errorc: chan of string)
+{
+ if(puts(iob, EOF, errorc) != -1)
+ iob.flush();
+ quit(errorc);
+ exit;
+}
+
+bundledir(path: string, d: Fsdata,
+ c: Fschan,
+ iob: ref Iobuf, errorc: chan of string)
+{
+ if(d.dir.mode & Sys->DMDIR){
+ path[len path] = '/';
+ for(;;){
+ (ent, reply) := <-c;
+ if(ent.dir == nil){
+ reply <-= Skip;
+ break;
+ }
+ if(puts(iob, dir2header(ent.dir), errorc) == -1){
+ reply <-= Quit;
+ quit(errorc);
+ }
+ reply <-= Down;
+ bundledir(path + ent.dir.name, ent, c, iob, errorc);
+ }
+ iob.putc('\n');
+ }else{
+ buf: array of byte;
+ reply: chan of int;
+ length := big d.dir.length;
+ n := big 0;
+ for(;;){
+ ((nil, buf), reply) = <-c;
+ if(buf == nil){
+ reply <-= Skip;
+ break;
+ }
+ if(write(iob, buf, len buf, errorc) != len buf){
+ reply <-= Quit;
+ quit(errorc);
+ }
+ n += big len buf;
+ if(n > length){ # should never happen
+ report(errorc, sys->sprint("%q is longer than expected (fatal)", path));
+ reply <-= Quit;
+ quit(errorc);
+ }
+ if(n == length){
+ reply <-= Skip;
+ break;
+ }
+ reply <-= Next;
+ }
+ if(n < length){
+ report(errorc, sys->sprint("%q is shorter than expected (%bd/%bd); adding null bytes", path, n, length));
+ buf = array[Sys->ATOMICIO] of {* => byte 0};
+ while(n < length){
+ nb := len buf;
+ if(length - n < big len buf)
+ nb = int (length - n);
+ if(write(iob, buf, nb, errorc) != nb){
+ (<-c).t1 <-= Quit;
+ quit(errorc);
+ }
+ report(errorc, sys->sprint("added %d null bytes", nb));
+ n += big nb;
+ }
+ }
+ }
+}
+
+dir2header(d: ref Sys->Dir): string
+{
+ return sys->sprint("%q %uo %q %q %ud %bd\n", d.name, d.mode, d.uid, d.gid, d.mtime, d.length);
+}
+
+puts(iob: ref Iobuf, s: string, errorc: chan of string): int
+{
+ {
+ if(iob.puts(s) == -1)
+ report(errorc, sys->sprint("write error: %r"));
+ return 0;
+ } exception {
+ "write on closed pipe" =>
+ return -1;
+ }
+}
+
+write(iob: ref Iobuf, buf: array of byte, n: int, errorc: chan of string): int
+{
+ {
+ nw := iob.write(buf, n);
+ if(nw < n){
+ if(nw >= 0)
+ report(errorc, "short write");
+ else{
+ report(errorc, sys->sprint("write error: %r"));
+ }
+ }
+ return nw;
+ } exception {
+ "write on closed pipe" =>
+ report(errorc, "write on closed pipe");
+ return -1;
+ }
+}
diff --git a/appl/cmd/fs/chstat.b b/appl/cmd/fs/chstat.b
new file mode 100644
index 00000000..e549527e
--- /dev/null
+++ b/appl/cmd/fs/chstat.b
@@ -0,0 +1,185 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fsfilter: Fsfilter;
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+Query: adt {
+ gate: Gatechan;
+ stat: Sys->Dir;
+ mask: int;
+ cflag: int;
+ reply: chan of int;
+
+ query: fn(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int;
+};
+
+types(): string
+{
+ return "xx-pp-ms-us-gs-ts-as-c";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ fsfilter = load Fsfilter Fsfilter->PATH;
+ if(fsfilter == nil)
+ badmod(Fsfilter->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ ws := Sys->nulldir;
+ mask := 0;
+ gate: ref Value;
+ cflag := 0;
+ for(; opts != nil; opts = tl opts){
+ o := (hd opts).args;
+ case (hd opts).opt {
+ 'p' =>
+ gate.discard();
+ gate = hd o;
+ 'm' =>
+ ok: int;
+ m := (hd o).s().i;
+ (ok, mask, ws.mode) = parsemode(m);
+ mask &= ~Sys->DMDIR;
+ if(ok == 0){
+ sys->fprint(sys->fildes(2), "fs: chstat: bad mode %#q\n", m);
+ gate.discard();
+ return nil;
+ }
+ 'u' =>
+ ws.uid = (hd o).s().i;
+ 'g' =>
+ ws.gid = (hd o).s().i;
+ 't' =>
+ ws.mtime = int (hd o).s().i;
+ 'a' =>
+ ws.atime = int (hd o).s().i;
+ 'c' =>
+ cflag++;
+ }
+ }
+
+ dst := chan of (Fsdata, chan of int);
+ p: Gatechan;
+ if(gate != nil)
+ p = gate.p().i;
+ spawn chstatproc((hd args).x().i, dst, p, ws, mask, cflag);
+ return ref Value.X(dst);
+}
+
+chstatproc(src, dst: Fschan, gate: Gatechan, stat: Sys->Dir, mask: int, cflag: int)
+{
+ fsfilter->filter(ref Query(gate, stat, mask, cflag, chan of int), src, dst);
+ if(gate != nil)
+ gate <-= ((nil, nil, 0), nil);
+}
+
+Query.query(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int
+{
+ c := 1;
+ if(q.gate != nil){
+ q.gate <-= ((d, name, depth), q.reply);
+ c = <-q.reply;
+ }
+ if(c){
+ if(q.cflag){
+ m := d.mode & 8r700;
+ d.mode = (d.mode & ~8r77)|(m>>3)|(m>>6);
+ }
+ stat := q.stat;
+ d.mode = (d.mode & ~q.mask) | (stat.mode & q.mask);
+ if(stat.uid != nil)
+ d.uid = stat.uid;
+ if(stat.gid != nil)
+ d.gid = stat.gid;
+ if(stat.mtime != ~0)
+ d.mtime = stat.mtime;
+ if(stat.atime != ~0)
+ d.atime = stat.atime;
+ }
+ return 1;
+}
+
+# stolen from /appl/cmd/chmod.b
+User: con 8r700;
+Group: con 8r070;
+Other: con 8r007;
+All: con User | Group | Other;
+
+Read: con 8r444;
+Write: con 8r222;
+Exec: con 8r111;
+parsemode(spec: string): (int, int, int)
+{
+ mask := Sys->DMAPPEND | Sys->DMEXCL | Sys->DMDIR | Sys->DMAUTH;
+loop:
+ for(i := 0; i < len spec; i++){
+ case spec[i] {
+ 'u' =>
+ mask |= User;
+ 'g' =>
+ mask |= Group;
+ 'o' =>
+ mask |= Other;
+ 'a' =>
+ mask |= All;
+ * =>
+ break loop;
+ }
+ }
+ if(i == len spec)
+ return (0, 0, 0);
+ if(i == 0)
+ mask |= All;
+
+ op := spec[i++];
+ if(op != '+' && op != '-' && op != '=')
+ return (0, 0, 0);
+
+ mode := 0;
+ for(; i < len spec; i++){
+ case spec[i]{
+ 'r' =>
+ mode |= Read;
+ 'w' =>
+ mode |= Write;
+ 'x' =>
+ mode |= Exec;
+ 'a' =>
+ mode |= Sys->DMAPPEND;
+ 'l' =>
+ mode |= Sys->DMEXCL;
+ 'd' =>
+ mode |= Sys->DMDIR;
+ 'A' =>
+ mode |= Sys->DMAUTH;
+ * =>
+ return (0, 0, 0);
+ }
+ }
+ if(op == '+' || op == '-')
+ mask &= mode;
+ if(op == '-')
+ mode = ~mode;
+ return (1, mask, mode);
+}
diff --git a/appl/cmd/fs/compose.b b/appl/cmd/fs/compose.b
new file mode 100644
index 00000000..69187d6b
--- /dev/null
+++ b/appl/cmd/fs/compose.b
@@ -0,0 +1,100 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Cmpchan,
+ Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+AinB: con 1<<3;
+BinA: con 1<<2;
+AoutB: con 1<<1;
+BoutA: con 1<<0;
+
+A: con AinB|AoutB;
+AoverB: con AinB|AoutB|BoutA;
+AatopB: con AinB|BoutA;
+AxorB: con AoutB|BoutA;
+
+B: con BinA|BoutA;
+BoverA: con BinA|BoutA|AoutB;
+BatopA: con BinA|AoutB;
+BxorA: con BoutA|AoutB;
+
+ops := array[] of {
+ AinB => "AinB",
+ BinA => "BinA",
+ AoutB => "AoutB",
+ BoutA => "BoutA",
+ A => "A",
+ AoverB => "AoverB",
+ AatopB => "AatopB",
+ AxorB => "AxorB",
+ B => "B",
+ BoverA => "BoverA",
+ BatopA => "BatopA",
+};
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+types(): string
+{
+ return "ms-d";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ c := chan of (ref Sys->Dir, ref Sys->Dir, chan of int);
+ s := (hd args).s().i;
+ for(i := 0; i < len ops; i++)
+ if(ops[i] == s)
+ break;
+ if(i == len ops){
+ sys->fprint(sys->fildes(2), "fs: join: bad op %q\n", s);
+ return nil;
+ }
+ spawn compose(c, i, opts != nil);
+ return ref Value.M(c);
+}
+
+compose(c: Cmpchan, op: int, dflag: int)
+{
+ t := array[4] of {* => 0};
+ if(op & AinB)
+ t[2r11] = 2r01;
+ if(op & BinA)
+ t[2r11] = 2r10;
+ if(op & AoutB)
+ t[2r01] = 2r01;
+ if(op & BoutA)
+ t[2r10] = 2r10;
+ if(dflag){
+ while(((d0, d1, reply) := <-c).t2 != nil){
+ x := (d1 != nil) << 1 | d0 != nil;
+ r := t[d0 != nil | (d1 != nil) << 1];
+ if(r == 0 && x == 2r11 && (d0.mode & d1.mode & Sys->DMDIR))
+ r = 2r11;
+ reply <-= r;
+ }
+ }else{
+ while(((d0, d1, reply) := <-c).t2 != nil)
+ reply <-= t[(d1 != nil) << 1 | d0 != nil];
+ }
+}
diff --git a/appl/cmd/fs/depth.b b/appl/cmd/fs/depth.b
new file mode 100644
index 00000000..19c03b2d
--- /dev/null
+++ b/appl/cmd/fs/depth.b
@@ -0,0 +1,49 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "ps";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ d := int (hd args).s().i;
+ if(d <= 0){
+ sys->fprint(sys->fildes(2), "fs: depth: invalid depth\n");
+ return nil;
+ }
+ c := chan of Gatequery;
+ spawn depthgate(c, d);
+ return ref Value.P(c);
+}
+
+depthgate(c: Gatechan, d: int)
+{
+ while((((dir, nil, depth), reply) := <-c).t0.t0 != nil)
+ reply <-= depth <= d;
+}
diff --git a/appl/cmd/fs/entries.b b/appl/cmd/fs/entries.b
new file mode 100644
index 00000000..56aac67f
--- /dev/null
+++ b/appl/cmd/fs/entries.b
@@ -0,0 +1,86 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "tx";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ sc := Entrychan(chan of int, chan of Entry);
+ spawn entriesproc((hd args).x().i, sc);
+ return ref Value.T(sc);
+}
+
+entriesproc(c: Fschan, sc: Entrychan)
+{
+ if(<-sc.sync == 0){
+ (<-c).t1 <-= Quit;
+ exit;
+ }
+ indent := 0;
+ names: list of string;
+ name: string;
+loop:
+ for(;;){
+ (d, reply) := <-c;
+ if(d.dir != nil){
+ p: string;
+ depth := indent;
+ if(d.dir.mode & Sys->DMDIR){
+ names = name :: names;
+ if(indent == 0)
+ name = d.dir.name;
+ else{
+ if(name[len name - 1] != '/')
+ name[len name] = '/';
+ name += d.dir.name;
+ }
+ indent++;
+ reply <-= Down;
+ p = name;
+ }else{
+ p = name;
+ if(p[len p - 1] != '/')
+ p[len p] = '/';
+ p += d.dir.name;
+ reply <-= Next;
+ }
+ if(p != nil)
+ sc.c <-= (d.dir, p, depth);
+ }else{
+ reply <-= Next;
+ if(d.dir == nil && d.data == nil){
+ if(--indent == 0)
+ break loop;
+ (name, names) = (hd names, tl names);
+ }
+ }
+ }
+ sc.c <-= Nilentry;
+}
diff --git a/appl/cmd/fs/eval.b b/appl/cmd/fs/eval.b
new file mode 100644
index 00000000..5eaf9291
--- /dev/null
+++ b/appl/cmd/fs/eval.b
@@ -0,0 +1,648 @@
+implement Eval;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "readdir.m";
+#include "env.m";
+# env: Env;
+#include "string.m";
+# str: String;
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Quit: import Fslib;
+
+# more general:
+# eval: fn[V, M](ctxt: ref Context, r: ref Report, expr: string, args:...) with {
+# V =>
+# typec: fn(t: self V): int;
+# cvt: fn(t: self V, tc: int): V;
+# cvt2s: fn(t: self V): (int, string);
+# cvt2v: fn(t: self V): chan of int;
+# mkstring: fn(s: string): V;
+# mkcmd: fn(c: ref Sh->Cmd): V;
+# discard: fn(t: self V);
+# type2s: fn(c: int): string;
+# loadmod: fn(cmd: string): M;
+# M =>
+# types: fn(): string;
+# init: fn();
+# run: fn(ctxt: ref Draw->Context, r: ref Report, cmd: string,
+# opts: list of (int, list of V), args: list of V): V;
+# }
+# how to call eval?
+# (eval with [V=>ref Value, M=>Fsmodule])(
+#
+# sort out error reporting; stderr is not good.
+
+
+# possible things to do:
+# pipe [-1pP] [-t command] command fs -> void
+# pipe all files in fs through command.
+# extract [-r root] gate fs -> fs
+# extract the first entry within fs which
+# passes through the gate.
+# if -r is specified, the entry is placed
+# within the given root, and may be a file,
+# otherwise files are not allowed.
+# apply string fs
+# for each file in fs, evaluates string as an fs expression
+# (which should yield fs), and replace the file in the
+# original hierarchy with the result.
+# e.g.
+# fs apply '{unbundle $file}' {filter {or {mode +d} *.bundle} .}
+# a bit fanciful this...
+# merge could take an optional boolean operator
+#
+# venti?
+#
+# Cmpgate: chan of Cmpgatequery;
+# Cmpgatequery: type (Entry, Entry, chan of int);
+# returns 00, 01, 10 or 11
+# used by merge to decide what to do when merging
+# used by write to decide what to do when writing
+#
+# cmpdate [-u] '>'
+# cmpquery command
+
+Eval: module {
+ types: fn(): string;
+ init: fn();
+ run: fn(ctxt: ref Draw->Context, r: ref Fslib->Report,
+ opts: list of Fslib->Option, args: list of ref Fslib->Value): ref Fslib->Value;
+ eval: fn(ctxt: ref Draw->Context, r: ref Fslib->Report,
+ expr: string, args: list of ref Fslib->Value, ret: int): ref Fslib->Value;
+};
+
+WORD, SHCMD, VAR: con iota;
+
+Evalstate: adt {
+ s: string;
+ spos: int;
+ drawctxt: ref Draw->Context;
+ report: ref Report;
+ args: array of ref Value;
+ verbose: int;
+
+ expr: fn(p: self ref Evalstate): ref Value;
+ getc: fn(p: self ref Evalstate): int;
+ ungetc: fn(p: self ref Evalstate);
+ gettok: fn(p: self ref Evalstate): (int, string);
+};
+
+ops: list of (string, Fsmodule);
+lock: chan of int;
+
+# to do:
+# - change value letters to more appropriate (e.g. fs->f, entries->e, gate->g).
+# - allow shell $variable expansions
+
+types(): string
+{
+ return "as-v";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: eval: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ fslib->init();
+# env = load Env Env->PATH;
+# if(env == nil)
+# badmod(Env->PATH);
+# str = load String String->PATH;
+# if(str == nil)
+# badmod(String->PATH);
+ lock = chan[1] of int;
+}
+
+run(ctxt: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ return (ref Evalstate((hd args).s().i, 0, ctxt, report, nil, opts != nil)).expr();
+}
+
+eval(ctxt: ref Draw->Context, report: ref Report,
+ expr: string, args: list of ref Value, rtype: int): ref Value
+{
+ a := array[len args] of ref Value;
+ for(i := 0; args != nil; args = tl args)
+ a[i++] = hd args;
+ e := ref Evalstate(expr, 0, ctxt, report, a, 0);
+ v := e.expr();
+ vl: list of ref Value;
+ for(i = 0; i < len a; i++)
+ if(a[i] != nil)
+ vl = a[i] :: vl;
+ nv := cvt(e, v, rtype);
+ if(nv == nil){
+ vl = v :: vl;
+ sys->fprint(stderr(), "fs: eval fn: %s cannot be converted to %s\n",
+ type2s(v.typec()), type2s(rtype));
+ }
+ if(vl != nil)
+ spawn discard(nil, vl);
+ return nv;
+}
+
+tok2s(t: int, s: string): string
+{
+ case t {
+ WORD =>
+ return s;
+ SHCMD =>
+ return "@";
+ VAR =>
+ return "$" + s;
+ }
+ return sys->sprint("%c", t);
+}
+
+# expr: WORD exprs
+# exprs:
+# | exprs '{' expr '}'
+# | exprs WORD
+# | exprs SHCMD
+# | exprs VAR
+Evalstate.expr(p: self ref Evalstate): ref Value
+{
+ args: list of ref Value;
+ t: int;
+ s: string;
+ {
+ (t, s) = p.gettok();
+ } exception e {
+ "parse error" =>
+ return nil;
+ }
+ if(t != WORD){
+ sys->fprint(stderr(), "fs: eval: syntax error (char %d), expected word, found %#q\n",
+ p.spos, tok2s(t, s));
+ return nil;
+ }
+ cmd := s;
+loop:
+ for(;;){
+ {
+ (t, s) = p.gettok();
+ } exception e {
+ "parse error" =>
+ spawn discard(nil, args);
+ return nil;
+ }
+ case t {
+ '{' =>
+ v := p.expr();
+ if(v == nil){
+ spawn discard(nil, args);
+ return nil;
+ }
+ args = v :: args;
+ '}' =>
+ break loop;
+ WORD =>
+ args = ref Value.S(s) :: args;
+ VAR =>
+ n := int s;
+ if(n < 0 || n >= len p.args){
+ sys->fprint(stderr(), "fs: eval: invalid arg reference $%s\n", s);
+ spawn discard(nil, args);
+ return nil;
+ }
+ if(p.args[n] == nil){
+ sys->fprint(stderr(), "fs: eval: cannot use $%d twice\n", n);
+ spawn discard(nil, args);
+ return nil;
+ }
+ args = p.args[n] :: args;
+ p.args[n] = nil;
+ SHCMD =>
+ if(sh == nil && (sh = load Sh Sh->PATH) == nil){
+ sys->fprint(stderr(), "fs: eval: cannot load %s: %r\n", Sh->PATH);
+ spawn discard(nil, args);
+ return nil;
+ }
+ (c, err) := sh->parse(s);
+ if(c == nil){
+ sys->fprint(stderr(), "fs: eval: cannot parse shell command @%s: %s\n", s, err);
+ spawn discard(nil, args);
+ return nil;
+ }
+ args = ref Value.C(c) :: args;
+ -1 =>
+ break loop;
+ * =>
+ spawn discard(nil, args);
+ sys->fprint(stderr(), "fs: eval: syntax error; unexpected token %d before char %d\n", t, p.spos);
+ return nil;
+ }
+ }
+ return runcmd(p, cmd, rev(args));
+}
+
+runcmd(p: ref Evalstate, cmd: string, args: list of ref Value): ref Value
+{
+ m := loadmodule(cmd);
+ if(m == nil){
+ spawn discard(nil, args);
+ return nil;
+ }
+ otype := m->types();
+ ok: int;
+ opts: list of Option;
+ (ok, opts, args) = cvtargs(p, args, cmd, otype);
+ if(ok == -1){
+ sys->fprint(stderr(), "fs: eval: usage: %s\n", fslib->cmdusage(cmd, otype));
+ spawn discard(opts, args);
+ return nil;
+ }
+ r := m->run(p.drawctxt, p.report, opts, args);
+ if(r == nil)
+ spawn discard(opts, args);
+ return r;
+}
+
+cvtargs(e: ref Evalstate, args: list of ref Value, cmd, otype: string): (int, list of Option, list of ref Value)
+{
+ ok: int;
+ opts: list of Option;
+ (nil, at, t) := fslib->splittype(otype);
+ (ok, opts, args) = cvtopts(e, t, cmd, args);
+ if(ok == -1)
+ return (-1, opts, args);
+ if(len at < 1 || at[0] == '*'){
+ sys->fprint(stderr(), "fs: eval: invalid type descriptor %#q for %#q\n", at, cmd);
+ return (-1, opts, args);
+ }
+ n := len args;
+ if(at[len at - 1] == '*'){
+ tc := at[len at - 2];
+ at = at[0:len at - 2];
+ for(i := len at; i < n; i++)
+ at[i] = tc;
+ }
+ if(n != len at){
+ sys->fprint(stderr(), "fs: eval: wrong number of arguments to %#q\n", cmd);
+ return (-1, opts, args);
+ }
+ d: list of ref Value;
+ (ok, args, d) = cvtvalues(e, at, cmd, args);
+ if(ok == -1)
+ args = join(args, d);
+ return (ok, opts, args);
+}
+
+cvtvalues(e: ref Evalstate, t: string, cmd: string, args: list of ref Value): (int, list of ref Value, list of ref Value)
+{
+ cargs: list of ref Value;
+ for(i := 0; i < len t; i++){
+ tc := t[i];
+ if(args == nil){
+ sys->fprint(stderr(), "fs: eval: %q missing argument of type %s\n", cmd, type2s(tc));
+ return (-1, cargs, args);
+ }
+ v := cvt(e, hd args, tc);
+ if(v == nil){
+ sys->fprint(stderr(), "fs: eval: %q: %s cannot be converted to %s\n",
+ cmd, type2s((hd args).typec()), type2s(tc));
+ return (-1, cargs, args);
+ }
+ cargs = v :: cargs;
+ args = tl args;
+ }
+ return (0, rev(cargs), args);
+}
+
+cvtopts(e: ref Evalstate, opttype: string, cmd: string, args: list of ref Value): (int, list of Option, list of ref Value)
+{
+ if(opttype == nil)
+ return (0, nil, args);
+ opts: list of Option;
+getopts:
+ while(args != nil){
+ s := "";
+ pick v := hd args {
+ S =>
+ s = v.i;
+ if(s == nil || s[0] != '-' || len s == 1)
+ s = nil;
+ else if(s == "--"){
+ args = tl args;
+ s = nil;
+ }
+ }
+ if(s == nil)
+ return (0, opts, args);
+ s = s[1:];
+ while(len s > 0){
+ opt := s[0];
+ if(((ok, t) := fslib->opttypes(opt, opttype)).t0 == -1){
+ sys->fprint(stderr(), "fs: eval: %s: unknown option -%c\n", cmd, opt);
+ return (-1, opts, args);
+ }
+ if(t == nil){
+ s = s[1:];
+ opts = (opt, nil) :: opts;
+ }else{
+ if(len s > 1)
+ args = ref Value.S(s[1:]) :: tl args;
+ else
+ args = tl args;
+ vl: list of ref Value;
+ (ok, vl, args) = cvtvalues(e, t, cmd, args);
+ if(ok == -1)
+ return (-1, opts, join(vl, args));
+ opts = (opt, vl) :: opts;
+ continue getopts;
+ }
+ }
+ args = tl args;
+ }
+ return (0, opts, args);
+}
+
+discard(ol: list of (int, list of ref Value), vl: list of ref Value)
+{
+ for(; ol != nil; ol = tl ol)
+ for(ovl := (hd ol).t1; ovl != nil; ovl = tl ovl)
+ vl = (hd ovl) :: vl;
+ for(; vl != nil; vl = tl vl)
+ (hd vl).discard();
+}
+
+loadmodule(cmd: string): Fsmodule
+{
+ lock <-= 0;
+ for(ol := ops; ol != nil; ol = tl ol)
+ if((hd ol).t0 == cmd)
+ break;
+ if(ol != nil){
+ <-lock;
+ return (hd ol).t1;
+ }
+ p := cmd + ".dis";
+ if(p[0] != '/' && !(p[0] == '.' && p[1] == '/'))
+ p = "/dis/fs/" + p;
+ m := load Fsmodule p;
+ if(m == nil){
+ sys->fprint(stderr(), "fs: eval: cannot load %s: %r\n", p);
+ sys->fprint(stderr(), "fs: eval: unknown verb %#q\n", cmd);
+ sys->werrstr(sys->sprint("cannot load module %q", cmd));
+ <-lock;
+ return nil;
+ }
+ {
+ m->init();
+ } exception e {
+ "fail:*" =>
+ <-lock;
+ sys->werrstr(sys->sprint("module init failed: %s", e[5:]));
+ return nil;
+ }
+ ops = (cmd, m) :: ops;
+ <-lock;
+ return m;
+}
+
+runexternal(p: ref Evalstate, cmd: string, t: string, opts: list of Option, args: list of ref Value): ref Value
+{
+ m := loadmodule(cmd);
+ if(m == nil)
+ return nil;
+ if(!fslib->typecompat(t, m->types())){
+ sys->fprint(stderr(), "fs: eval: %s has incompatible type\n", cmd);
+ sys->fprint(stderr(), "fs: eval: expected usage: %s\n", fslib->cmdusage(cmd, t));
+ sys->fprint(stderr(), "fs: eval: actually usage: %s\n", fslib->cmdusage(cmd, m->types()));
+ return nil;
+ }
+ return m->run(p.drawctxt, p.report, opts, args);
+}
+
+cvt(e: ref Evalstate, v: ref Value, t: int): ref Value
+{
+ {
+ return cvt1(e, v, t);
+ } exception {
+ "type conversion" =>
+ return nil;
+ }
+}
+
+cvt1(e: ref Evalstate, v: ref Value, t: int): ref Value
+{
+ if(v.typec() == t)
+ return v;
+ r: ref Value;
+ case t {
+ 't' =>
+ r = runexternal(e, "entries", "tx", nil, cvt1(e, v, 'x') :: nil);
+ 'x' =>
+ r = runexternal(e, "walk", "xs", nil, cvt1(e, v, 's') :: nil);
+ 'p' =>
+ r = runexternal(e, "match", "ps", nil, cvt1(e, v, 's') :: nil);
+ 's' =>
+ r = runexternal(e, "run", "sc", nil, cvt1(e, v, 'c') :: nil);
+ 'v' =>
+ r = runexternal(e, "print", "vt", nil, cvt1(e, v, 't') :: nil);
+ }
+ if(r == nil)
+ raise "type conversion";
+ return r;
+}
+
+Evalstate.getc(p: self ref Evalstate): int
+{
+ c := -1;
+ if(p.spos < len p.s)
+ c = p.s[p.spos];
+ p.spos++;
+ return c;
+}
+
+Evalstate.ungetc(p: self ref Evalstate)
+{
+ p.spos--;
+}
+
+# XXX backslash escapes newline?
+Evalstate.gettok(p: self ref Evalstate): (int, string)
+{
+ while ((c := p.getc()) == ' ' || c == '\t')
+ ;
+ t: int;
+ s: string;
+
+ case c {
+ -1 =>
+ t = -1;
+ '\n' =>
+ t = '\n';
+ '{' =>
+ t = '{';
+ '}' =>
+ t = '}';
+ '@' => # embedded shell command
+ while((nc := p.getc()) == ' ' || nc == '\t')
+ ;
+ if(nc != '{'){
+ sys->fprint(stderr(), "fs: eval: expected '{' after '@'\n");
+ raise "parse error";
+ }
+ s = "{";
+ d := 1;
+ getcmd:
+ while((nc = p.getc()) != -1){
+ s[len s] = nc;
+ case nc {
+ '{' =>
+ d++;
+ '}' =>
+ if(--d == 0)
+ break getcmd;
+ '\'' =>
+ s += getqword(p, 1);
+ }
+ }
+ if(nc == -1){
+ sys->fprint(stderr(), "fs: eval: unbalanced '{' in shell command\n");
+ raise "parse error";
+ }
+ t = SHCMD;
+ '$' =>
+ t = VAR;
+ s = getvar(p);
+ '\'' =>
+ s = getqword(p, 0);
+ t = WORD;
+ * =>
+ do {
+ s[len s] = c;
+ c = p.getc();
+ if (in(c, " \t{}\n")){
+ p.ungetc();
+ break;
+ }
+ } while (c >= 0);
+ t = WORD;
+ }
+ return (t, s);
+}
+
+getvar(p: ref Evalstate): string
+{
+ c := p.getc();
+ if(c == -1){
+ sys->fprint(stderr(), "fs: eval: unexpected eof after '$'\n");
+ raise "parse error";
+ }
+ v: string;
+ while(in(c, " \t\n@{}'") == 0){
+ v[len v] = c;
+ c = p.getc();
+ }
+ p.ungetc();
+ for(i := 0; i < len v; i++)
+ if(v[i] < '0' || v[i] > '9')
+ break;
+ if(i < len v || v == nil){
+ sys->fprint(stderr(), "fs: eval: invalid $ reference $%q\n", v);
+ raise "parse error";
+ }
+ return v;
+}
+# v: string;
+# if(c == '\''){
+# v = getqword(p, 0);
+# c = p.getc();
+# } else{
+# v[0] = c;
+# while((c = p.getc()) != -1){
+# if(in(c, "a-zA-Z0-9*_") == 0) # heuristic stolen from rc
+# break;
+# v[len v] = c;
+# }
+# }
+# vl := str->unquoted(env->getenv(v));
+# if(vl == nil){
+# sys->fprint(stderr(), "fs: eval: shell variable $%q has %d elements\n", v, len vl);
+# raise "parse error";
+# }
+# val := hd vl;
+# if(c == -1 || in(c, " \t@{}\n")){
+# p.ungetc();
+# return (WORD, val);
+# }
+# (t, s) = p.gettok();
+# if(t != WORD){
+# sys->fprint(stderr(), "fs: eval: expected word after $%q\n", v);
+# raise "parse error";
+# }
+# s = val + s;
+#}
+
+in(c: int, s: string): int
+{
+ for(i := 0; i < len s; i++)
+ if(s[i] == c)
+ return 1;
+ return 0;
+}
+
+# get a quoted word; the starting quote has already been seen
+getqword(p: ref Evalstate, keepq: int): string
+{
+ s := "";
+ for(;;) {
+ while ((nc := p.getc()) != '\'' && nc >= 0)
+ s[len s] = nc;
+ if (nc == -1){
+ sys->fprint(stderr(), "fs: eval: unterminated quote\n");
+ raise "parse error";
+ }
+ if (p.getc() != '\'') {
+ p.ungetc();
+ if(keepq)
+ s[len s] = '\'';
+ return s;
+ }
+ s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy)
+ if(keepq)
+ s[len s] = '\'';
+ }
+}
+
+rev[T](x: list of T): list of T
+{
+ l: list of T;
+ for(; x != nil; x = tl x)
+ l = hd x :: l;
+ return l;
+}
+
+# join x to y, leaving result in arbitrary order.
+join[T](x, y: list of T): list of T
+{
+ if(len x > len y)
+ (x, y) = (y, x);
+ for(; x != nil; x = tl x)
+ y = hd x :: y;
+ return y;
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
diff --git a/appl/cmd/fs/exec.b b/appl/cmd/fs/exec.b
new file mode 100644
index 00000000..60beb74e
--- /dev/null
+++ b/appl/cmd/fs/exec.b
@@ -0,0 +1,162 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "fslib.m";
+ fslib: Fslib;
+ Option, Value, Entrychan, Report: import fslib;
+
+# usage: exec [-n nfiles] [-t endcmd] [-pP] command entries
+types(): string
+{
+ return "vct-ns-tc-p-P";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ sh = load Sh Sh->PATH;
+ if(sh == nil)
+ badmod(Sh->PATH);
+ sh->initialise();
+}
+
+run(drawctxt: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ n := 1;
+ pflag := 0;
+ tcmd: ref Sh->Cmd;
+ for(; opts != nil; opts = tl opts){
+ o := hd opts;
+ case o.opt {
+ 'n' =>
+ if((n = int (hd o.args).s().i) <= 0){
+ sys->fprint(sys->fildes(2), "fs: exec: invalid argument to -n\n");
+ return nil;
+ }
+ 't' =>
+ tcmd = (hd o.args).c().i;
+ 'p' =>
+ pflag = 1;
+ 'P' =>
+ pflag = 2;
+ }
+ }
+ if(pflag && n > 1){
+ sys->fprint(sys->fildes(2), "fs: exec: cannot specify -p with -n %d\n", n);
+ return nil;
+ }
+ cmd := (hd args).c().i;
+ c := (hd tl args).t().i;
+ sync := chan of int;
+ spawn execproc(drawctxt, sync, n, pflag, c, cmd, tcmd, report.start("exec"));
+ sync <-= 1;
+ return ref Value.V(sync);
+}
+
+execproc(drawctxt: ref Draw->Context, sync: chan of int, n, pflag: int,
+ c: Entrychan, cmd, tcmd: ref Sh->Cmd, errorc: chan of string)
+{
+ sys->pctl(Sys->NEWFD, 0::1::2::nil);
+ ctxt := Context.new(drawctxt);
+ <-sync;
+ if(<-sync == 0){
+ c.sync <-= 0;
+ errorc <-= nil;
+ exit;
+ }
+ c.sync <-= 1;
+ argv := ref Sh->Listnode(cmd, nil) :: nil;
+
+ fl: list of ref Sh->Listnode;
+ nf := 0;
+ while(((d, p, nil) := <-c.c).t0 != nil){
+ fl = ref Sh->Listnode(nil, p) :: fl;
+ if(++nf >= n){
+ ctxt.set("file", rev(fl));
+ if(pflag)
+ setstatenv(ctxt, d, pflag);
+ fl = nil;
+ nf = 0;
+ {ctxt.run(argv, 0);} exception {"fail:*" =>;}
+ }
+ }
+ if(nf > 0){
+ ctxt.set("file", rev(fl));
+ {ctxt.run(argv, 0);} exception {"fail:*" =>;}
+ }
+ if(tcmd != nil){
+ ctxt.set("file", nil);
+ {ctxt.run(ref Sh->Listnode(tcmd, nil) :: nil, 0);} exception {"fail:*" =>;}
+ }
+ errorc <-= nil;
+}
+
+setenv(ctxt: ref Context, var: string, val: list of string)
+{
+ ctxt.set(var, sh->stringlist2list(val));
+}
+
+setstatenv(ctxt: ref Context, dir: ref Sys->Dir, pflag: int)
+{
+ setenv(ctxt, "mode", modes(dir.mode) :: nil);
+ setenv(ctxt, "uid", dir.uid :: nil);
+ setenv(ctxt, "mtime", string dir.mtime :: nil);
+ setenv(ctxt, "length", string dir.length :: nil);
+
+ if(pflag > 1){
+ setenv(ctxt, "name", dir.name :: nil);
+ setenv(ctxt, "gid", dir.gid :: nil);
+ setenv(ctxt, "muid", dir.muid :: nil);
+ setenv(ctxt, "qid", sys->sprint("16r%ubx", dir.qid.path) :: string dir.qid.vers :: nil);
+ setenv(ctxt, "atime", string dir.atime :: nil);
+ setenv(ctxt, "dtype", sys->sprint("%c", dir.dtype) :: nil);
+ setenv(ctxt, "dev", string dir.dev :: nil);
+ }
+}
+
+mtab := array[] of {
+ "---", "--x", "-w-", "-wx",
+ "r--", "r-x", "rw-", "rwx"
+};
+
+modes(mode: int): string
+{
+ s: string;
+
+ if(mode & Sys->DMDIR)
+ s = "d";
+ else if(mode & Sys->DMAPPEND)
+ s = "a";
+ else if(mode & Sys->DMAUTH)
+ s = "A";
+ else
+ s = "-";
+ if(mode & Sys->DMEXCL)
+ s += "l";
+ else
+ s += "-";
+ s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7];
+ return s;
+}
+
+rev[T](x: list of T): list of T
+{
+ l: list of T;
+ for(; x != nil; x = tl x)
+ l = hd x :: l;
+ return l;
+}
diff --git a/appl/cmd/fs/filter.b b/appl/cmd/fs/filter.b
new file mode 100644
index 00000000..9275cc7f
--- /dev/null
+++ b/appl/cmd/fs/filter.b
@@ -0,0 +1,64 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fsfilter: Fsfilter;
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+
+Query: adt {
+ gate: Gatechan;
+ dflag: int;
+ reply: chan of int;
+ query: fn(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int;
+};
+
+types(): string
+{
+ return "xpx-d";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ fsfilter = load Fsfilter Fsfilter->PATH;
+ if(fsfilter == nil)
+ badmod(Fsfilter->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ dst := chan of (Fsdata, chan of int);
+ spawn filterproc((hd tl args).x().i, dst, (hd args).p().i, opts != nil);
+ return ref Value.X(dst);
+}
+
+filterproc(src, dst: Fschan, gate: Gatechan, dflag: int)
+{
+ fsfilter->filter(ref Query(gate, dflag, chan of int), src, dst);
+ gate <-= ((nil, nil, 0), nil);
+}
+
+Query.query(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int
+{
+ if(depth == 0 || (q.dflag && (d.mode & Sys->DMDIR)))
+ return 1;
+ q.gate <-= ((d, name, depth), q.reply);
+ return <-q.reply;
+}
diff --git a/appl/cmd/fs/ls.b b/appl/cmd/fs/ls.b
new file mode 100644
index 00000000..70beae48
--- /dev/null
+++ b/appl/cmd/fs/ls.b
@@ -0,0 +1,97 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "daytime.m";
+ daytime: Daytime;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Option, Value, Entrychan, Report: import fslib;
+
+types(): string
+{
+ return "vt-u-m";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: ls: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ badmod(Daytime->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ sync := chan of int;
+ spawn lsproc(sync, opts, (hd args).t().i, daytime, report.start("ls"));
+ return ref Value.V(sync);
+}
+
+lsproc(sync: chan of int, opts: list of Option, c: Entrychan, daytime: Daytime, errorc: chan of string)
+{
+ now := daytime->now();
+ mflag := uflag := 0;
+ if(<-sync == 0){
+ c.sync <-= 0;
+ errorc <-= nil;
+ }
+ c.sync <-= 1;
+ for(; opts != nil; opts = tl opts){
+ case (hd opts).opt {
+ 'm' =>
+ mflag = 1;
+ 'u' =>
+ uflag = 1;
+ }
+ }
+ while(((dir, p, nil) := <-c.c).t0 != nil){
+ t := dir.mtime;
+ if(uflag)
+ t = dir.atime;
+ s := sys->sprint("%s %c %d %s %s %bud %s %s\n",
+ modes(dir.mode), dir.dtype, dir.dev,
+ dir.uid, dir.gid, dir.length,
+ daytime->filet(now, dir.mtime), p);
+ if(mflag)
+ s = "[" + dir.muid + "] " + s;
+ sys->print("%s", s);
+ }
+ errorc <-= nil;
+}
+
+mtab := array[] of {
+ "---", "--x", "-w-", "-wx",
+ "r--", "r-x", "rw-", "rwx"
+};
+
+modes(mode: int): string
+{
+ s: string;
+
+ if(mode & Sys->DMDIR)
+ s = "d";
+ else if(mode & Sys->DMAPPEND)
+ s = "a";
+ else if(mode & Sys->DMAUTH)
+ s = "A";
+ else
+ s = "-";
+ if(mode & Sys->DMEXCL)
+ s += "l";
+ else
+ s += "-";
+ s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7];
+ return s;
+}
diff --git a/appl/cmd/fs/match.b b/appl/cmd/fs/match.b
new file mode 100644
index 00000000..331867a9
--- /dev/null
+++ b/appl/cmd/fs/match.b
@@ -0,0 +1,79 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "filepat.m";
+ filepat: Filepat;
+include "regex.m";
+ regex: Regex;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "ps-a-r";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ regex = load Regex Regex->PATH;
+ if(regex == nil)
+ badmod(Regex->PATH);
+ filepat = load Filepat Filepat->PATH;
+ if(filepat == nil)
+ badmod(Filepat->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ pat := (hd args).s().i;
+ aflag := rflag := 0;
+ for(; opts != nil; opts = tl opts){
+ case (hd opts).opt {
+ 'a' =>
+ aflag = 1;
+ 'r' =>
+ rflag = 1;
+ }
+ }
+ v := ref Value.P(chan of Gatequery);
+ re: Regex->Re;
+ if(rflag){
+ err: string;
+ (re, err) = regex->compile(pat, 0);
+ if(re == nil){
+ sys->fprint(sys->fildes(2), "fs: match: regex error on %#q: %s\n", pat, err);
+ return nil;
+ }
+ }
+ spawn matchproc(v.i, aflag, pat, re);
+ return v;
+}
+
+matchproc(c: Gatechan, all: int, pat: string, re: Regex->Re)
+{
+ while((((d, name, nil), reply) := <-c).t0.t0 != nil){
+ if(all == 0)
+ name = d.name;
+ if(re != nil)
+ reply <-= regex->execute(re, name) != nil; # XXX should anchor it?
+ else
+ reply <-= filepat->match(pat, name);
+ }
+}
diff --git a/appl/cmd/fs/merge.b b/appl/cmd/fs/merge.b
new file mode 100644
index 00000000..977102b2
--- /dev/null
+++ b/appl/cmd/fs/merge.b
@@ -0,0 +1,187 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s: import fslib;
+ Fschan, Fsdata, Entrychan, Cmpchan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+# e.g....
+# fs select {mode -d} {merge -c {compose -d AoutB} {filter {not {path /chan /dev /usr/rog /n/local /net}} /} {merge {proto FreeBSD} {proto Hp} {proto Irix} {proto Linux} {proto MacOSX} {proto Nt} {proto Nt.ti} {proto Nt.ti925} {proto Plan9} {proto Plan9.ti} {proto Plan9.ti925} {proto Solaris} {proto authsrv} {proto dl} {proto dlsrc} {proto ep7} {proto inferno} {proto inferno.ti} {proto ipaqfs} {proto minitel} {proto os} {proto scheduler.client} {proto scheduler.server} {proto sds} {proto src} {proto src.ti} {proto sword} {proto ti925.ti} {proto ti925bin} {proto tipaq} {proto umec} {proto utils} {proto utils.ti}}} >[2] /dev/null
+
+types(): string
+{
+ return "xxxx*-1-cm";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil){
+ sys->fprint(sys->fildes(2), "fs: cannot load %s: %r\n", Fslib->PATH);
+ raise "fail:bad module";
+ }
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ recurse := 1;
+ cmp: Cmpchan;
+ for(; opts != nil; opts = tl opts){
+ case (hd opts).opt {
+ '1' =>
+ recurse = 0;
+ 'c' =>
+ cmp = (hd (hd opts).args).m().i;
+ }
+ }
+ dst := chan of (Fsdata, chan of int);
+ spawn mergeproc((hd args).x().i, (hd tl args).x().i, dst, recurse, cmp, tl tl args == nil);
+ for(args = tl tl args; args != nil; args = tl args){
+ dst1 := chan of (Fsdata, chan of int);
+ spawn mergeproc(dst, (hd args).x().i, dst1, recurse, cmp, tl args == nil);
+ dst = dst1;
+ }
+ return ref Value.X(dst);
+}
+
+# merge two trees; assume directories are alphabetically sorted.
+mergeproc(c0, c1, dst: Fschan, recurse: int, cmp: Cmpchan, killcmp: int)
+{
+ myreply := chan of int;
+ ((d0, nil), reply0) := <-c0;
+ ((d1, nil), reply1) := <-c1;
+
+ if(compare(cmp, d0, d1) == 2r10)
+ dst <-= ((d1, nil), myreply);
+ else
+ dst <-= ((d0, nil), myreply);
+ r := <-myreply;
+ reply0 <-= r;
+ reply1 <-= r;
+ if(r == Down){
+ {
+ mergedir(c0, c1, dst, recurse, cmp);
+ } exception {"exit" =>;}
+ }
+ if(cmp != nil && killcmp)
+ cmp <-= (nil, nil, nil);
+}
+
+mergedir(c0, c1, dst: Fschan, recurse: int, cmp: Cmpchan)
+{
+ myreply := chan of int;
+ reply0, reply1: chan of int;
+ d0, d1: ref Sys->Dir;
+ eof0 := eof1 := 0;
+ for(;;){
+ if(!eof0 && d0 == nil){
+ ((d0, nil), reply0) = <-c0;
+ if(d0 == nil){
+ reply0 <-= Next;
+ eof0 = 1;
+ }
+ }
+ if(!eof1 && d1 == nil){
+ ((d1, nil), reply1) = <-c1;
+ if(d1 == nil){
+ reply1 <-= Next;
+ eof1 = 1;
+ }
+ }
+ if(eof0 && eof1)
+ break;
+
+ (wd0, wd1) := (d0, d1);
+ if(d0 != nil && d1 != nil && d0.name != d1.name){
+ if(d0.name < d1.name)
+ wd1 = nil;
+ else
+ wd0 = nil;
+ }
+
+ wc0, wc1: Fschan;
+ wreply0, wreply1: chan of int;
+ weof0, weof1: int;
+
+ c := compare(cmp, wd0, wd1);
+ if(wd0 != nil && wd1 != nil){
+ if(c != 0 && recurse && (wd0.mode & wd1.mode & Sys->DMDIR) != 0){
+ dst <-= ((wd0, nil), myreply);
+ r := <-myreply;
+ reply0 <-= r;
+ reply1 <-= r;
+ d0 = d1 = nil;
+ case r {
+ Quit =>
+ raise "exit";
+ Skip =>
+ return;
+ Down =>
+ mergedir(c0, c1, dst, 1, cmp);
+ }
+ continue;
+ }
+ # when we can't merge and there's a clash, choose c0 over c1, unless cmp says otherwise
+ if(c == 2r10){
+ reply0 <-= Next;
+ d0 = nil;
+ }else{
+ reply1 <-= Next;
+ d1 = nil;
+ }
+ }
+ if(c & 2r01){
+ (wd0, wc0, wreply0, weof0) = (d0, c0, reply0, eof0);
+ (wd1, wc1, wreply1, weof1) = (d1, c1, reply1, eof1);
+ d0 = nil;
+ }else if(c & 2r10){
+ (wd0, wc0, wreply0, weof0) = (d1, c1, reply1, eof1);
+ (wd1, wc1, wreply1, weof1) = (d0, c0, reply0, eof0);
+ d1 = nil;
+ }else{
+ if(wd0 == nil){
+ reply1 <-= Next;
+ d1 = nil;
+ }else{
+ reply0 <-= Next;
+ d0 = nil;
+ }
+ continue;
+ }
+ dst <-= ((wd0, nil), myreply);
+ r := <-myreply;
+ wreply0 <-= r;
+ if(r == Down)
+ r = fslib->copy(wc0, dst); # XXX hmm, maybe this should be a mergedir()
+ case r {
+ Quit or
+ Skip =>
+ if(wd1 == nil && !weof1)
+ (nil, wreply1) = <-wc1;
+ wreply1 <-= r;
+ if(r == Quit)
+ raise "exit";
+ return;
+ }
+ }
+ dst <-= ((nil, nil), myreply);
+ if(<-myreply == Quit)
+ raise "exit";
+}
+
+compare(cmp: Cmpchan, d0, d1: ref Sys->Dir): int
+{
+ mask := (d0 != nil) | (d1 != nil) << 1;
+ if(cmp == nil)
+ return mask;
+ reply := chan of int;
+ cmp <-= (d0, d1, reply);
+ return <-reply & mask;
+}
diff --git a/appl/cmd/fs/mergewrite.b b/appl/cmd/fs/mergewrite.b
new file mode 100644
index 00000000..3ff1b1f1
--- /dev/null
+++ b/appl/cmd/fs/mergewrite.b
@@ -0,0 +1,186 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "readdir.m";
+ readdir: Readdir;
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, quit, report: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Cmpchan, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "vmsx"; # XXX bad argument ordering...
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil){
+ sys->fprint(sys->fildes(2), "fs: mergewrite: cannot load %s: %r\n", Readdir->PATH);
+ raise "fail:bad module";
+ }
+ readdir->init(nil, 0);
+
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil){
+ sys->fprint(sys->fildes(2), "fs: mergewrite: cannot load %s: %r\n", Fslib->PATH);
+ raise "fail:bad module";
+ }
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ sync := chan of int;
+ spawn fswriteproc(sync, (hd args).m().i, (hd tl args).s().i, (hd tl tl args).x().i, report.start("mergewrite"));
+ <-sync;
+ return ref Value.V(sync);
+}
+
+fswriteproc(sync: chan of int, cmp: Cmpchan, root: string, c: Fschan, errorc: chan of string)
+{
+ sys->pctl(Sys->FORKNS, nil);
+ sync <-= 1;
+ if(<-sync == 0){
+ (<-c).t1 <-= Quit;
+ quit(errorc);
+ }
+
+ ((d, nil), reply) := <-c;
+ if(root != nil){
+ d = ref *d;
+ d.name = root;
+ }
+ fswritedir(d.name, cmp, d, reply, c, errorc);
+ quit(errorc);
+}
+
+fswritedir(path: string, cmp: Cmpchan, dir: ref Sys->Dir, dreply: chan of int, c: Fschan, errorc: chan of string)
+{
+ fd: ref Sys->FD;
+ if(dir.mode & Sys->DMDIR){
+ fd = sys->create(dir.name, Sys->OREAD, dir.mode|8r300);
+ made := fd != nil;
+ if(fd == nil && (fd = sys->open(dir.name, Sys->OREAD)) == nil){
+ dreply <-= Next;
+ report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, dir.mode|8r300));
+ return;
+ }
+ # XXX if we haven't just made it, we should chmod the old entry u+w to enable writing.
+ if(sys->chdir(dir.name) == -1){ # XXX beware of names starting with '#'
+ dreply <-= Next;
+ report(errorc, sys->sprint("cannot cd to %q: %r", path));
+ fd = nil;
+ sys->remove(dir.name);
+ return;
+ }
+ dreply <-= Down;
+ entries: array of ref Sys->Dir;
+ if(made == 0)
+ entries = readdir->readall(fd, Readdir->NAME|Readdir->COMPACT).t0;
+ i := 0;
+ eod := 0;
+ d0, d1: ref Sys->Dir;
+ reply: chan of int;
+ path[len path] = '/';
+ for(;;){
+ if(!eod && d0 == nil){
+ ((d0, nil), reply) = <-c;
+ if(d0 == nil){
+ reply <-= Next;
+ eod = 1;
+ }
+ }
+ if(d1 == nil && i < len entries)
+ d1 = entries[i++];
+ if(d0 == nil && d1 == nil)
+ break;
+
+ (wd0, wd1) := (d0, d1);
+ if(d0 != nil && d1 != nil && d0.name != d1.name){
+ if(d0.name < d1.name)
+ wd1 = nil;
+ else
+ wd0 = nil;
+ }
+ r := compare(cmp, wd0, wd1);
+ if(wd1 != nil && (r & 2r10) == 0){
+ if(wd1.mode & Sys->DMDIR)
+ rmdir(wd1.name);
+ else
+ remove(wd1.name);
+ d1 = nil;
+ }
+ if(wd0 != nil){
+ if((r & 2r01) == 0)
+ reply <-= Next;
+ else
+ fswritedir(path + wd0.name, cmp, d0, reply, c, errorc);
+ d0 = nil;
+ }
+ }
+ sys->chdir("..");
+ if((dir.mode & 8r300) != 8r300){
+ ws := Sys->nulldir;
+ ws.mode = dir.mode;
+ if(sys->fwstat(fd, ws) == -1)
+ report(errorc, sys->sprint("cannot wstat %q: %r", path));
+ }
+ }else{
+ fd = sys->create(dir.name, Sys->OWRITE, dir.mode);
+ if(fd == nil){
+ dreply <-= Next;
+ report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, dir.mode|8r300));
+ return;
+ }
+ dreply <-= Down;
+ while((((nil, buf), reply) := <-c).t0.data != nil){
+ nw := sys->write(fd, buf, len buf);
+ if(nw < len buf){
+ if(nw == -1)
+ errorc <-= sys->sprint("error writing %q: %r", path);
+ else
+ errorc <-= sys->sprint("short write");
+ reply <-= Skip;
+ break;
+ }
+ reply <-= Next;
+ }
+ reply <-= Next;
+ }
+}
+
+rmdir(name: string)
+{
+ (d, n) := readdir->init(name, Readdir->NONE|Readdir->COMPACT);
+ for(i := 0; i < n; i++){
+ path := name+"/"+d[i].name;
+ if(d[i].mode & Sys->DMDIR)
+ rmdir(path);
+ else
+ remove(path);
+ }
+ remove(name);
+}
+
+remove(name: string)
+{
+ if(sys->remove(name) < 0)
+ sys->fprint(sys->fildes(2), "mergewrite: cannot remove %q: %r\n", name);
+}
+
+compare(cmp: Cmpchan, d0, d1: ref Sys->Dir): int
+{
+ mask := (d0 != nil) | (d1 != nil) << 1;
+ if(cmp == nil)
+ return mask;
+ reply := chan of int;
+ cmp <-= (d0, d1, reply);
+ return <-reply & mask;
+}
diff --git a/appl/cmd/fs/mkfile b/appl/cmd/fs/mkfile
new file mode 100644
index 00000000..37fb5e12
--- /dev/null
+++ b/appl/cmd/fs/mkfile
@@ -0,0 +1,60 @@
+<../../../mkconfig
+# fs write /n/local/n/fossil/usr/inferno {filter {and {not {or *.dis *.sbl}} {path /appl/cmd/fs /module/fslib.m /appl/lib/fslib.b /appl/cmd/fs.b /man/1/fs}} /}
+TARG=\
+ and.dis\
+ bundle.dis\
+ chstat.dis\
+ compose.dis\
+ depth.dis\
+ entries.dis\
+ eval.dis\
+ exec.dis\
+ filter.dis\
+ ls.dis\
+ match.dis\
+ merge.dis\
+ mergewrite.dis\
+ mode.dis\
+ not.dis\
+ or.dis\
+ path.dis\
+ pipe.dis\
+ print.dis\
+ proto.dis\
+ query.dis\
+ run.dis\
+ select.dis\
+ setroot.dis\
+ size.dis\
+ unbundle.dis\
+ walk.dis\
+ write.dis\
+ void.dis\
+
+
+INS= ${TARG:%=$ROOT/dis/fs/%}
+
+SYSMODULES=\
+ bufio.m\
+ draw.m\
+ sh.m\
+ sys.m\
+ bundle.m\
+ fslib.m\
+
+DISBIN=$ROOT/dis/fs
+
+<$ROOT/mkfiles/mkdis
+
+all:V: $TARG
+
+install:V: $INS
+
+nuke:V: clean
+ rm -f $INS
+
+clean:V:
+ rm -f *.dis *.sbl
+
+uninstall:V:
+ rm -f $INS
diff --git a/appl/cmd/fs/mode.b b/appl/cmd/fs/mode.b
new file mode 100644
index 00000000..83d385d7
--- /dev/null
+++ b/appl/cmd/fs/mode.b
@@ -0,0 +1,120 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+# XXX implement octal modes.
+
+User: con 8r700;
+Group: con 8r070;
+Other: con 8r007;
+All: con User | Group | Other;
+
+Read: con 8r444;
+Write: con 8r222;
+Exec: con 8r111;
+
+types(): string
+{
+ return "ps";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ spec := (hd args).s().i;
+ (ok, mask, mode) := parsemode(spec);
+ if(ok == 0){
+ sys->fprint(sys->fildes(2), "fs: mode: bad mode %#q\n", spec);
+ return nil;
+ }
+ c := chan of Gatequery;
+ spawn modegate(c, mask, mode);
+ return ref Value.P(c);
+}
+
+modegate(c: Gatechan, mask, mode: int)
+{
+ m := mode & mask;
+ while((((d, nil, nil), reply) := <-c).t0.t0 != nil)
+ reply <-= ((d.mode & mask) ^ m) == 0;
+}
+
+# stolen from /appl/cmd/chmod.b
+parsemode(spec: string): (int, int, int)
+{
+ mask := Sys->DMAPPEND | Sys->DMEXCL | Sys->DMDIR | Sys->DMAUTH;
+loop:
+ for(i := 0; i < len spec; i++){
+ case spec[i] {
+ 'u' =>
+ mask |= User;
+ 'g' =>
+ mask |= Group;
+ 'o' =>
+ mask |= Other;
+ 'a' =>
+ mask |= All;
+ * =>
+ break loop;
+ }
+ }
+ if(i == len spec)
+ return (0, 0, 0);
+ if(i == 0)
+ mask |= All;
+
+ op := spec[i++];
+ if(op != '+' && op != '-' && op != '=')
+ return (0, 0, 0);
+
+ mode := 0;
+ for(; i < len spec; i++){
+ case spec[i]{
+ 'r' =>
+ mode |= Read;
+ 'w' =>
+ mode |= Write;
+ 'x' =>
+ mode |= Exec;
+ 'a' =>
+ mode |= Sys->DMAPPEND;
+ 'l' =>
+ mode |= Sys->DMEXCL;
+ 'd' =>
+ mode |= Sys->DMDIR;
+ 'A' =>
+ mode |= Sys->DMAUTH;
+ * =>
+ return (0, 0, 0);
+ }
+ }
+ if(op == '+' || op == '-')
+ mask &= mode;
+ if(op == '-')
+ mode = ~mode;
+ return (1, mask, mode);
+}
+
+
diff --git a/appl/cmd/fs/not.b b/appl/cmd/fs/not.b
new file mode 100644
index 00000000..e318f855
--- /dev/null
+++ b/appl/cmd/fs/not.b
@@ -0,0 +1,48 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "pp";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ c := chan of Gatequery;
+ spawn notgate(c, (hd args).p().i);
+ return ref Value.P(c);
+}
+
+notgate(c, sub: Gatechan)
+{
+ myreply := chan of int;
+ while(((d, reply) := <-c).t0.t0 != nil){
+ sub <-= (d, myreply);
+ reply <-= !<-myreply;
+ }
+ sub <-= (Nilentry, nil);
+}
diff --git a/appl/cmd/fs/or.b b/appl/cmd/fs/or.b
new file mode 100644
index 00000000..ca6668d1
--- /dev/null
+++ b/appl/cmd/fs/or.b
@@ -0,0 +1,65 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "pppp*";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ c := chan of Gatequery;
+ spawn orgate(c, args);
+ return ref Value.P(c);
+}
+
+orgate(c: Gatechan, args: list of ref Value)
+{
+ sub: list of Gatechan;
+ for(; args != nil; args = tl args)
+ sub = (hd args).p().i :: sub;
+ sub = rev(sub);
+ myreply := chan of int;
+ while(((d, reply) := <-c).t0.t0 != nil){
+ for(l := sub; l != nil; l = tl l){
+ (hd l) <-= (d, myreply);
+ if(<-myreply)
+ break;
+ }
+ reply <-= l != nil;
+ }
+ for(; sub != nil; sub = tl sub)
+ hd sub <-= (Nilentry, nil);
+}
+
+rev[T](x: list of T): list of T
+{
+ l: list of T;
+ for(; x != nil; x = tl x)
+ l = hd x :: l;
+ return l;
+}
diff --git a/appl/cmd/fs/path.b b/appl/cmd/fs/path.b
new file mode 100644
index 00000000..2b8c98a0
--- /dev/null
+++ b/appl/cmd/fs/path.b
@@ -0,0 +1,77 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "pss*-x";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ # XXX cleanname all paths?
+ c := chan of Gatequery;
+ p: list of string;
+ for(; args != nil; args = tl args)
+ p = (hd args).s().i :: p;
+ spawn pathgate(c, opts != nil, p);
+ return ref Value.P(c);
+}
+
+pathgate(c: Gatechan, xflag: int, paths: list of string)
+{
+ if(xflag){
+ while((((d, path, nil), reply) := <-c).t0.t0 != nil){
+ for(q := paths; q != nil; q = tl q){
+ r := 1;
+ p := hd q;
+ if(len path > len p)
+ r = path[len p] != '/' || path[0:len p] != p;
+ else if(len path == len p)
+ r = path != p;
+ if(r == 0)
+ break;
+ }
+ reply <-= q == nil;
+ }
+ }else{
+ while((((d, path, nil), reply) := <-c).t0.t0 != nil){
+ for(q := paths; q != nil; q = tl q){
+ r := 0;
+ p := hd q;
+ if(len path > len p)
+ r = path[len p] == '/' && path[0:len p] == p;
+ else if(len path == len p)
+ r = path == p;
+ else
+ r = p[len path] == '/' && p[0:len path] == path;
+ if(r)
+ break;
+ }
+ reply <-= q != nil;
+ }
+ }
+}
diff --git a/appl/cmd/fs/pipe.b b/appl/cmd/fs/pipe.b
new file mode 100644
index 00000000..665abdeb
--- /dev/null
+++ b/appl/cmd/fs/pipe.b
@@ -0,0 +1,223 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "fslib.m";
+ fslib: Fslib;
+ Option, Value, Fschan, Report, quit: import fslib;
+ Skip, Next, Down, Quit: import fslib;
+
+
+# pipe the contents of the files in a filesystem through
+# a command. -1 causes one command only to be executed.
+# -p and -P (exclusive to -1) cause stat modes to be set in the shell environment.
+types(): string
+{
+ return "vcx-1-p-P";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ sh = load Sh Sh->PATH;
+ if(sh == nil)
+ badmod(Sh->PATH);
+ sh->initialise();
+}
+
+run(drawctxt: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ n := 1;
+ oneflag := pflag := 0;
+ for(; opts != nil; opts = tl opts){
+ o := hd opts;
+ case o.opt {
+ '1' =>
+ oneflag = 1;
+ 'p' =>
+ pflag = 1;
+ 'P' =>
+ pflag = 2;
+ }
+ }
+ if(pflag && oneflag){
+ sys->fprint(sys->fildes(2), "fs: exec: cannot specify -p with -1\n");
+ return nil;
+ }
+ cmd := (hd args).c().i;
+ c := (hd tl args).x().i;
+ sync := chan of int;
+ spawn execproc(drawctxt, sync, oneflag, pflag, c, cmd, report.start("exec"));
+ sync <-= 1;
+ return ref Value.V(sync);
+}
+
+execproc(drawctxt: ref Draw->Context, sync: chan of int, oneflag, pflag: int,
+ c: Fschan, cmd: ref Sh->Cmd, errorc: chan of string)
+{
+ sys->pctl(Sys->NEWFD, 0::1::2::nil);
+ ctxt := Context.new(drawctxt);
+ <-sync;
+ if(<-sync == 0){
+ (<-c).t1 <-= Quit;
+ quit(errorc);
+ }
+ argv := ref Sh->Listnode(cmd, nil) :: nil;
+ fd: ref Sys->FD;
+ result := chan of string;
+ if(oneflag){
+ fd = popen(ctxt, argv, result);
+ if(fd == nil){
+ (<-c).t1 <-= Quit;
+ quit(errorc);
+ }
+ }
+
+ names: list of string;
+ name: string;
+ indent := 0;
+ for(;;){
+ (d, reply) := <-c;
+ if(d.dir == nil){
+ reply <-= Next;
+ if(--indent == 0){
+ break;
+ }
+ (name, names) = (hd names, tl names);
+ continue;
+ }
+ if((d.dir.mode & Sys->DMDIR) != 0){
+ reply <-= Down;
+ names = name :: names;
+ if(indent > 0 && name != nil && name[len name - 1] != '/')
+ name[len name] = '/';
+ name += d.dir.name;
+ indent++;
+ continue;
+ }
+ if(!oneflag){
+ p := name;
+ if(p != nil && p[len p - 1] != '/')
+ p[len p] = '/';
+ setenv(ctxt, "file", p + d.dir.name :: nil);
+ if(pflag)
+ setstatenv(ctxt, d.dir, pflag);
+ fd = popen(ctxt, argv, result);
+ }
+ if(fd == nil){
+ reply <-= Next;
+ continue;
+ }
+ reply <-= Down;
+ for(;;){
+ data: array of byte;
+ ((nil, data), reply) = <-c;
+ reply <-= Next;
+ if(data == nil)
+ break;
+ n := -1;
+ {n = sys->write(fd, data, len data);}exception {"write on closed pipe" => ;}
+ if(n != len data){
+ if(oneflag){
+ (<-c).t1 <-= Quit;
+ quit(errorc);
+ }
+ (<-c).t1 <-= Skip;
+ break;
+ }
+ }
+ if(!oneflag){
+ fd = nil;
+ <-result;
+ }
+ }
+ fd = nil;
+ if(oneflag)
+ <-result;
+ quit(errorc);
+}
+
+popen(ctxt: ref Context, argv: list of ref Sh->Listnode, result: chan of string): ref Sys->FD
+{
+ sync := chan of int;
+ fds := array[2] of ref Sys->FD;
+ sys->pipe(fds);
+ spawn runcmd(ctxt, argv, fds[0], sync, result);
+ <-sync;
+ return fds[1];
+}
+
+runcmd(ctxt: ref Context, argv: list of ref Sh->Listnode, stdin: ref Sys->FD, sync: chan of int, result: chan of string)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ sys->dup(stdin.fd, 0);
+ stdin = nil;
+ sys->pctl(Sys->NEWFD, 0::1::2::nil);
+ ctxt = ctxt.copy(0);
+ sync <-= 0;
+ r := ctxt.run(argv, 0);
+ ctxt = nil;
+ sys->pctl(Sys->NEWFD, nil);
+ result <-=r;
+}
+
+setenv(ctxt: ref Context, var: string, val: list of string)
+{
+ ctxt.set(var, sh->stringlist2list(val));
+}
+
+setstatenv(ctxt: ref Context, dir: ref Sys->Dir, pflag: int)
+{
+ setenv(ctxt, "mode", modes(dir.mode) :: nil);
+ setenv(ctxt, "uid", dir.uid :: nil);
+ setenv(ctxt, "mtime", string dir.mtime :: nil);
+ setenv(ctxt, "length", string dir.length :: nil);
+
+ if(pflag > 1){
+ setenv(ctxt, "name", dir.name :: nil);
+ setenv(ctxt, "gid", dir.gid :: nil);
+ setenv(ctxt, "muid", dir.muid :: nil);
+ setenv(ctxt, "qid", sys->sprint("16r%ubx", dir.qid.path) :: string dir.qid.vers :: nil);
+ setenv(ctxt, "atime", string dir.atime :: nil);
+ setenv(ctxt, "dtype", sys->sprint("%c", dir.dtype) :: nil);
+ setenv(ctxt, "dev", string dir.dev :: nil);
+ }
+}
+
+mtab := array[] of {
+ "---", "--x", "-w-", "-wx",
+ "r--", "r-x", "rw-", "rwx"
+};
+
+modes(mode: int): string
+{
+ s: string;
+
+ if(mode & Sys->DMDIR)
+ s = "d";
+ else if(mode & Sys->DMAPPEND)
+ s = "a";
+ else if(mode & Sys->DMAUTH)
+ s = "A";
+ else
+ s = "-";
+ if(mode & Sys->DMEXCL)
+ s += "l";
+ else
+ s += "-";
+ s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7];
+ return s;
+}
diff --git a/appl/cmd/fs/print.b b/appl/cmd/fs/print.b
new file mode 100644
index 00000000..21761e0e
--- /dev/null
+++ b/appl/cmd/fs/print.b
@@ -0,0 +1,51 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "vt";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ sync := chan of int;
+ spawn printproc(sync, (hd args).t().i, report.start("print"));
+ return ref Value.V(sync);
+}
+
+printproc(sync: chan of int, c: Entrychan, errorc: chan of string)
+{
+ if(<-sync == 0){
+ c.sync <-= 0;
+ quit(errorc);
+ exit;
+ }
+ c.sync <-= 1;
+ while(((d, p, nil) := <-c.c).t0 != nil)
+ sys->print("%s\n", p);
+ quit(errorc);
+}
diff --git a/appl/cmd/fs/proto.b b/appl/cmd/fs/proto.b
new file mode 100644
index 00000000..bc836f44
--- /dev/null
+++ b/appl/cmd/fs/proto.b
@@ -0,0 +1,388 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "readdir.m";
+ readdir: Readdir;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, report, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+File: adt {
+ name: string;
+ mode: int;
+ owner: string;
+ group: string;
+ old: string;
+ flags: int;
+ sub: cyclic array of ref File;
+};
+
+Proto: adt {
+ indent: int;
+ lastline: string;
+ iob: ref Iobuf;
+};
+
+Star, Plus: con 1<<iota;
+
+types(): string
+{
+ return "xs-rs";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: proto: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ badmod(Readdir->PATH);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ badmod(Bufio->PATH);
+ str = load String String->PATH;
+ if(str == nil)
+ badmod(String->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ protofile := (hd args).s().i;
+ rootpath: string;
+ if(opts != nil)
+ rootpath = (hd (hd opts).args).s().i;
+ if(rootpath == nil)
+ rootpath = "/";
+
+ proto := ref Proto(0, nil, nil);
+ if((proto.iob = bufio->open(protofile, Sys->OREAD)) == nil){
+ sys->fprint(sys->fildes(2), "fs: proto: cannot open %q: %r\n", protofile);
+ return nil;
+ }
+ root := ref File(rootpath, ~0, nil, nil, nil, 0, nil);
+ (root.flags, root.sub) = readproto(proto, -1);
+ c := chan of (Fsdata, chan of int);
+ spawn protowalk(c, root, report.start("proto"));
+ return ref Value.X(c);
+}
+
+protowalk(c: Fschan, root: ref File, errorc: chan of string)
+{
+ protowalk1(c, root.flags, root.name, file2dir(root, nil), root.sub, errorc);
+ quit(errorc);
+}
+
+protowalk1(c: Fschan, flags: int, path: string, d: ref Sys->Dir,
+ sub: array of ref File, errorc: chan of string): int
+{
+ reply := chan of int;
+ c <-= ((d, nil), reply);
+ case r := <-reply {
+ Quit =>
+ quit(errorc);
+ Next or
+ Skip =>
+ return r;
+ }
+ (a, n) := readdir->init(path, Readdir->NAME|Readdir->COMPACT);
+ if(len a == 0){
+ c <-= ((nil, nil), reply);
+ if(<-reply == Quit)
+ quit(errorc);
+ return Next;
+ }
+ j := 0;
+ prevsub: string;
+ for(i := 0; i < n; i++){
+ for(; j < len sub; j++){
+ s := sub[j].name;
+ if(s == prevsub){
+ report(errorc, sys->sprint("duplicate entry %s", pathconcat(path, s)));
+ continue; # eliminate duplicates in proto
+ }
+ if(s >= a[i].name || sub[j].old != nil)
+ break;
+ report(errorc, sys->sprint("%s not found", pathconcat(path, s)));
+ }
+ foundsub := j < len sub && (sub[j].name == a[i].name || sub[j].old != nil);
+ if(foundsub || flags&Plus ||
+ (flags&Star && (a[i].mode & Sys->DMDIR)==0)){
+ f: ref File;
+ if(foundsub){
+ f = sub[j++];
+ prevsub = f.name;
+ }
+ p: string;
+ d: ref Sys->Dir;
+ if(foundsub && f.old != nil){
+ p = f.old;
+ (ok, xd) := sys->stat(p);
+ if(ok == -1){
+ report(errorc, sys->sprint("cannot stat %q: %r", p));
+ continue;
+ }
+ d = ref xd;
+ }else{
+ p = pathconcat(path, a[i].name);
+ d = a[i];
+ }
+
+ d = file2dir(f, d);
+ r: int;
+ if((d.mode & Sys->DMDIR) == 0)
+ r = walkfile(c, p, d, errorc);
+ else if(flags & Plus)
+ r = protowalk1(c, Plus, p, d, nil, errorc);
+ else
+ r = protowalk1(c, f.flags, p, d, f.sub, errorc);
+ if(r == Skip)
+ return Next;
+ }
+ }
+ c <-= ((nil, nil), reply);
+ if(<-reply == Quit)
+ quit(errorc);
+ return Next;
+}
+
+pathconcat(p, name: string): string
+{
+ if(p != nil && p[len p - 1] != '/')
+ p[len p] = '/';
+ p += name;
+ return p;
+}
+
+# from(ish) walk.b
+walkfile(c: Fschan, path: string, d: ref Sys->Dir, errorc: chan of string): int
+{
+ reply := chan of int;
+ fd := sys->open(path, Sys->OREAD);
+ if(fd == nil){
+ report(errorc, sys->sprint("cannot open %q: %r", path));
+ return Next;
+ }
+ c <-= ((d, nil), reply);
+ case r := <-reply {
+ Quit =>
+ quit(errorc);
+ Next or
+ Skip =>
+ return r;
+ }
+ length := d.length;
+ for(n := big 0; n < length; ){
+ nr := Sys->ATOMICIO;
+ if(n + big Sys->ATOMICIO > length)
+ nr = int (length - n);
+ buf := array[nr] of byte;
+ nr = sys->read(fd, buf, nr);
+ if(nr <= 0){
+ if(nr < 0)
+ report(errorc, sys->sprint("error reading %q: %r", path));
+ else
+ report(errorc, sys->sprint("%q is shorter than expected (%bd/%bd)",
+ path, n, length));
+ break;
+ }else if(nr < len buf)
+ buf = buf[0:nr];
+ c <-= ((nil, buf), reply);
+ case <-reply {
+ Quit =>
+ quit(errorc);
+ Skip =>
+ return Next;
+ }
+ n += big nr;
+ }
+ c <-= ((nil, nil), reply);
+ if(<-reply == Quit)
+ quit(errorc);
+ return Next;
+}
+
+readproto(proto: ref Proto, indent: int): (int, array of ref File)
+{
+ a := array[10] of ref File;
+ n := 0;
+ flags := 0;
+ while((f := readline(proto, indent)) != nil){
+ if(f.name == "*")
+ flags |= Star;
+ else if(f.name == "+")
+ flags |= Plus;
+ else{
+ (f.flags, f.sub) = readproto(proto, proto.indent);
+ if(n == len a)
+ a = (array[n * 2] of ref File)[0:] = a;
+ a[n++] = f;
+ }
+ }
+ if(n < len a)
+ a = (array[n] of ref File)[0:] = a[0:n];
+ mergesort(a, array[n] of ref File);
+ return (flags, a);
+}
+
+readline(proto: ref Proto, indent: int): ref File
+{
+ s: string;
+ if(proto.lastline != nil){
+ s = proto.lastline;
+ proto.lastline = nil;
+ }else if(proto.indent == -1)
+ return nil;
+ else if((s = proto.iob.gets('\n')) == nil){
+ proto.indent = -1;
+ return nil;
+ }
+ spc := 0;
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ if(c == ' ')
+ spc++;
+ else if(c == '\t')
+ spc += 8;
+ else
+ break;
+ }
+ if(i == len s || s[i] == '#' || s[i] == '\n')
+ return readline(proto, indent); # XXX sort out tail recursion!
+ if(spc <= indent){
+ proto.lastline = s;
+ return nil;
+ }
+ proto.indent = spc;
+ (n, toks) := sys->tokenize(s, " \t\n");
+ f := ref File(nil, ~0, nil, nil, nil, 0, nil);
+ (f.name, toks) = (getname(hd toks, 0), tl toks);
+ if(toks == nil)
+ return f;
+ (f.mode, toks) = (getmode(hd toks), tl toks);
+ if(toks == nil)
+ return f;
+ (f.owner, toks) = (getname(hd toks, 1), tl toks);
+ if(toks == nil)
+ return f;
+ (f.group, toks) = (getname(hd toks, 1), tl toks);
+ if(toks == nil)
+ return f;
+ (f.old, toks) = (hd toks, tl toks);
+ return f;
+}
+
+mergesort(a, b: array of ref File)
+{
+ r := len a;
+ if (r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m]);
+ mergesort(a[m:], b[m:]);
+ b[0:] = a;
+ for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if(b[i].name > b[j].name)
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if (i < m)
+ a[k:] = b[i:m];
+ else if (j < r)
+ a[k:] = b[j:r];
+ }
+}
+
+getname(s: string, allowminus: int): string
+{
+ if(s == nil)
+ return nil;
+ if(allowminus && s == "-")
+ return nil;
+ if(s[0] == '$')
+ return getenv(s[1:]);
+ return s;
+}
+
+getenv(s: string): string
+{
+ # XXX implement env variables
+ return nil;
+}
+
+getmode(s: string): int
+{
+ s = getname(s, 1);
+ if(s == nil)
+ return ~0;
+ m := 0;
+ i := 0;
+ if(s[i] == 'd'){
+ m |= Sys->DMDIR;
+ i++;
+ }
+ if(i < len s && s[i] == 'a'){
+ m |= Sys->DMAPPEND;
+ i++;
+ }
+ if(i < len s && s[i] == 'l'){
+ m |= Sys->DMEXCL;
+ i++;
+ }
+ (xmode, t) := str->toint(s, 8);
+ if(t != nil){
+ # report(aux.errorc, "bad mode specification %q", s);
+ return ~0;
+ }
+ return xmode | m;
+}
+
+file2dir(f: ref File, old: ref Sys->Dir): ref Sys->Dir
+{
+ d := ref Sys->nulldir;
+ if(old != nil){
+ if(old.dtype != 'M'){
+ d.uid = "sys";
+ d.gid = "sys";
+ xmode := (old.mode >> 6) & 7;
+ d.mode = old.mode | xmode | (xmode << 3);
+ }else{
+ d.uid = old.uid;
+ d.gid = old.gid;
+ d.mode = old.mode;
+ }
+ d.length = old.length;
+ d.mtime = old.mtime;
+ d.atime = old.atime;
+ d.muid = old.muid;
+ d.name = old.name;
+ }
+ if(f != nil){
+ d.name = f.name;
+ if(f.owner != nil)
+ d.uid = f.owner;
+ if(f.group != nil)
+ d.gid = f.group;
+ if(f.mode != ~0)
+ d.mode = f.mode;
+ }
+ return d;
+}
diff --git a/appl/cmd/fs/query.b b/appl/cmd/fs/query.b
new file mode 100644
index 00000000..421be1d9
--- /dev/null
+++ b/appl/cmd/fs/query.b
@@ -0,0 +1,130 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "fslib.m";
+ fslib: Fslib;
+ Option, Value, Gatechan, Gatequery, Report, Nilentry: import fslib;
+
+types(): string
+{
+ return "pc-p-P";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: query: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ sh = load Sh Sh->PATH;
+ if(sh == nil)
+ badmod(Sh->PATH);
+}
+
+run(drawctxt: ref Draw->Context, nil: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ pflag := 0;
+ for(; opts != nil; opts = tl opts){
+ o := hd opts;
+ case o.opt {
+ 'p' =>
+ pflag = 1;
+ 'P' =>
+ pflag = 2;
+ }
+ }
+
+ v := ref Value.P(chan of Gatequery);
+ spawn querygate(drawctxt, v.i, (hd args).c().i, pflag);
+ v.i <-= (Nilentry, nil);
+ return v;
+}
+
+querygate(drawctxt: ref Draw->Context, c: Gatechan, cmd: ref Sh->Cmd, pflag: int)
+{
+ sys->pctl(Sys->NEWFD, 0::1::2::nil);
+ ctxt := Context.new(drawctxt);
+ <-c;
+ argv := ref Sh->Listnode(cmd, nil) :: nil;
+ while((((d, p, nil), reply) := <-c).t0.t0 != nil){
+ ctxt.set("file", ref Sh->Listnode(nil, p) :: nil);
+ if(pflag)
+ setstatenv(ctxt, d, pflag);
+ err := "";
+ {
+ err = ctxt.run(argv, 0);
+ } exception e {
+ "fail:*" =>
+ err = e;
+ }
+ reply <-= (err == nil);
+ }
+}
+
+# XXX shouldn't duplicate this...
+
+setenv(ctxt: ref Context, var: string, val: list of string)
+{
+ ctxt.set(var, sh->stringlist2list(val));
+}
+
+setstatenv(ctxt: ref Context, dir: ref Sys->Dir, pflag: int)
+{
+ setenv(ctxt, "mode", modes(dir.mode) :: nil);
+ setenv(ctxt, "uid", dir.uid :: nil);
+ setenv(ctxt, "mtime", string dir.mtime :: nil);
+ setenv(ctxt, "length", string dir.length :: nil);
+
+ if(pflag > 1){
+ setenv(ctxt, "name", dir.name :: nil);
+ setenv(ctxt, "gid", dir.gid :: nil);
+ setenv(ctxt, "muid", dir.muid :: nil);
+ setenv(ctxt, "qid", sys->sprint("16r%ubx", dir.qid.path) :: string dir.qid.vers :: nil);
+ setenv(ctxt, "atime", string dir.atime :: nil);
+ setenv(ctxt, "dtype", sys->sprint("%c", dir.dtype) :: nil);
+ setenv(ctxt, "dev", string dir.dev :: nil);
+ }
+}
+
+start(startc: chan of (string, chan of string), name: string): chan of string
+{
+ c := chan of string;
+ startc <-= (name, c);
+ return c;
+}
+
+mtab := array[] of {
+ "---", "--x", "-w-", "-wx",
+ "r--", "r-x", "rw-", "rwx"
+};
+
+modes(mode: int): string
+{
+ s: string;
+
+ if(mode & Sys->DMDIR)
+ s = "d";
+ else if(mode & Sys->DMAPPEND)
+ s = "a";
+ else if(mode & Sys->DMAUTH)
+ s = "A";
+ else
+ s = "-";
+ if(mode & Sys->DMEXCL)
+ s += "l";
+ else
+ s += "-";
+ s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7];
+ return s;
+}
diff --git a/appl/cmd/fs/readfile.b b/appl/cmd/fs/readfile.b
new file mode 100644
index 00000000..4a52ae08
--- /dev/null
+++ b/appl/cmd/fs/readfile.b
@@ -0,0 +1,144 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, report, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+this is a bad idea, i think
+i think walk + filter + setroot is good enough.
+
+types(): string
+{
+ # usage: readfile [-f file] name
+ return "xs-fs";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: readfile: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ path: string;
+ f := (hd args).s().i;
+ fd: ref Sys->FD;
+ seekable: int;
+ if(f == "-"){
+ if(opts == nil){
+ sys->fprint(sys->fildes(2), "fs: readfile: must specify a path when reading stdin\n");
+ return nil;
+ }
+ fd = sys->fildes(0);
+ seekable = 0;
+ }else{
+ fd = sys->open(f, Sys->OREAD);
+ seekable = isseekable(fd);
+ }
+ if(fd == nil){
+ sys->fprint(sys->fildes(2), "fs: readfile: cannot open %s: %r\n", f);
+ return nil;
+ }
+ if(opts != nil)
+ path = (hd (hd opts).args).s().i;
+ else
+ path = f;
+
+ (root, file) := pathsplit(path);
+ if(file == nil || file == "." || file == ".."){
+ sys->fprint(sys->fildes(2), "fs: readfile: invalid filename %q\n", fname);
+ return nil;
+ }
+ d.name = file;
+ v := ref Value.X(chan of (Fsdata, chan of int));
+ spawn readproc(v.i, fd, root, ref d, seekable, report.start("read"));
+ return v;
+}
+
+readproc(c: Fschan, fd: ref Sys->FD, root: string, d: ref Sys->Dir, seekable: int, errorc: chan of string)
+{
+ reply := chan of int;
+ rd := ref Sys->nulldir;
+ rd.name = root;
+ c <-= ((rd, nil), reply);
+ if(<-reply != Down)
+ quit(errorc);
+
+ c <-= ((d, nil), reply);
+ case <-reply {
+ Down =>
+ sendfile(c, fd, errorc);
+ Skip or
+ Quit =>
+ quit(errorc);
+ }
+ c <-= ((nil, nil), reply);
+ <-reply;
+ quit(errorc);
+}
+
+sendfile(c: Fschan, data: list of array of byte, length: big, errorc: chan of string)
+{
+ reply := chan of int;
+ for(;;){
+ buf: array of byte;
+ if(fd != nil){
+ buf := array[Sys->ATOMICIO] of byte;
+ if((n := sys->read(fd, buf, len buf)) <= 0){
+ if(n < 0)
+ report(errorc, sys->sprint("read error: %r"));
+ c <-= ((nil, nil), reply);
+ if(<-reply == Quit)
+ quit(errorc);
+ return;
+ }
+ c <-= ((nil, buf), reply);
+ case <-reply {
+ Quit =>
+ quit(errorc);
+ Skip =>
+ return;
+ }
+ }
+}
+
+pathsplit(p: string): (string, string)
+{
+ for (i := len p - 1; i >= 0; i--)
+ if (p[i] != '/')
+ break;
+ if (i < 0)
+ return (p, nil);
+ p = p[0:i+1];
+ for (i = len p - 1; i >=0; i--)
+ if (p[i] == '/')
+ break;
+ if (i < 0)
+ return (".", p);
+ return (p[0:i+1], p[i+1:]);
+}
+
+# dodgy heuristic... avoid, or using the stat-length of pipes and net connections
+isseekable(fd: ref Sys->FD): int
+{
+ (ok, stat) := sys->stat(iob.fd);
+ if(ok != -1 && stat.dtype == '|' || stat.dtype == 'I')
+ return 0;
+ return 1;
+}
diff --git a/appl/cmd/fs/run.b b/appl/cmd/fs/run.b
new file mode 100644
index 00000000..a5734d7c
--- /dev/null
+++ b/appl/cmd/fs/run.b
@@ -0,0 +1,60 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "sc";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ sh = load Sh Sh->PATH;
+ if(sh == nil)
+ badmod(Sh->PATH);
+ sh->initialise();
+}
+
+run(drawctxt: ref Draw->Context, nil: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ c := (hd args).c().i;
+ ctxt := Context.new(drawctxt);
+ ctxt.setlocal("s", nil);
+ {
+ ctxt.run(ref Sh->Listnode(c, nil)::nil, 0);
+ } exception e {
+ "fail:*" =>
+ sys->fprint(sys->fildes(2), "fs: run: exception %q raised in %s\n", e[5:], sh->cmd2string(c));
+ return nil;
+ }
+ sl := ctxt.get("s");
+ if(sl == nil || tl sl != nil){
+ sys->fprint(sys->fildes(2), "fs: run: $s has %d members; exactly one is required\n", len sl);
+ return nil;
+ }
+ s := (hd sl).word;
+ if(s == nil && (hd sl).cmd != nil)
+ s = sh->cmd2string((hd sl).cmd);
+ return ref Value.S(s);
+}
diff --git a/appl/cmd/fs/select.b b/appl/cmd/fs/select.b
new file mode 100644
index 00000000..9fe5e4b0
--- /dev/null
+++ b/appl/cmd/fs/select.b
@@ -0,0 +1,56 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "tpt";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ dst := Entrychan(chan of int, chan of Entry);
+ spawn selectproc((hd tl args).t().i, dst, (hd args).p().i);
+ return ref Value.T(dst);
+}
+
+selectproc(src, dst: Entrychan, query: Gatechan)
+{
+ if(<-dst.sync == 0){
+ query <-= (Nilentry, nil);
+ src.sync <-= 0;
+ exit;
+ }
+ src.sync <-= 1;
+ reply := chan of int;
+ while((d := <-src.c).t0 != nil){
+ query <-= (d, reply);
+ if(<-reply)
+ dst.c <-= d;
+ }
+ dst.c <-= Nilentry;
+ query <-= (Nilentry, nil);
+}
diff --git a/appl/cmd/fs/setroot.b b/appl/cmd/fs/setroot.b
new file mode 100644
index 00000000..d39a4cf4
--- /dev/null
+++ b/appl/cmd/fs/setroot.b
@@ -0,0 +1,104 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+# set the root
+types(): string
+{
+ return "xsx-c";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ root := (hd args).s().i;
+ if(root == nil && opts == nil){
+ sys->fprint(sys->fildes(2), "fs: setroot: empty path\n");
+ return nil;
+ }
+ v := ref Value.X(chan of (Fsdata, chan of int));
+ spawn setroot((hd tl args).x().i, v.i, root, opts != nil);
+ return v;
+}
+
+setroot(src, dst: Fschan, root: string, cflag: int)
+{
+ ((d, nil), reply) := <-src;
+ if(cflag){
+ createroot(src, dst, root, d, reply);
+ }else{
+ myreply := chan of int;
+ rd := ref *d;
+ rd.name = root;
+ dst <-= ((rd, nil), myreply);
+ if(<-myreply == Down){
+ reply <-= Down;
+ fslib->copy(src, dst);
+ }
+ }
+}
+
+createroot(src, dst: Fschan, root: string, d: ref Sys->Dir, reply: chan of int)
+{
+ if(root == nil)
+ root = d.name;
+ (n, elems) := sys->tokenize(root, "/"); # XXX should really do a cleanname first
+ if(root[0] == '/'){
+ elems = "/" :: elems;
+ n++;
+ }
+ myreply := chan of int;
+ lev := 0;
+ r := -1;
+ for(; elems != nil; elems = tl elems){
+ rd := ref *d;
+ rd.name = hd elems;
+ dst <-= ((rd, nil), myreply);
+ case r = <-myreply {
+ Quit =>
+ (<-src).t1 <-= Quit;
+ exit;
+ Skip =>
+ break;
+ Next =>
+ lev++;
+ break;
+ }
+ lev++;
+ }
+ if(r == Down){
+ reply <-= Down;
+ if(fslib->copy(src, dst) == Quit)
+ exit;
+ }else
+ reply <-= Quit;
+ while(lev-- > 1){
+ dst <-= ((nil, nil), myreply);
+ if(<-myreply == Quit){
+ (<-src).t1 <-= Quit;
+ exit;
+ }
+ }
+}
diff --git a/appl/cmd/fs/size.b b/appl/cmd/fs/size.b
new file mode 100644
index 00000000..d72edd99
--- /dev/null
+++ b/appl/cmd/fs/size.b
@@ -0,0 +1,54 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "vt";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ sync := chan of int;
+ spawn sizeproc(sync, (hd args).t().i, report.start("size"));
+ return ref Value.V(sync);
+}
+
+sizeproc(sync: chan of int, c: Entrychan, errorc: chan of string)
+{
+ if(<-sync == 0){
+ c.sync <-= 0;
+ quit(errorc);
+ exit;
+ }
+ c.sync <-= 1;
+
+ size := big 0;
+ while(((d, nil, nil) := <-c.c).t0 != nil)
+ size += d.length;
+ sys->print("%bd\n", size);
+ quit(errorc);
+}
diff --git a/appl/cmd/fs/template.b b/appl/cmd/fs/template.b
new file mode 100644
index 00000000..9b08f589
--- /dev/null
+++ b/appl/cmd/fs/template.b
@@ -0,0 +1,35 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "nil";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+}
diff --git a/appl/cmd/fs/unbundle.b b/appl/cmd/fs/unbundle.b
new file mode 100644
index 00000000..ad500e8e
--- /dev/null
+++ b/appl/cmd/fs/unbundle.b
@@ -0,0 +1,259 @@
+implement Unbundle;
+include "sys.m";
+ sys: Sys;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "bundle.m";
+ bundle: Bundle;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value,quit, report: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Quit, Next, Skip, Down,
+ Option: import Fslib;
+include "unbundle.m";
+
+types(): string
+{
+ return "xs";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ badmod(Bufio->PATH);
+ str = load String String->PATH;
+ if(str == nil)
+ badmod(String->PATH);
+ bundle = load Bundle Bundle->PATH;
+ if(bundle == nil)
+ badmod(Bundle->PATH);
+ bundle->init();
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ p := (hd args).s().i;
+ iob: ref Bufio->Iobuf;
+ if(p == "-")
+ iob = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ else
+ iob = bufio->open(p, Sys->OREAD);
+ if(iob == nil){
+ sys->fprint(sys->fildes(2), "fs: unbundle: cannot open %q: %r\n", p);
+ return nil;
+ }
+ seekable := p != "-";
+ if(seekable)
+ seekable = isseekable(iob.fd);
+ return ref Value.X(unbundle(report, iob, seekable, Sys->ATOMICIO));
+}
+
+# dodgy heuristic... avoid, or using the stat-length of pipes and net connections
+isseekable(fd: ref Sys->FD): int
+{
+ (ok, stat) := sys->fstat(fd);
+ if(ok != -1 && stat.dtype == '|' || stat.dtype == 'I')
+ return 0;
+ return 1;
+}
+
+unbundle(r: ref Report, iob: ref Iobuf, seekable, blocksize: int): Fschan
+{
+ c := chan of (Fsdata, chan of int);
+ spawn unbundleproc(iob, c, seekable, blocksize, r.start("bundle"));
+ return c;
+}
+
+EOF: con "end of archive\n";
+
+unbundleproc(iob: ref Iobuf, c: Fschan, seekable, blocksize: int, errorc: chan of string)
+{
+ reply := chan of int;
+ p := iob.gets('\n');
+ # XXX overall header?
+ if(p == nil || p == EOF){
+ fslib->sendnulldir(c);
+ quit(errorc);
+ }
+ d := header2dir(p);
+ if(d == nil){
+ fslib->sendnulldir(c);
+ report(errorc, "invalid first header");
+ quit(errorc);
+ }
+ if((d.mode & Sys->DMDIR) == 0){
+ fslib->sendnulldir(c);
+ report(errorc, "first entry is not a directory");
+ quit(errorc);
+ }
+ c <-= ((d, nil), reply);
+ case r := <-reply {
+ Down =>
+ unbundledir(iob, c, 0, seekable, blocksize, errorc);
+ c <-= ((nil, nil), reply);
+ <-reply;
+ Skip or
+ Next =>
+ unbundledir(iob, c, 1, seekable, blocksize, errorc);
+ Quit =>
+ break;
+ }
+ quit(errorc);
+}
+
+unbundledir(iob: ref Iobuf, c: Fschan,
+ skipping, seekable, blocksize: int, errorc: chan of string): int
+{
+ reply := chan of int;
+ while((p := iob.gets('\n')) != nil){
+ if(p == EOF)
+ break;
+ if(p[0] == '\n')
+ break;
+ d := header2dir(p);
+ if(d == nil){
+ report(errorc, sys->sprint("invalid bundle header %q", p[0:len p - 1]));
+ return -1;
+ }
+ if(d.mode & Sys->DMDIR){
+ if(skipping)
+ continue;
+ c <-= ((d, nil), reply);
+ case <-reply {
+ Quit =>
+ quit(errorc);
+ Down =>
+ r := unbundledir(iob, c, 0, seekable, blocksize, errorc);
+ c <-= ((nil, nil), reply);
+ if(<-reply == Quit)
+ quit(errorc);
+ if(r == -1)
+ return -1;
+ Skip =>
+ if(unbundledir(iob, c, 1, seekable, blocksize, errorc) == -1)
+ return -1;
+ skipping = 1;
+ Next =>
+ if(unbundledir(iob, c, 1, seekable, blocksize, errorc) == -1)
+ return -1;
+ }
+ }else{
+ if(skipping){
+ if(skipdata(iob, d.length, seekable) == -1)
+ return -1;
+ }else{
+ case unbundlefile(iob, d, c, errorc, seekable, blocksize) {
+ -1 =>
+ return -1;
+ Skip =>
+ skipping = 1;
+ }
+ }
+ }
+ }
+ if(p == nil)
+ report(errorc, "unexpected eof");
+ return 0;
+}
+
+skipdata(iob: ref Iobuf, length: big, seekable: int): int
+{
+ if(seekable){
+ iob.seek(big length, Sys->SEEKRELA);
+ return 0;
+ }
+ buf := array[Sys->ATOMICIO] of byte;
+ for(n := big 0; n < length; ){
+ nb := Sys->ATOMICIO;
+ if(length - n < big Sys->ATOMICIO)
+ nb = int (length - n);
+ nb = iob.read(buf, nb);
+ if(nb <= 0)
+ return -1;
+ n += big nb;
+ }
+ return 0;
+}
+
+unbundlefile(iob: ref Iobuf, d: ref Sys->Dir,
+ c: Fschan, errorc: chan of string, seekable, blocksize: int): int
+{
+ reply := chan of int;
+ c <-= ((d, nil), reply);
+ case <-reply {
+ Quit =>
+ quit(errorc);
+ Skip =>
+ if(skipdata(iob, d.length, seekable) == -1)
+ return -1;
+ return Skip;
+ Next =>
+ if(skipdata(iob, d.length, seekable) == -1)
+ return -1;
+ return Next;
+ }
+ length := d.length;
+ for(n := big 0; n < length; ){
+ nr := blocksize;
+ if(n + big blocksize > length)
+ nr = int (length - n);
+ buf := array[nr] of byte;
+ nr = iob.read(buf, nr);
+ if(nr <= 0){
+ if(nr < 0)
+ report(errorc, sys->sprint("read error: %r"));
+ else
+ report(errorc, sys->sprint("premature eof"));
+ return -1;
+ }else if(nr < len buf)
+ buf = buf[0:nr];
+ c <-= ((nil, buf), reply);
+ n += big nr;
+ case <-reply {
+ Quit =>
+ quit(errorc);
+ Skip =>
+ if(skipdata(iob, length - n, seekable) == -1)
+ return -1;
+ return Next;
+ }
+ }
+ c <-= ((nil, nil), reply);
+ if(<-reply == Quit)
+ quit(errorc);
+ return Next;
+}
+
+header2dir(s: string): ref Sys->Dir
+{
+ toks := str->unquoted(s);
+ nf := len toks;
+ if(nf != 6)
+ return nil;
+ d := ref Sys->nulldir;
+ (d.name, toks) = (hd toks, tl toks);
+ (d.mode, toks) = (str->toint(hd toks, 8).t0, tl toks);
+ (d.uid, toks) = (hd toks, tl toks);
+ (d.gid, toks) = (hd toks, tl toks);
+ (d.mtime, toks) = (int hd toks, tl toks);
+ (d.length, toks) = (big hd toks, tl toks);
+ return d;
+}
diff --git a/appl/cmd/fs/void.b b/appl/cmd/fs/void.b
new file mode 100644
index 00000000..0f8c72fa
--- /dev/null
+++ b/appl/cmd/fs/void.b
@@ -0,0 +1,33 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, Option: import fslib;
+
+types(): string
+{
+ return "vv";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: void: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ return (hd args).v();
+}
diff --git a/appl/cmd/fs/walk.b b/appl/cmd/fs/walk.b
new file mode 100644
index 00000000..1c5016bd
--- /dev/null
+++ b/appl/cmd/fs/walk.b
@@ -0,0 +1,233 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "readdir.m";
+ readdir: Readdir;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, report, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+Loopcheck: adt {
+ a: array of list of ref Sys->Dir;
+
+ new: fn(): ref Loopcheck;
+ enter: fn(l: self ref Loopcheck, d: ref Sys->Dir): int;
+ leave: fn(l: self ref Loopcheck, d: ref Sys->Dir);
+};
+
+types(): string
+{
+ return "xs-bs";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: walk: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ badmod(Readdir->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ path := (hd args).s().i;
+ (ok, d) := sys->stat(path);
+ if(ok== -1){
+ sys->fprint(sys->fildes(2), "fs: walk: cannot stat %q: %r\n", path);
+ return nil;
+ }
+ if((d.mode & Sys->DMDIR) == 0){
+ # XXX could produce an fs containing just the single file.
+ # would have to split the path though.
+ sys->fprint(sys->fildes(2), "fs: walk: %q is not a directory\n", path);
+ return nil;
+ }
+ sync := chan of int;
+ c := chan of (Fsdata, chan of int);
+ spawn fswalkproc(sync, path, c, Sys->ATOMICIO, report.start("walk"));
+ <-sync;
+ return ref Value.X(c);
+}
+
+# XXX need to avoid loops in the filesystem...
+fswalkproc(sync: chan of int, path: string, c: Fschan, blocksize: int, errorc: chan of string)
+{
+ sys->pctl(Sys->FORKNS, nil);
+ sync <-= 1;
+ # XXX could allow a single root file?
+ if(sys->chdir(path) == -1){
+ report(errorc, sys->sprint("cannot cd to %q: %r", path));
+ fslib->sendnulldir(c);
+ quit(errorc);
+ }
+ (ok, d) := sys->stat(".");
+ if(ok == -1){
+ report(errorc, sys->sprint("cannot stat %q: %r", path));
+ fslib->sendnulldir(c);
+ quit(errorc);
+ }
+ d.name = path;
+ reply := chan of int;
+ c <-= ((ref d, nil), reply);
+ if(<-reply == Down){
+ loopcheck := Loopcheck.new();
+ loopcheck.enter(ref d);
+ if(path[len path - 1] != '/')
+ path[len path] = '/';
+ fswalkdir(path, c, blocksize, loopcheck, errorc);
+ c <-= ((nil, nil), reply);
+ <-reply;
+ }
+ quit(errorc);
+}
+
+fswalkdir(path: string, c: Fschan, blocksize: int, loopcheck: ref Loopcheck, errorc: chan of string)
+{
+ reply := chan of int;
+ (a, n) := readdir->init(".", Readdir->NAME|Readdir->COMPACT);
+ if(n == -1){
+ report(errorc, sys->sprint("cannot readdir %q: %r", path));
+ return;
+ }
+ for(i := 0; i < n; i++)
+ if(a[i].mode & Sys->DMDIR)
+ if(loopcheck.enter(a[i]) == 0)
+ a[i].dtype = ~0;
+directory:
+ for(i = 0; i < n; i++){
+ if(a[i].mode & Sys->DMDIR){
+ d := a[i];
+ if(d.dtype == ~0){
+ report(errorc, sys->sprint("filesystem loop at %#q", path + d.name));
+ continue;
+ }
+ if(sys->chdir("./" + d.name) == -1){
+ report(errorc, sys->sprint("cannot cd to %#q: %r", path + a[i].name));
+ continue;
+ }
+ c <-= ((d, nil), reply);
+ case <-reply {
+ Quit =>
+ quit(errorc);
+ Down =>
+ fswalkdir(path + a[i].name + "/", c, blocksize, loopcheck, errorc);
+ c <-= ((nil, nil), reply);
+ if(<-reply == Quit)
+ quit(errorc);
+ Skip =>
+ sys->chdir("..");
+ i++;
+ break directory;
+ Next =>
+ break;
+ }
+ if(sys->chdir("..") == -1) # XXX what should we do if this fails?
+ report(errorc, sys->sprint("failed to cd .. from %#q: %r\n", path + a[i].name));
+
+ } else {
+ if(fswalkfile(path, a[i], c, blocksize, errorc) == Skip)
+ break directory;
+ }
+ }
+ for(i = n - 1; i >= 0; i--)
+ if(a[i].mode & Sys->DMDIR && a[i].dtype != ~0)
+ loopcheck.leave(a[i]);
+}
+
+fswalkfile(path: string, d: ref Sys->Dir, c: Fschan, blocksize: int, errorc: chan of string): int
+{
+ reply := chan of int;
+ fd := sys->open(d.name, Sys->OREAD);
+ if(fd == nil){
+ report(errorc, sys->sprint("cannot open %q: %r", path+d.name));
+ return Next;
+ }
+ c <-= ((d, nil), reply);
+ case <-reply {
+ Quit =>
+ quit(errorc);
+ Skip =>
+ return Skip;
+ Next =>
+ return Next;
+ Down =>
+ break;
+ }
+ length := d.length;
+ for(n := big 0; n < length; ){
+ nr := blocksize;
+ if(n + big blocksize > length)
+ nr = int (length - n);
+ buf := array[nr] of byte;
+ nr = sys->read(fd, buf, nr);
+ if(nr <= 0){
+ if(nr < 0)
+ report(errorc, sys->sprint("error reading %q: %r", path + d.name));
+ else
+ report(errorc, sys->sprint("%q is shorter than expected (%bd/%bd)",
+ path + d.name, n, length));
+ break;
+ }else if(nr < len buf)
+ buf = buf[0:nr];
+ c <-= ((nil, buf), reply);
+ case <-reply {
+ Quit =>
+ quit(errorc);
+ Skip =>
+ return Next;
+ }
+ n += big nr;
+ }
+ c <-= ((nil, nil), reply);
+ if(<-reply == Quit)
+ quit(errorc);
+ return Next;
+}
+
+HASHSIZE: con 32;
+
+issamedir(d0, d1: ref Sys->Dir): int
+{
+ (q0, q1) := (d0.qid, d1.qid);
+ return q0.path == q1.path &&
+ q0.qtype == q1.qtype &&
+ d0.dtype == d1.dtype &&
+ d0.dev == d1.dev;
+}
+
+Loopcheck.new(): ref Loopcheck
+{
+ return ref Loopcheck(array[HASHSIZE] of list of ref Sys->Dir);
+}
+
+# XXX we're assuming no-one modifies the values in d behind our back...
+Loopcheck.enter(l: self ref Loopcheck, d: ref Sys->Dir): int
+{
+ slot := int d.qid.path & (HASHSIZE-1);
+ for(ll := l.a[slot]; ll != nil; ll = tl ll)
+ if(issamedir(d, hd ll))
+ return 0;
+ l.a[slot] = d :: l.a[slot];
+ return 1;
+}
+
+Loopcheck.leave(l: self ref Loopcheck, d: ref Sys->Dir)
+{
+ slot := int d.qid.path & (HASHSIZE-1);
+ l.a[slot] = tl l.a[slot];
+}
diff --git a/appl/cmd/fs/write.b b/appl/cmd/fs/write.b
new file mode 100644
index 00000000..934d4d67
--- /dev/null
+++ b/appl/cmd/fs/write.b
@@ -0,0 +1,111 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, quit, report: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "vsx";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil){
+ sys->fprint(sys->fildes(2), "fs: write: cannot load %s: %r\n", Fslib->PATH);
+ raise "fail:bad module";
+ }
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ sync := chan of int;
+ spawn fswriteproc(sync, (hd args).s().i, (hd tl args).x().i, report.start("fswrite"));
+ <-sync;
+ return ref Value.V(sync);
+}
+
+fswriteproc(sync: chan of int, root: string, c: Fschan, errorc: chan of string)
+{
+ sys->pctl(Sys->FORKNS, nil);
+ sync <-= 1;
+ if(<-sync == 0){
+ (<-c).t1 <-= Quit;
+ quit(errorc);
+ }
+
+ (d, reply) := <-c;
+ if(root != nil){
+ d.dir = ref *d.dir;
+ d.dir.name = root;
+ }
+ fswritedir(d.dir.name, d, reply, c, errorc);
+ quit(errorc);
+}
+
+fswritedir(path: string, d: Fsdata, dreply: chan of int, c: Fschan, errorc: chan of string)
+{
+ fd: ref Sys->FD;
+ if(d.dir.mode & Sys->DMDIR){
+ fd = sys->create(d.dir.name, Sys->OREAD, d.dir.mode|8r300);
+ if(fd == nil && (fd = sys->open(d.dir.name, Sys->OREAD)) == nil){
+ dreply <-= Next;
+ report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, d.dir.mode|8r300));
+ return;
+ }
+ if(sys->chdir(d.dir.name) == -1){ # XXX beware of names starting with '#'
+ dreply <-= Next;
+ report(errorc, sys->sprint("cannot cd to %q: %r", path));
+ fd = nil;
+ sys->remove(d.dir.name);
+ return;
+ }
+ dreply <-= Down;
+ path[len path] = '/';
+ for(;;){
+ (ent, reply) := <-c;
+ if(ent.dir == nil){
+ reply <-= Next;
+ break;
+ }
+ fswritedir(path + ent.dir.name, ent, reply, c, errorc);
+ }
+ sys->chdir("..");
+ if((d.dir.mode & 8r300) != 8r300){
+ ws := Sys->nulldir;
+ ws.mode = d.dir.mode;
+ if(sys->fwstat(fd, ws) == -1)
+ report(errorc, sys->sprint("cannot wstat %q: %r", path));
+ }
+ }else{
+ fd = sys->create(d.dir.name, Sys->OWRITE, d.dir.mode);
+ if(fd == nil){
+ dreply <-= Next;
+ report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, d.dir.mode|8r300));
+ return;
+ }
+ dreply <-= Down;
+ while((((nil, buf), reply) := <-c).t0.data != nil){
+ nw := sys->write(fd, buf, len buf);
+ if(nw < len buf){
+ if(nw == -1)
+ errorc <-= sys->sprint("error writing %q: %r", path);
+ else
+ errorc <-= sys->sprint("short write");
+ reply <-= Skip;
+ break;
+ }
+ reply <-= Next;
+ }
+ reply <-= Next;
+ }
+}
diff --git a/appl/cmd/ftest.b b/appl/cmd/ftest.b
new file mode 100644
index 00000000..46fc6c54
--- /dev/null
+++ b/appl/cmd/ftest.b
@@ -0,0 +1,153 @@
+implement Ftest;
+#
+# test file permissions or attributes
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+stderr: ref Sys->FD;
+
+Ftest: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Topr, Topw, Topx, Tope, Topf, Topd, Tops: con iota;
+
+init(nil: ref Draw->Context, argl: list of string)
+{
+ if(argl == nil)
+ return;
+
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ if (tl argl == nil)
+ usage();
+
+ a := hd tl argl;
+ argl = tl tl argl;
+ ok := 0;
+ case a {
+ "-f" =>
+ ok = filck(nxtarg(argl), Topf);
+ "-d" =>
+ ok = filck(nxtarg(argl), Topd);
+ "-r" =>
+ ok = filck(nxtarg(argl), Topr);
+ "-w" =>
+ ok = filck(nxtarg(argl), Topw);
+ "-x" =>
+ ok = filck(nxtarg(argl), Topx);
+ "-e" =>
+ ok = filck(nxtarg(argl), Tope);
+ "-s" =>
+ ok = filck(nxtarg(argl), Tops);
+ "-t" =>
+ fd := 1;
+ if (argl != nil) {
+ if (!isint(hd argl)) {
+ sys->fprint(stderr, "ftest: bad argument to -t\n");
+ usage();
+ }
+ fd = int hd argl;
+ }
+ ok = isatty(fd);
+ * =>
+ sys->fprint(stderr, "test: unknown option %s\n", a);
+ usage();
+ }
+ if (!ok)
+ raise "fail:false";
+}
+
+nxtarg(argl: list of string): string
+{
+ if(argl == nil) {
+ sys->fprint(stderr, "test: argument expected\n");
+ usage();
+ }
+ return hd argl;
+}
+
+usage()
+{
+ sys->fprint(stderr, "usage: (ftest -fdrwxes file)|(ftest -t fdno)\n");
+ raise "fail:usage";
+}
+
+isint(s: string): int
+{
+ if(s == nil)
+ return 0;
+ for(i := 0; i < len s; i++)
+ if(s[i] < '0' || s[i] > '9')
+ return 0;
+ return 1;
+}
+
+
+filck(fname: string, Top: int): int
+{
+ (ok, dir) := sys->stat(fname);
+
+ if(ok >= 0) {
+ ok = 0;
+ case Top {
+ Topr => # readable
+ ok = permck(dir, 8r004);
+ Topw => # writable
+ ok = permck(dir, 8r002);
+ Topx => # executable
+ ok = permck(dir, 8r001);
+ Tope => # exists
+ ok = 1;
+ Topf => # is a regular file
+ ok = (dir.mode & Sys->DMDIR) == 0;
+ Topd => # is a directory
+ ok = (dir.mode & Sys->DMDIR) != 0;
+ Tops => # has length > 0
+ ok = dir.length > big 0;
+ }
+ }
+
+ return ok > 0;
+}
+
+permck(dir: Sys->Dir, mask: int): int
+{
+ uid, gid: string;
+ fd := sys->open("/dev/user", Sys->OREAD);
+ if(fd != nil) {
+ buf := array [28] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n > 0)
+ uid = string buf[0:n];
+ }
+ # how do I find out what my group is?
+
+ ok := dir.mode & mask<<0;
+ if(!ok && dir.gid == gid)
+ ok = dir.mode & mask<<3;
+ if(!ok && dir.uid == uid)
+ ok = dir.mode & mask<<6;
+
+ return ok > 0;
+}
+
+isatty(fd: int): int
+{
+ d1, d2: Sys->Dir;
+
+ ok: int;
+ (ok, d1) = sys->fstat(sys->fildes(fd));
+ if(ok < 0)
+ return 0;
+ (ok, d2) = sys->stat("/dev/cons");
+ if(ok < 0)
+ return 0;
+
+ return d1.dtype==d2.dtype && d1.dev==d2.dev && d1.qid.path==d2.qid.path;
+}
diff --git a/appl/cmd/ftpfs.b b/appl/cmd/ftpfs.b
new file mode 100644
index 00000000..010fe9ee
--- /dev/null
+++ b/appl/cmd/ftpfs.b
@@ -0,0 +1,1959 @@
+implement Ftpfs;
+
+include "sys.m";
+ sys: Sys;
+ FD, Connection, Dir: import Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ time: Daytime;
+ Tm: import time;
+
+include "string.m";
+ str: String;
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+
+include "factotum.m";
+
+Ftpfs: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+#
+# File system node. Refers to parent and file structure.
+# Siblings are linked. The head is parent.children.
+#
+
+Node: adt
+{
+ dir: Dir;
+ uniq: int;
+ parent: cyclic ref Node;
+ sibs: cyclic ref Node;
+ children: cyclic ref Node;
+ file: cyclic ref File;
+ depth: int;
+ remname: string;
+ cached: int;
+ valid: int;
+
+ extendpath: fn(parent: self ref Node, elem: string): ref Node;
+ fixsymbolic: fn(n: self ref Node);
+ invalidate: fn(n: self ref Node);
+ markcached: fn(n: self ref Node);
+ uncache: fn(n: self ref Node);
+ uncachedir: fn(parent: self ref Node, child: ref Node);
+
+ stat: fn(n: self ref Node): array of byte;
+ qid: fn(n: self ref Node): Sys->Qid;
+
+ fileget: fn(n: self ref Node): ref File;
+ filefree: fn(n: self ref Node);
+ fileclean: fn(n: self ref Node);
+ fileisdirty: fn(n: self ref Node): int;
+ filedirty: fn(n: self ref Node);
+ fileread: fn(n: self ref Node, b: array of byte, off, c: int): int;
+ filewrite: fn(n: self ref Node, b: array of byte, off, c: int): int;
+
+ action: fn(n: self ref Node, cmd: string): int;
+ createdir: fn(n: self ref Node): int;
+ createfile: fn(n: self ref Node): int;
+ changedir: fn(n: self ref Node): int;
+ docreate: fn(n: self ref Node): int;
+ pathname: fn(n: self ref Node): string;
+ readdir: fn(n: self ref Node): int;
+ readfile: fn(n: self ref Node): int;
+ removedir: fn(n: self ref Node): int;
+ removefile: fn(n: self ref Node): int;
+};
+
+#
+# Styx protocol file identifier.
+#
+
+Fid: adt
+{
+ fid: int;
+ node: ref Node;
+ busy: int;
+};
+
+#
+# Foreign file with cache.
+#
+
+File: adt
+{
+ cache: array of byte;
+ length: int;
+ offset: int;
+ fd: ref FD;
+ inuse, dirty: int;
+ atime: int;
+ node: cyclic ref Node;
+ tempname: string;
+
+ createtmp: fn(f: self ref File): ref FD;
+};
+
+ftp: Connection;
+dfid: ref FD;
+dfidiob: ref Iobuf;
+buffresidue: int = 0;
+tbuff: array of byte;
+rbuff: array of byte;
+ccfd: ref FD;
+stdin, stderr: ref FD;
+
+fids: list of ref Fid;
+
+BSZ: con 8192;
+Chunk: con 1024;
+Nfiles: con 128;
+
+CHSYML: con 16r40000000;
+
+mountpoint: string = "/n/ftp";
+user: string = nil;
+password: string;
+hostname: string = "kremvax";
+anon: string = "anon";
+
+firewall: string = "tcp!$proxy!402";
+myname: string = "anon";
+myhost: string = "lucent.com";
+proxyid: string;
+proxyhost: string;
+
+errstr: string;
+net: string;
+port: int;
+
+Enosuchfile: con "file does not exist";
+Eftpproto: con "ftp protocol error";
+Eshutdown: con "remote shutdown";
+Eioerror: con "io error";
+Enotadirectory: con "not a directory";
+Eisadirectory: con "is a directory";
+Epermission: con "permission denied";
+Ebadoffset: con "bad offset";
+Ebadlength: con "bad length";
+Enowstat: con "wstat not implemented";
+Emesgmismatch: con "message size mismatch";
+
+remdir: ref Node;
+remroot: ref Node;
+remrootpath: string;
+
+heartbeatpid: int;
+
+#
+# FTP protocol codes are 3 digits >= 100.
+# The code type is obtained by dividing by 100.
+#
+
+Syserr: con -2;
+Syntax: con -1;
+Shutdown: con 0;
+Extra: con 1;
+Success: con 2;
+Incomplete: con 3;
+TempFail: con 4;
+PermFail: con 5;
+Impossible: con 6;
+Err: con 7;
+
+debug: int = 0;
+quiet: int = 0;
+active: int = 0;
+cdtoroot: int = 0;
+
+proxy: int = 0;
+
+mountfd: ref FD;
+styxfd: ref FD;
+
+#
+# Set up FDs for service.
+#
+
+connect(): string
+{
+ pip := array[2] of ref Sys->FD;
+ if(sys->pipe(pip) < 0)
+ return sys->sprint("can't create pipe: %r");
+ mountfd = pip[0];
+ styxfd = pip[1];
+ return nil;
+}
+
+#shut(s: string)
+#{
+# sys->print("ftpfs: %s shutdown\n", s);
+#}
+
+#
+# Mount server. Must be spawned because it does
+# an attach transaction.
+#
+
+mount(mountpoint: string)
+{
+ if (sys->mount(mountfd, nil, mountpoint, sys->MREPL | sys->MCREATE, nil) < 0) {
+ sys->print("mount %s failed: %r\n", mountpoint);
+ shutdown();
+ }
+ mountfd = nil;
+}
+
+#
+# Keep the link alive.
+#
+
+beatquanta: con 10;
+beatlimit: con 10;
+beatcount: int;
+activity: int;
+transfer: int;
+
+heartbeat(pidc: chan of int)
+{
+ pid := sys->pctl(0, nil);
+ pidc <-= pid;
+ for (;;) {
+ sys->sleep(beatquanta * 1000);
+ if (activity || transfer) {
+ beatcount = 0;
+ activity = 0;
+ continue;
+ }
+ beatcount++;
+ if (beatcount == beatlimit) {
+ acquire();
+ if (sendrequest("NOOP", 0) == Success)
+ getreply(0);
+ release();
+ beatcount = 0;
+ activity = 0;
+ }
+ }
+}
+
+#
+# Control lock.
+#
+
+ctllock: chan of int;
+
+acquire()
+{
+ ctllock <-= 1;
+}
+
+release()
+{
+ <-ctllock;
+}
+
+#
+# Data formatting routines.
+#
+
+sendreply(r: ref Rmsg)
+{
+ if (debug)
+ sys->print("> %s\n", r.text());
+ a := r.pack();
+ if(styx->write(styxfd, a, len a) != len a)
+ sys->print("ftpfs: error replying: %r\n");
+}
+
+rerror(tag: int, s: string)
+{
+ if (debug)
+ sys->print("error: %s\n", s);
+ sendreply(ref Rmsg.Error(tag, s));
+}
+
+seterr(e: int, s: string): int
+{
+ case e {
+ Syserr =>
+ errstr = Eioerror;
+ Syntax =>
+ errstr = Eftpproto;
+ Shutdown =>
+ errstr = Eshutdown;
+ * =>
+ errstr = s;
+ }
+ return -1;
+}
+
+#
+# Node routines.
+#
+
+anode: Node;
+npath: int = 1;
+
+newnode(parent: ref Node, name: string): ref Node
+{
+ n := ref anode;
+ n.dir.name = name;
+ n.dir.atime = time->now();
+ n.children = nil;
+ n.remname = name;
+ if (parent != nil) {
+ n.parent = parent;
+ n.sibs = parent.children;
+ parent.children = n;
+ n.depth = parent.depth + 1;
+ n.valid = 0;
+ } else {
+ n.parent = n;
+ n.sibs = nil;
+ n.depth = 0;
+ n.valid = 1;
+ n.dir.uid = anon;
+ n.dir.gid = anon;
+ n.dir.mtime = n.dir.atime;
+ }
+ n.file = nil;
+ n.uniq = npath++;
+ n.cached = 0;
+ return n;
+}
+
+Node.extendpath(parent: self ref Node, elem: string): ref Node
+{
+ n: ref Node;
+
+ for (n = parent.children; n != nil; n = n.sibs)
+ if (n.dir.name == elem)
+ return n;
+ return newnode(parent, elem);
+}
+
+Node.markcached(n: self ref Node)
+{
+ n.cached = 1;
+ n.dir.atime = time->now();
+}
+
+Node.uncache(n: self ref Node)
+{
+ if (n.fileisdirty())
+ n.createfile();
+ n.filefree();
+ n.cached = 0;
+}
+
+Node.uncachedir(parent: self ref Node, child: ref Node)
+{
+ sp: ref Node;
+
+ if (parent == nil || parent == child)
+ return;
+ for (sp = parent.children; sp != nil; sp = sp.sibs)
+ if (sp != child && sp.file != nil && !sp.file.dirty && sp.file.fd != nil) {
+ sp.filefree();
+ sp.cached = 0;
+ }
+}
+
+Node.invalidate(node: self ref Node)
+{
+ n: ref Node;
+
+ node.uncachedir(nil);
+ for (n = node.children; n != nil; n = n.sibs) {
+ n.cached = 0;
+ n.invalidate();
+ n.valid = 0;
+ }
+}
+
+Node.fixsymbolic(n: self ref Node)
+{
+ if (n.changedir() == 0) {
+ n.dir.mode |= Sys->DMDIR;
+ n.dir.qid.qtype = Sys->QTDIR;
+ } else
+ n.dir.qid.qtype = Sys->QTFILE;
+ n.dir.mode &= ~CHSYML;
+}
+
+Node.stat(n: self ref Node): array of byte
+{
+ return styx->packdir(n.dir);
+}
+
+Node.qid(n: self ref Node): Sys->Qid
+{
+ if(n.dir.mode & Sys->DMDIR)
+ return Sys->Qid(big n.uniq, 0, Sys->QTDIR);
+ return Sys->Qid(big n.uniq, 0, Sys->QTFILE);
+}
+
+#
+# File routines.
+#
+
+ntmp: int;
+files: list of ref File;
+nfiles: int;
+afile: File;
+atime: int;
+
+#
+# Allocate a file structure for a node. If too many
+# are already allocated discard the oldest.
+#
+
+Node.fileget(n: self ref Node): ref File
+{
+ f, o: ref File;
+ l: list of ref File;
+
+ if (n.file != nil)
+ return n.file;
+ o = nil;
+ for (l = files; l != nil; l = tl l) {
+ f = hd l;
+ if (f.inuse == 0)
+ break;
+ if (!f.dirty && (o == nil || o.atime > f.atime))
+ o = f;
+ }
+ if (l == nil) {
+ if (nfiles == Nfiles && o != nil) {
+ o.node.uncache();
+ f = o;
+ }
+ else {
+ f = ref afile;
+ files = f :: files;
+ nfiles++;
+ }
+ }
+ n.file = f;
+ f.node = n;
+ f.atime = atime++;
+ f.inuse = 1;
+ f.dirty = 0;
+ f.length = 0;
+ f.fd = nil;
+ return f;
+}
+
+#
+# Create a temporary file for a local copy of a file.
+# If too many are open uncache parent.
+#
+
+File.createtmp(f: self ref File): ref FD
+{
+ t := "/tmp/ftp." + string time->now() + "." + string ntmp;
+ if (ntmp >= 16)
+ f.node.parent.uncachedir(f.node);
+ f.fd = sys->create(t, Sys->ORDWR | Sys->ORCLOSE, 8r600);
+ f.tempname = t;
+ f.offset = 0;
+ ntmp++;
+ return f.fd;
+}
+
+#
+# Read 'c' bytes at offset 'off' from a file into buffer 'b'.
+#
+
+Node.fileread(n: self ref Node, b: array of byte, off, c: int): int
+{
+ f: ref File;
+ t, i: int;
+
+ f = n.file;
+ if (off + c > f.length)
+ c = f.length - off;
+ for (t = 0; t < c; t += i) {
+ if (off >= f.length)
+ return t;
+ if (off < Chunk) {
+ i = c;
+ if (off + i > Chunk)
+ i = Chunk - off;
+ b[t:] = f.cache[off: off + i];
+ }
+ else {
+ if (f.offset != off) {
+ if (sys->seek(f.fd, big off, Sys->SEEKSTART) < big 0) {
+ f.offset = -1;
+ return seterr(Err, sys->sprint("seek temp failed: %r"));
+ }
+ }
+ if (t == 0)
+ i = sys->read(f.fd, b, c - t);
+ else
+ i = sys->read(f.fd, rbuff, c - t);
+ if (i < 0) {
+ f.offset = -1;
+ return seterr(Err, sys->sprint("read temp failed: %r"));
+ }
+ if (i == 0)
+ break;
+ if (t > 0)
+ b[t:] = rbuff[0: i];
+ f.offset = off + i;
+ }
+ off += i;
+ }
+ return t;
+}
+
+#
+# Write 'c' bytes at offset 'off' to a file from buffer 'b'.
+#
+
+Node.filewrite(n: self ref Node, b: array of byte, off, c: int): int
+{
+ f: ref File;
+ t, i: int;
+
+ f = n.fileget();
+ if (f.cache == nil)
+ f.cache = array[Chunk] of byte;
+ for (t = 0; t < c; t += i) {
+ if (off < Chunk) {
+ i = c;
+ if (off + i > Chunk)
+ i = Chunk - off;
+ f.cache[off:] = b[t: t + i];
+ }
+ else {
+ if (f.fd == nil) {
+ if (f.createtmp() == nil)
+ return seterr(Err, sys->sprint("temp file: %r"));
+ if (sys->write(f.fd, f.cache, Chunk) != Chunk) {
+ f.offset = -1;
+ return seterr(Err, sys->sprint("write temp failed: %r"));
+ }
+ f.offset = Chunk;
+ f.length = Chunk;
+ }
+ if (f.offset != off) {
+ if (off > f.length) {
+ # extend the file with zeroes
+ # sparse files may not be supported
+ }
+ if (sys->seek(f.fd, big off, Sys->SEEKSTART) < big 0) {
+ f.offset = -1;
+ return seterr(Err, sys->sprint("seek temp failed: %r"));
+ }
+ }
+ i = sys->write(f.fd, b[t:len b], c - t);
+ if (i != c - t) {
+ f.offset = -1;
+ return seterr(Err, sys->sprint("write temp failed: %r"));
+ }
+ }
+ off += i;
+ f.offset = off;
+ }
+ if (off > f.length)
+ f.length = off;
+ return t;
+}
+
+Node.filefree(n: self ref Node)
+{
+ f: ref File;
+
+ f = n.file;
+ if (f == nil)
+ return;
+ if (f.fd != nil) {
+ ntmp--;
+ f.fd = nil;
+ f.tempname = nil;
+ }
+ f.cache = nil;
+ f.length = 0;
+ f.inuse = 0;
+ f.dirty = 0;
+ n.file = nil;
+}
+
+Node.fileclean(n: self ref Node)
+{
+ if (n.file != nil)
+ n.file.dirty = 0;
+}
+
+Node.fileisdirty(n: self ref Node): int
+{
+ return n.file != nil && n.file.dirty;
+}
+
+Node.filedirty(n: self ref Node)
+{
+ f: ref File;
+
+ f = n.fileget();
+ f.dirty = 1;
+}
+
+#
+# Fid management.
+#
+
+afid: Fid;
+
+getfid(fid: int): ref Fid
+{
+ l: list of ref Fid;
+ f, ff: ref Fid;
+
+ ff = nil;
+ for (l = fids; l != nil; l = tl l) {
+ f = hd l;
+ if (f.fid == fid) {
+ if (f.busy)
+ return f;
+ else {
+ ff = f;
+ break;
+ }
+ } else if (ff == nil && !f.busy)
+ ff = f;
+ }
+ if (ff == nil) {
+ ff = ref afid;
+ fids = ff :: fids;
+ }
+ ff.node = nil;
+ ff.fid = fid;
+ return ff;
+}
+
+#
+# FTP protocol.
+#
+
+fail(s: int, l: string)
+{
+ case s {
+ Syserr =>
+ sys->print("read fail: %r\n");
+ Syntax =>
+ sys->print("%s\n", Eftpproto);
+ Shutdown =>
+ sys->print("%s\n", Eshutdown);
+ * =>
+ sys->print("unexpected response: %s\n", l);
+ }
+ exit;
+}
+
+getfullreply(echo: int): (int, int, string)
+{
+ reply := "";
+ s: string;
+ code := -1;
+ do{
+ s = dfidiob.gets('\n');
+ if(s == nil)
+ return (Shutdown, 0, nil);
+ if(len s >= 2 && s[len s-1] == '\n'){
+ if (s[len s - 2] == '\r')
+ s = s[0: len s - 2];
+ else
+ s = s[0: len s - 1];
+ }
+ if (debug || echo)
+ sys->print("%s\n", s);
+ reply = reply+s;
+ if(code < 0){
+ if(len s < 3)
+ return (Syntax, 0, nil);
+ code = int s[0:3];
+ if(s[3] != '-')
+ break;
+ }
+ }while(len s < 4 || int s[0:3] != code || s[3] != ' ');
+
+ if(code < 100)
+ return (Syntax, 0, nil);
+ return (code / 100, code, reply);
+}
+
+getreply(echo: int): (int, string)
+{
+ (c, code, s) := getfullreply(echo);
+ return (c, s);
+}
+
+sendrequest2(req: string, echo: int, figleaf: string): int
+{
+ activity = 1;
+ if (debug || echo) {
+ if (figleaf == nil)
+ figleaf = req;
+ sys->print("%s\n", figleaf);
+ }
+ b := array of byte (req + "\r\n");
+ n := sys->write(dfid, b, len b);
+ if (n < 0)
+ return Syserr;
+ if (n != len b)
+ return Shutdown;
+ return Success;
+}
+
+sendrequest(req: string, echo: int): int
+{
+ return sendrequest2(req, echo, req);
+}
+
+sendfail(s: int)
+{
+ case s {
+ Syserr =>
+ sys->print("write fail: %r\n");
+ Shutdown =>
+ sys->print("%s\n", Eshutdown);
+ * =>
+ sys->print("internal error\n");
+ }
+ exit;
+}
+
+dataport(l: list of string): string
+{
+ s := "tcp!" + hd l;
+ l = tl l;
+ s = s + "." + hd l;
+ l = tl l;
+ s = s + "." + hd l;
+ l = tl l;
+ s = s + "." + hd l;
+ l = tl l;
+ return s + "!" + string ((int hd l * 256) + (int hd tl l));
+}
+
+commas(l: list of string): string
+{
+ s := hd l;
+ l = tl l;
+ while (l != nil) {
+ s = s + "," + hd l;
+ l = tl l;
+ }
+ return s;
+}
+
+third(cmd: string): ref FD
+{
+ acquire();
+ for (;;) {
+ (n, data) := sys->dial(firewall, nil);
+ if (n < 0) {
+ if (debug)
+ sys->print("dial %s failed: %r\n", firewall);
+ break;
+ }
+ t := sys->sprint("\n%s!*\n\n%s\n%s\n1\n-1\n-1\n", proxyhost, myhost, myname);
+ b := array of byte t;
+ n = sys->write(data.dfd, b, len b);
+ if (n < 0) {
+ if (debug)
+ sys->print("firewall write failed: %r\n");
+ break;
+ }
+ b = array[256] of byte;
+ n = sys->read(data.dfd, b, len b);
+ if (n < 0) {
+ if (debug)
+ sys->print("firewall read failed: %r\n");
+ break;
+ }
+ (c, k) := sys->tokenize(string b[:n], "\n");
+ if (c < 2) {
+ if (debug)
+ sys->print("bad response from firewall\n");
+ break;
+ }
+ if (hd k != "0") {
+ if (debug)
+ sys->print("firewall connect: %s\n", hd tl k);
+ break;
+ }
+ p := hd tl k;
+ if (debug)
+ sys->print("portid %s\n", p);
+ (c, k) = sys->tokenize(p, "!");
+ if (c < 3) {
+ if (debug)
+ sys->print("bad portid from firewall\n");
+ break;
+ }
+ n = int hd tl tl k;
+ (c, k) = sys->tokenize(hd tl k, ".");
+ if (c != 4) {
+ if (debug)
+ sys->print("bad portid ip address\n");
+ break;
+ }
+ t = sys->sprint("PORT %s,%d,%d", commas(k), n / 256, n & 255);
+ r := sendrequest(t, 0);
+ if (r != Success)
+ break;
+ (r, nil) = getreply(0);
+ if (r != Success)
+ break;
+ r = sendrequest(cmd, 0);
+ if (r != Success)
+ break;
+ (r, nil) = getreply(0);
+ if (r != Extra)
+ break;
+ n = sys->read(data.dfd, b, len b);
+ if (n < 0) {
+ if (debug)
+ sys->print("firewall read failed: %r\n");
+ break;
+ }
+ b = array of byte "0\n?\n";
+ n = sys->write(data.dfd, b, len b);
+ if (n < 0) {
+ if (debug)
+ sys->print("firewall write failed: %r\n");
+ break;
+ }
+ release();
+ return data.dfd;
+ }
+ release();
+ return nil;
+}
+
+passive(cmd: string): ref FD
+{
+ acquire();
+ if (sendrequest("PASV", 0) != Success) {
+ release();
+ return nil;
+ }
+ (r, m) := getreply(0);
+ release();
+ if (r != Success)
+ return nil;
+ (nil, p) := str->splitl(m, "(");
+ if (p == nil)
+ str->splitl(m, "0-9");
+ else
+ p = p[1:len p];
+ (c, l) := sys->tokenize(p, ",");
+ if (c < 6) {
+ sys->print("data: %s\n", m);
+ return nil;
+ }
+ a := dataport(l);
+ if (debug)
+ sys->print("data dial %s\n", a);
+ (s, d) := sys->dial(a, nil);
+ if (s < 0)
+ return nil;
+ acquire();
+ r = sendrequest(cmd, 0);
+ if (r != Success) {
+ release();
+ return nil;
+ }
+ (r, m) = getreply(0);
+ release();
+ if (r != Extra)
+ return nil;
+ return d.dfd;
+}
+
+getnet(dir: string): (string, int)
+{
+ buf := array[50] of byte;
+ n := dir + "/local";
+ lfd := sys->open(n, Sys->OREAD);
+ if (lfd == nil) {
+ if (debug)
+ sys->fprint(stderr, "open %s: %r\n", n);
+ return (nil, 0);
+ }
+ length := sys->read(lfd, buf, len buf);
+ if (length < 0) {
+ if (debug)
+ sys->fprint(stderr, "read%s: %r\n", n);
+ return (nil, 0);
+ }
+ (r, l) := sys->tokenize(string buf[0:length], "!");
+ if (r != 2) {
+ if (debug)
+ sys->fprint(stderr, "tokenize(%s) returned (%d)\n", string buf[0:length], r);
+ return (nil, 0);
+ }
+ if (debug)
+ sys->print("net is %s!%d\n", hd l, int hd tl l);
+ return (hd l, int hd tl l);
+}
+
+activate(cmd: string): ref FD
+{
+ r: int;
+
+ listenport, dataport: Connection;
+ m: string;
+
+ (r, listenport) = sys->announce("tcp!" + net + "!0");
+ if (r < 0)
+ return nil;
+ (x1, x2) := getnet(listenport.dir);
+ (x3, x4) := sys->tokenize(x1, ".");
+ t := sys->sprint("PORT %s,%d,%d", commas(x4), int x2 / 256, int x2&255);
+ acquire();
+ r = sendrequest(t, 0);
+ if (r != Success) {
+ release();
+ return nil;
+ }
+ (r, m) = getreply(0);
+ if (r != Success) {
+ release();
+ return nil;
+ }
+ r = sendrequest(cmd, 0);
+ if (r != Success) {
+ release();
+ return nil;
+ }
+ (r, m) = getreply(0);
+ release();
+ if (r != Extra)
+ return nil;
+ (r, dataport) = sys->listen(listenport);
+ if (r < 0) {
+ sys->fprint(stderr, "activate: listen failed: %r\n");
+ return nil;
+ }
+ fd := sys->open(dataport.dir + "/data", sys->ORDWR);
+ if (debug)
+ sys->print("activate: data connection on %s\n", dataport.dir);
+ if (fd == nil) {
+ sys->fprint(stderr, "activate: open of %s failed: %r\n", dataport.dir);
+ return nil;
+ }
+ return fd;
+}
+
+data(cmd: string): ref FD
+{
+ if (proxy)
+ return third(cmd);
+ else if (active)
+ return activate(cmd);
+ else
+ return passive(cmd);
+}
+
+#
+# File list cracking routines.
+#
+
+fields(l: list of string, n: int): array of string
+{
+ a := array[n] of string;
+ for (i := 0; i < n; i++) {
+ a[i] = hd l;
+ l = tl l;
+ }
+ return a;
+}
+
+now: ref Tm;
+months: con "janfebmaraprmayjunjulaugsepoctnovdec";
+
+cracktime(month, day, year, hms: string): int
+{
+ tm: Tm;
+
+ if (now == nil)
+ now = time->local(time->now());
+ tm = *now;
+ if (month[0] >= '0' && month[0] <= '9') {
+ tm.mon = int month - 1;
+ if (tm.mon < 0 || tm.mon > 11)
+ tm.mon = 5;
+ }
+ else if (len month >= 3) {
+ month = str->tolower(month[0:3]);
+ for (i := 0; i < 36; i += 3)
+ if (month == months[i:i+3]) {
+ tm.mon = i / 3;
+ break;
+ }
+ }
+ tm.mday = int day;
+ if (hms != nil) {
+ (h, z) := str->splitl(hms, "apAP");
+ (a, b) := str->splitl(h, ":");
+ tm.hour = int a;
+ if (b != nil) {
+ (c, d) := str->splitl(b[1:len b], ":");
+ tm.min = int c;
+ if (d != nil)
+ tm.sec = int d[1:len d];
+ }
+ if (z != nil && str->tolower(z)[0] == 'p')
+ tm.hour += 12;
+ }
+ if (year != nil) {
+ tm.year = int year;
+ if (tm.year >= 1900)
+ tm.year -= 1900;
+ }
+ else {
+ if (tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1))
+ tm.year--;
+ }
+ return time->tm2epoch(ref tm);
+}
+
+crackmode(p: string): int
+{
+ flags := 0;
+ case len p {
+ 10 => # unix and new style plan 9
+ case p[0] {
+ 'l' =>
+ return CHSYML | 0777;
+ 'd' =>
+ flags = Sys->DMDIR;
+ }
+ p = p[1:10];
+ 11 => # old style plan 9
+ if (p[0] == 'l')
+ flags = Sys->DMDIR;
+ p = p[2:11];
+ * =>
+ return Sys->DMDIR | 0777;
+ }
+ mode := 0;
+ n := 0;
+ for (i := 0; i < 3; i++) {
+ mode <<= 3;
+ if (p[n] == 'r')
+ mode |= 4;
+ if (p[n+1] == 'w')
+ mode |= 2;
+ case p[n+2] {
+ 'x' or 's' or 'S' =>
+ mode |= 1;
+ }
+ n += 3;
+ }
+ return mode | flags;
+}
+
+crackdir(p: string): (string, Dir)
+{
+ d: Dir;
+ ln, a: string;
+
+ (n, l) := sys->tokenize(p, " \t\r\n");
+ f := fields(l, n);
+ if (n > 2 && f[n - 2] == "->")
+ n -= 2;
+ case n {
+ 8 => # ls -l
+ ln = f[7];
+ d.uid = f[2];
+ d.gid = f[2];
+ d.mode = crackmode(f[0]);
+ d.length = big f[3];
+ (a, nil) = str->splitl(f[6], ":");
+ if (len a != len f[6])
+ d.atime = cracktime(f[4], f[5], nil, f[6]);
+ else
+ d.atime = cracktime(f[4], f[5], f[6], nil);
+ 9 => # ls -lg
+ ln = f[8];
+ d.uid = f[2];
+ d.gid = f[3];
+ d.mode = crackmode(f[0]);
+ d.length = big f[4];
+ (a, nil) = str->splitl(f[7], ":");
+ if (len a != len f[7])
+ d.atime = cracktime(f[5], f[6], nil, f[7]);
+ else
+ d.atime = cracktime(f[5], f[6], f[7], nil);
+ 10 => # plan 9
+ ln = f[9];
+ d.uid = f[3];
+ d.gid = f[4];
+ d.mode = crackmode(f[0]);
+ d.length = big f[5];
+ (a, nil) = str->splitl(f[8], ":");
+ if (len a != len f[8])
+ d.atime = cracktime(f[6], f[7], nil, f[8]);
+ else
+ d.atime = cracktime(f[6], f[7], f[8], nil);
+ 4 => # NT
+ ln = f[3];
+ d.uid = anon;
+ d.gid = anon;
+ if (f[2] == "<DIR>") {
+ d.length = big 0;
+ d.mode = Sys->DMDIR | 8r777;
+ }
+ else {
+ d.mode = 8r666;
+ d.length = big f[2];
+ }
+ (n, l) = sys->tokenize(f[0], "/-");
+ if (n == 3)
+ d.atime = cracktime(hd l, hd tl l, f[2], f[1]);
+ 1 => # ls
+ ln = f[0];
+ d.uid = anon;
+ d.gid = anon;
+ d.mode = 0777;
+ d.atime = 0;
+ * =>
+ return (nil, d);
+ }
+ if (ln == "." || ln == "..")
+ return (nil, d);
+ d.mtime = d.atime;
+ d.name = ln;
+ return (ln, d);
+}
+
+longls := 1;
+
+Node.readdir(n: self ref Node): int
+{
+ f: ref FD;
+ p: ref Node;
+
+ if (n.changedir() < 0)
+ return -1;
+ transfer = 1;
+ for (;;) {
+ if (longls) {
+ f = data("LIST -la");
+ if (f == nil) {
+ longls = 0;
+ continue;
+ }
+ }
+ else {
+ f = data("LIST");
+ if (f == nil) {
+ transfer = 0;
+ return seterr(Err, Enosuchfile);
+ }
+ }
+ break;
+ }
+ b := bufio->fopen(f, sys->OREAD);
+ if (b == nil) {
+ transfer = 0;
+ return seterr(Err, Eioerror);
+ }
+ while ((s := b.gets('\n')) != nil) {
+ if (debug)
+ sys->print("%s", s);
+ (l, d) := crackdir(s);
+ if (l == nil)
+ continue;
+ p = n.extendpath(l);
+ p.dir = d;
+ p.valid = 1;
+ }
+ b = nil;
+ f = nil;
+ (r, nil) := getreply(0);
+ transfer = 0;
+ if (r != Success)
+ return seterr(Err, Enosuchfile);
+ return 0;
+}
+
+Node.readfile(n: self ref Node): int
+{
+ c: int;
+
+ if (n.parent.changedir() < 0)
+ return -1;
+ transfer = 1;
+ f := data("RETR " + n.remname);
+ if (f == nil) {
+ transfer = 0;
+ return seterr(Err, Enosuchfile);
+ }
+ off := 0;
+ while ((c = sys->read(f, tbuff, BSZ)) > 0) {
+ if (n.filewrite(tbuff, off, c) != c) {
+ off = -1;
+ break;
+ }
+ off += c;
+ }
+ if (c < 0) {
+ transfer = 0;
+ return seterr(Err, Eioerror);
+ }
+ f = nil;
+ if(off == 0)
+ n.filewrite(tbuff, off, 0);
+ (s, nil) := getreply(0);
+ transfer = 0;
+ if (s != Success)
+ return seterr(s, Enosuchfile);
+ return off;
+}
+
+path(a, b: string): string
+{
+ if (a == nil)
+ return b;
+ if (b == nil)
+ return a;
+ if (a[len a - 1] == '/')
+ return a + b;
+ else
+ return a + "/" + b;
+}
+
+Node.pathname(n: self ref Node): string
+{
+ s: string;
+
+ while (n != n.parent) {
+ s = path(n.remname, s);
+ n = n.parent;
+ }
+ return path(remrootpath, s);
+}
+
+Node.changedir(n: self ref Node): int
+{
+ t: ref Node;
+ d: string;
+
+ t = n;
+ if (t == remdir)
+ return 0;
+ if (n.depth == 0)
+ d = remrootpath;
+ else
+ d = n.pathname();
+ remdir.uncachedir(nil);
+ acquire();
+ r := sendrequest("CWD " + d, 0);
+ if (r == Success)
+ (r, nil) = getreply(0);
+ release();
+ case r {
+ Success
+# or Incomplete
+ =>
+ remdir = n;
+ return 0;
+ * =>
+ return seterr(r, Enosuchfile);
+ }
+}
+
+Node.docreate(n: self ref Node): int
+{
+ f: ref FD;
+
+ transfer = 1;
+ f = data("STOR " + n.remname);
+ if (f == nil) {
+ transfer = 0;
+ return -1;
+ }
+ off := 0;
+ for (;;) {
+ r := n.fileread(tbuff, off, BSZ);
+ if (r <= 0)
+ break;
+ if (sys->write(f, tbuff, r) < 0) {
+ off = -1;
+ break;
+ }
+ off += r;
+ }
+ transfer = 0;
+ return off;
+}
+
+Node.createfile(n: self ref Node): int
+{
+ if (n.parent.changedir() < 0)
+ return -1;
+ off := n.docreate();
+ if (off < 0)
+ return -1;
+ (r, nil) := getreply(0);
+ if (r != Success)
+ return -1;
+ return off;
+}
+
+Node.action(n: self ref Node, cmd: string): int
+{
+ if (n.parent.changedir() < 0)
+ return -1;
+ acquire();
+ r := sendrequest(cmd + " " + n.dir.name, 0);
+ if (r == Success)
+ (r, nil) = getreply(0);
+ release();
+ if (r != Success)
+ return -1;
+ return 0;
+}
+
+Node.createdir(n: self ref Node): int
+{
+ return n.action("MKD");
+}
+
+Node.removefile(n: self ref Node): int
+{
+ return n.action("DELE");
+}
+
+Node.removedir(n: self ref Node): int
+{
+ return n.action("RMD");
+}
+
+pwd(s: string): string
+{
+ (nil, s) = str->splitl(s, "\"");
+ if (s == nil || len s < 2)
+ return "/";
+ (s, nil) = str->splitl(s[1:len s], "\"");
+ return s;
+}
+
+#
+# User info for firewall.
+#
+getuser()
+{
+ b := array[Sys->NAMEMAX] of byte;
+ f := sys->open("/dev/user", Sys->OREAD);
+ if (f != nil) {
+ n := sys->read(f, b, len b);
+ if (n > 0)
+ myname = string b[:n];
+ else if (n == 0)
+ sys->print("warning: empty /dev/user\n");
+ else
+ sys->print("warning: could not read /dev/user: %r\n");
+ } else
+ sys->print("warning: could not open /dev/user: %r\n");
+ f = sys->open("/dev/sysname", Sys->OREAD);
+ if (f != nil) {
+ n := sys->read(f, b, len b);
+ if (n > 0)
+ myhost = string b[:n];
+ else if (n == 0)
+ sys->print("warning: empty /dev/sysname\n");
+ else
+ sys->print("warning: could not read /dev/sysname: %r\n");
+ } else
+ sys->print("warning: could not open /dev/sysname: %r\n");
+ if (debug)
+ sys->print("proxy %s for %s@%s\n", firewall, myname, myhost);
+}
+
+server()
+{
+ while((t := Tmsg.read(styxfd, 0)) != nil){
+ if (debug)
+ sys->print("< %s\n", t.text());
+ pick x := t {
+ Readerror =>
+ sys->print("ftpfs: read error on mount point: %s\n", x.error);
+ kill(heartbeatpid);
+ exit;
+ Version =>
+ versionT(x);
+ Auth =>
+ authT(x);
+ Attach =>
+ attachT(x);
+ Clunk =>
+ clunkT(x);
+ Create =>
+ createT(x);
+ Flush =>
+ flushT(x);
+ Open =>
+ openT(x);
+ Read =>
+ readT(x);
+ Remove =>
+ removeT(x);
+ Stat =>
+ statT(x);
+ Walk =>
+ walkT(x);
+ Write =>
+ writeT(x);
+ Wstat =>
+ wstatT(x);
+ * =>
+ rerror(t.tag, "unimp");
+ }
+ }
+ if (debug)
+ sys->print("ftpfs: server: exiting\n");
+ kill(heartbeatpid);
+}
+
+raw(on: int)
+{
+ if(ccfd == nil) {
+ ccfd = sys->open("/dev/consctl", Sys->OWRITE);
+ if(ccfd == nil) {
+ sys->fprint(stderr, "ftpfs: cannot open /dev/consctl: %r\n");
+ return;
+ }
+ }
+ if(on)
+ sys->fprint(ccfd, "rawon");
+ else
+ sys->fprint(ccfd, "rawoff");
+}
+
+prompt(p: string, def: string, echo: int): string
+{
+ if (def == nil)
+ sys->print("%s: ", p);
+ else
+ sys->print("%s[%s]: ", p, def);
+ if (!echo)
+ raw(1);
+ b := bufio->fopen(stdin, Sys->OREAD);
+ s := b.gets(int '\n');
+ if (!echo) {
+ raw(0);
+ sys->print("\n");
+ }
+ if(s != nil)
+ s = s[0:len s - 1];
+ if (s == "")
+ return def;
+ return s;
+}
+
+#
+# Entry point. Load modules and initiate protocol.
+#
+
+nomod(s: string)
+{
+ sys->fprint(sys->fildes(2), "ftpfs: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ l: string;
+ rv: int;
+ code: int;
+
+ if (sys == nil)
+ sys = load Sys Sys->PATH;
+ stdin = sys->fildes(0);
+ stderr = sys->fildes(2);
+
+ time = load Daytime Daytime->PATH;
+ if (time == nil)
+ nomod(Daytime->PATH);
+ str = load String String->PATH;
+ if (str == nil)
+ nomod(String->PATH);
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ nomod(Bufio->PATH);
+ styx = load Styx Styx->PATH;
+ if (styx == nil)
+ nomod(Styx->PATH);
+ styx->init();
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+
+ # parse arguments
+ # [-/dpq] [-m mountpoint] [-a password] host
+ arg->init(args);
+ arg->setusage("ftpfs [-/dpq] [-m mountpoint] [-a password] ftphost");
+ keyspec := "";
+ while((op := arg->opt()) != 0)
+ case op {
+ 'd' =>
+ debug++;
+ '/' =>
+ cdtoroot = 1;
+ 'p' =>
+ active = 1;
+ 'q' =>
+ quiet = 1;
+ 'm' =>
+ mountpoint = arg->earg();
+ 'a' =>
+ password = arg->earg();
+ user = "anonymous";
+ 'k' =>
+ keyspec = arg->earg();
+ * =>
+ arg->usage();
+ }
+ argv := arg->argv();
+ if (len argv != 1)
+ arg->usage();
+ arg = nil;
+ hostname = hd argv;
+
+ if (len hostname > 6 && hostname[:6] == "proxy!") {
+ hostname = hostname[6:];
+ proxy = 1;
+ }
+
+ if (proxy) {
+ if (!quiet)
+ sys->print("dial firewall service %s\n", firewall);
+ (rv, ftp) = sys->dial(firewall, nil);
+ if (rv < 0) {
+ sys->print("dial %s failed: %r\n", firewall);
+ exit;
+ }
+ dfid = ftp.dfd;
+ getuser();
+ t := sys->sprint("\ntcp!%s!tcp.21\n\n%s\n%s\n0\n-1\n-1\n", hostname, myhost, myname);
+ if (debug)
+ sys->print("request%s\n", t);
+ b := array of byte t;
+ rv = sys->write(dfid, b, len b);
+ if (rv < 0) {
+ sys->print("firewall write failed: %r\n");
+ exit;
+ }
+ b = array[256] of byte;
+ rv = sys->read(dfid, b, len b);
+ if (rv < 0) {
+ sys->print("firewall read failed: %r\n");
+ return;
+ }
+ (c, k) := sys->tokenize(string b[:rv], "\n");
+ if (c < 2) {
+ sys->print("bad response from firewall\n");
+ exit;
+ }
+ if (hd k != "0") {
+ sys->print("firewall connect: %s\n", hd tl k);
+ exit;
+ }
+ proxyid = hd tl k;
+ if (debug)
+ sys->print("proxyid %s\n", proxyid);
+ (c, k) = sys->tokenize(proxyid, "!");
+ if (c < 3) {
+ sys->print("bad proxyid from firewall\n");
+ exit;
+ }
+ proxyhost = (hd k) + "!" + (hd tl k);
+ if (debug)
+ sys->print("proxyhost %s\n", proxyhost);
+ } else {
+ d := "tcp!" + hostname + "!ftp";
+ (rv, ftp) = sys->dial(d, nil);
+ if (debug)
+ sys->print("localdir %s\n", ftp.dir);
+ if (rv < 0) {
+ sys->print("dial %s failed: %r\n", d);
+ exit;
+ }
+ dfid = ftp.dfd;
+ }
+ dfidiob = bufio->fopen(dfid, sys->OREAD);
+ (net, port) = getnet(ftp.dir);
+ tbuff = array[BSZ] of byte;
+ rbuff = array[BSZ] of byte;
+ (rv, l) = getreply(!quiet);
+ if (rv != Success)
+ fail(rv, l);
+ if (user == nil) {
+ getuser();
+ user = myname;
+ user = prompt("User", user, 1);
+ }
+ rv = sendrequest("USER " + user, 0);
+ if (rv != Success)
+ sendfail(rv);
+ (rv, code, l) = getfullreply(!quiet);
+ if (rv != Success) {
+ if (rv != Incomplete)
+ fail(rv, l);
+ if (code == 331) {
+ if(password == nil){
+ factotum := load Factotum Factotum->PATH;
+ if(factotum != nil){
+ factotum->init();
+ if(user != nil && keyspec == nil)
+ keyspec = sys->sprint("user=%q", user);
+ (nil, password) = factotum->getuserpasswd(sys->sprint("proto=pass server=%s service=ftp %s", hostname, keyspec));
+ }
+ if(password == nil)
+ password = prompt("Password", nil, 0);
+ }
+ rv = sendrequest2("PASS " + password, 0, "PASS XXXX");
+ if (rv != Success)
+ sendfail(rv);
+ (rv, l) = getreply(0);
+ if (rv != Success)
+ fail(rv, l);
+ }
+ }
+ if (cdtoroot) {
+ rv = sendrequest("CWD /", 0);
+ if (rv != Success)
+ sendfail(rv);
+ (rv, l) = getreply(0);
+ if (rv != Success)
+ fail(rv, l);
+ }
+ rv = sendrequest("TYPE I", 0);
+ if (rv != Success)
+ sendfail(rv);
+ (rv, l) = getreply(0);
+ if (rv != Success)
+ fail(rv, l);
+ rv = sendrequest("PWD", 0);
+ if (rv != Success)
+ sendfail(rv);
+ (rv, l) = getreply(0);
+ if (rv != Success)
+ fail(rv, l);
+ remrootpath = pwd(l);
+ remroot = newnode(nil, "/");
+ remroot.dir.mode = Sys->DMDIR | 8r777;
+ remroot.dir.qid.qtype = Sys->QTDIR;
+ remdir = remroot;
+ l = connect();
+ if (l != nil) {
+ sys->print("%s\n", l);
+ exit;
+ }
+ ctllock = chan[1] of int;
+ spawn mount(mountpoint);
+ pidc := chan of int;
+ spawn heartbeat(pidc);
+ heartbeatpid = <-pidc;
+ if (debug)
+ sys->print("heartbeatpid %d\n", heartbeatpid);
+ spawn server(); # dies when receive on chan fails
+}
+
+kill(pid: int): int
+{
+ if (debug)
+ sys->print("killing %d\n", pid);
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if (fd == nil) {
+ sys->print("kill: open failed\n");
+ return -1;
+ }
+ if (sys->write(fd, array of byte "kill", 4) != 4) {
+ sys->print("kill: write failed\n");
+ return -1;
+ }
+ return 0;
+}
+
+shutdown()
+{
+ mountfd = nil;
+}
+
+#
+# Styx transactions.
+#
+
+versionT(t: ref Tmsg.Version)
+{
+ (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION);
+ sendreply(ref Rmsg.Version(t.tag, msize, version));
+}
+
+authT(t: ref Tmsg.Auth)
+{
+ sendreply(ref Rmsg.Error(t.tag, "authentication not required"));
+}
+
+flushT(t: ref Tmsg.Flush)
+{
+ sendreply(ref Rmsg.Flush(t.tag));
+}
+
+attachT(t: ref Tmsg.Attach)
+{
+ f := getfid(t.fid);
+ f.busy = 1;
+ f.node = remroot;
+ sendreply(ref Rmsg.Attach(t.tag, remroot.qid()));
+}
+
+walkT(t: ref Tmsg.Walk)
+{
+ f := getfid(t.fid);
+ qids: array of Sys->Qid;
+ node := f.node;
+ if(len t.names > 0){
+ qids = array[len t.names] of Sys->Qid;
+ for(i := 0; i < len t.names; i++) {
+ if ((node.dir.mode & Sys->DMDIR) == 0){
+ if(i == 0)
+ return rerror(t.tag, Enotadirectory);
+ break;
+ }
+ if (t.names[i] == "..")
+ node = node.parent;
+ else if (t.names[i] != ".") {
+ if (t.names[i] == ".flush.ftpfs") {
+ node.invalidate();
+ node.readdir();
+ qids[i] = node.qid();
+ continue;
+ }
+ node = node.extendpath(t.names[i]);
+ if (node.parent.cached) {
+ if (!node.valid) {
+ if(i == 0)
+ return rerror(t.tag, Enosuchfile);
+ break;
+ }
+ if ((node.dir.mode & CHSYML) != 0)
+ node.fixsymbolic();
+ } else if (!node.valid) {
+ if (node.changedir() == 0){
+ node.dir.qid.qtype = Sys->QTDIR;
+ node.dir.mode |= Sys->DMDIR;
+ }else{
+ node.dir.qid.qtype = Sys->QTFILE;
+ node.dir.mode &= ~Sys->DMDIR;
+ }
+ }
+ qids[i] = node.qid();
+ }
+ }
+ if(i < len t.names){
+ sendreply(ref Rmsg.Walk(t.tag, qids[0:i]));
+ return;
+ }
+ }
+ if(t.newfid != t.fid){
+ n := getfid(t.newfid);
+ if(n.busy)
+ return rerror(t.tag, "fid in use");
+ n.busy = 1;
+ n.node = node;
+ }else
+ f.node = node;
+ sendreply(ref Rmsg.Walk(t.tag, qids));
+}
+
+openT(t: ref Tmsg.Open)
+{
+ f := getfid(t.fid);
+ if ((f.node.dir.mode & Sys->DMDIR) != 0 && t.mode != Sys->OREAD) {
+ rerror(t.tag, Epermission);
+ return;
+ }
+ if ((t.mode & Sys->OTRUNC) != 0) {
+ f.node.uncache();
+ f.node.parent.uncache();
+ f.node.filedirty();
+ } else if (!f.node.cached) {
+ f.node.filefree();
+ if ((f.node.dir.mode & Sys->DMDIR) != 0) {
+ f.node.invalidate();
+ if (f.node.readdir() < 0) {
+ rerror(t.tag, Enosuchfile);
+ return;
+ }
+ }
+ else {
+ if (f.node.readfile() < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ }
+ f.node.markcached();
+ }
+ sendreply(ref Rmsg.Open(t.tag, f.node.qid(), Styx->MAXFDATA));
+}
+
+createT(t: ref Tmsg.Create)
+{
+ f := getfid(t.fid);
+ if ((f.node.dir.mode & Sys->DMDIR) == 0) {
+ rerror(t.tag, Enotadirectory);
+ return;
+ }
+ f.node = f.node.extendpath(t.name);
+ f.node.uncache();
+ if ((t.perm & Sys->DMDIR) != 0) {
+ if (f.node.createdir() < 0) {
+ rerror(t.tag, Epermission);
+ return;
+ }
+ }
+ else
+ f.node.filedirty();
+ f.node.parent.invalidate();
+ f.node.parent.uncache();
+ sendreply(ref Rmsg.Create(t.tag, f.node.qid(), Styx->MAXFDATA));
+}
+
+readT(t: ref Tmsg.Read)
+{
+ f := getfid(t.fid);
+ count := t.count;
+
+ if (count < 0)
+ return rerror(t.tag, Ebadlength);
+ if (count > Styx->MAXFDATA)
+ count = Styx->MAXFDATA;
+ if (t.offset < big 0)
+ return rerror(t.tag, Ebadoffset);
+ rv := 0;
+ if ((f.node.dir.mode & Sys->DMDIR) != 0) {
+ offset := int t.offset;
+ for (p := f.node.children; offset > 0 && p != nil; p = p.sibs)
+ if (p.valid)
+ offset -= len p.stat();
+ for (; rv < count && p != nil; p = p.sibs) {
+ if (p.valid) {
+ if ((p.dir.mode & CHSYML) != 0)
+ p.fixsymbolic();
+ a := p.stat();
+ size := len a;
+ if(rv+size > count)
+ break;
+ tbuff[rv:] = a;
+ rv += size;
+ }
+ }
+ } else {
+ if (!f.node.cached && f.node.readfile() < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ f.node.markcached();
+ rv = f.node.fileread(tbuff, int t.offset, count);
+ if (rv < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ }
+ sendreply(ref Rmsg.Read(t.tag, tbuff[0:rv]));
+}
+
+writeT(t: ref Tmsg.Write)
+{
+ f := getfid(t.fid);
+ if ((f.node.dir.mode & Sys->DMDIR) != 0) {
+ rerror(t.tag, Eisadirectory);
+ return;
+ }
+ count := f.node.filewrite(t.data, int t.offset, len t.data);
+ if (count < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ f.node.filedirty();
+ sendreply(ref Rmsg.Write(t.tag, count));
+}
+
+clunkT(t: ref Tmsg.Clunk)
+{
+ f := getfid(t.fid);
+ if (f.node.fileisdirty()) {
+ if (f.node.createfile() < 0)
+ sys->print("ftpfs: could not create %s\n", f.node.pathname());
+ f.node.fileclean();
+ f.node.uncache();
+ }
+ f.busy = 0;
+ sendreply(ref Rmsg.Clunk(t.tag));
+}
+
+removeT(t: ref Tmsg.Remove)
+{
+ f := getfid(t.fid);
+ if ((f.node.dir.mode & Sys->DMDIR) != 0) {
+ if (f.node.removedir() < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ }
+ else {
+ if (f.node.removefile() < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ }
+ f.node.parent.uncache();
+ f.node.uncache();
+ f.node.valid = 0;
+ f.busy = 0;
+ sendreply(ref Rmsg.Remove(t.tag));
+}
+
+statT(t: ref Tmsg.Stat)
+{
+ f := getfid(t.fid);
+ n := f.node.parent;
+ if (!n.cached) {
+ n.invalidate();
+ n.readdir();
+ n.markcached();
+ }
+ if (!f.node.valid) {
+ rerror(t.tag, Enosuchfile);
+ return;
+ }
+ sendreply(ref Rmsg.Stat(t.tag, f.node.dir));
+}
+
+wstatT(t: ref Tmsg.Wstat)
+{
+ rerror(t.tag, Enowstat);
+}
diff --git a/appl/cmd/getauthinfo.b b/appl/cmd/getauthinfo.b
new file mode 100644
index 00000000..84c0f1d4
--- /dev/null
+++ b/appl/cmd/getauthinfo.b
@@ -0,0 +1,185 @@
+implement Getauthinfo;
+
+#
+# get and save a certificate from a signer in exchange for a valid secret
+#
+
+include "sys.m";
+ sys: Sys;
+ stdin, stdout, stderr: ref Sys->FD;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+
+include "security.m";
+ login: Login;
+
+include "string.m";
+ str: String;
+
+include "promptstring.b";
+
+Getauthinfo: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(stderr, "usage: getauthinfo {net!hostname | default | /file}\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stdin = sys->fildes(0);
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+
+ # Disable echoing in RAWON mode
+ RAWON_STR = nil;
+
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ keyname := hd argv;
+ if(keyname == nil)
+ usage();
+
+ kr = load Keyring Keyring->PATH;
+ if(kr == nil)
+ nomod(Keyring->PATH);
+
+ str = load String String->PATH;
+ if(str == nil)
+ nomod(String->PATH);
+
+ login = load Login Login->PATH;
+ if(login == nil)
+ nomod(Login->PATH);
+
+ user := user();
+ path := keyname;
+ if(path[0] != '/' || len path < 2 || path[0:2] != "./")
+ path = "/usr/" + user + "/keyring/" + keyname;
+
+ signer := defaultsigner();
+ if(signer == nil){
+ sys->fprint(stderr, "getauthinfo: warning: can't get default signer server name\n");
+ signer = "$SIGNER";
+ }
+
+ passwd := "";
+ save := "yes";
+ redo := "yes";
+ for(;;) {
+ signer = promptstring("use signer", signer, RAWOFF);
+ user = promptstring("remote user name", user, RAWOFF);
+ passwd = promptstring("password", passwd, RAWON);
+
+ info := logon(user, passwd, signer, path, save);
+ if(info != nil)
+ break;
+ }
+}
+
+logon(user, passwd, server, path, save: string): ref Keyring->Authinfo
+{
+ (err, info) := login->login(user, passwd, "net!"+server+"!inflogin");
+ if(err != nil){
+ sys->fprint(stderr, "getauthinfo: failed to authenticate: %s\n", err);
+ return nil;
+ }
+
+ # save the info somewhere for later access
+ save = promptstring("save in file", save, RAWOFF);
+ if(save[0] != 'y'){
+ (dir, file) := str->splitr(path, "/");
+ if(sys->bind("#s", dir, Sys->MBEFORE) < 0){
+ sys->fprint(stderr, "getauthinfo: can't bind file channel on %s: %r\n", dir);
+ return nil;
+ }
+ filio := sys->file2chan(dir, file);
+ if(filio == nil) {
+ sys->fprint(stderr, "getauthinfo: can't make file2chan %s: %r\n", path);
+ return nil;
+ }
+ sync := chan of int;
+ spawn infofile(filio, sync);
+ <-sync;
+ }
+
+ if(kr->writeauthinfo(path, info) < 0) {
+ sys->fprint(stderr, "getauthinfo: can't write certificate to %s: %r\n", path);
+ return nil;
+ }
+
+ return info;
+}
+
+user(): string
+{
+ sys = load Sys Sys->PATH;
+
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil)
+ return "";
+
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+
+ return string buf[0:n];
+}
+
+infofile(fileio: ref Sys->FileIO, sync: chan of int)
+{
+ infodata := array[0] of byte;
+
+ sys->pctl(Sys->NEWPGRP|Sys->NEWFD, nil);
+ sync <-= 1;
+
+ for(;;) alt {
+ (off, nbytes, fid, rc) := <-fileio.read =>
+ if(rc == nil)
+ break;
+ if(off > len infodata){
+ rc <-= (nil, nil);
+ } else {
+ if(off + nbytes > len infodata)
+ nbytes = len infodata - off;
+ rc <-= (infodata[off:off+nbytes], nil);
+ }
+
+ (off, data, fid, wc) := <-fileio.write =>
+ if(wc == nil)
+ break;
+
+ if(off != len infodata){
+ wc <-= (0, "cannot be rewritten");
+ } else {
+ nid := array[len infodata+len data] of byte;
+ nid[0:] = infodata;
+ nid[len infodata:] = data;
+ infodata = nid;
+ wc <-= (len data, nil);
+ }
+ data = nil;
+ }
+}
+
+# get default signer server name
+defaultsigner(): string
+{
+ return "$SIGNER";
+}
+
+nomod(s: string)
+{
+ sys->fprint(stderr, "getauthinfo: can't load %s: %r\n", s);
+ raise "fail:load";
+}
diff --git a/appl/cmd/getfile.b b/appl/cmd/getfile.b
new file mode 100644
index 00000000..ec0ff34c
--- /dev/null
+++ b/appl/cmd/getfile.b
@@ -0,0 +1,74 @@
+implement Getfile;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+ draw: Draw;
+ Rect: import draw;
+include "tk.m";
+ tk: Tk;
+include "wmlib.m";
+ wmlib: Wmlib;
+include "arg.m";
+
+Getfile: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(stderr, "usage: getfile [-g geom] [-d startdir] [pattern...]\n");
+ raise "fail:usage";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ wmlib = load Wmlib Wmlib->PATH;
+ if (wmlib == nil) {
+ sys->fprint(stderr, "getfile: cannot load %s: %r\n", Wmlib->PATH);
+ raise "fail:bad module";
+ }
+ arg := load Arg Arg->PATH;
+ if (arg == nil) {
+ sys->fprint(stderr, "getfile: cannot load %s: %r\n", Arg->PATH);
+ raise "fail:bad module";
+ }
+
+ if (ctxt == nil) {
+ sys->fprint(stderr, "getfile: no window context\n");
+ raise "fail:bad context";
+ }
+
+ wmlib->init();
+
+ startdir := ".";
+ geom := "-x " + string (ctxt.screen.image.r.dx() / 5) +
+ " -y " + string (ctxt.screen.image.r.dy() / 5);
+ title := "Select a file";
+ arg->init(argv);
+ while (opt := arg->opt()) {
+ case opt {
+ 'g' =>
+ geom = arg->arg();
+ 'd' =>
+ startdir = arg->arg();
+ 't' =>
+ title = arg->arg();
+ * =>
+ sys->fprint(stderr, "getfile: unknown option -%c\n", opt);
+ usage();
+ }
+ }
+ if (geom == nil || startdir == nil || title == nil)
+ usage();
+ top := tk->toplevel(ctxt.screen, geom);
+ argv = arg->argv();
+ arg = nil;
+ sys->print("%s\n", wmlib->filename(ctxt.screen, top, title, argv, startdir));
+}
diff --git a/appl/cmd/gettar.b b/appl/cmd/gettar.b
new file mode 100644
index 00000000..4429ab24
--- /dev/null
+++ b/appl/cmd/gettar.b
@@ -0,0 +1,248 @@
+implement Gettar;
+
+include "sys.m";
+ sys: Sys;
+ print, sprint, fprint: import sys;
+ stdin, stderr: ref sys->FD;
+
+include "draw.m";
+
+include "arg.m";
+
+TBLOCK: con 512; # tar logical blocksize
+
+Header: adt{
+ name: string;
+ size: int;
+ mode: int;
+ mtime: int;
+ skip: int;
+};
+
+Gettar: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+error(mess: string)
+{
+ fprint(stderr,"gettar: %s\n",mess);
+ raise "fail:error";
+}
+
+verbose := 0;
+NBLOCK: con 20; # traditional blocking factor for efficient read
+tarbuf := array[NBLOCK*TBLOCK] of byte; # static buffer
+nblock := NBLOCK; # how many blocks of data are in tarbuf
+recno := NBLOCK; # how many blocks in tarbuf have been consumed
+
+getblock(): array of byte
+{
+ if(recno>=nblock){
+ i := sys->read(stdin,tarbuf,TBLOCK*NBLOCK);
+ if(i==0)
+ return nil;
+ if(i<0)
+ error(sys->sprint("read error: %r"));
+ if(i%TBLOCK!=0)
+ error("blocksize error");
+ nblock = i/TBLOCK;
+ recno = 0;
+ }
+ recno++;
+ return tarbuf[(recno-1)*TBLOCK:recno*TBLOCK];
+}
+
+
+octal(b:array of byte): int
+{
+ sum := 0;
+ for(i:=0; i<len b; i++){
+ bi := int b[i];
+ if(bi==' ') continue;
+ if(bi==0) break;
+ sum = 8*sum + bi-'0';
+ }
+ return sum;
+}
+
+nullterm(b:array of byte): string
+{
+ for(i:=0; i<len b; i++)
+ if(b[i]==byte 0) break;
+ return string b[0:i];
+}
+
+getdir(): ref Header
+{
+ dblock := getblock();
+ if(len dblock==0)
+ return nil;
+ if(dblock[0]==byte 0)
+ return nil;
+
+ name := nullterm(dblock[0:100]);
+ if(int dblock[345]!=0)
+ name = nullterm(dblock[345:500])+"/"+name;
+ if(!absolute){
+ if(name[0] == '#')
+ name = "./"+name;
+ else if(name[0] == '/')
+ name = "."+name;
+ }
+
+ magic := string(dblock[257:262]);
+ if(magic[0]!=0 && magic!="ustar")
+ error("bad magic "+name);
+ chksum := octal(dblock[148:156]);
+ for(ci:=148; ci<156; ci++)
+ dblock[ci] = byte ' ';
+ for(i:=0; i<TBLOCK; i++)
+ chksum -= int dblock[i];
+ if(chksum!=0)
+ error("directory checksum error "+name);
+
+ skip := 1;
+ size := 0;
+ mode := 0;
+ mtime := 0;
+ case int dblock[156]{
+ '0' or '7' or 0 =>
+ skip = 0;
+ size = octal(dblock[124:136]);
+ mode = 8r777 & octal(dblock[100: 108]);
+ mtime = octal(dblock[136:148]);
+ '1' =>
+ fprint(stderr,"gettar: skipping link %s -> %s\n",name,string(dblock[157:257]));
+ '2' or 's' =>
+ fprint(stderr,"gettar: skipping symlink %s\n",name);
+ '3' or '4' or '6' =>
+ fprint(stderr,"gettar: skipping special file %s\n",name);
+ '5' =>
+ if(name[(len name)-1]=='/')
+ checkdir(name+".");
+ else
+ checkdir(name+"/.");
+ * =>
+ error(sprint("unrecognized typeflag %d for %s",int dblock[156],name));
+ }
+ return ref Header(name, size, mode, mtime, skip);
+}
+
+keep := 0;
+absolute := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stdin = sys->fildes(0);
+ stderr = sys->fildes(2);
+ ofile: ref sys->FD;
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("gettar [-kTRv] [file ...]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'k' => keep = 1;
+ 'v' => verbose = 1;
+ 'R' => absolute = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ while((file := getdir())!=nil){
+ if(!file.skip){
+ if((args == nil || matched(file.name, args)) && !(keep && exists(file.name))){
+ if(verbose)
+ sys->fprint(stderr, "%s\n", file.name);
+ checkdir(file.name);
+ ofile = sys->create(file.name, Sys->OWRITE, 8r666);
+ if(ofile==nil){
+ fprint(stderr, "gettar: cannot create %s: %r\n",file.name);
+ file.skip = 1;
+ }
+ }else
+ file.skip = 1;
+ }
+ bytes := file.size;
+ blocks := (bytes+TBLOCK-1)/TBLOCK;
+ if(file.skip){
+ for(; blocks>0; blocks--)
+ getblock();
+ continue;
+ }
+
+ for(; blocks>0; blocks--){
+ buf := getblock();
+ nwrite := bytes;
+ if(nwrite>TBLOCK)
+ nwrite = TBLOCK;
+ if(sys->write(ofile,buf,nwrite)!=nwrite)
+ error(sprint("write error for %s: %r",file.name));
+ bytes -= nwrite;
+ }
+ ofile = nil;
+ stat := sys->nulldir;
+ stat.mode = file.mode;
+ stat.mtime = file.mtime;
+ rc := sys->wstat(file.name,stat);
+ if(rc<0){
+ # try just the mode
+ stat.mtime = ~0;
+ rc = sys->wstat(file.name, stat);
+ if(rc < 0)
+ fprint(stderr,"gettar: cannot set mode/mtime %s %#o %ud: %r\n",file.name, file.mode, file.mtime);
+ }
+ }
+}
+
+checkdir(name: string)
+{
+ (nc,compl) := sys->tokenize(name,"/");
+ path := "";
+ while(compl!=nil){
+ comp := hd compl;
+ if(comp=="..")
+ error(".. pathnames forbidden");
+ if(nc>1){
+ if(path=="")
+ path = comp;
+ else
+ path += "/"+comp;
+ (rc,stat) := sys->stat(path);
+ if(rc<0){
+ fd := sys->create(path,Sys->OREAD,Sys->DMDIR+8r777);
+ if(fd==nil)
+ error(sprint("cannot mkdir %s: %r",path));
+ fd = nil;
+ }else if(stat.mode&Sys->DMDIR==0)
+ error(sprint("found non-directory at %s",path));
+ }
+ nc--; compl = tl compl;
+ }
+}
+
+exists(path: string): int
+{
+ return sys->stat(path).t0 >= 0;
+}
+
+matched(n: string, names: list of string): int
+{
+ for(; names != nil; names = tl names){
+ p := hd names;
+ if(prefix(p, n))
+ return 1;
+ }
+ return 0;
+}
+
+prefix(p: string, s: string): int
+{
+ l := len p;
+ if(l > len s)
+ return 0;
+ return p == s[0:l] && (l == len s || s[l] == '/');
+}
diff --git a/appl/cmd/gif2bit.b b/appl/cmd/gif2bit.b
new file mode 100644
index 00000000..1bd35521
--- /dev/null
+++ b/appl/cmd/gif2bit.b
@@ -0,0 +1,101 @@
+#
+# gif2bit -
+#
+# A simple command line utility for converting GIF images to
+# inferno bitmaps.
+#
+# Craig Newell, Jan. 1999 CraigN@cheque.uq.edu.au
+#
+implement gif2bit;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Display: import draw;
+include "string.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "imagefile.m";
+
+mod_name := "gif2bit";
+
+gif2bit : module
+{
+ init: fn(ctx: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->print("usage: %s <GIF file>\n", mod_name);
+ exit;
+}
+
+init(ctx: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ # check arguments
+ if (argv == nil)
+ usage();
+ mod_name = hd argv;
+ argv = tl argv;
+ if (argv == nil)
+ usage();
+ s := hd argv;
+ if (len s && s[0] == '-')
+ usage();
+
+ # load the modules
+ str := load String String->PATH;
+ draw = load Draw Draw->PATH;
+ bufio = load Bufio Bufio->PATH;
+ remap := load Imageremap Imageremap->PATH;
+ imgfile := load RImagefile RImagefile->READGIFPATH;
+ imgfile->init(bufio);
+
+ # open the display
+ display: ref Draw->Display;
+ if (ctx == nil) {
+ display = Display.allocate(nil);
+ } else {
+ display = ctx.display;
+ }
+
+ # process all the files
+ while (argv != nil) {
+
+ # get the filenames
+ gif_name := hd argv;
+ argv = tl argv;
+ (base_name, nil) := str->splitstrl(gif_name, ".gif");
+ bit_name := base_name + ".bit";
+
+ i := bufio->open(gif_name, Bufio->OREAD);
+ if (i == nil) {
+ sys->print("%s: unable to open <%s>\n", mod_name, gif_name);
+ continue;
+ }
+ (raw_img, errstr) := imgfile->read(i);
+ if (errstr != nil) {
+ sys->print("%s: %s\n", mod_name, errstr);
+ continue;
+ }
+ i.close();
+
+ (img, errstr1) := remap->remap(raw_img, display, 0);
+ if (errstr1 != nil) {
+ sys->print("%s: %s\n", mod_name, errstr1);
+ continue;
+ }
+
+ ofd := sys->create(bit_name, Sys->OWRITE, 8r644);
+ if (ofd == nil) {
+ sys->print("%s: unable to create <%s>\n", mod_name, bit_name);
+ continue;
+ }
+ display.writeimage(ofd, img);
+ ofd = nil;
+ }
+}
diff --git a/appl/cmd/grep.b b/appl/cmd/grep.b
new file mode 100644
index 00000000..d534de3b
--- /dev/null
+++ b/appl/cmd/grep.b
@@ -0,0 +1,155 @@
+implement Grep;
+
+include "sys.m";
+ sys: Sys;
+ FD: import Sys;
+ stdin, stderr, stdout: ref FD;
+
+include "draw.m";
+ Context: import Draw;
+
+include "regex.m";
+ regex: Regex;
+ Re: import regex;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+
+
+Grep: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+multi: int;
+lflag, nflag, vflag, iflag, Lflag, sflag: int = 0;
+
+badmodule(path: string)
+{
+ sys->fprint(stderr, "grep: cannot load %s: %r\n", path);
+ raise "fail:bad module";
+}
+
+init(nil: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stdin = sys->fildes(0);
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+
+ regex = load Regex Regex->PATH;
+ if(regex == nil)
+ badmodule(Regex->PATH);
+
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ badmodule(Bufio->PATH);
+
+ arg->init(argv);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'l' =>
+ lflag = 1;
+ 'n' =>
+ nflag = 1;
+ 'v' =>
+ vflag = 1;
+ 'i' =>
+ iflag = 1;
+ 'L' =>
+ Lflag = 1;
+ 's' =>
+ sflag = 1;
+ * =>
+ usage();
+ }
+ }
+ argv = arg->argv();
+ arg = nil;
+
+ if(argv == nil)
+ usage();
+ pattern := hd argv;
+ argv = tl argv;
+ if (iflag)
+ pattern = tolower(pattern);
+ (re, err) := regex->compile(pattern,0);
+ if(re == nil) {
+ sys->fprint(stderr, "grep: %s\n", err);
+ raise "fail:bad regex";
+ }
+
+ matched := 0;
+ if(argv == nil)
+ matched = grep(re, bufio->fopen(stdin, Bufio->OREAD), "stdin");
+ else {
+ multi = (tl argv != nil);
+ for (; argv != nil; argv = tl argv) {
+ f := bufio->open(hd argv, Bufio->OREAD);
+ if(f == nil)
+ sys->fprint(stderr, "grep: cannot open %s: %r\n", hd argv);
+ else
+ matched += grep(re, f, hd argv);
+ }
+ }
+ if (!matched)
+ raise "fail:no matches";
+}
+
+usage()
+{
+ sys->fprint(stderr, "usage: grep [-lnviLs] pattern [file...]\n");
+ raise "fail:usage";
+}
+
+grep(re: Re, f: ref Iobuf, file: string): int
+{
+ matched := 0;
+ for(line := 1; ; line++) {
+ s := t := f.gets('\n');
+ if(s == nil)
+ break;
+ if (iflag)
+ s = tolower(s);
+ if((regex->executese(re, s, (0, len s-1), 1, 1) != nil) ^ vflag) {
+ matched = 1;
+ if(lflag || sflag) {
+ if (!sflag)
+ sys->print("%s\n", file);
+ return matched;
+ }
+ if (!Lflag) {
+ if(nflag)
+ if(multi)
+ sys->print("%s:%d: %s", file, line, t);
+ else
+ sys->print("%d:%s", line, t);
+ else
+ if(multi)
+ sys->print("%s: %s", file, t);
+ else
+ sys->print("%s", t);
+ }
+ }
+ }
+ if (Lflag && matched == 0 && !sflag)
+ sys->print("%s\n", file);
+ return matched;
+}
+
+tolower(s: string): string
+{
+ for (i := 0; i < len s; i++) {
+ c := s[i];
+ if (c >= 'A' && c <= 'Z')
+ s[i] = c - 'A' + 'a';
+ }
+ return s;
+}
diff --git a/appl/cmd/gunzip.b b/appl/cmd/gunzip.b
new file mode 100644
index 00000000..6cb9eaf8
--- /dev/null
+++ b/appl/cmd/gunzip.b
@@ -0,0 +1,139 @@
+implement Gunzip;
+
+include "sys.m";
+ sys: Sys;
+ fprint, sprint: import sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "filter.m";
+ inflate: Filter;
+
+Gunzip: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+argv0: con "gunzip";
+stderr: ref Sys->FD;
+
+INFLATEPATH: con "/dis/lib/inflate.dis";
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ fatal(sys->sprint("cannot load %s: %r", Bufio->PATH));
+ str = load String String->PATH;
+ if (bufio == nil)
+ fatal(sys->sprint("cannot load %s: %r", String->PATH));
+ inflate = load Filter INFLATEPATH;
+ if (inflate == nil)
+ fatal(sys->sprint("cannot load %s: %r", INFLATEPATH));
+
+ inflate->init();
+
+ if(argv != nil)
+ argv = tl argv;
+
+ ok := 1;
+ if(len argv == 0){
+ bin := bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ bout := bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ ok = gunzip(bin, bout, "stdin", "stdout");
+ bout.close();
+ } else {
+ for(; argv != nil; argv = tl argv)
+ ok &= gunzipf(hd argv);
+ }
+ if(ok == 0)
+ raise "fail:errors";
+}
+
+gunzipf(file: string): int
+{
+ bin := bufio->open(file, Bufio->OREAD);
+ if(bin == nil){
+ fprint(stderr, "%s: can't open %s: %r\n", argv0, file);
+ return 0;
+ }
+
+ (nil, ofile) := str->splitr(file, "/");
+ n := len ofile;
+ if(n < 4 || ofile[n-3:] != ".gz"){
+ fprint(stderr, "%s: .gz extension required: %s\n", argv0, file);
+ bin.close();
+ return 0;
+ } else
+ ofile = ofile[:n-3];
+ bout := bufio->create(ofile, Bufio->OWRITE, 8r666);
+ if(bout == nil){
+ fprint(stderr, "%s: can't open %s: %r\n", argv0, ofile);
+ bin.close();
+ return 0;
+ }
+
+ ok := gunzip(bin, bout, file, ofile);
+ bin.close();
+ bout.close();
+ if(ok) {
+ # did possibly rename file and update modification time here.
+ if (sys->remove(file) == -1)
+ sys->fprint(stderr, "%s: cannot remove %s: %r\n", argv0, file);
+ }
+
+ return ok;
+}
+
+gunzip(bin, bout: ref Iobuf, fin, fout: string): int
+{
+ rq := inflate->start("h");
+ for(;;) {
+ pick m := <-rq {
+ Fill =>
+ n := bin.read(m.buf, len m.buf);
+ m.reply <-= n;
+ if (n == -1) {
+ sys->fprint(stderr, "%s: %s: read error: %r\n", argv0, fin);
+ return 0;
+ }
+ Result =>
+ if (len m.buf > 0) {
+ n := bout.write(m.buf, len m.buf);
+ if (n != len m.buf) {
+ m.reply <-= -1;
+ sys->fprint(stderr, "%s: %s: write error: %r\n", argv0, fout);
+ return 0;
+ }
+ m.reply <-= 0;
+ }
+ #Info =>
+ # if m.msg begins with "file", it's the original filename of the compressed file.
+ # if m.msg begins with "mtime", it's the original modification time.
+ Finished =>
+ if (bout.flush() != 0) {
+ sys->fprint(stderr, "%s: %s: flush error: %r\n", argv0, fout);
+ return 0;
+ }
+ return 1;
+ Error =>
+ sys->fprint(stderr, "%s: %s: inflate error: %s\n", argv0, fin, m.e);
+ return 0;
+ }
+ }
+}
+
+fatal(msg: string)
+{
+ fprint(stderr, "%s: %s\n", argv0, msg);
+ raise "fail:error";
+}
diff --git a/appl/cmd/gzip.b b/appl/cmd/gzip.b
new file mode 100644
index 00000000..b87186d0
--- /dev/null
+++ b/appl/cmd/gzip.b
@@ -0,0 +1,228 @@
+implement Gzip;
+
+include "sys.m";
+ sys: Sys;
+ print, fprint: import sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "filter.m";
+ deflate: Filter;
+
+DEFLATEPATH: con "/dis/lib/deflate.dis";
+
+Gzip: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Arg: adt
+{
+ argv: list of string;
+ c: int;
+ opts: string;
+
+ init: fn(argv: list of string): ref Arg;
+ opt: fn(arg: self ref Arg): int;
+ arg: fn(arg: self ref Arg): string;
+};
+
+argv0: con "gzip";
+stderr: ref Sys->FD;
+debug := 0;
+verbose := 0;
+level := 0;
+
+usage()
+{
+ fprint(stderr, "usage: %s [-vD1-9] [file ...]\n", argv0);
+ raise "fail:usage";
+}
+
+nomod(path: string)
+{
+ sys->fprint(stderr, "%s: cannot load %s: %r\n", argv0, path);
+ raise "fail:bad module";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ nomod(Bufio->PATH);
+ str = load String String->PATH;
+ if (str == nil)
+ nomod(String->PATH);
+ daytime = load Daytime Daytime->PATH;
+ if (daytime == nil)
+ nomod(Daytime->PATH);
+ deflate = load Filter DEFLATEPATH;
+ if(deflate == nil)
+ nomod(DEFLATEPATH);
+
+ arg := Arg.init(argv);
+ level = 6;
+ while(c := arg.opt()){
+ case c{
+ 'D' =>
+ debug++;
+ 'v' =>
+ verbose++;
+ '1' to '9' =>
+ level = c - '0';
+ * =>
+ usage();
+ }
+ }
+
+ deflate->init();
+
+ argv = arg.argv;
+
+ ok := 1;
+ if(len argv == 0){
+ bin := bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ bout := bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ ok = gzip(nil, daytime->now(), bin, bout, "stdin", "stdout");
+ bout.close();
+ bin.close();
+ }else{
+ for(; argv != nil; argv = tl argv)
+ ok &= gzipf(hd argv);
+ }
+ exit;
+}
+
+gzipf(file: string): int
+{
+ bin := bufio->open(file, Bufio->OREAD);
+ if(bin == nil){
+ fprint(stderr, "%s: can't open %s: %r\n", argv0, file);
+ return 0;
+ }
+ (ok, dir) := sys->fstat(bin.fd);
+ if(ok >= 0)
+ mtime := dir.mtime;
+ else
+ mtime = daytime->now();
+
+ (nil, ofile) := str->splitr(file, "/");
+ ofile += ".gz";
+ bout := bufio->create(ofile, Bufio->OWRITE, 8r666);
+ if(bout == nil){
+ fprint(stderr, "%s: can't open %s: %r\n", argv0, ofile);
+ bin.close();
+ return 0;
+ }
+
+ ok = gzip(file, mtime, bin, bout, file, ofile);
+ bout.close();
+ bin.close();
+ if (ok)
+ sys->remove(file);
+ else
+ sys->remove(ofile);
+
+ return ok;
+}
+
+gzip(nil: string, nil: int, bin, bout: ref Iobuf, fin, fout: string): int
+{
+ param := "h" + string level;
+ incount := outcount := 0;
+ if (debug)
+ param += "dv";
+ rq := deflate->start(param);
+ crc := 0;
+ for (;;) {
+ pick m := <-rq {
+ Fill =>
+ n := bin.read(m.buf, len m.buf);
+ m.reply <-= n;
+ if (n == -1) {
+ sys->fprint(stderr, "%s: error reading %s: %r\n", argv0, fin);
+ return 0;
+ }
+ incount += n;
+ Result =>
+ n := len m.buf;
+ if (bout.write(m.buf, n) != n) {
+ sys->fprint(stderr, "%s: error writing %s: %r\n", argv0, fout);
+ m.reply <-= -1;
+ return 0;
+ }
+ m.reply <-= 0;
+ outcount += n;
+ Info =>
+ sys->fprint(stderr, "%s\n", m.msg);
+ Finished =>
+ comp := 0.0;
+ if (incount > 0)
+ comp = 1.0 - real outcount / real incount;
+ if (verbose)
+ sys->fprint(stderr, "%s: %5.2f%%\n", fin, comp * 100.0);
+ return 1;
+ Error =>
+ sys->fprint(stderr, "%s: error compressing %s: %s\n", argv0, fin, m.e);
+ return 0;
+ }
+ }
+}
+
+fatal(msg: string)
+{
+ fprint(stderr, "%s: %s\n", argv0, msg);
+ exit;
+}
+
+Arg.init(argv: list of string): ref Arg
+{
+ if(argv != nil)
+ argv = tl argv;
+ return ref Arg(argv, 0, nil);
+}
+
+Arg.opt(arg: self ref Arg): int
+{
+ if(arg.opts != ""){
+ arg.c = arg.opts[0];
+ arg.opts = arg.opts[1:];
+ return arg.c;
+ }
+ if(arg.argv == nil)
+ return arg.c = 0;
+ arg.opts = hd arg.argv;
+ if(len arg.opts < 2 || arg.opts[0] != '-')
+ return arg.c = 0;
+ arg.argv = tl arg.argv;
+ if(arg.opts == "--")
+ return arg.c = 0;
+ arg.c = arg.opts[1];
+ arg.opts = arg.opts[2:];
+ return arg.c;
+}
+
+Arg.arg(arg: self ref Arg): string
+{
+ s := arg.opts;
+ arg.opts = "";
+ if(s != "")
+ return s;
+ if(arg.argv == nil)
+ return "";
+ s = hd arg.argv;
+ arg.argv = tl arg.argv;
+ return s;
+}
diff --git a/appl/cmd/idea.b b/appl/cmd/idea.b
new file mode 100644
index 00000000..b597f8da
--- /dev/null
+++ b/appl/cmd/idea.b
@@ -0,0 +1,116 @@
+implement Idea;
+
+#
+# Copyright © 2002 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "keyring.m";
+ keyring: Keyring;
+
+Idea: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+decerr(s: string)
+{
+ sys->fprint(sys->fildes(2), "decrypt error: %s (wrong password ?)\n", s);
+ exit;
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stdin := sys->fildes(0);
+ stdout := sys->fildes(1);
+
+ bufio = load Bufio Bufio->PATH;
+ keyring = load Keyring Keyring->PATH;
+
+ obuf := array[8] of byte;
+ buf := array[8] of byte;
+ key := array[16] of byte;
+
+ argc := len argv;
+ if((argc != 3 && argc != 4) || (hd tl argv != "-e" && hd tl argv != "-d") || len hd tl tl argv != 16){
+ sys->fprint(sys->fildes(2), "usage: idea -[e | d] <16 char key> [inputfile]\n");
+ exit;
+ }
+ dec := hd tl argv == "-d";
+ if(argc == 4){
+ s := hd tl tl tl argv;
+ stdin = sys->open(s, Sys->OREAD);
+ if(stdin == nil){
+ sys->fprint(sys->fildes(2), "cannot open %s\n", s);
+ exit;
+ }
+ if(dec){
+ l := len s;
+ if(s[l-3: l] != ".id"){
+ sys->fprint(sys->fildes(2), "input file not a .id file\n");
+ exit;
+ }
+ s = s[0: l-3];
+ }
+ else
+ s += ".id";
+ stdout = sys->create(s, Sys->OWRITE, 8r666);
+ if(stdout == nil){
+ sys->fprint(sys->fildes(2), "cannot create %s\n", s);
+ exit;
+ }
+ }
+ for(i := 0; i < 16; i++)
+ key[i] = byte (hd tl tl argv)[i];
+ is := keyring->ideasetup(key, nil);
+ m := om := 0;
+ bin := bufio->fopen(stdin, Bufio->OREAD);
+ bout := bufio->fopen(stdout, Bufio->OWRITE);
+ for(;;){
+ n := bin.read(buf[m: ], 8-m);
+ if(n <= 0)
+ break;
+ m += n;
+ if(m == 8){
+ keyring->ideaecb(is, buf, 8, dec);
+ if(dec){ # leave last block around
+ if(om > 0)
+ bout.write(obuf, 8);
+ obuf[0: ] = buf[0: 8];
+ om = 8;
+ }
+ else
+ bout.write(buf, 8);
+ m = 0;
+ }
+ }
+ if(dec){
+ if(om != 8)
+ decerr("no last block");
+ if(m != 0)
+ decerr("last block not 8 bytes long");
+ m = int obuf[7];
+ if(m < 0 || m > 7)
+ decerr("bad modulus");
+ for(i = m; i < 8-1; i++)
+ if(obuf[i] != byte 0)
+ decerr("byte not 0");
+ bout.write(obuf, m);
+ }
+ else{
+ for(i = m; i < 8; i++)
+ buf[i] = byte 0;
+ buf[7] = byte m;
+ keyring->ideaecb(is, buf, 8, dec);
+ bout.write(buf, 8);
+ }
+ bout.flush();
+ bin.close();
+ bout.close();
+}
diff --git a/appl/cmd/import.b b/appl/cmd/import.b
new file mode 100644
index 00000000..657deb38
--- /dev/null
+++ b/appl/cmd/import.b
@@ -0,0 +1,192 @@
+implement Import;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+include "keyring.m";
+include "security.m";
+include "factotum.m";
+include "encoding.m";
+include "arg.m";
+
+Import: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+factotumfile := "/mnt/factotum/rpc";
+
+fail(status, msg: string)
+{
+ sys->fprint(sys->fildes(2), "import: %s\n", msg);
+ raise "fail:"+status;
+}
+
+nomod(mod: string)
+{
+ fail("load", sys->sprint("can't load %s: %r", mod));
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ factotum := load Factotum Factotum->PATH;
+ if(factotum == nil)
+ nomod(Factotum->PATH);
+ factotum->init();
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+
+ arg->init(args);
+ arg->setusage("import [-a|-b] [-c] [-e enc digest] host file [localfile]");
+ flags := 0;
+ cryptalg := ""; # will be rc4_256 sha1
+ keyspec := "";
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' =>
+ flags |= Sys->MAFTER;
+ 'b' =>
+ flags |= Sys->MBEFORE;
+ 'c' =>
+ flags |= Sys->MCREATE;
+ 'e' =>
+ cryptalg = arg->earg();
+ if(cryptalg == "clear")
+ cryptalg = nil;
+ 'k' =>
+ keyspec = arg->earg();
+ '9' =>
+ ;
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(len args != 2 && len args != 3)
+ arg->usage();
+ arg = nil;
+ addr := hd args;
+ file := hd tl args;
+ mountpt := file;
+ if(len args > 2)
+ mountpt = hd tl tl args;
+
+ sys->pctl(Sys->FORKFD, nil);
+
+ facfd := sys->open(factotumfile, Sys->ORDWR);
+ if(facfd == nil)
+ fail("factotum", sys->sprint("can't open %s: %r", factotumfile));
+
+ dest := netmkaddr(addr, "net", "exportfs");
+ (ok, c) := sys->dial(dest, nil);
+ if(ok < 0)
+ fail("dial failed", sys->sprint("can't dial %s: %r", dest));
+ ai := factotum->proxy(c.dfd, facfd, "proto=p9any role=client "+keyspec);
+ if(ai == nil)
+ fail("auth", sys->sprint("can't authenticate import: %r"));
+ if(sys->fprint(c.dfd, "%s", file) < 0)
+ fail("import", sys->sprint("can't write to remote: %r"));
+ buf := array[256] of byte;
+ if((n := sys->read(c.dfd, buf, len buf)) != 2 || buf[0] != byte 'O' || buf[1] != byte 'K'){
+ if(n >= 4)
+ sys->werrstr("bad remote tree: "+string buf[0:n]);
+ fail("import", sys->sprint("import %s %s: %r", addr, file));
+ }
+ if(cryptalg != nil){
+ if(ai.secret == nil)
+ fail("import", "factotum didn't establish shared secret");
+ random := load Random Random->PATH;
+ if(random == nil)
+ nomod(Random->PATH);
+ kr := load Keyring Keyring->PATH;
+ if(kr == nil)
+ nomod(Keyring->PATH);
+ base64 := load Encoding Encoding->BASE64PATH;
+ if(base64 == nil)
+ nomod(Encoding->BASE64PATH);
+ if(sys->fprint(c.dfd, "impo nofilter ssl\n") < 0)
+ fail("import", sys->sprint("can't write to remote: %r"));
+ key := array[16] of byte; # myrand[4] secret[8] hisrand[4]
+ key[0:] = random->randombuf(Random->ReallyRandom, 4);
+ ns := len ai.secret;
+ if(ns > 8)
+ ns = 8;
+ key[4:] = ai.secret[0:ns];
+ if(sys->write(c.dfd, key, 4) != 4)
+ fail("import", sys->sprint("can't write key to remote: %r"));
+ if(readn(c.dfd, key[12:], 4) != 4)
+ fail("import", sys->sprint("can't read remote key: %r"));
+ digest := array[Keyring->SHA1dlen] of byte;
+ kr->sha1(key, len key, digest, nil);
+ err: string;
+ (c.dfd, err) = pushssl(c.dfd, base64->dec(S(digest[0:10])), base64->dec(S(digest[10:20])), cryptalg);
+ if(err != nil)
+ fail("import", sys->sprint("can't push security layer: %s", err));
+ }else
+ if(sys->fprint(c.dfd, "impo nofilter clear\n") < 0)
+ fail("import", sys->sprint("can't write to remote: %r"));
+ afd := sys->fauth(c.dfd, "");
+ if(afd != nil)
+ factotum->proxy(afd, facfd, "proto=p9any role=client");
+ if(sys->mount(c.dfd, afd, mountpt, flags, "") < 0)
+ fail("mount failed", sys->sprint("import %s %s: mount failed: %r", addr, file));
+}
+
+readn(fd: ref Sys->FD, buf: array of byte, nb: int): int
+{
+ for(nr := 0; nr < nb;){
+ n := sys->read(fd, buf[nr:], nb-nr);
+ if(n <= 0){
+ if(nr == 0)
+ return n;
+ break;
+ }
+ nr += n;
+ }
+ return nr;
+}
+
+S(a: array of byte): string
+{
+ s := "";
+ for(i:=0; i<len a; i++)
+ s += sys->sprint("%.2ux", int a[i]);
+ return s;
+}
+
+pushssl(fd: ref Sys->FD, secretin, secretout: array of byte, alg: string): (ref Sys->FD, string)
+{
+ ssl := load SSL SSL->PATH;
+ if(ssl == nil)
+ nomod(SSL->PATH);
+
+ (err, c) := ssl->connect(fd);
+ if(err != nil)
+ return (nil, "can't connect ssl: " + err);
+
+ err = ssl->secret(c, secretin, secretout);
+ if(err != nil)
+ return (nil, "can't write secret: " + err);
+ if(sys->fprint(c.cfd, "alg %s", alg) < 0)
+ return (nil, sys->sprint("can't push algorithm %s: %r", alg));
+
+ return (c.dfd, nil);
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/cmd/install/NOTICE b/appl/cmd/install/NOTICE
new file mode 100644
index 00000000..1c591576
--- /dev/null
+++ b/appl/cmd/install/NOTICE
@@ -0,0 +1,6 @@
+Most of the code in this directory is a limbo version of Russ Cox's wrap, the
+software package manager that was written for Plan9 distributions. His original
+C code may have been modularized and partly rewritten to use limbo features,
+but the credit and thanks must go to Russ for developing the original system.
+
+
diff --git a/appl/cmd/install/applylog.b b/appl/cmd/install/applylog.b
new file mode 100644
index 00000000..6a1b2e63
--- /dev/null
+++ b/appl/cmd/install/applylog.b
@@ -0,0 +1,699 @@
+implement Applylog;
+
+#
+# apply a plan 9-style replica log
+# this version applies everything and doesn't use the database
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "keyring.m";
+ kr: Keyring;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "logs.m";
+ logs: Logs;
+ Db, Entry, Byname, Byseq: import logs;
+ S: import logs;
+
+include "arg.m";
+
+Applylog: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Apply, Applydb, Install, Asis, Skip: con iota;
+
+client: ref Db; # client current state from client log
+updates: ref Db; # state delta from new section of server log
+
+nerror := 0;
+nconflict := 0;
+debug := 0;
+verbose := 0;
+resolve := 0;
+setuid := 0;
+setgid := 0;
+nflag := 0;
+timefile: string;
+clientroot: string;
+srvroot: string;
+logfd: ref Sys->FD;
+now := 0;
+gen := 0;
+noerr := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ bufio = load Bufio Bufio->PATH;
+ ensure(bufio, Bufio->PATH);
+ str = load String String->PATH;
+ ensure(str, String->PATH);
+ kr = load Keyring Keyring->PATH;
+ ensure(kr, Keyring->PATH);
+ daytime = load Daytime Daytime->PATH;
+ ensure(daytime, Daytime->PATH);
+ logs = load Logs Logs->PATH;
+ ensure(logs, Logs->PATH);
+ logs->init(bufio);
+
+ arg := load Arg Arg->PATH;
+ ensure(arg, Arg->PATH);
+ arg->init(args);
+ arg->setusage("applylog [-vuged] [-sc] [-T timefile] clientlog clientroot serverroot [path ... ] <serverlog");
+ dump := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'T' => timefile = arg->earg();
+ 'd' => dump = 1; debug = 1;
+ 'e' => noerr = 1;
+ 'g' => setgid = 1;
+ 'n' => nflag = 1; verbose = 1;
+ 's' or 'c' => resolve = o;
+ 'u' => setuid = 1;
+ 'v' => verbose = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(len args < 3)
+ arg->usage();
+ arg = nil;
+
+ now = daytime->now();
+ client = Db.new("client log");
+ updates = Db.new("update log");
+ clientlog := hd args; args = tl args;
+ clientroot = hd args; args = tl args;
+ srvroot = hd args; args = tl args;
+ if(args != nil)
+ error("restriction by path not yet done");
+
+ checkroot(clientroot, "client root");
+ checkroot(srvroot, "server root");
+
+ # replay the client log to build last installation state of files taken from server
+ if(nflag)
+ logfd = sys->open(clientlog, Sys->OREAD);
+ else
+ logfd = sys->open(clientlog, Sys->ORDWR);
+ if(logfd == nil)
+ error(sys->sprint("can't open %s: %r", clientlog));
+ f := bufio->fopen(logfd, Sys->OREAD);
+ if(f == nil)
+ error(sys->sprint("can't open %s: %r", clientlog));
+ while((log := readlog(f)) != nil)
+ replaylog(client, log);
+ f = nil;
+ sys->seek(logfd, big 0, 2);
+ if(dump)
+ dumpstate();
+ if(debug){
+ sys->print(" CLIENT STATE\n");
+ client.sort(Byname);
+ dumpdb(client, 0);
+ }
+
+ # read server's log and use the new section to build a sequence of update actions
+ minseq := big 0;
+ if(timefile != nil)
+ minseq = readseq(timefile);
+ f = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ while((log = readlog(f)) != nil)
+ if(log.seq > minseq)
+ update(updates, updates.look(log.path), log);
+ updates.sort(Byseq);
+ if(debug){
+ sys->print(" SEQUENCED UPDATES\n");
+ dumpdb(updates, 1);
+ }
+
+ # apply those actions
+ maxseq := minseq;
+ skip := 0;
+ for(i := 0; i < updates.nstate; i++){
+ e := updates.state[i];
+ ce := client.look(e.path);
+ if(ce != nil && ce.seq >= e.seq){ # replay
+ if(debug)
+ sys->print("replay %c %q\n", e.action, e.path);
+ if(!nflag && !skip)
+ maxseq = e.seq;
+ continue;
+ }
+ if(verbose)
+ sys->print("%s\n", e.sumtext());
+ case chooseaction(e) {
+ Install =>
+ if(debug)
+ sys->print("resolve %q to install\n", e.path);
+ c := e;
+ c.action = 'a'; # force (re)creation/installation
+ if(!enact(c)){
+ skip = 1;
+ continue; # don't update db
+ }
+ Apply =>
+ if(!enact(e)){
+ skip = 1;
+ continue; # don't update db
+ }
+ Applydb =>
+ if(debug)
+ sys->print("resolve %q to update db\n", e.path);
+ # carry on to update the log
+ Asis =>
+ if(debug)
+ sys->print("resolve %q to client\n", e.path);
+ #continue; # ?
+ Skip =>
+ if(debug)
+ sys->print("conflict %q\n", e.path);
+ skip = 1;
+ continue;
+ * =>
+ error("internal error: unexpected result from chooseaction");
+ }
+ # action complete: add to client log
+ if(ce == nil)
+ ce = client.entry(e.seq, e.path, e.d);
+ ce.update(e);
+ if(!nflag){
+ if(!skip)
+ maxseq = e.seq;
+ if(logfd != nil){
+ # append action, now accepted, to client's own log
+ if(sys->fprint(logfd, "%s\n", e.logtext()) < 0)
+ error(sys->sprint("error writing to %q: %r", clientlog));
+ }
+ }
+ }
+ sys->fprint(sys->fildes(2), "maxseq: %bud %bud\n", maxseq>>32, maxseq & 16rFFFFFFFF);
+ if(!nflag && !skip && timefile != nil)
+ writeseq(timefile, maxseq);
+ if(nconflict)
+ raise sys->sprint("fail:%d conflicts", nconflict);
+ if(nerror)
+ raise sys->sprint("fail:%d errors", nerror);
+}
+
+checkroot(dir: string, what: string)
+{
+ (ok, d) := sys->stat(dir);
+ if(ok < 0)
+ error(sys->sprint("can't stat %s %q: %r", what, dir));
+ if((d.mode & Sys->DMDIR) == 0)
+ error(sys->sprint("%s %q: not a directory", what, dir));
+}
+
+readlog(in: ref Iobuf): ref Entry
+{
+ (e, err) := Entry.read(in);
+ if(err != nil)
+ error(err);
+ return e;
+}
+
+readseq(file: string): big
+{
+ fd := sys->open(file, Sys->OREAD);
+ if(fd == nil)
+ error(sys->sprint("can't open %q: %r", file));
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ error(sys->sprint("can't read valid seq from %q", file));
+ (nf, flds) := sys->tokenize(string buf[0:n], " \t\n");
+ if(nf != 2)
+ error(sys->sprint("illegal sequence number in %q", file));
+ n0 := bigof(hd flds, 10);
+ n1 := bigof(hd tl flds, 10);
+ return (n0 << 32) | n1;
+}
+
+writeseq(file: string, n: big)
+{
+ fd := sys->create(file, Sys->OWRITE, 8r666);
+ if(fd == nil)
+ error(sys->sprint("can't create %q: %r", file));
+ if(sys->fprint(fd, "%11bud %11bud", n>>32, n&16rFFFFFFFF) < 0)
+ error(sys->sprint("error writing seq to %q: %r", file));
+}
+
+#
+# replay a log to reach the state wrt files previously taken from the server
+#
+replaylog(db: ref Db, log: ref Entry)
+{
+ e := db.look(log.path);
+ indb := e != nil && !e.removed();
+ case log.action {
+ 'a' => # add new file
+ if(indb){
+ note(sys->sprint("%q duplicate create", log.path));
+ return;
+ }
+ 'c' => # contents
+ if(!indb){
+ note(sys->sprint("%q contents but no entry", log.path));
+ return;
+ }
+ 'd' => # delete
+ if(!indb){
+ note(sys->sprint("%q deleted but no entry", log.path));
+ return;
+ }
+ if(e.d.mtime > log.d.mtime){
+ note(sys->sprint("%q deleted but it's newer", log.path));
+ return;
+ }
+ 'm' => # metadata
+ if(!indb){
+ note(sys->sprint("%q metadata but no entry", log.path));
+ return;
+ }
+ * =>
+ error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF));
+ }
+ update(db, e, log);
+}
+
+#
+# update file state e to reflect the effect of the log,
+# creating a new entry if necessary
+#
+update(db: ref Db, e: ref Entry, log: ref Entry)
+{
+ if(e == nil)
+ e = db.entry(log.seq, log.path, log.d);
+ e.update(log);
+}
+
+chooseaction(e: ref Entry): int
+{
+ cf := logs->mkpath(clientroot, e.path);
+ sf := logs->mkpath(srvroot, e.serverpath);
+ (ishere, cd) := sys->stat(logs->mkpath(clientroot, e.path));
+ ishere = ishere >= 0; # in local file system
+ db := client.look(e.path);
+ indb := db != nil && !db.removed(); # previously arrived from server
+
+ unchanged := indb && ishere && (samestat(db.d, cd) || samecontents(sf, cf)) || !indb && !ishere;
+ if(unchanged && (e.action != 'm' || samemeta(db.d, cd)))
+ return Apply;
+ if(!ishere && e.action == 'd'){
+ if(indb)
+ return Applydb;
+ return Asis;
+ }
+ case resolve {
+ 'c' =>
+ return Asis;
+ 's' =>
+ if(!ishere || e.action == 'm' && !unchanged)
+ return Install;
+ return Apply;
+ * =>
+ # describe source of conflict
+ if(indb){
+ if(ishere){
+ if(e.action == 'm' && unchanged && !samemeta(db.d, cd))
+ conflict(e.path, "locally modified metadata", action(e.action));
+ else
+ conflict(e.path, "locally modified", action(e.action));
+ }else
+ conflict(e.path, "locally removed", action(e.action));
+ }else{
+ if(db != nil)
+ conflict(e.path, "locally retained or recreated", action(e.action)); # server installed it but later removed it
+ else
+ conflict(e.path, "locally created", action(e.action));
+ }
+ return Skip;
+ }
+}
+
+enact(e: ref Entry): int
+{
+ if(nflag)
+ return 0;
+ srcfile := logs->mkpath(srvroot, e.serverpath);
+ dstfile := logs->mkpath(clientroot, e.path);
+ case e.action {
+ 'a' => # create and copy in
+ if(debug)
+ sys->print("create %q\n", dstfile);
+ if(e.d.mode & Sys->DMDIR)
+ err := mkdir(dstfile, e);
+ else
+ err = copyin(srcfile, dstfile, 1, e);
+ if(err != nil){
+ if(noerr)
+ error(err);
+ warn(err);
+ return 0;
+ }
+ 'c' => # contents
+ err := copyin(srcfile, dstfile, 0, e);
+ if(err != nil){
+ if(noerr)
+ error(err);
+ warn(err);
+ return 0;
+ }
+ 'd' => # delete
+ if(debug)
+ sys->print("remove %q\n", dstfile);
+ if(remove(dstfile) < 0){
+ warn(sys->sprint("can't remove %q: %r", dstfile));
+ return 0;
+ }
+ 'm' => # metadata
+ if(debug)
+ sys->print("wstat %q\n", dstfile);
+ d := sys->nulldir;
+ d.mode = e.d.mode;
+ if(sys->wstat(dstfile, d) < 0)
+ warn(sys->sprint("%q: can't change mode to %uo", dstfile, d.mode));
+ if(setgid){
+ d = sys->nulldir;
+ d.gid = e.d.gid;
+ if(sys->wstat(dstfile, d) < 0)
+ warn(sys->sprint("%q: can't change gid to %q", dstfile, d.gid));
+ }
+ if(setuid){
+ d = sys->nulldir;
+ d.uid = e.d.uid;
+ if(sys->wstat(dstfile, d) < 0)
+ warn(sys->sprint("%q: can't change uid to %q", dstfile, d.uid));
+ }
+ * =>
+ error(sys->sprint("unexpected log operation: %c %q", e.action, e.path));
+ return 0;
+ }
+ return 1;
+}
+
+rev[T](l: list of T): list of T
+{
+ rl: list of T;
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+ensure[T](m: T, path: string)
+{
+ if(m == nil)
+ error(sys->sprint("can't load %s: %r", path));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: %s\n", s);
+ raise "fail:error";
+}
+
+note(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: note: %s\n", s);
+}
+
+warn(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: warning: %s\n", s);
+ nerror++;
+}
+
+conflict(name: string, why: string, wont: string)
+{
+ sys->fprint(sys->fildes(2), "%q: %s; will not %s\n", name, why, wont);
+ nconflict++;
+}
+
+action(a: int): string
+{
+ case a {
+ 'a' => return "create";
+ 'c' => return "update";
+ 'd' => return "delete";
+ 'm' => return "update metadata";
+ * => return sys->sprint("unknown action %c", a);
+ }
+}
+
+samecontents(path1, path2: string): int
+{
+ f1 := sys->open(path1, Sys->OREAD);
+ if(f1 == nil)
+ return 0;
+ f2 := sys->open(path2, Sys->OREAD);
+ if(f2 == nil)
+ return 0;
+ b1 := array[Sys->ATOMICIO] of byte;
+ b2 := array[Sys->ATOMICIO] of byte;
+ n := 256; # start with something small; dis files and big executables should fail more quickly
+ n1, n2: int;
+ do{
+ n1 = sys->read(f1, b1, n);
+ n2 = sys->read(f2, b2, n);
+ if(n1 != n2)
+ return 0;
+ for(i := 0; i < n1; i++)
+ if(b1[i] != b2[i])
+ return 0;
+ n += len b1 - n;
+ }while(n1 > 0);
+ return 1;
+}
+
+samestat(a: Sys->Dir, b: Sys->Dir): int
+{
+ # doesn't check permission/ownership, does check QTDIR/QTFILE
+ if(a.mode & Sys->DMDIR)
+ return (b.mode & Sys->DMDIR) != 0;
+ return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name?
+}
+
+samemeta(a: Sys->Dir, b: Sys->Dir): int
+{
+ return a.mode == b.mode && (!setuid || a.uid == b.uid) && (!setgid || a.gid == b.gid) && samestat(a, b);
+}
+
+bigof(s: string, base: int): big
+{
+ (b, r) := str->tobig(s, base);
+ if(r != nil)
+ error("cruft in integer field in log entry: "+s);
+ return b;
+}
+
+intof(s: string, base: int): int
+{
+ return int bigof(s, base);
+}
+
+mkdir(dstpath: string, e: ref Entry): string
+{
+ fd := create(dstpath, Sys->OREAD, e.d.mode);
+ if(fd == nil)
+ return sys->sprint("can't mkdir %q: %r", dstpath);
+ fchmod(fd, e.d.mode);
+ if(setgid)
+ fchgrp(fd, e.d.gid);
+ if(setuid)
+ fchown(fd, e.d.uid);
+# e.d.mtime = now;
+ return nil;
+}
+
+fchmod(fd: ref Sys->FD, mode: int)
+{
+ d := sys->nulldir;
+ d.mode = mode;
+ if(sys->fwstat(fd, d) < 0)
+ warn(sys->sprint("%q: can't set mode %o: %r", sys->fd2path(fd), mode));
+}
+
+fchgrp(fd: ref Sys->FD, gid: string)
+{
+ d := sys->nulldir;
+ d.gid = gid;
+ if(sys->fwstat(fd, d) < 0)
+ warn(sys->sprint("%q: can't set group id %s: %r", sys->fd2path(fd), gid));
+}
+
+fchown(fd: ref Sys->FD, uid: string)
+{
+ d := sys->nulldir;
+ d.uid = uid;
+ if(sys->fwstat(fd, d) < 0)
+ warn(sys->sprint("%q: can't set user id %s: %r", sys->fd2path(fd), uid));
+}
+
+copyin(srcpath: string, dstpath: string, dowstat: int, e: ref Entry): string
+{
+ if(debug)
+ sys->print("copyin %q -> %q\n", srcpath, dstpath);
+ f := sys->open(srcpath, Sys->OREAD);
+ if(f == nil)
+ return sys->sprint("can't open %q: %r", srcpath);
+ t: ref Sys->FD;
+ (ok, nil) := sys->stat(dstpath);
+ if(ok < 0){
+ t = create(dstpath, Sys->OWRITE, e.d.mode | 8r222);
+ if(t == nil)
+ return sys->sprint("can't create %q: %r", dstpath);
+ # TO DO: force access to parent directory
+ dowstat = 1;
+ }else{
+ t = sys->open(dstpath, Sys->OWRITE|Sys->OTRUNC);
+ if(t == nil){
+ err := sys->sprint("%r");
+ if(!contains(err, "permission"))
+ return sys->sprint("can't overwrite %q: %s", dstpath, err);
+ }
+ }
+ (nw, err) := copy(f, t);
+ if(err != nil)
+ return err;
+ if(nw != e.d.length)
+ warn(sys->sprint("%q: log said %bud bytes, copied %bud bytes", dstpath, e.d.length, nw));
+ f = nil;
+ if(dowstat){
+ fchmod(t, e.d.mode);
+ if(setgid)
+ fchgrp(t, e.d.gid);
+ if(setuid)
+ fchown(t, e.d.uid);
+ }
+ nd := sys->nulldir;
+ nd.mtime = e.d.mtime;
+ if(sys->fwstat(t, nd) < 0)
+ warn(sys->sprint("%q: can't set mtime: %r", dstpath));
+ return nil;
+}
+
+copy(f: ref Sys->FD, t: ref Sys->FD): (big, string)
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ nw := big 0;
+ while((n := sys->read(f, buf, len buf)) > 0){
+ if(sys->write(t, buf, n) != n)
+ return (nw, sys->sprint("error writing %q: %r", sys->fd2path(t)));
+ nw += big n;
+ }
+ if(n < 0)
+ return (nw, sys->sprint("error reading %q: %r", sys->fd2path(f)));
+ return (nw, nil);
+}
+
+contents(e: ref Entry): string
+{
+ s := "";
+ for(cl := e.contents; cl != nil; cl = tl cl)
+ s += " " + hd cl;
+ return s;
+}
+
+dumpstate()
+{
+ for(i := 0; i < client.nstate; i++)
+ sys->print("%d\t%s\n", i, client.state[i].text());
+}
+
+dumpdb(db: ref Db, tag: int)
+{
+ for(i := 0; i < db.nstate; i++){
+ if(!tag)
+ s := db.state[i].dbtext();
+ else
+ s = db.state[i].text();
+ if(s != nil)
+ sys->print("%s\n", s);
+ }
+}
+
+#
+# perhaps these should be in a utility module
+#
+parent(name: string): string
+{
+ slash := -1;
+ for(i := 0; i < len name; i++)
+ if(name[i] == '/')
+ slash = i;
+ if(slash > 0)
+ return name[0:slash];
+ return "/";
+}
+
+writableparent(name: string): (int, string)
+{
+ p := parent(name);
+ (ok, d) := sys->stat(p);
+ if(ok < 0)
+ return (-1, nil);
+ nd := sys->nulldir;
+ nd.mode |= 8r222;
+ sys->wstat(p, nd);
+ return (d.mode, p);
+}
+
+create(name: string, rw: int, mode: int): ref Sys->FD
+{
+ fd := sys->create(name, rw, mode);
+ if(fd == nil){
+ err := sys->sprint("%r");
+ if(!contains(err, "permission")){
+ sys->werrstr(err);
+ return nil;
+ }
+ (pm, p) := writableparent(name);
+ if(pm >= 0){
+ fd = sys->create(name, rw, mode);
+ d := sys->nulldir;
+ d.mode = pm;
+ sys->wstat(p, d);
+ }
+ sys->werrstr(err);
+ }
+ return fd;
+}
+
+remove(name: string): int
+{
+ if(sys->remove(name) >= 0)
+ return 0;
+ err := sys->sprint("%r");
+ if(contains(err, "entry not found") || contains(err, "not exist"))
+ return 0;
+ (pm, p) := writableparent(name);
+ rc := sys->remove(name);
+ d := sys->nulldir;
+ if(pm >= 0){
+ d.mode = pm;
+ sys->wstat(p, d);
+ }
+ sys->werrstr(err);
+ return rc;
+}
+
+contains(s: string, sub: string): int
+{
+ return str->splitstrl(s, sub).t1 != nil;
+}
diff --git a/appl/cmd/install/arch.b b/appl/cmd/install/arch.b
new file mode 100644
index 00000000..3f4d660d
--- /dev/null
+++ b/appl/cmd/install/arch.b
@@ -0,0 +1,288 @@
+implement Arch;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "daytime.m";
+ daytime : Daytime;
+include "string.m";
+ str : String;
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+include "sh.m";
+include "arch.m";
+
+addp := 1;
+
+buf := array[Sys->ATOMICIO] of byte;
+
+init(bio: Bufio)
+{
+ sys = load Sys Sys->PATH;
+ if(bio == nil)
+ bufio = load Bufio Bufio->PATH;
+ else
+ bufio = bio;
+ daytime = load Daytime Daytime->PATH;
+ str = load String String->PATH;
+}
+
+addperms(p: int)
+{
+ addp = p;
+}
+
+openarch(file : string) : ref Archive
+{
+ return openarch0(file, 1);
+}
+
+openarchfs(file : string) : ref Archive
+{
+ return openarch0(file, 0);
+}
+
+openarch0(file : string, newpgrp : int) : ref Archive
+{
+ pid := 0;
+ canseek := 1;
+ b := bufio->open(file, Bufio->OREAD);
+ if (b == nil)
+ return nil;
+ if (b.getb() == 16r1f && ((c := b.getb()) == 16r8b || c == 16r9d)) {
+ # spawn gunzip
+ canseek = 0;
+ (b, pid) = gunzipstream(file, newpgrp);
+ if (b == nil)
+ return nil;
+ }
+ else
+ b.seek(big 0, Bufio->SEEKSTART);
+ ar := ref Archive;
+ ar.b = b;
+ ar.nexthdr = 0;
+ ar.canseek = canseek;
+ ar.pid = pid;
+ ar.hdr = ref Ahdr;
+ ar.hdr.d = ref Sys->Dir;
+ return ar;
+}
+
+EOARCH : con "end of archive\n";
+PREMEOARCH : con "premature end of archive";
+NFLDS : con 6;
+
+openarchgz(file : string) : (string, ref Sys->FD)
+{
+ ar := openarch(file);
+ if (ar == nil || ar.canseek)
+ return (nil, nil);
+ (newfile, fd) := opentemp("wrap.gz");
+ if (fd == nil)
+ return (nil, nil);
+ bout := bufio->fopen(fd, Bufio->OWRITE);
+ if (bout == nil)
+ return (nil, nil);
+ while ((a := gethdr(ar)) != nil) {
+ if (len a.name >= 5 && a.name[0:5] == "/wrap") {
+ puthdr(bout, a.name, a.d);
+ getfile(ar, bout, int a.d.length);
+ }
+ else
+ break;
+ }
+ closearch(ar);
+ bout.puts(EOARCH);
+ bout.flush();
+ sys->seek(fd, big 0, Sys->SEEKSTART);
+ return (newfile, fd);
+}
+
+gunzipstream(file : string, newpgrp : int) : (ref Iobuf, int)
+{
+ p := array[2] of ref Sys->FD;
+ if (sys->pipe(p) < 0)
+ return (nil, 0);
+ fd := sys->open(file, Sys->OREAD);
+ if (fd == nil)
+ return (nil, 0);
+ b := bufio->fopen(p[0], Bufio->OREAD);
+ if (b == nil)
+ return (nil, 0);
+ c := chan of int;
+ spawn gunzip(fd, p[1], c, newpgrp);
+ pid := <- c;
+ p[0] = p[1] = nil;
+ if (pid < 0)
+ return (nil, 0);
+ return (b, pid);
+}
+
+GUNZIP : con "/dis/gunzip.dis";
+
+gunzip(stdin : ref Sys->FD, stdout : ref Sys->FD, c : chan of int, newpgrp : int)
+{
+ if (newpgrp)
+ pid := sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ else
+ pid = sys->pctl(Sys->FORKFD, nil);
+ sys->dup(stdin.fd, 0);
+ sys->dup(stdout.fd, 1);
+ sys->dup(1, 2);
+ stdin = stdout = nil;
+ cmd := load Command GUNZIP;
+ if (cmd == nil) {
+ c <-= -1;
+ return;
+ }
+ c <-= pid;
+ cmd->init(nil, GUNZIP :: nil);
+}
+
+closearch(ar : ref Archive)
+{
+ if (ar.pid != 0) {
+ fd := sys->open("#p/" + string ar.pid + "/ctl", sys->OWRITE);
+ if (fd != nil)
+ sys->fprint(fd, "killgrp");
+ }
+ ar.b.close();
+ ar.b = nil;
+}
+
+gethdr(ar : ref Archive) : ref Ahdr
+{
+ a := ar.hdr;
+ b := ar.b;
+ m := int b.offset();
+ n := ar.nexthdr;
+ if (m != n) {
+ if (ar.canseek)
+ b.seek(big n, Bufio->SEEKSTART);
+ else {
+ if (m > n)
+ fatal(sys->sprint("bad offset in gethdr: m=%d n=%d", m, n));
+ if(drain(ar, n-m) < 0)
+ return nil;
+ }
+ }
+ if ((s := b.gets('\n')) == nil) {
+ ar.err = PREMEOARCH;
+ return nil;
+ }
+# fd := sys->open("./debug", Sys->OWRITE);
+# sys->seek(fd, 0, Sys->SEEKEND);
+# sys->fprint(fd, "gethdr: %d %d %d %d %s\n", ar.canseek, m, n, b.offset(), s);
+# fd = nil;
+ if (s == EOARCH)
+ return nil;
+ (nf, fs) := sys->tokenize(s, " \t\n");
+ if(nf != NFLDS) {
+ ar.err = "too few fields in file header";
+ return nil;
+ }
+ a.name = hd fs; fs = tl fs;
+ (a.d.mode, nil) = str->toint(hd fs, 8); fs = tl fs;
+ a.d.uid = hd fs; fs = tl fs;
+ a.d.gid = hd fs; fs = tl fs;
+ (a.d.mtime, nil) = str->toint(hd fs, 10); fs = tl fs;
+ (tmp, nil) := str->toint(hd fs, 10); fs = tl fs;
+ a.d.length = big tmp;
+ ar.nexthdr = int (b.offset()+a.d.length);
+ return a;
+}
+
+getfile(ar : ref Archive, bout : ref Bufio->Iobuf, n : int) : string
+{
+ err: string;
+ bin := ar.b;
+ while (n > 0) {
+ m := len buf;
+ if (n < m)
+ m = n;
+ p := bin.read(buf, m);
+ if (p != m)
+ return PREMEOARCH;
+ p = bout.write(buf, m);
+ if (p != m)
+ err = sys->sprint("cannot write: %r");
+ n -= m;
+ }
+ return err;
+}
+
+puthdr(b : ref Iobuf, name : string, d : ref Sys->Dir)
+{
+ mode := d.mode;
+ if(addp){
+ mode |= 8r664;
+ if(mode & Sys->DMDIR || mode & 8r111)
+ mode |= 8r111;
+ }
+ b.puts(sys->sprint("%s %uo %s %s %ud %d\n", name, mode, d.uid, d.gid, d.mtime, int d.length));
+}
+
+putstring(b : ref Iobuf, s : string)
+{
+ b.puts(s);
+}
+
+putfile(b : ref Iobuf, f : string, n : int) : string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if (fd == nil)
+ return sys->sprint("cannot open %s: %r", f);
+ i := 0;
+ for (;;) {
+ m := sys->read(fd, buf, len buf);
+ if (m < 0)
+ return sys->sprint("cannot read %s: %r", f);
+ if (m == 0)
+ break;
+ if (b.write(buf, m) != m)
+ return sys->sprint("%s: cannot write: %r", f);
+ i += m;
+ }
+ if (i != n) {
+ b.seek(big (n-i), Sys->SEEKRELA);
+ return sys->sprint("%s: %d bytes written: should be %d", f, i, n);
+ }
+ return nil;
+}
+
+putend(b : ref Iobuf)
+{
+ b.puts(EOARCH);
+ b.flush();
+}
+
+drain(ar : ref Archive, n : int) : int
+{
+ while (n > 0) {
+ m := n;
+ if (m > len buf)
+ m = len buf;
+ p := ar.b.read(buf, m);
+ if (p != m){
+ ar.err = "unexpectedly short read";
+ return -1;
+ }
+ n -= m;
+ }
+ return 0;
+}
+
+opentemp(prefix: string): (string, ref Sys->FD)
+{
+ name := sys->sprint("/tmp/%s.%ud.%d", prefix, daytime->now(), sys->pctl(0, nil));
+ # would use ORCLOSE here but it messes up under Nt
+ fd := sys->create(name, Sys->ORDWR, 8r600);
+ return (name, fd);
+}
+
+fatal(s : string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/install/arch.m b/appl/cmd/install/arch.m
new file mode 100644
index 00000000..03837445
--- /dev/null
+++ b/appl/cmd/install/arch.m
@@ -0,0 +1,36 @@
+Arch : module
+{
+ PATH : con "/dis/install/arch.dis";
+
+ Ahdr : adt {
+ name : string;
+ modestr : string;
+ d : ref Sys->Dir;
+ };
+
+ Archive : adt {
+ b : ref Bufio->Iobuf;
+ nexthdr : int;
+ canseek : int;
+ pid : int;
+ hdr : ref Ahdr;
+ err : string;
+ };
+
+ init: fn(bio: Bufio);
+
+ openarch: fn(name : string) : ref Archive;
+ openarchfs: fn(name : string) : ref Archive;
+ openarchgz: fn(name : string) : (string, ref Sys->FD);
+ gethdr: fn(ar : ref Archive) : ref Ahdr;
+ getfile: fn(ar : ref Archive, bout : ref Bufio->Iobuf, n : int) : string;
+ drain: fn(ar : ref Archive, n : int) : int;
+ closearch: fn(ar : ref Archive);
+
+ puthdr: fn(b : ref Bufio->Iobuf, name : string, d : ref Sys->Dir);
+ putstring: fn(b : ref Bufio->Iobuf, s : string);
+ putfile: fn(b : ref Bufio->Iobuf, f : string, n : int) : string;
+ putend: fn(b : ref Bufio->Iobuf);
+
+ addperms: fn(p: int);
+};
diff --git a/appl/cmd/install/archfs.b b/appl/cmd/install/archfs.b
new file mode 100644
index 00000000..3705aee9
--- /dev/null
+++ b/appl/cmd/install/archfs.b
@@ -0,0 +1,579 @@
+implement Archfs;
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+include "bufio.m";
+ bufio : Bufio;
+include "arg.m";
+ arg : Arg;
+include "string.m";
+ str : String;
+include "daytime.m";
+ daytime : Daytime;
+include "styx.m";
+ styx: Styx;
+include "archfs.m";
+include "arch.m";
+ arch : Arch;
+
+# add write some day
+
+Iobuf : import bufio;
+Tmsg, Rmsg: import styx;
+
+Einuse : con "fid already in use";
+Ebadfid : con "bad fid";
+Eopen : con "fid already opened";
+Enotfound : con "file does not exist";
+Enotdir : con "not a directory";
+Eperm : con "permission denied";
+Ebadarg : con "bad argument";
+Eexists : con "file already exists";
+
+UID : con "inferno";
+GID : con "inferno";
+
+DEBUG: con 0;
+
+Dir : adt {
+ dir : Sys->Dir;
+ offset : int;
+ parent : cyclic ref Dir;
+ child : cyclic ref Dir;
+ sibling : cyclic ref Dir;
+};
+
+Fid : adt {
+ fid : int;
+ open: int;
+ dir : ref Dir;
+ next : cyclic ref Fid;
+};
+
+HTSZ : con 32;
+fidtab := array[HTSZ] of ref Fid;
+
+root : ref Dir;
+qid : int;
+mtpt := "/mnt";
+bio : ref Iobuf;
+buf : array of byte;
+skip := 0;
+
+# Archfs : module
+# {
+# init : fn(ctxt : ref Draw->Context, args : list of string);
+# };
+
+init(nil : ref Draw->Context, args : list of string)
+{
+ init0(nil, args, nil);
+}
+
+initc(args : list of string, c : chan of int)
+{
+ init0(nil, args, c);
+}
+
+chanint : chan of int;
+
+init0(nil : ref Draw->Context, args : list of string, chi : chan of int)
+{
+ chanint = chi;
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg = load Arg Arg->PATH;
+ str = load String String->PATH;
+ daytime = load Daytime Daytime->PATH;
+ styx = load Styx Styx->PATH;
+ arch = load Arch Arch->PATH;
+ if (bufio == nil || arg == nil || styx == nil || arch == nil)
+ fatal("failed to load modules", 1);
+ styx->init();
+ arch->init(bufio);
+ arg->init(args);
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'm' =>
+ mtpt = arg->arg();
+ if (mtpt == nil)
+ fatal("mount point missing", 1);
+ 's' =>
+ skip = 1;
+ }
+ }
+ args = arg->argv();
+ if (args == nil)
+ fatal("missing archive file", 1);
+ buf = array[Sys->ATOMICIO] of byte;
+ # root = newdir("/", UID, GID, 8r755|Sys->DMDIR, daytime->now());
+ root = newdir(basename(mtpt), UID, GID, 8r755|Sys->DMDIR, daytime->now());
+ root.parent = root;
+ readarch(hd args, tl args);
+ p := array[2] of ref Sys->FD;
+ if(sys->pipe(p) < 0)
+ fatal("can't create pipe", 1);
+ ch := chan of ref Tmsg;
+ sync := chan of int;
+ spawn reader(p[1], ch, sync);
+ <- sync;
+ pidch := chan of int;
+ spawn serve(p[1], ch, pidch);
+ pid := <- pidch;
+ if(sys->mount(p[0], nil, mtpt, Sys->MREPL, nil) < 0)
+ fatal(sys->sprint("cannot mount archive on %s: %r", mtpt), 1);
+ p[0] = p[1] = nil;
+ if (chi != nil) {
+ chi <-= pid;
+ chanint = nil;
+ }
+}
+
+reply(fd: ref Sys->FD, m: ref Rmsg): int
+{
+ if(DEBUG)
+ sys->fprint(sys->fildes(2), "R: %s\n", m.text());
+ s := m.pack();
+ if(s == nil)
+ return -1;
+ return sys->write(fd, s, len s);
+}
+
+error(fd: ref Sys->FD, m: ref Tmsg, e : string)
+{
+ reply(fd, ref Rmsg.Error(m.tag, e));
+}
+
+reader(fd: ref Sys->FD, ch: chan of ref Tmsg, sync: chan of int)
+{
+ sys->pctl(Sys->NEWFD|Sys->NEWNS, fd.fd :: nil);
+ sync <-= 1;
+ while((m := Tmsg.read(fd, Styx->MAXRPC)) != nil && tagof m != tagof Tmsg.Readerror)
+ ch <-= m;
+ ch <-= m;
+}
+
+serve(fd: ref Sys->FD, ch : chan of ref Tmsg, pidch : chan of int)
+{
+ e : string;
+ f : ref Fid;
+
+ pidch <-= sys->pctl(0, nil);
+ for (;;) {
+ m0 := <- ch;
+ if (m0 == nil)
+ return;
+ if(DEBUG)
+ sys->fprint(sys->fildes(2), "T: %s\n", m0.text());
+ pick m := m0 {
+ Readerror =>
+ fatal("read error on styx server", 1);
+ Version =>
+ (s, v) := styx->compatible(m, Styx->MAXRPC, Styx->VERSION);
+ reply(fd, ref Rmsg.Version(m.tag, s, v));
+ Auth =>
+ error(fd, m, "no authentication required");
+ Flush =>
+ reply(fd, ref Rmsg.Flush(m.tag));
+ Walk =>
+ (f, e) = mapfid(m.fid);
+ if (e != nil) {
+ error(fd, m, e);
+ continue;
+ }
+ if (f.open) {
+ error(fd, m, Eopen);
+ continue;
+ }
+ err := 0;
+ dir := f.dir;
+ nq := 0;
+ nn := len m.names;
+ qids := array[nn] of Sys->Qid;
+ if(nn > 0){
+ for(k := 0; k < nn; k++){
+ if ((dir.dir.mode & Sys->DMDIR) == 0) {
+ if(k == 0){
+ error(fd, m, Enotdir);
+ err = 1;
+ }
+ break;
+ }
+ dir = lookup(dir, m.names[k]);
+ if (dir == nil) {
+ if(k == 0){
+ error(fd, m, Enotfound);
+ err = 1;
+ }
+ break;
+ }
+ qids[nq++] = dir.dir.qid;
+ }
+ }
+ if(err)
+ continue;
+ if(nq < nn)
+ qids = qids[0: nq];
+ if(nq == nn){
+ if(m.newfid != m.fid){
+ f = newfid(m.newfid);
+ if (f == nil) {
+ error(fd, m, Einuse);
+ continue;
+ }
+ }
+ f.dir = dir;
+ }
+ reply(fd, ref Rmsg.Walk(m.tag, qids));
+ Open =>
+ (f, e) = mapfid(m.fid);
+ if (e != nil) {
+ error(fd, m, e);
+ continue;
+ }
+ if (m.mode & (Sys->OWRITE|Sys->ORDWR|Sys->OTRUNC|Sys->ORCLOSE)) {
+ error(fd, m, Eperm);
+ continue;
+ }
+ f.open = 1;
+ reply(fd, ref Rmsg.Open(m.tag, f.dir.dir.qid, Styx->MAXFDATA));
+ Create =>
+ error(fd, m, Eperm);
+ Read =>
+ (f, e) = mapfid(m.fid);
+ if (e != nil) {
+ error(fd, m, e);
+ continue;
+ }
+ data := readdir(f.dir, int m.offset, m.count);
+ reply(fd, ref Rmsg.Read(m.tag, data));
+ Write =>
+ error(fd, m, Eperm);
+ Clunk =>
+ (f, e) = mapfid(m.fid);
+ if (e != nil) {
+ error(fd, m, e);
+ continue;
+ }
+ freefid(f);
+ reply(fd, ref Rmsg.Clunk(m.tag));
+ Stat =>
+ (f, e) = mapfid(m.fid);
+ if (e != nil) {
+ error(fd, m, e);
+ continue;
+ }
+ reply(fd, ref Rmsg.Stat(m.tag, f.dir.dir));
+ Remove =>
+ error(fd, m, Eperm);
+ Wstat =>
+ error(fd, m, Eperm);
+ Attach =>
+ f = newfid(m.fid);
+ if (f == nil) {
+ error(fd, m, Einuse);
+ continue;
+ }
+ f.dir = root;
+ reply(fd, ref Rmsg.Attach(m.tag, f.dir.dir.qid));
+ * =>
+ fatal("unknown styx message", 1);
+ }
+ }
+}
+
+newfid(fid : int) : ref Fid
+{
+ (f, nil) := mapfid(fid);
+ if(f != nil)
+ return nil;
+ f = ref Fid;
+ f.fid = fid;
+ f.open = 0;
+ hv := hashval(fid);
+ f.next = fidtab[hv];
+ fidtab[hv] = f;
+ return f;
+}
+
+freefid(f: ref Fid)
+{
+ hv := hashval(f.fid);
+ lf : ref Fid;
+ for(ff := fidtab[hv]; ff != nil; ff = ff.next){
+ if(f == ff){
+ if(lf == nil)
+ fidtab[hv] = ff.next;
+ else
+ lf.next = ff.next;
+ return;
+ }
+ lf = ff;
+ }
+ fatal("cannot find fid", 1);
+}
+
+mapfid(fid : int) : (ref Fid, string)
+{
+ hv := hashval(fid);
+ for (f := fidtab[hv]; f != nil; f = f.next)
+ if (int f.fid == fid)
+ break;
+ if (f == nil)
+ return (nil, Ebadfid);
+ if (f.dir == nil)
+ return (nil, Enotfound);
+ return (f, nil);
+}
+
+hashval(n : int) : int
+{
+ return (n & ~Sys->DMDIR)%HTSZ;
+}
+
+readarch(f : string, args : list of string)
+{
+ ar := arch->openarchfs(f);
+ if(ar == nil || ar.b == nil)
+ fatal(sys->sprint("cannot open %s(%r)\n", f), 1);
+ bio = ar.b;
+ while ((a := arch->gethdr(ar)) != nil) {
+ if (args != nil) {
+ if (!selected(a.name, args)) {
+ if (skip)
+ return;
+ arch->drain(ar, int a.d.length);
+ continue;
+ }
+ mkdirs("/", a.name);
+ }
+ d := mkdir(a.name, a.d.mode, a.d.mtime, a.d.uid, a.d.gid, 0);
+ if((a.d.mode & Sys->DMDIR) == 0) {
+ d.dir.length = a.d.length;
+ d.offset = int bio.offset();
+ }
+ arch->drain(ar, int a.d.length);
+ }
+ if (ar.err != nil)
+ fatal(ar.err, 0);
+}
+
+selected(s: string, args: list of string): int
+{
+ for(; args != nil; args = tl args)
+ if(fileprefix(hd args, s))
+ return 1;
+ return 0;
+}
+
+fileprefix(prefix, s: string): int
+{
+ n := len prefix;
+ m := len s;
+ if(n > m || !str->prefix(prefix, s))
+ return 0;
+ if(m > n && s[n] != '/')
+ return 0;
+ return 1;
+}
+
+basename(f : string) : string
+{
+ for (i := len f; i > 0; )
+ if (f[--i] == '/')
+ return f[i+1:];
+ return f;
+}
+
+split(p : string) : (string, string)
+{
+ if (p == nil)
+ fatal("nil string in split", 1);
+ if (p[0] != '/')
+ fatal("p0 not / in split", 1);
+ while (p[0] == '/')
+ p = p[1:];
+ i := 0;
+ while (i < len p && p[i] != '/')
+ i++;
+ if (i == len p)
+ return (p, nil);
+ else
+ return (p[0:i], p[i:]);
+}
+
+mkdirs(basedir, name: string)
+{
+ (nil, names) := sys->tokenize(name, "/");
+ while(names != nil) {
+ # sys->print("mkdir %s\n", basedir);
+ mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1);
+ if(tl names == nil)
+ break;
+ basedir = basedir + "/" + hd names;
+ names = tl names;
+ }
+}
+
+readdir(d : ref Dir, offset : int, n : int) : array of byte
+{
+ if (d.dir.mode & Sys->DMDIR)
+ return readd(d, offset, n);
+ else
+ return readf(d, offset, n);
+}
+
+readd(d : ref Dir, o : int, n : int) : array of byte
+{
+ k := 0;
+ m := 0;
+ b := array[n] of byte;
+ for (s := d.child; s != nil; s = s.sibling) {
+ l := styx->packdirsize(s.dir);
+ if(k < o){
+ k += l;
+ continue;
+ }
+ if(m+l > n)
+ break;
+ b[m: ] = styx->packdir(s.dir);
+ m += l;
+ }
+ return b[0: m];
+}
+
+readf(d : ref Dir, offset : int, n : int) : array of byte
+{
+ leng := int d.dir.length;
+ if (offset+n > leng)
+ n = leng-offset;
+ if (n <= 0 || offset < 0)
+ return nil;
+ bio.seek(big (d.offset+offset), Bufio->SEEKSTART);
+ a := array[n] of byte;
+ p := 0;
+ m := 0;
+ for ( ; n != 0; n -= m) {
+ l := len buf;
+ if (n < l)
+ l = n;
+ m = bio.read(buf, l);
+ if (m <= 0 || m != l)
+ fatal("premature eof", 1);
+ a[p:] = buf[0:m];
+ p += m;
+ }
+ return a;
+}
+
+mkdir(f : string, mode : int, mtime : int, uid : string, gid : string, existsok : int) : ref Dir
+{
+ if (f == "/")
+ return nil;
+ d := newdir(basename(f), uid, gid, mode, mtime);
+ addfile(d, f, existsok);
+ return d;
+}
+
+addfile(d : ref Dir, path : string, existsok : int)
+{
+ elem : string;
+
+ opath := path;
+ p := prev := root;
+ basedir := "";
+# sys->print("addfile %s : %s\n", d.dir.name, path);
+ while (path != nil) {
+ (elem, path) = split(path);
+ basedir += "/" + elem;
+ op := p;
+ p = lookup(p, elem);
+ if (path == nil) {
+ if (p != nil) {
+ if (!existsok && (p.dir.mode&Sys->DMDIR) == 0)
+ sys->fprint(sys->fildes(2), "addfile: %s already there", opath);
+ # fatal(sys->sprint("addfile: %s already there", opath), 1);
+ return;
+ }
+ if (prev.child == nil)
+ prev.child = d;
+ else {
+ for (s := prev.child; s.sibling != nil; s = s.sibling)
+ ;
+ s.sibling = d;
+ }
+ d.parent = prev;
+ }
+ else {
+ if (p == nil) {
+ mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1);
+ p = lookup(op, elem);
+ if (p == nil)
+ fatal("bad file system", 1);
+ }
+ }
+ prev = p;
+ }
+}
+
+lookup(p : ref Dir, f : string) : ref Dir
+{
+ if ((p.dir.mode&Sys->DMDIR) == 0)
+ fatal("not a directory in lookup", 1);
+ if (f == ".")
+ return p;
+ if (f == "..")
+ return p.parent;
+ for (d := p.child; d != nil; d = d.sibling)
+ if (d.dir.name == f)
+ return d;
+ return nil;
+}
+
+newdir(name, uid, gid : string, mode, mtime : int) : ref Dir
+{
+ dir : Sys->Dir;
+
+ dir.name = name;
+ dir.uid = uid;
+ dir.gid = gid;
+ dir.qid.path = big (qid++);
+ if(mode&Sys->DMDIR)
+ dir.qid.qtype = Sys->QTDIR;
+ else
+ dir.qid.qtype = Sys->QTFILE;
+ dir.qid.vers = 0;
+ dir.mode = mode;
+ dir.atime = dir.mtime = mtime;
+ dir.length = big 0;
+ dir.dtype = 'X';
+ dir.dev = 0;
+
+ d := ref Dir;
+ d.dir = dir;
+ d.offset = 0;
+ return d;
+}
+
+# pr(d : ref Dir)
+# {
+# dir := d.dir;
+# sys->print("%s %s %s %x %x %x %d %d %d %d %d %d\n",
+# dir.name, dir.uid, dir.gid, dir.qid.path, dir.qid.vers, dir.mode, dir.atime, dir.mtime, dir.length, dir.dtype, dir.dev, d.offset);
+# }
+
+fatal(e : string, pr: int)
+{
+ if(pr){
+ sys->fprint(sys->fildes(2), "fatal: %s\n", e);
+ if (chanint != nil)
+ chanint <-= -1;
+ }
+ else{
+ # probably not an archive file
+ if (chanint != nil)
+ chanint <-= -2;
+ }
+ exit;
+}
diff --git a/appl/cmd/install/archfs.m b/appl/cmd/install/archfs.m
new file mode 100644
index 00000000..57c32542
--- /dev/null
+++ b/appl/cmd/install/archfs.m
@@ -0,0 +1,7 @@
+Archfs : module
+{
+ PATH : con "/dis/install/archfs.dis";
+
+ init : fn(ctxt : ref Draw->Context, args : list of string);
+ initc : fn(args : list of string, c : chan of int);
+};
diff --git a/appl/cmd/install/ckproto.b b/appl/cmd/install/ckproto.b
new file mode 100644
index 00000000..1e214f96
--- /dev/null
+++ b/appl/cmd/install/ckproto.b
@@ -0,0 +1,267 @@
+implement Ckproto;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "arg.m";
+ arg: Arg;
+include "readdir.m";
+ readdir : Readdir;
+include "proto.m";
+ proto : Proto;
+include "protocaller.m";
+ protocaller : Protocaller;
+
+WARN, ERROR, FATAL : import Protocaller;
+
+Ckproto: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+ protofile: fn(new : string, old : string, d : ref Sys->Dir);
+ protoerr: fn(lev : int, line : int, err : string);
+};
+
+Dir : adt {
+ name : string;
+ proto : string;
+ parent : cyclic ref Dir;
+ child : cyclic ref Dir;
+ sibling : cyclic ref Dir;
+};
+
+root := "/";
+droot : ref Dir;
+protof : string;
+stderr : ref Sys->FD;
+omitgen := 0; # forget generated files
+verbose : int;
+ckmode: int;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg = load Arg Arg->PATH;
+ readdir = load Readdir Readdir->PATH;
+ proto = load Proto Proto->PATH;
+ protocaller = load Protocaller "$self";
+
+ stderr = sys->fildes(2);
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil);
+ arg->init(args);
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'r' =>
+ root = arg->arg();
+ if (root == nil)
+ fatal("missing argument to -r");
+ 'o' =>
+ omitgen = 1;
+ 'v' =>
+ verbose = 1;
+ 'm' =>
+ ckmode = 1;
+ * =>
+ fatal("usage: install/ckproto [-o] [-v] [-m] [-r root] protofile ....");
+ }
+ }
+ droot = ref Dir("/", nil, nil, nil, nil);
+ droot.parent = droot;
+ args = arg->argv();
+ while (args != nil) {
+ protof = hd args;
+ proto->rdproto(hd args, root, protocaller);
+ args = tl args;
+ }
+ if (verbose)
+ prtree(droot, -1);
+ ckdir(root, droot);
+}
+
+protofile(new : string, old : string, nil : ref Sys->Dir)
+{
+ if (verbose) {
+ if (old == new)
+ sys->print("%s\n", new);
+ else
+ sys->print("%s %s\n", new, old);
+ }
+ addfile(droot, old);
+ if (new != old)
+ addfile(droot, new);
+}
+
+protoerr(lev : int, line : int, err : string)
+{
+ s := "line " + string line + " : " + err;
+ case lev {
+ WARN => warn(s);
+ ERROR => error(s);
+ FATAL => fatal(s);
+ }
+}
+
+ckdir(d : string, dird : ref Dir)
+{
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ dire := lookup(dird, dir[i].name);
+ if(omitgen && generated(dir[i].name))
+ continue;
+ if (dire == nil){
+ sys->print("%s missing\n", mkpath(d, dir[i].name));
+ continue;
+ }
+ if(ckmode){
+ if(dir[i].mode & Sys->DMDIR){
+ if((dir[i].mode & 8r775) != 8r775)
+ sys->print("directory %s not 775 at least\n", mkpath(d, dir[i].name));
+ }
+ else{
+ if((dir[i].mode & 8r664) != 8r664)
+ sys->print("file %s not 664 at least\n", mkpath(d, dir[i].name));
+ }
+ }
+ if (dir[i].mode & Sys->DMDIR)
+ ckdir(mkpath(d, dir[i].name), dire);
+ }
+}
+
+addfile(root : ref Dir, path : string)
+{
+ elem : string;
+
+ # ckexists(path);
+
+ curd := root;
+ opath := path;
+ while (path != nil) {
+ (elem, path) = split(path);
+ d := lookup(curd, elem);
+ if (d == nil) {
+ d = ref Dir(elem, protof, curd, nil, nil);
+ if (curd.child == nil)
+ curd.child = d;
+ else {
+ prev, this : ref Dir;
+
+ for (this = curd.child; this != nil; this = this.sibling) {
+ if (elem < this.name) {
+ d.sibling = this;
+ if (prev == nil)
+ curd.child = d;
+ else
+ prev.sibling = d;
+ break;
+ }
+ prev = this;
+ }
+ if (this == nil)
+ prev.sibling = d;
+ }
+ }
+ else if (path == nil && d.proto == protof)
+ sys->print("%s repeated in proto %s\n", opath, protof);
+ curd = d;
+ }
+}
+
+lookup(p : ref Dir, f : string) : ref Dir
+{
+ if (f == ".")
+ return p;
+ if (f == "..")
+ return p.parent;
+ for (d := p.child; d != nil; d = d.sibling) {
+ if (d.name == f)
+ return d;
+ if (d.name > f)
+ return nil;
+ }
+ return nil;
+}
+
+prtree(root : ref Dir, indent : int)
+{
+ if (indent >= 0)
+ sys->print("%s%s\n", string array[indent] of { * => byte '\t' }, root.name);
+ for (s := root.child; s != nil; s = s.sibling)
+ prtree(s, indent+1);
+}
+
+mkpath(prefix, elem: string): string
+{
+ slash1 := slash2 := 0;
+ if (len prefix > 0)
+ slash1 = prefix[len prefix - 1] == '/';
+ if (len elem > 0)
+ slash2 = elem[0] == '/';
+ if (slash1 && slash2)
+ return prefix+elem[1:];
+ if (!slash1 && !slash2)
+ return prefix+"/"+elem;
+ return prefix+elem;
+}
+
+split(p : string) : (string, string)
+{
+ if (p == nil)
+ fatal("nil string in split");
+ if (p[0] != '/')
+ fatal("p0 notg / in split");
+ while (p[0] == '/')
+ p = p[1:];
+ i := 0;
+ while (i < len p && p[i] != '/')
+ i++;
+ if (i == len p)
+ return (p, nil);
+ else
+ return (p[0:i], p[i:]);
+}
+
+
+gens := array[] of {
+ "dis", "sbl", "out", "0", "1", "2", "5", "8", "k", "q", "v", "t"
+};
+
+generated(f : string) : int
+{
+ for (i := len f -1; i >= 0; i--)
+ if (f[i] == '.')
+ break;
+ if (i < 0)
+ return 0;
+ suff := f[i+1:];
+ for (i = 0; i < len gens; i++)
+ if (suff == gens[i])
+ return 1;
+ return 0;
+}
+
+warn(s: string)
+{
+ sys->print("%s: %s\n", protof, s);
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "%s: %s\n", protof, s);
+ exit;;
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "fatal: %s\n", s);
+ exit;
+}
+
+ckexists(path: string)
+{
+ s := mkpath(root, path);
+ (ok, nil) := sys->stat(s);
+ if(ok < 0)
+ sys->print("%s does not exist\n", s);
+}
diff --git a/appl/cmd/install/create.b b/appl/cmd/install/create.b
new file mode 100644
index 00000000..848fdc6b
--- /dev/null
+++ b/appl/cmd/install/create.b
@@ -0,0 +1,445 @@
+implement Create;
+
+include "sys.m";
+ sys: Sys;
+ Dir, sprint, fprint: import sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "arg.m";
+ arg: Arg;
+include "daytime.m";
+include "keyring.m";
+ keyring : Keyring;
+include "sh.m";
+include "wrap.m";
+ wrap : Wrap;
+include "arch.m";
+ arch : Arch;
+include "proto.m";
+ proto : Proto;
+include "protocaller.m";
+ protocaller : Protocaller;
+
+WARN, ERROR, FATAL : import Protocaller;
+
+Create: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+ protofile: fn(new : string, old : string, d : ref Sys->Dir);
+ protoerr: fn(lev : int, line : int, err : string);
+};
+
+bout: ref Iobuf; # stdout when writing archive
+protof: string;
+notesf: string;
+oldroot: string;
+buf: array of byte;
+buflen := 1024-8;
+verb: int;
+xflag: int;
+stderr: ref Sys->FD;
+uid, gid : string;
+desc : string;
+pass : int;
+update : int;
+md5s : ref Keyring->DigestState;
+w : ref Wrap->Wrapped;
+root := "/";
+prefix, notprefix: list of string;
+onlist: list of (string, string); # NEW
+remfile: string;
+
+n2o(n: string): string
+{
+ for(onl := onlist; onl != nil; onl = tl onl)
+ if((hd onl).t1 == n)
+ return (hd onl).t0;
+ return n;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ str = load String String->PATH;
+ arg = load Arg Arg->PATH;
+ wrap = load Wrap Wrap->PATH;
+ wrap->init(bufio);
+ arch = load Arch Arch->PATH;
+ arch->init(bufio);
+ daytime := load Daytime Daytime->PATH;
+ now := daytime->now();
+ # {
+ # for(i := 0; i < 21; i++){
+ # n := now+(i-9)*100000000;
+ # sys->print("%d -> %s\n", n, wrap->now2string(n));
+ # if(wrap->string2now(wrap->now2string(n)) != n)
+ # sys->print("%d wrong\n", n);
+ # }
+ # }
+ daytime = nil;
+ proto = load Proto Proto->PATH;
+ protocaller = load Protocaller "$self";
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil);
+ stderr = sys->fildes(2);
+ if(arg == nil)
+ error(sys->sprint("can't load %s: %r", Arg->PATH));
+ name := "";
+ desc = "inferno";
+ tostdout := 0;
+ not := 0;
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'n' =>
+ not = 1;
+ 'o' =>
+ tostdout = 1;
+ 'p' =>
+ protof = reqarg("proto file (-p)");
+ 'r' =>
+ root = reqarg("root directory (-r)");
+ 's' =>
+ oldroot = reqarg("source directory (-d)");
+ 'u' =>
+ update = 1;
+ 'v' =>
+ verb = 1;
+ 'x' =>
+ xflag = 1;
+ 'N' =>
+ uid = reqarg("user name (-U)");
+ 'G' =>
+ gid = reqarg("group name (-G)");
+ 'd' or 'D' =>
+ desc = reqarg("product description (-D)");
+ 't' =>
+ rt := reqarg("package time (-t)");
+ now = int rt;
+ 'i' =>
+ notesf = reqarg("file (-i)");
+ 'R' =>
+ remfile = reqarg("remove file (-R)");
+ 'P' =>
+ arch->addperms(0);
+ * =>
+ usage();
+ }
+
+ args = arg->argv();
+ if(args == nil)
+ usage();
+ if (tostdout || xflag) {
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if(bout == nil)
+ error(sys->sprint("can't open standard output for archive: %r"));
+ }
+ else {
+ # ar := sys->sprint("%ud", now);
+ ar := wrap->now2string(now, 0);
+ bout = bufio->create(ar, Sys->OWRITE, 8r664);
+ if(bout == nil)
+ error(sys->sprint("can't create %s for archive: %r", ar));
+ sys->print("archiving package %s to %s\n", hd args, ar);
+ }
+ buf = array [buflen] of byte;
+ name = hd args;
+ if(update){
+ if(not)
+ notprefix = tl args;
+ else
+ prefix = tl args;
+ }
+ else if (tl args != nil)
+ fatal("only one name allowed");
+ if (!xflag)
+ digest := wrapinit(name, now);
+ fprint(stderr, "processing %s\n", protof);
+ proto->rdproto(protof, oldroot, protocaller);
+ if (!xflag)
+ wrapend(digest);
+ if (!xflag)
+ fprint(stderr, "file system made\n");
+ arch->putend(bout);
+ exits();
+}
+
+protofile(new : string, old : string, d : ref Sys->Dir)
+{
+ if(xflag && bout != nil){
+ bout.puts(sys->sprint("%s\t%d\t%bd\n", new, d.mtime, d.length));
+ return;
+ }
+ d.uid = uid;
+ d.gid = gid;
+ if (!(d.mode & Sys->DMDIR)) {
+ # if(verb)
+ # fprint(stderr, "%s\n", new);
+ f := sys->open(old, Sys->OREAD);
+ if(f == nil){
+ warn(sys->sprint("can't open %s: %r", old));
+ return;
+ }
+ }
+ mkarch(new, old, d);
+}
+
+protoerr(lev : int, line : int, err : string)
+{
+ s := "line " + string line + " : " + err;
+ case lev {
+ WARN => warn(s);
+ ERROR => error(s);
+ FATAL => fatal(s);
+ }
+}
+
+quit()
+{
+ if(bout != nil)
+ bout.flush();
+ exits();
+}
+
+reqarg(what: string): string
+{
+ if((o := arg->arg()) == nil){
+ sys->fprint(stderr, "missing %s\n", what);
+ exits();
+ }
+ return o;
+}
+
+puthdr(f : string, d: ref Dir)
+{
+ if (d.mode & Sys->DMDIR)
+ d.length = big 0;
+ arch->puthdr(bout, f, d);
+}
+
+error(s: string)
+{
+ fprint(stderr, "%s: %s\n", protof, s);
+ quit();
+}
+
+fatal(s: string)
+{
+ fprint(stderr, "fatal: %s\n", s);
+ exits();
+}
+
+warn(s: string)
+{
+ fprint(stderr, "%s: %s\n", protof, s);
+}
+
+usage()
+{
+ fprint(stderr, "usage: install/create [-ovx] [-N uid] [-G gid] [-r root] [-d desc] [-s src-fs] [-p proto] name\n");
+ fprint(stderr, "or install/create -u [-ovx] [-N uid] [-G gid] [-r root] [-d desc] [-s src-fs] [-p proto] old-package [prefix ...]\n");
+ exits();
+}
+
+wrapinit(name : string, t : int) : array of byte
+{
+ rmfile : string;
+ rmfd: ref Sys->FD;
+
+ if (uid == nil)
+ uid = "inferno";
+ if (gid == nil)
+ gid = "inferno";
+ if (update) {
+ w = wrap->openwraphdr(name, root, nil, 0);
+ if (w == nil)
+ fatal("no such package found");
+ # ignore any updates - NEW commented out
+ # while (w.nu > 0 && w.u[w.nu-1].typ == wrap->UPD)
+ # w.nu--;
+
+ # w.nu = 1; NEW commented out
+ if (protof == nil)
+ protof = w.u[0].dir + "/proto";
+ name = w.name;
+ }
+ else {
+ if (protof == nil)
+ fatal("proto file missing");
+ }
+ (md5file, md5fd) := opentemp("wrap.md5", t);
+ if (md5fd == nil)
+ fatal(sys->sprint("cannot create %s", md5file));
+ keyring = load Keyring Keyring->PATH;
+ md5s = keyring->md5(nil, 0, nil, nil);
+ md5b := bufio->fopen(md5fd, Bufio->OWRITE);
+ if (md5b == nil)
+ fatal(sys->sprint("cannot open %s", md5file));
+ fprint(stderr, "wrap pass %s\n", protof);
+ obout := bout;
+ bout = md5b;
+ pass = 0;
+ proto->rdproto(protof, oldroot, protocaller);
+ bout.flush();
+ bout = md5b = nil;
+ digest := array[keyring->MD5dlen] of { * => byte 0 };
+ keyring->md5(nil, 0, digest, md5s);
+ md5s = nil;
+ (md5sort, md5sfd) := opentemp("wrap.md5s", t);
+ if (md5sfd == nil)
+ fatal(sys->sprint("cannot create %s", md5sort));
+ endc := chan of int;
+ md5fd = nil; # close md5file
+ spawn fsort(md5sfd, md5file, endc);
+ md5sfd = nil;
+ res := <- endc;
+ if (res < 0)
+ fatal("sort failed");
+ if (update) {
+ (rmfile, rmfd) = opentemp("wrap.rm", t);
+ if (rmfd == nil)
+ fatal(sys->sprint("cannot create %s", rmfile));
+ rmed: list of string;
+ for(i := w.nu-1; i >= 0; i--){ # NEW does loop
+ w.u[i].bmd5.seek(big 0, Bufio->SEEKSTART);
+ while ((p := w.u[i].bmd5.gets('\n')) != nil) {
+ if(prefix != nil && !wrap->match(p, prefix))
+ continue;
+ if(notprefix != nil && !wrap->notmatch(p, notprefix))
+ continue;
+ (q, nil) := str->splitl(p, " ");
+ q = pathcat(root, q);
+ (ok, nil) := sys->stat(q);
+ if(ok < 0)
+ (ok, nil) = sys->stat(n2o(q));
+ if (len q >= 7 && q[len q - 7:] == "emu.new") # quick hack for now
+ continue;
+ if (ok < 0){
+ for(r := rmed; r != nil; r = tl r) # NEW to avoid duplication
+ if(hd r == q)
+ break;
+ if(r == nil){
+ # sys->fprint(rmfd, "%s\n", q);
+ rmed = q :: rmed;
+ }
+ }
+ }
+ }
+ for(r := rmed; r != nil; r = tl r)
+ sys->fprint(rmfd, "%s\n", hd r);
+ if(remfile != nil){
+ rfd := sys->open(remfile, Sys->OREAD);
+ rbuf := array[128] of byte;
+ for(;;){
+ n := sys->read(rfd, rbuf, 128);
+ if(n <= 0)
+ break;
+ sys->write(rmfd, rbuf, n);
+ }
+ }
+ rmfd = nil;
+ rmed = nil;
+ }
+ bout = obout;
+ if (update)
+ wrap->putwrap(bout, name, t, desc, w.tfull, prefix == nil && notprefix == nil, uid, gid);
+ else
+ wrap->putwrap(bout, name, t, desc, 0, 1, uid, gid);
+ wrap->putwrapfile(bout, name, t, "proto", protof, uid, gid);
+ wrap->putwrapfile(bout, name, t, "md5sum", md5sort, uid, gid);
+ if (update)
+ wrap->putwrapfile(bout, name, t, "remove", rmfile, uid, gid);
+ if(notesf != nil)
+ wrap->putwrapfile(bout, name, t, "notes", notesf, uid, gid);
+ md5s = keyring->md5(nil, 0, nil, nil);
+ pass = 1;
+ return digest;
+}
+
+wrapend(digest : array of byte)
+{
+ digest0 := array[keyring->MD5dlen] of { * => byte 0 };
+ keyring->md5(nil, 0, digest0, md5s);
+ md5s = nil;
+ if (wrap->memcmp(digest, digest0, keyring->MD5dlen) != 0)
+ warn(sys->sprint("files changed underfoot %s %s", wrap->md5conv(digest), wrap->md5conv(digest0)));
+}
+
+mkarch(new : string, old : string, d : ref Dir)
+{
+ if(pass == 0 && old != new)
+ onlist = (old, new) :: onlist;
+ if(prefix != nil && !wrap->match(new, prefix))
+ return;
+ if(notprefix != nil && !wrap->notmatch(new, notprefix))
+ return;
+ digest := array[keyring->MD5dlen] of { * => byte 0 };
+ wrap->md5file(old, digest);
+ (ok, nil) := wrap->getfileinfo(w, new, digest, nil, nil);
+ if (ok >= 0)
+ return;
+ n := array of byte new;
+ keyring->md5(n, len n, nil, md5s);
+ if (pass == 0) {
+ bout.puts(sys->sprint("%s %s\n", new, wrap->md5conv(digest)));
+ return;
+ }
+ if(verb)
+ fprint(stderr, "%s\n", new);
+ puthdr(new, d);
+ if(!(d.mode & Sys->DMDIR)) {
+ err := arch->putfile(bout, old, int d.length);
+ if (err != nil)
+ warn(err);
+ }
+}
+
+fsort(fd : ref Sys->FD, file : string, c : chan of int)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ sys->dup(fd.fd, 1);
+ cmd := "/dis/sort.dis";
+ m := load Command cmd;
+ if(m == nil) {
+ c <-= -1;
+ return;
+ }
+ m->init(nil, cmd :: file :: nil);
+ c <-= 0;
+}
+
+tmpfiles: list of string;
+
+opentemp(prefix: string, t: int): (string, ref Sys->FD)
+{
+ name := sys->sprint("/tmp/%s.%ud.%d", prefix, t, sys->pctl(0, nil));
+ fd := sys->create(name, Sys->ORDWR, 8r666);
+ # fd := sys->create(name, Sys->ORDWR | Sys->ORCLOSE, 8r666); not on Nt
+ tmpfiles = name :: tmpfiles;
+ return (name, fd);
+}
+
+exits()
+{
+ wrap->end();
+ for( ; tmpfiles != nil; tmpfiles = tl tmpfiles)
+ sys->remove(hd tmpfiles);
+ exit;
+}
+
+pathcat(s : string, t : string) : string
+{
+ if (s == nil) return t;
+ if (t == nil) return s;
+ slashs := s[len s - 1] == '/';
+ slasht := t[0] == '/';
+ if (slashs && slasht)
+ return s + t[1:];
+ if (!slashs && !slasht)
+ return s + "/" + t;
+ return s + t;
+}
diff --git a/appl/cmd/install/eproto.b b/appl/cmd/install/eproto.b
new file mode 100644
index 00000000..b87a4390
--- /dev/null
+++ b/appl/cmd/install/eproto.b
@@ -0,0 +1,357 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "readdir.m";
+ readdir: Readdir;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, report, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+File: adt {
+ name: string;
+ mode: int;
+ owner: string;
+ group: string;
+ old: string;
+ flags: int;
+ sub: cyclic array of ref File;
+};
+
+Proto: adt {
+ indent: int;
+ lastline: string;
+ iob: ref Iobuf;
+};
+
+Star, Plus: con 1<<iota;
+
+types(): string
+{
+ return "ts-rs";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: eproto: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ badmod(Readdir->PATH);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ badmod(Bufio->PATH);
+ str = load String String->PATH;
+ if(str == nil)
+ badmod(String->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ protofile := (hd args).s().i;
+ rootpath: string;
+ if(opts != nil)
+ rootpath = (hd (hd opts).args).s().i;
+ if(rootpath == nil)
+ rootpath = "/";
+
+ proto := ref Proto(0, nil, nil);
+ if((proto.iob = bufio->open(protofile, Sys->OREAD)) == nil){
+ sys->fprint(sys->fildes(2), "fs: eproto: cannot open %q: %r\n", protofile);
+ return nil;
+ }
+ root := ref File(rootpath, ~0, nil, nil, nil, 0, nil);
+ (root.flags, root.sub) = readproto(proto, -1);
+ c := Entrychan(chan of int, chan of Entry);
+ spawn protowalk(c, root, report.start("proto"));
+ return ref Value.T(c);
+}
+
+protowalk(c: Entrychan, root: ref File, errorc: chan of string)
+{
+ if(<-c.sync == 0){
+ quit(errorc);
+ exit;
+ }
+ protowalk1(c, root.flags, root.name, file2dir(root, nil), root.sub, -1, errorc);
+ c.c <-= (nil, nil, 0);
+ quit(errorc);
+}
+
+protowalk1(c: Entrychan, flags: int, path: string, d: ref Sys->Dir,
+ sub: array of ref File, depth: int, errorc: chan of string): int
+{
+ if(depth >= 0)
+ c.c <-= (d, path, depth);
+ depth++;
+ (a, n) := readdir->init(path, Readdir->NAME|Readdir->COMPACT);
+ j := 0;
+ prevsub: string;
+ for(i := 0; i < n; i++){
+ for(; j < len sub; j++){
+ s := sub[j].name;
+ if(s == prevsub){
+ report(errorc, sys->sprint("duplicate entry %s", pathconcat(path, s)));
+ continue; # eliminate duplicates in proto
+ }
+ if(s >= a[i].name || sub[j].old != nil)
+ break;
+ report(errorc, sys->sprint("%s not found", pathconcat(path, s)));
+ }
+ foundsub := j < len sub && (sub[j].name == a[i].name || sub[j].old != nil);
+ if(foundsub || flags&Plus ||
+ (flags&Star && (a[i].mode & Sys->DMDIR)==0)){
+ f: ref File;
+ if(foundsub){
+ f = sub[j++];
+ prevsub = f.name;
+ }
+ p: string;
+ d: ref Sys->Dir;
+ if(foundsub && f.old != nil){
+ p = f.old;
+ (ok, xd) := sys->stat(p);
+ if(ok == -1){
+ report(errorc, sys->sprint("cannot stat %q: %r", p));
+ continue;
+ }
+ d = ref xd;
+ }else{
+ p = pathconcat(path, a[i].name);
+ d = a[i];
+ }
+
+ d = file2dir(f, d);
+ r: int;
+ if((d.mode & Sys->DMDIR) == 0)
+ r = walkfile(c, p, d, depth, errorc);
+ else if(flags & Plus)
+ r = protowalk1(c, Plus, p, d, nil, depth, errorc);
+ else
+ r = protowalk1(c, f.flags, p, d, f.sub, depth, errorc);
+ if(r == Skip)
+ return Next;
+ }
+ }
+ return Next;
+}
+
+pathconcat(p, name: string): string
+{
+ if(p != nil && p[len p - 1] != '/')
+ p[len p] = '/';
+ return p+name;
+}
+
+# from(ish) walk.b
+walkfile(c: Entrychan, path: string, d: ref Sys->Dir, depth: int, errorc: chan of string): int
+{
+ fd := sys->open(path, Sys->OREAD);
+ if(fd == nil)
+ report(errorc, sys->sprint("cannot open %q: %r", path));
+ else
+ c.c <-= (d, path, depth);
+ return Next;
+}
+
+readproto(proto: ref Proto, indent: int): (int, array of ref File)
+{
+ a := array[10] of ref File;
+ n := 0;
+ flags := 0;
+ while((f := readline(proto, indent)) != nil){
+ if(f.name == "*")
+ flags |= Star;
+ else if(f.name == "+")
+ flags |= Plus;
+ else{
+ (f.flags, f.sub) = readproto(proto, proto.indent);
+ if(n == len a)
+ a = (array[n * 2] of ref File)[0:] = a;
+ a[n++] = f;
+ }
+ }
+ if(n < len a)
+ a = (array[n] of ref File)[0:] = a[0:n];
+ mergesort(a, array[n] of ref File);
+ return (flags, a);
+}
+
+readline(proto: ref Proto, indent: int): ref File
+{
+ s: string;
+ if(proto.lastline != nil){
+ s = proto.lastline;
+ proto.lastline = nil;
+ }else if(proto.indent == -1)
+ return nil;
+ else if((s = proto.iob.gets('\n')) == nil){
+ proto.indent = -1;
+ return nil;
+ }
+ spc := 0;
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ if(c == ' ')
+ spc++;
+ else if(c == '\t')
+ spc += 8;
+ else
+ break;
+ }
+ if(i == len s || s[i] == '#' || s[i] == '\n')
+ return readline(proto, indent); # XXX sort out tail recursion!
+ if(spc <= indent){
+ proto.lastline = s;
+ return nil;
+ }
+ proto.indent = spc;
+ (nil, toks) := sys->tokenize(s, " \t\n");
+ f := ref File(nil, ~0, nil, nil, nil, 0, nil);
+ (f.name, toks) = (getname(hd toks, 0), tl toks);
+ if(toks == nil)
+ return f;
+ (f.mode, toks) = (getmode(hd toks), tl toks);
+ if(toks == nil)
+ return f;
+ (f.owner, toks) = (getname(hd toks, 1), tl toks);
+ if(toks == nil)
+ return f;
+ (f.group, toks) = (getname(hd toks, 1), tl toks);
+ if(toks == nil)
+ return f;
+ (f.old, toks) = (hd toks, tl toks);
+ return f;
+}
+
+mergesort(a, b: array of ref File)
+{
+ r := len a;
+ if (r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m]);
+ mergesort(a[m:], b[m:]);
+ b[0:] = a;
+ for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if(b[i].name > b[j].name)
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if (i < m)
+ a[k:] = b[i:m];
+ else if (j < r)
+ a[k:] = b[j:r];
+ }
+}
+
+getname(s: string, allowminus: int): string
+{
+ if(s == nil)
+ return nil;
+ if(allowminus && s == "-")
+ return nil;
+ if(s[0] == '$'){
+ s = getenv(s[1:]);
+ if(s == nil)
+ ; # TO DO: w.warn(sys->sprint("can't read environment variable %s", s));
+ return s;
+ }
+ return s;
+}
+
+getenv(s: string): string
+{
+ if(s == "user")
+ return readfile("/dev/user"); # more accurate?
+ return readfile("/env/"+s);
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if(fd != nil){
+ a := array[256] of byte;
+ n := sys->read(fd, a, len a);
+ if(n > 0)
+ return string a[0:n];
+ }
+ return nil;
+}
+
+getmode(s: string): int
+{
+ s = getname(s, 1);
+ if(s == nil)
+ return ~0;
+ m := 0;
+ i := 0;
+ if(s[i] == 'd'){
+ m |= Sys->DMDIR;
+ i++;
+ }
+ if(i < len s && s[i] == 'a'){
+ m |= Sys->DMAPPEND;
+ i++;
+ }
+ if(i < len s && s[i] == 'l'){
+ m |= Sys->DMEXCL;
+ i++;
+ }
+ (xmode, t) := str->toint(s, 8);
+ if(t != nil){
+ # report(aux.errorc, "bad mode specification %q", s);
+ return ~0;
+ }
+ return xmode | m;
+}
+
+file2dir(f: ref File, old: ref Sys->Dir): ref Sys->Dir
+{
+ d := ref Sys->nulldir;
+ if(old != nil){
+ if(old.dtype != 'M'){
+ d.uid = "sys";
+ d.gid = "sys";
+ xmode := (old.mode >> 6) & 7;
+ d.mode = old.mode | xmode | (xmode << 3);
+ }else{
+ d.uid = old.uid;
+ d.gid = old.gid;
+ d.mode = old.mode;
+ }
+ d.length = old.length;
+ d.mtime = old.mtime;
+ d.atime = old.atime;
+ d.muid = old.muid;
+ d.name = old.name;
+ }
+ if(f != nil){
+ d.name = f.name;
+ if(f.owner != nil)
+ d.uid = f.owner;
+ if(f.group != nil)
+ d.gid = f.group;
+ if(f.mode != ~0)
+ d.mode = f.mode;
+ }
+ return d;
+}
diff --git a/appl/cmd/install/info.b b/appl/cmd/install/info.b
new file mode 100644
index 00000000..1c95128f
--- /dev/null
+++ b/appl/cmd/install/info.b
@@ -0,0 +1,73 @@
+implement Info;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "daytime.m";
+ daytime: Daytime;
+include "arg.m";
+ arg: Arg;
+include "wrap.m";
+ wrap : Wrap;
+
+Info: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+root : string;
+
+TYPLEN : con 4;
+typestr := array[TYPLEN] of { "???", "package", "update", "full update" };
+
+fatal(err : string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", err);
+ raise "fail:error";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ arg = load Arg Arg->PATH;
+ wrap = load Wrap Wrap->PATH;
+ wrap->init(bufio);
+
+ arg->init(args);
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'r' =>
+ root = arg->arg();
+ if (root == nil)
+ fatal("missing root name");
+ * =>
+ fatal(sys->sprint("bad argument -%c", c));
+ }
+ }
+ args = arg->argv();
+ if (args == nil || tl args != nil)
+ fatal("usage: install/info [-r root] package");
+ w := wrap->openwraphdr(hd args, root, nil, 0);
+ if (w == nil)
+ fatal("no such package found");
+ tm := daytime->text(daytime->local(w.tfull));
+ sys->print("%s (complete as of %s)\n", w.name, tm[0:28]);
+ for (i := w.nu; --i >= 0;) {
+ typ := w.u[i].typ;
+ if (typ < 0 || typ >= TYPLEN)
+ sys->print("%s", typestr[0]);
+ else
+ sys->print("%s", typestr[typ]);
+ sys->print(" %s", wrap->now2string(w.u[i].time, 0));
+ if (typ & wrap->UPD)
+ sys->print(" updating %s", wrap->now2string(w.u[i].utime, 0));
+ if (w.u[i].desc != nil)
+ sys->print(": %s", w.u[i].desc);
+ sys->print("\n");
+ }
+ wrap->end();
+}
diff --git a/appl/cmd/install/inst.b b/appl/cmd/install/inst.b
new file mode 100644
index 00000000..dfec4785
--- /dev/null
+++ b/appl/cmd/install/inst.b
@@ -0,0 +1,500 @@
+implement Inst;
+
+include "sys.m";
+ sys: Sys;
+ Dir, sprint, fprint: import sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "arg.m";
+ arg: Arg;
+include "keyring.m";
+ keyring : Keyring;
+include "arch.m";
+ arch : Arch;
+include "wrap.m";
+ wrap : Wrap;
+
+Inst: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+LEN: con Sys->ATOMICIO;
+
+tflag := 0;
+uflag := 0;
+hflag := 0;
+vflag := 0;
+fflag := 1;
+stderr: ref Sys->FD;
+bout: ref Iobuf;
+argv0 := "inst";
+oldw, w : ref Wrap->Wrapped;
+root := "/";
+force := 0;
+stoponerr := 1;
+
+# membogus(argv: list of string)
+# {
+#
+# }
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ error(sys->sprint("cannot load %s: %r\n", Bufio->PATH));
+
+ str = load String String->PATH;
+ if(str == nil)
+ error(sys->sprint("cannot load %s: %r\n", String->PATH));
+
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ error(sys->sprint("cannot load %s: %r\n", Arg->PATH));
+ keyring = load Keyring Keyring->PATH;
+ if(keyring == nil)
+ error(sys->sprint("cannot load %s: %r\n", Keyring->PATH));
+ arch = load Arch Arch->PATH;
+ if(arch == nil)
+ error(sys->sprint("cannot load %s: %r\n", Arch->PATH));
+ arch->init(bufio);
+ wrap = load Wrap Wrap->PATH;
+ if(wrap == nil)
+ error(sys->sprint("cannot load %s: %r\n", Wrap->PATH));
+ wrap->init(bufio);
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'f' =>
+ fflag = 0;
+ 'h' =>
+ hflag = 1;
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if(bout == nil)
+ error(sys->sprint("can't access standard output: %r"));
+ 't' =>
+ tflag = 1;
+ 'u' =>
+ uflag = 1;
+ 'v' =>
+ vflag = 1;
+ 'r' =>
+ root = arg->arg();
+ if (root == nil)
+ fatal("root missing");
+ 'F' =>
+ force = 1;
+ 'c' =>
+ stoponerr = 0;
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ if (args == nil)
+ usage();
+ ar := arch->openarch(hd args);
+ if(ar == nil || ar.b == nil)
+ error(sys->sprint("can't access %s: %r", hd args));
+ w = wrap->openwraphdr(hd args, root, nil, 0);
+ if (w == nil)
+ fatal("no such package found");
+ if(w.nu != 1)
+ fatal("strange package: more than one piece");
+ if (force == 0)
+ oldw = wrap->openwrap(w.name, root, 0);
+ if (force == 0 && w.u[0].utime && (oldw == nil || oldw.tfull < w.u[0].utime)){
+ tfull: int;
+ if(oldw == nil)
+ tfull = -1;
+ else
+ tfull = oldw.tfull;
+ fatal(sys->sprint("need %s version of %s already installed (pkg %d)", wrap->now2string(w.u[0].utime, 0), w.name, tfull));
+ }
+ args = tl args;
+ digest := array[Keyring->MD5dlen] of byte;
+ digest0 := array[Keyring->MD5dlen] of byte;
+ digest1 := array[Keyring->MD5dlen] of byte;
+
+ while ((a := arch->gethdr(ar)) != nil) {
+ why := "";
+ docopy := 0;
+ if(force)
+ docopy = 1;
+ else if(a.d.mode & Sys->DMDIR)
+ docopy = 1;
+ else if(wrap->md5file(root+a.name, digest) < 0)
+ docopy = 1;
+ else{
+ wrap->md5filea(root+a.name, digest1);
+ (ok, t) := wrap->getfileinfo(oldw, a.name, digest, nil, digest1);
+ if (ok >= 0) {
+ if(t > w.u[0].time){
+ docopy = 0;
+ why = "version from newer package exists";
+ }
+ else
+ docopy = 1;
+ }
+ else {
+ (ok, t) = wrap->getfileinfo(oldw, a.name, nil, nil, nil);
+ if(ok >= 0){
+ docopy = 0;
+ why = "locally modified";
+ }
+ else{
+ docopy = 0;
+ why = "locally created";
+ }
+ }
+ }
+ if(!docopy){
+ wrap->md5sum(ar.b, digest0, int a.d.length);
+ if(wrap->memcmp(digest, digest0, Keyring->MD5dlen))
+ skipfile(a.name, why);
+ continue;
+ }
+ if(args != nil){
+ if(!selected(a.name, args)){
+ arch->drain(ar, int a.d.length);
+ continue;
+ }
+ if (!hflag)
+ mkdirs(root, a.name);
+ }
+ name := pathcat(root, a.name);
+ if(hflag){
+ bout.puts(sys->sprint("%s %uo %s %s %ud %d\n",
+ name, a.d.mode, a.d.uid, a.d.gid, a.d.mtime, int a.d.length));
+ arch->drain(ar, int a.d.length);
+ continue;
+ }
+ if(a.d.mode & Sys->DMDIR)
+ mkdir(name, a.d);
+ else
+ extract(ar, name, a.d);
+ }
+ arch->closearch(ar);
+ if(ar.err == nil){
+ # fprint(stderr, "done\n");
+ quit(nil);
+ }
+ else {
+ fprint(stderr, "%s\n", ar.err);
+ quit("eof");
+ }
+}
+
+skipfile(f : string, why : string)
+{
+ sys->fprint(stderr, "skipping %s: %s\n", f, why);
+}
+
+skiprmfile(f: string, why: string)
+{
+ sys->fprint(stderr, "not removing %s: %s\n", f, why);
+}
+
+doremove(s : string)
+{
+ p := pathcat(root, s);
+ digest := array[Keyring->MD5dlen] of { * => byte 0 };
+ digest1 := array[Keyring->MD5dlen] of { * => byte 0 };
+ if(wrap->md5file(p, digest) < 0)
+ ;
+ else{
+ wrap->md5filea(p, digest1);
+ (ok, nil) := wrap->getfileinfo(oldw, s, digest, nil, digest1);
+ if(force == 0 && ok < 0)
+ skiprmfile(p, "locally modified");
+ else{
+ if (vflag)
+ sys->print("rm %s\n", p);
+ remove(p);
+ }
+ }
+}
+
+quit(s: string)
+{
+ if (s == nil) {
+ p := w.u[0].dir + "/remove";
+ if ((b := bufio->open(p, Bufio->OREAD)) != nil) {
+ while ((t := b.gets('\n')) != nil) {
+ lt := len t;
+ if (t[lt-1] == '\n')
+ t = t[0:lt-1];
+ doremove(t);
+ }
+ }
+ }
+ if(bout != nil)
+ bout.flush();
+ if(wrap != nil)
+ wrap->end();
+ if(s != nil)
+ raise "fail: "+s;
+ else
+ fprint(stderr, "done\n");
+ exit;
+}
+
+fileprefix(prefix, s: string): int
+{
+ n := len prefix;
+ m := len s;
+ if(n > m || !str->prefix(prefix, s))
+ return 0;
+ if(m > n && s[n] != '/')
+ return 0;
+ return 1;
+}
+
+selected(s: string, args: list of string): int
+{
+ for(; args != nil; args = tl args)
+ if(fileprefix(hd args, s))
+ return 1;
+ return 0;
+}
+
+mkdirs(basedir, name: string)
+{
+ (nil, names) := sys->tokenize(name, "/");
+ while(names != nil) {
+ create(basedir, Sys->OREAD, 8r775|Sys->DMDIR);
+ if(tl names == nil)
+ break;
+ basedir = basedir + "/" + hd names;
+ names = tl names;
+ }
+}
+
+mkdir(name: string, dir : ref Sys->Dir)
+{
+ d: Dir;
+ i: int;
+
+ if(vflag) {
+ MTPT : con "/n/remote";
+ s := name;
+ if (len name >= len MTPT && name[0:len MTPT] == MTPT)
+ s = name[len MTPT:];
+ sys->print("installing directory %s\n", s);
+ }
+ fd := create(name, Sys->OREAD, dir.mode);
+ if(fd == nil) {
+ err := sys->sprint("%r");
+ (i, d) = sys->stat(name);
+ if(i < 0 || !(d.mode & Sys->DMDIR)){
+ werr(sys->sprint("can't make directory %s: %s", name, err));
+ return;
+ }
+ }
+ else {
+ (i, d) = sys->fstat(fd);
+ if(i < 0)
+ warn(sys->sprint("can't stat %s: %r", name));
+ fd = nil;
+ }
+ d = sys->nulldir;
+ (nil, p) := str->splitr(name, "/");
+ if(p == nil)
+ p = name;
+ d.name = p;
+ d.mode = dir.mode;
+ if(tflag || uflag)
+ d.mtime = dir.mtime;
+ if(uflag){
+ d.uid = dir.uid;
+ d.gid = dir.gid;
+ }
+ fd = nil;
+ if(sys->wstat(name, d) < 0){
+ e := sys->sprint("%r");
+ if(wstat(name, d) < 0)
+ warn(sys->sprint("can't set modes for %s: %s", name, e));
+ }
+ if(uflag){
+ (i, d) = sys->stat(name);
+ if(i < 0)
+ warn(sys->sprint("can't reread modes for %s: %r", name));
+ if(dir.uid != d.uid)
+ warn(sys->sprint("%s: uid mismatch %s %s", name, dir.uid, d.uid));
+ if(dir.gid != d.gid)
+ warn(sys->sprint("%s: gid mismatch %s %s", name, dir.gid, d.gid));
+ }
+}
+
+extract(ar : ref Arch->Archive, name: string, dir : ref Sys->Dir)
+{
+ sfd := create(name, Sys->OWRITE, dir.mode);
+ if(sfd == nil) {
+ if(!fflag || remove(name) == -1 ||
+ (sfd = create(name, Sys->OWRITE, dir.mode)) == nil) {
+ werr(sys->sprint("can't make file %s: %r", name));
+ arch->drain(ar, int dir.length);
+ return;
+ }
+ }
+ b := bufio->fopen(sfd, Bufio->OWRITE);
+ if (b == nil) {
+ warn(sys->sprint("can't open file %s for bufio : %r", name));
+ arch->drain(ar, int dir.length);
+ return;
+ }
+ err := arch->getfile(ar, b, int dir.length);
+ if (err != nil) {
+ if (len err >= 9 && err[0:9] == "premature")
+ fatal(err);
+ else
+ warn(err);
+ }
+ (i, d) := sys->fstat(b.fd);
+ if(i < 0)
+ warn(sys->sprint("can't stat %s: %r", name));
+ d = sys->nulldir;
+ (nil, p) := str->splitr(name, "/");
+ if(p == nil)
+ p = name;
+ d.name = p;
+ d.mode = dir.mode;
+ if(tflag || uflag)
+ d.mtime = dir.mtime;
+ if(uflag){
+ d.uid = dir.uid;
+ d.gid = dir.gid;
+ }
+ if(b.flush() == Bufio->ERROR)
+ werr(sys->sprint("error writing %s: %r", name));
+ b.close();
+ sfd = nil;
+ if(sys->wstat(name, d) < 0){
+ e := sys->sprint("%r");
+ if(wstat(name, d) < 0)
+ warn(sys->sprint("can't set modes for %s: %s", name, e));
+ }
+ if(uflag){
+ (i, d) = sys->stat(name);
+ if(i < 0)
+ warn(sys->sprint("can't reread modes for %s: %r", name));
+ if(d.uid != dir.uid)
+ warn(sys->sprint("%s: uid mismatch %s %s", name, dir.uid, d.uid));
+ if(d.gid != dir.gid)
+ warn(sys->sprint("%s: gid mismatch %s %s", name, dir.gid, d.gid));
+ }
+}
+
+error(s: string)
+{
+ fprint(stderr, "%s: %s\n", argv0, s);
+ quit("error");
+}
+
+werr(s: string)
+{
+ fprint(stderr, "%s: %s\n", argv0, s);
+ if(stoponerr)
+ quit("werr");
+}
+
+warn(s: string)
+{
+ fprint(stderr, "%s: %s\n", argv0, s);
+}
+
+usage()
+{
+ fprint(stderr, "Usage: inst [-h] [-u] [-v] [-f] [-c] [-F] [-r dest-root] [file ...]\n");
+ raise "fail: usage";
+}
+
+fatal(s : string)
+{
+ sys->fprint(stderr, "inst: %s\n", s);
+ if(wrap != nil)
+ wrap->end();
+ exit;
+}
+
+parent(name : string) : string
+{
+ slash := -1;
+ for (i := 0; i < len name; i++)
+ if (name[i] == '/')
+ slash = i;
+ if (slash > 0)
+ return name[0:slash];
+ return "/";
+}
+
+create(name : string, rw : int, mode : int) : ref Sys->FD
+{
+ fd := sys->create(name, rw, mode);
+ if (fd == nil) {
+ p := parent(name);
+ (ok, d) := sys->stat(p);
+ if (ok < 0)
+ return nil;
+ omode := d.mode;
+ d = sys->nulldir;
+ d.mode = omode | 8r222; # ensure parent is writable
+ sys->wstat(p, d);
+ fd = sys->create(name, rw, mode);
+ d.mode = omode;
+ sys->wstat(p, d);
+ }
+ return fd;
+}
+
+remove(name : string) : int
+{
+ if (sys->remove(name) < 0) {
+ (ok, d) := sys->stat(name);
+ if (ok < 0)
+ return -1;
+ omode := d.mode;
+ d.mode |= 8r222;
+ sys->wstat(name, d);
+ if (sys->remove(name) >= 0)
+ return 0;
+ d.mode = omode;
+ sys->wstat(name, d);
+ return -1;
+ }
+ return 0;
+}
+
+wstat(name : string, d : Dir) : int
+{
+ (ok, dir) := sys->stat(name);
+ if (ok < 0)
+ return -1;
+ omode := dir.mode;
+ dir.mode |= 8r222;
+ sys->wstat(name, dir);
+ if (sys->wstat(name, d) >= 0)
+ return 0;
+ dir.mode = omode;
+ sys->wstat(name, dir);
+ return -1;
+}
+
+pathcat(s : string, t : string) : string
+{
+ if (s == nil) return t;
+ if (t == nil) return s;
+ slashs := s[len s - 1] == '/';
+ slasht := t[0] == '/';
+ if (slashs && slasht)
+ return s + t[1:];
+ if (!slashs && !slasht)
+ return s + "/" + t;
+ return s + t;
+}
diff --git a/appl/cmd/install/install.b b/appl/cmd/install/install.b
new file mode 100644
index 00000000..858f3a27
--- /dev/null
+++ b/appl/cmd/install/install.b
@@ -0,0 +1,430 @@
+implement Install;
+
+#
+# Determine which packages need installing and calls install/inst
+# to actually install each one
+#
+
+# usage: install/install -d -F -g -s -u -i installdir -p platform -r root -P package
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "arg.m";
+ arg: Arg;
+include "readdir.m";
+ readdir : Readdir;
+include "sh.m";
+
+Install: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# required dirs, usually in the standard inferno root.
+# The network download doesn't include them because of
+# problems with versions of tar that won't create empty dirs
+# so we'll make sure they exist.
+
+reqdirs := array [] of {
+ "/mnt",
+ "/mnt/wrap",
+ "/n",
+ "/n/remote",
+ "/tmp",
+};
+
+YES, NO, QUIT, ERR : con iota;
+INST : con "install/inst"; # actual install program
+MTPT : con "/n/remote"; # mount point for user's inferno root
+
+debug := 0;
+force := 0;
+exitemu := 0;
+uflag := 0;
+stderr : ref Sys->FD;
+installdir := "/install";
+platform := "Plan9";
+lcplatform : string;
+root := "/usr/inferno";
+local: int;
+global: int = 1;
+waitfd : ref Sys->FD;
+
+Product : adt {
+ name : string;
+ pkgs : ref Package;
+ nxt : ref Product;
+};
+
+Package : adt {
+ name : string;
+ nxt : ref Package;
+};
+
+instprods : ref Product; # products/packages already installed
+
+# platform independent packages
+xpkgs := array[] of { "inferno", "utils", "src", "ipaq", "minitel", "sds" };
+ypkgs: list of string;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ # Hack for network download...
+ # make sure the dirs we need exist
+ for (dirix := 0; dirix < len reqdirs; dirix++) {
+ dir := reqdirs[dirix];
+ (exists, nil) := sys->stat(dir);
+ if (exists == -1) {
+ fd := sys->create(dir, Sys->OREAD, Sys->DMDIR + 8r7775);
+ if (fd == nil)
+ fatal(sys->sprint("cannot create directory %s: %r\n", dir));
+ fd = nil;
+ }
+ }
+
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ fatal(sys->sprint("cannot load %s: %r\n", Bufio->PATH));
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ fatal(sys->sprint("cannot load %s: %r\n", Readdir->PATH));
+ str = load String String->PATH;
+ if(str == nil)
+ fatal(sys->sprint("cannot load %s: %r\n", String->PATH));
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ fatal(sys->sprint("cannot load %s: %r\n", Arg->PATH));
+ arg->init(args);
+ while((c := arg->opt()) != 0) {
+ case c {
+ 'd' =>
+ debug = 1;
+ 'F' =>
+ force = 1;
+ 's' =>
+ exitemu = 1;
+ 'i' =>
+ installdir = arg->arg();
+ if (installdir == nil)
+ fatal("install directory missing");
+ 'p' =>
+ platform = arg->arg();
+ if (platform == nil)
+ fatal("platform missing");
+ 'P' =>
+ pkg := arg->arg();
+ if (pkg == nil)
+ fatal("package missing");
+ ypkgs = pkg :: ypkgs;
+ 'r' =>
+ root = arg->arg();
+ if (root == nil)
+ fatal("inferno root missing");
+ 'u' =>
+ uflag = 1;
+ 'g' =>
+ global = 0;
+ '*' =>
+ usage();
+ }
+ }
+ if (arg->argv() != nil)
+ usage();
+ lcplatform = str->tolower(platform);
+ (ok, dir) := sys->stat(installdir);
+ if (ok < 0)
+ fatal(sys->sprint("cannot open install directory %s", installdir));
+ nt := lcplatform == "nt";
+ if (nt) {
+ # root os of the form ?:/.........
+ if (len root < 3 || root[1] != ':' || root[2] != '/')
+ fatal(sys->sprint("root %s not of the form ?:/.......", root));
+ spec := root[0:2];
+ root = root[2:];
+ if (sys->bind("#U"+spec, MTPT, Sys->MREPL|Sys->MCREATE) < 0)
+ fatal(sys->sprint("cannot bind to drive %s", spec));
+ }
+ else {
+ if (root[0] != '/')
+ fatal(sys->sprint("root %s must be an absolute path name", root));
+ if (sys->bind("#U*", MTPT, Sys->MREPL|Sys->MCREATE) < 0)
+ fatal("cannot bind to system root");
+ }
+ (ok, dir) = sys->stat(MTPT+root);
+ if (ok >= 0) {
+ if ((dir.mode & Sys->DMDIR) == 0)
+ fatal(sys->sprint("inferno root %s is not a directory", root));
+ }
+ else if (sys->create(MTPT+root, Sys->OREAD, 8r775 | Sys->DMDIR) == nil)
+ fatal(sys->sprint("cannot create inferno root %s: %r", root));
+ # need a writable tmp directory /tmp in case installing from CD
+ (ok, dir) = sys->stat(MTPT+root+"/tmp");
+ if (ok >= 0) {
+ if ((dir.mode & Sys->DMDIR) == 0)
+ fatal(sys->sprint("inferno root tmp %s is not a directory", root+"/tmp"));
+ }
+ else if (sys->create(MTPT+root+"/tmp", Sys->OREAD, 8r775 | Sys->DMDIR) == nil)
+ fatal(sys->sprint("cannot create inferno root tmp %s: %r", root+"/tmp"));
+ if (sys->bind(MTPT+root, MTPT, Sys->MREPL | Sys->MCREATE) < 0)
+ fatal("cannot bind inferno root");
+ if (sys->bind(MTPT+"/tmp", "/tmp", Sys->MREPL | Sys->MCREATE) < 0)
+ fatal("cannot bind inferno root tmp");
+ root = MTPT;
+
+ if (nt || 1)
+ local = 1;
+ else {
+ sys->print("You can either install software specific to %s only or\n", platform);
+ sys->print(" install software for all platforms that we support.\n");
+ sys->print("If you are unsure what to do, answer yes to the question following.\n");
+ sys->print(" You can install the remainder of the software at a later date if desired.\n");
+ sys->print("\n");
+ b := bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ if (b == nil)
+ fatal("cannot open stdin");
+ for (;;) {
+ sys->print("Install software specific to %s only ? (yes/no/quit) ", platform);
+ resp := getresponse(b);
+ ans := answer(resp);
+ if (ans == QUIT)
+ exit;
+ else if (ans == ERR)
+ sys->print("bad response %s\n\n", resp);
+ else {
+ local = ans == YES;
+ break;
+ }
+ }
+ }
+ instprods = dowraps(root+"/wrap");
+ doprods(installdir);
+ if (!nt)
+ sys->print("installation complete\n");
+ if (exitemu)
+ shutdown();
+}
+
+getresponse(b : ref Iobuf) : string
+{
+ s := b.gets('\n');
+ while (s != nil && (s[0] == ' ' || s[0] == '\t'))
+ s = s[1:];
+ while (s != nil && ((c := s[len s - 1]) == ' ' || c == '\t' || c == '\n'))
+ s = s[0: len s - 1];
+ return s;
+}
+
+answer(s : string) : int
+{
+ s = str->tolower(s);
+ if (s == "y" || s == "yes")
+ return YES;
+ if (s == "n" || s == "no")
+ return NO;
+ if (s == "q" || s == "quit")
+ return QUIT;
+ return ERR;
+}
+
+usage()
+{
+ fatal("Usage: install [-d] [-F] [-s] [-u] [-i installdir ] [-p platform ] [-r root]");
+}
+
+fatal(s : string)
+{
+ sys->fprint(stderr, "install: %s\n", s);
+ exit;
+}
+
+dowraps(d : string) : ref Product
+{
+ p : ref Product;
+
+ # make an inventory of what is already apparently installed
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ if (dir[i].mode & Sys->DMDIR) {
+ p = ref Product(str->tolower(dir[i].name), nil, p);
+ p.pkgs = dowrap(d + "/" + dir[i].name);
+ }
+ }
+ return p;
+}
+
+dowrap(d : string) : ref Package
+{
+ p : ref Package;
+
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++)
+ p = ref Package(dir[i].name, p);
+ return p;
+}
+
+doprods(d : string)
+{
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ if (dir[i].mode & Sys->DMDIR)
+ doprod(str->tolower(dir[i].name), d + "/" + dir[i].name);
+ }
+}
+
+doprod(pr : string, d : string)
+{
+ # base package, updates and update packages have the name
+ # <timestamp> or <timestamp.gz>
+ if (!wanted(pr))
+ return;
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ pk := dir[i].name;
+ l := len pk;
+ if (l >= 4 && pk[l-3:l] == ".gz")
+ pk = pk[0:l-3];
+ else if (l >= 5 && (pk[l-4:] == ".tgz" || pk[l-4:] == ".9gz"))
+ pk = pk[0:l-4];
+ dopkg(pk, pr, d+"/"+dir[i].name);
+
+ }
+}
+
+dopkg(pk : string, pr : string, d : string)
+{
+ if (!installed(pk, pr))
+ install(d);
+}
+
+installed(pkg : string, prd : string) : int
+{
+ for (pr := instprods; pr != nil; pr = pr.nxt) {
+ if (pr.name == prd) {
+ for (pk := pr.pkgs; pk != nil; pk = pk.nxt) {
+ if (pk.name == pkg)
+ return 1;
+ }
+ return 0;
+ }
+ }
+ return 0;
+}
+
+lookup(pr : string) : int
+{
+ for (i := 0; i < len xpkgs; i++) {
+ if (xpkgs[i] == pr)
+ return i;
+ }
+ return -1;
+}
+
+plookup(pr: string): int
+{
+ for(ps := ypkgs; ps != nil; ps = tl ps)
+ if(pr == hd ps)
+ return 1;
+ return 0;
+}
+
+wanted(pr : string) : int
+{
+ if (!local || global)
+ return 1;
+ if(ypkgs != nil) # overrides everything else
+ return plookup(pr);
+ found := lookup(pr);
+ if (found >= 0)
+ return 1;
+ return pr == lcplatform || prefix(lcplatform, pr);
+}
+
+install(d : string)
+{
+ if (waitfd == nil)
+ waitfd = openwait(sys->pctl(0, nil));
+ sys->fprint(stderr, "installing package %s\n", d);
+ if (debug)
+ return;
+ c := chan of int;
+ args := "-t" :: "-v" :: "-r" :: root :: d :: nil;
+ if (uflag)
+ args = "-u" :: args;
+ if (force)
+ args = "-F" :: args;
+ spawn exec(INST, INST :: args, c);
+ execpid := <- c;
+ wait(waitfd, execpid);
+}
+
+exec(cmd : string, argl : list of string, ci : chan of int)
+{
+ ci <-= sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: stderr.fd :: nil);
+ file := cmd;
+ if(len file<4 || file[len file-4:] !=".dis")
+ file += ".dis";
+ c := load Command file;
+ if(c == nil) {
+ err := sys->sprint("%r");
+ if(file[0] !='/' && file[0:2] !="./") {
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ if(c == nil)
+ fatal(sys->sprint("%s: %s\n", cmd, err));
+ }
+ c->init(nil, argl);
+}
+
+openwait(pid : int) : ref Sys->FD
+{
+ w := sys->sprint("#p/%d/wait", pid);
+ fd := sys->open(w, Sys->OREAD);
+ if (fd == nil)
+ fatal("fd == nil in wait");
+ return fd;
+}
+
+wait(wfd : ref Sys->FD, wpid : int)
+{
+ n : int;
+
+ buf := array[Sys->WAITLEN] of byte;
+ status := "";
+ for(;;) {
+ if ((n = sys->read(wfd, buf, len buf)) < 0)
+ fatal("bad read in wait");
+ status = string buf[0:n];
+ break;
+ }
+ if (int status != wpid)
+ fatal("bad status in wait");
+ if(status[len status - 1] != ':')
+ fatal(sys->sprint("%s\n", status));
+}
+
+shutdown()
+{
+ fd := sys->open("/dev/sysctl", sys->OWRITE);
+ if(fd == nil)
+ fatal("cannot shutdown emu");
+ if (sys->write(fd, array of byte "halt", 4) < 0)
+ fatal(sys->sprint("shutdown: write failed: %r\n"));
+}
+
+prefix(s, t : string) : int
+{
+ if (len s <= len t)
+ return t[0:len s] == s;
+ return 0;
+}
diff --git a/appl/cmd/install/log.b b/appl/cmd/install/log.b
new file mode 100644
index 00000000..d624f446
--- /dev/null
+++ b/appl/cmd/install/log.b
@@ -0,0 +1,76 @@
+implement Fsmodule;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "sh.m";
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "vt-us-gs";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: log: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ badmod(Daytime->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ uid, gid: string;
+ for(; opts != nil; opts = tl opts){
+ o := hd (hd opts).args;
+ case (hd opts).opt {
+ 'u' => uid = o.s().i;
+ 'g' => gid = o.s().i;
+ }
+ }
+ sync := chan of int;
+ spawn logproc(sync, (hd args).t().i, report.start("log"), uid, gid);
+ return ref Value.V(sync);
+}
+
+logproc(sync: chan of int, c: Entrychan, errorc: chan of string, uid: string, gid: string)
+{
+ if(<-sync == 0){
+ c.sync <-= 0;
+ quit(errorc);
+ exit;
+ }
+ c.sync <-= 1;
+
+ now := daytime->now();
+ for(seq := 0; ((d, p, nil) := <-c.c).t0 != nil; seq++){
+ if(uid != nil)
+ d.uid = uid;
+ if(gid != nil)
+ d.gid = gid;
+ sys->print("%ud %ud %c %q - - %uo %q %q %ud %bd%s\n", now, seq, 'a', p, d.mode, d.uid, d.gid, d.mtime, d.length, "");
+ }
+ quit(errorc);
+}
diff --git a/appl/cmd/install/logs.b b/appl/cmd/install/logs.b
new file mode 100644
index 00000000..20135622
--- /dev/null
+++ b/appl/cmd/install/logs.b
@@ -0,0 +1,287 @@
+implement Logs;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "logs.m";
+
+Hashsize: con 1024;
+Incr: con 500;
+
+init(bio: Bufio): string
+{
+ sys = load Sys Sys->PATH;
+ bufio = bio;
+ str = load String String->PATH;
+ if(str == nil)
+ return sys->sprint("can't load %s: %r", String->PATH);
+ return nil;
+}
+
+Entry.read(in: ref Iobuf): (ref Entry, string)
+{
+ if((s := in.gets('\n')) == nil)
+ return (nil, nil);
+ if(s[len s-1] == '\n')
+ s = s[0:len s-1];
+
+ e := ref Entry;
+ e.x = -1;
+
+ l := str->unquoted(s);
+ fields := array[11] of string;
+ for(i := 0; l != nil; l = tl l)
+ fields[i++] = S(hd l);
+
+ # time gen verb path serverpath mode uid gid mtime length
+ # 1064889121 4 a sys/src/cmd/ip/httpd/webls.denied - 664 sys sys 1064887847 3
+ # time[0] gen[1] op[2] path[3] (serverpath|"-")[4] mode[5] uid[6] gid[7] mtime[8] length[9]
+
+ if(i < 10 || len fields[2] != 1)
+ return (nil, sys->sprint("bad log entry: %q", s));
+ e.action = fields[2][0];
+ case e.action {
+ 'a' or 'c' or 'd' or 'm' =>
+ ;
+ * =>
+ return (nil, sys->sprint("bad log entry: %q", s));
+ }
+
+ time := bigof(fields[0], 10);
+ sgen := bigof(fields[1], 10);
+ e.seq = (time << 32) | sgen; # for easier comparison
+
+ # time/gen check
+ # name check
+
+ if(fields[4] == "-") # undocumented
+ fields[4] = fields[3];
+ e.path = fields[3];
+ e.serverpath = fields[4];
+ e.d = sys->nulldir;
+ {
+ e.d.mode = intof(fields[5], 8);
+ e.d.qid.qtype = e.d.mode>>24;
+ e.d.uid = fields[6];
+ if(e.d.uid == "-")
+ e.d.uid = "";
+ e.d.gid = fields[7];
+ if(e.d.gid == "-")
+ e.d.gid = "";
+ e.d.mtime = intof(fields[8], 10);
+ e.d.length = bigof(fields[9], 10);
+ }exception ex {
+ "log format:*" =>
+ return (nil, sys->sprint("%s in log entry %q", ex, s));
+ }
+ e.contents = fields[10] :: nil; # optional
+ return (e, nil);
+}
+
+rev[T](l: list of T): list of T
+{
+ rl: list of T;
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+bigof(s: string, base: int): big
+{
+ (b, r) := str->tobig(s, base);
+ if(r != nil)
+ raise "invalid integer field";
+ return b;
+}
+
+intof(s: string, base: int): int
+{
+ return int bigof(s, base);
+}
+
+mkpath(root: string, name: string): string
+{
+ if(len root > 0 && root[len root-1] != '/' && (len name == 0 || name[0] != '/'))
+ return root+"/"+name;
+ return root+name;
+}
+
+contents(e: ref Entry): string
+{
+ if(e.contents == nil)
+ return "";
+ s := "";
+ for(cl := e.contents; cl != nil; cl = tl cl)
+ s += " " + hd cl;
+ return s[1:];
+}
+
+Entry.text(e: self ref Entry): string
+{
+ a := e.action;
+ if(a == 0)
+ a = '?';
+ return sys->sprint("%bd %bd %q [%d] %c m=%uo l=%bd t=%ud c=%q", e.seq>>32, e.seq & 16rFFFFFFFF, e.path, e.x, a, e.d.mode, e.d.length, e.d.mtime, contents(e));
+}
+
+Entry.sumtext(e: self ref Entry): string
+{
+ case e.action {
+ 'a' or 'm' =>
+ return sys->sprint("%c %q %uo %q %q %ud", e.action, e.path, e.d.mode, e.d.uid, e.d.gid, e.d.mtime);
+ 'd' or 'c' =>
+ return sys->sprint("%c %q", e.action, e.path);
+ * =>
+ return sys->sprint("? %q", e.path);
+ }
+}
+
+Entry.dbtext(e: self ref Entry): string
+{
+ # path dpath|"-" mode uid gid mtime length
+ return sys->sprint("%bd %bd %q - %uo %q %q %ud %bd%s", e.seq>>32, e.seq & 16rFFFFFFFF, e.path, e.d.mode, e.d.uid, e.d.gid, e.d.mtime, e.d.length, contents(e));
+}
+
+Entry.logtext(e: self ref Entry): string
+{
+ # gen n act path spath|"-" dpath|"-" mode uid gid mtime length
+ a := e.action;
+ if(a == 0)
+ a = '?';
+ sf := e.serverpath;
+ if(sf == nil || sf == e.path)
+ sf = "-";
+ return sys->sprint("%bd %bd %c %q %q %uo %q %q %ud %bd%s", e.seq>>32, e.seq & 16rFFFFFFFF, a, e.path, sf, e.d.mode, e.d.uid, e.d.gid, e.d.mtime, e.d.length, contents(e));
+}
+
+Entry.remove(e: self ref Entry)
+{
+ e.action = 'd';
+}
+
+Entry.removed(e: self ref Entry): int
+{
+ return e.action == 'd';
+}
+
+Entry.update(e: self ref Entry, n: ref Entry)
+{
+ if(n == nil)
+ return;
+ if(n.action == 'd')
+ e.contents = nil;
+ else
+ e.d = n.d;
+ if(n.action != 'm' || e.action == 'd')
+ e.action = n.action;
+ e.serverpath = S(n.serverpath);
+ for(nl := rev(n.contents); nl != nil; nl = tl nl)
+ e.contents = hd nl :: e.contents;
+ if(n.seq > e.seq)
+ e.seq = n.seq;
+}
+
+Db.new(name: string): ref Db
+{
+ db := ref Db;
+ db.name = name;
+ db.stateht = array[Hashsize] of list of ref Entry;
+ db.nstate = 0;
+ db.state = array[50] of ref Entry;
+ return db;
+}
+
+Db.look(db: self ref Db, name: string): ref Entry
+{
+ (b, nil) := hash(name, len db.stateht);
+ for(l := db.stateht[b]; l != nil; l = tl l)
+ if((hd l).path == name)
+ return hd l;
+ return nil;
+}
+
+Db.entry(db: self ref Db, seq: big, name: string, d: Sys->Dir): ref Entry
+{
+ e := ref Entry;
+ e.action = 'a';
+ e.seq = seq;
+ e.path = name;
+ e.d = d;
+ e.x = db.nstate++;
+ if(e.x >= len db.state){
+ a := array[len db.state + Incr] of ref Entry;
+ a[0:] = db.state;
+ db.state = a;
+ }
+ db.state[e.x] = e;
+ (b, nil) := hash(name, len db.stateht);
+ db.stateht[b] = e :: db.stateht[b];
+ return e;
+}
+
+Db.sort(db: self ref Db, key: int)
+{
+ sortentries(db.state[0:db.nstate], key);
+}
+
+sortentries(a: array of ref Entry, key: int): (array of ref Entry, int)
+{
+ mergesort(a, array[len a] of ref Entry, key);
+ return (a, len a);
+}
+
+mergesort(a, b: array of ref Entry, key: int)
+{
+ r := len a;
+ if(r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m], key);
+ mergesort(a[m:], b[m:], key);
+ b[0:] = a;
+ for((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if(key==Byname && b[i].path > b[j].path || key==Byseq && b[i].seq > b[j].seq)
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if(i < m)
+ a[k:] = b[i:m];
+ else if(j < r)
+ a[k:] = b[j:r];
+ }
+}
+
+strings: array of list of string;
+
+S(s: string): string
+{
+ if(strings == nil)
+ strings = array[257] of list of string;
+ h := hash(s, len strings).t0;
+ for(sl := strings[h]; sl != nil; sl = tl sl)
+ if(hd sl == s)
+ return hd sl;
+ strings[h] = s :: strings[h];
+ return s;
+}
+
+hash(s: string, n: int): (int, int)
+{
+ # hashpjw
+ h := 0;
+ for(i:=0; i<len s; i++){
+ h = (h<<4) + s[i];
+ if((g := h & int 16rF0000000) != 0)
+ h ^= ((g>>24) & 16rFF) | g;
+ }
+ return ((h&~(1<<31))%n, h);
+}
diff --git a/appl/cmd/install/logs.m b/appl/cmd/install/logs.m
new file mode 100644
index 00000000..bed3c68d
--- /dev/null
+++ b/appl/cmd/install/logs.m
@@ -0,0 +1,44 @@
+Logs: module
+{
+ PATH: con "/dis/install/logs.dis";
+
+ Entry: adt
+ {
+ seq: big; # time<<32 | gen
+ action: int;
+ path: string;
+ serverpath: string;
+ x: int;
+ d: Sys->Dir;
+ contents: list of string; # MD5 hash of content, most recent first
+
+ read: fn(in: ref Bufio->Iobuf): (ref Entry, string);
+ remove: fn(e: self ref Entry);
+ removed: fn(e: self ref Entry): int;
+ update: fn(e: self ref Entry, n: ref Entry);
+ text: fn(e: self ref Entry): string;
+ dbtext: fn(e: self ref Entry): string;
+ sumtext: fn(e: self ref Entry): string;
+ logtext: fn(e: self ref Entry): string;
+ };
+
+ Db: adt
+ {
+ name: string;
+ state: array of ref Entry;
+ nstate: int;
+ stateht: array of list of ref Entry;
+
+ new: fn(name: string): ref Db;
+ entry: fn(db: self ref Db, seq: big, name: string, d: Sys->Dir): ref Entry;
+ look: fn(db: self ref Db, name: string): ref Entry;
+ sort: fn(db: self ref Db, byname: int);
+ };
+
+ Byseq, Byname: con iota;
+
+ init: fn(bio: Bufio): string;
+
+ S: fn(s: string): string;
+ mkpath: fn(root: string, name: string): string;
+};
diff --git a/appl/cmd/install/mergelog.b b/appl/cmd/install/mergelog.b
new file mode 100644
index 00000000..8998d8a9
--- /dev/null
+++ b/appl/cmd/install/mergelog.b
@@ -0,0 +1,239 @@
+implement Mergelog;
+
+#
+# combine old and new log sections into one with the most recent data
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "keyring.m";
+ kr: Keyring;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "logs.m";
+ logs: Logs;
+ Db, Entry, Byname, Byseq: import logs;
+ S: import logs;
+
+include "arg.m";
+
+Mergelog: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Apply, Applydb, Install, Asis, Skip: con iota;
+
+client: ref Db; # client current state from client log
+updates: ref Db; # state delta from new section of server log
+
+nerror := 0;
+nconflict := 0;
+debug := 0;
+verbose := 0;
+resolve := 0;
+setuid := 0;
+setgid := 0;
+nflag := 0;
+timefile: string;
+clientroot: string;
+srvroot: string;
+logfd: ref Sys->FD;
+now := 0;
+gen := 0;
+noerr := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ bufio = load Bufio Bufio->PATH;
+ ensure(bufio, Bufio->PATH);
+ str = load String String->PATH;
+ ensure(str, String->PATH);
+ kr = load Keyring Keyring->PATH;
+ ensure(kr, Keyring->PATH);
+ daytime = load Daytime Daytime->PATH;
+ ensure(daytime, Daytime->PATH);
+ logs = load Logs Logs->PATH;
+ ensure(logs, Logs->PATH);
+ logs->init(bufio);
+
+ arg := load Arg Arg->PATH;
+ ensure(arg, Arg->PATH);
+ arg->init(args);
+ arg->setusage("mergelog [-vd] oldlog [path ... ] <newlog");
+ dump := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => dump = 1; debug = 1;
+ 'v' => verbose = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(len args < 3)
+ arg->usage();
+ arg = nil;
+
+ now = daytime->now();
+ client = Db.new("existing log");
+ updates = Db.new("update log");
+ clientlog := hd args; args = tl args;
+ if(args != nil)
+ error("restriction by path not yet done");
+
+ # replay the client log to build last installation state of files taken from server
+ logfd = sys->open(clientlog, Sys->OREAD);
+ if(logfd == nil)
+ error(sys->sprint("can't open %s: %r", clientlog));
+ f := bufio->fopen(logfd, Sys->OREAD);
+ if(f == nil)
+ error(sys->sprint("can't open %s: %r", clientlog));
+ while((log := readlog(f)) != nil)
+ replaylog(client, log);
+ f = nil;
+
+ # read new log entries and use the new section to build a sequence of update actions
+ f = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ while((log = readlog(f)) != nil)
+ replaylog(client, log);
+ client.sort(Byseq);
+ dumpdb(client);
+ if(nerror)
+ raise sys->sprint("fail:%d errors", nerror);
+}
+
+readlog(in: ref Iobuf): ref Entry
+{
+ (e, err) := Entry.read(in);
+ if(err != nil)
+ error(err);
+ return e;
+}
+
+#
+# replay a log to reach the state wrt files previously taken from the server
+#
+replaylog(db: ref Db, log: ref Entry)
+{
+ e := db.look(log.path);
+ indb := e != nil && !e.removed();
+ case log.action {
+ 'a' => # add new file
+ if(indb){
+ note(sys->sprint("%q duplicate create", log.path));
+ return;
+ }
+ 'c' => # contents
+ if(!indb){
+ note(sys->sprint("%q contents but no entry", log.path));
+ return;
+ }
+ 'd' => # delete
+ if(!indb){
+ note(sys->sprint("%q deleted but no entry", log.path));
+ return;
+ }
+ if(e.d.mtime > log.d.mtime){
+ note(sys->sprint("%q deleted but it's newer", log.path));
+ return;
+ }
+ 'm' => # metadata
+ if(!indb){
+ note(sys->sprint("%q metadata but no entry", log.path));
+ return;
+ }
+ * =>
+ error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF));
+ }
+ update(db, e, log);
+}
+
+#
+# update file state e to reflect the effect of the log,
+# creating a new entry if necessary
+#
+update(db: ref Db, e: ref Entry, log: ref Entry)
+{
+ if(e == nil)
+ e = db.entry(log.seq, log.path, log.d);
+ e.update(log);
+}
+
+rev[T](l: list of T): list of T
+{
+ rl: list of T;
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+ensure[T](m: T, path: string)
+{
+ if(m == nil)
+ error(sys->sprint("can't load %s: %r", path));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: %s\n", s);
+ raise "fail:error";
+}
+
+note(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: note: %s\n", s);
+}
+
+warn(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: warning: %s\n", s);
+ nerror++;
+}
+
+samestat(a: Sys->Dir, b: Sys->Dir): int
+{
+ # doesn't check permission/ownership, does check QTDIR/QTFILE
+ if(a.mode & Sys->DMDIR)
+ return (b.mode & Sys->DMDIR) != 0;
+ return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name?
+}
+
+samemeta(a: Sys->Dir, b: Sys->Dir): int
+{
+ return a.mode == b.mode && (!setuid || a.uid == b.uid) && (!setgid || a.gid == b.gid) && samestat(a, b);
+}
+
+bigof(s: string, base: int): big
+{
+ (b, r) := str->tobig(s, base);
+ if(r != nil)
+ error("cruft in integer field in log entry: "+s);
+ return b;
+}
+
+intof(s: string, base: int): int
+{
+ return int bigof(s, base);
+}
+
+dumpdb(db: ref Db)
+{
+ for(i := 0; i < db.nstate; i++){
+ s := db.state[i].text();
+ if(s != nil)
+ sys->print("%s\n", s);
+ }
+}
diff --git a/appl/cmd/install/mkfile b/appl/cmd/install/mkfile
new file mode 100644
index 00000000..5da4b55c
--- /dev/null
+++ b/appl/cmd/install/mkfile
@@ -0,0 +1,43 @@
+<../../../mkconfig
+
+TARG=\
+ create.dis\
+ info.dis\
+ wdiff.dis\
+ inst.dis\
+ wrap.dis\
+ archfs.dis\
+ install.dis\
+ arch.dis\
+ proto.dis\
+ ckproto.dis\
+ proto2list.dis\
+ wrap2list.dis\
+ wfind.dis\
+ mkproto.dis\
+ applylog.dis\
+ logs.dis\
+ log.dis\
+ mergelog.dis\
+ updatelog.dis\
+ eproto.dis\
+
+MODULES=\
+ wrap.m\
+ arch.m\
+ archfs.m\
+ logs.m\
+ proto.m\
+ protocaller.m\
+
+SYSMODULES=\
+ arg.m\
+ bufio.m\
+ sys.m\
+ draw.m\
+ bufio.m\
+ string.m\
+
+DISBIN=$ROOT/dis/install
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/install/mkproto.b b/appl/cmd/install/mkproto.b
new file mode 100644
index 00000000..cee3fd21
--- /dev/null
+++ b/appl/cmd/install/mkproto.b
@@ -0,0 +1,99 @@
+#
+# Copyright © 2000 Vita Nuova (Holdings) Limited. All rights reserved.
+#
+
+implement Mkproto;
+
+# make a proto description of the directory or file
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "readdir.m";
+ readdir: Readdir;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Mkproto: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "Usage: mkproto [ file|directory ... ]\n");
+ raise "fail:usage";
+}
+
+not: list of string;
+bout: ref Iobuf;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ readdir = load Readdir Readdir->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ argv = tl argv;
+ while (argv != nil && hd argv != nil && (hd argv)[0] == '-') {
+ not = (hd argv)[1:] :: not;
+ argv = tl argv;
+ }
+ if (argv == nil)
+ visit(".", nil, -1);
+ else if (tl argv == nil)
+ visit(hd argv, nil, -1);
+ else {
+ for ( ; argv != nil; argv = tl argv)
+ visit(hd argv, hd argv, 0);
+ }
+ bout.flush();
+}
+
+warn(s: string)
+{
+ sys->fprint(sys->fildes(2), "mkproto: %s\n", s);
+}
+
+visit(fulln: string, reln: string, depth: int)
+{
+ if (depth == 0) {
+ for (n := not; n != nil; n = tl n) {
+ if (hd n == reln) {
+ # sys->fprint(stderr, "skipping %s\n", reln);
+ return;
+ }
+ }
+ # sys->fprint(stderr, "doing %s\n", reln);
+ }
+ (ok, d) := sys->stat(fulln);
+ if(ok < 0){
+ warn(sys->sprint("cannot stat %s: %r", fulln));
+ return;
+ }
+ if (depth >= 0)
+ visitf(fulln, reln, d, depth);
+ if (d.mode & Sys->DMDIR)
+ visitd(fulln, reln, d, depth);
+}
+
+visitd(fulln: string, nil: string, nil: Sys->Dir, depth: int)
+{
+ (dir, n) := readdir->init(fulln, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ path := "/"+dir[i].name;
+ visit(fulln+path, dir[i].name, depth+1);
+ }
+}
+
+visitf(nil: string, reln: string, nil: Sys->Dir, depth: int)
+{
+ for (i := 0; i < depth; i++)
+ bout.putc('\t');
+ bout.puts(sys->sprint("%q\n", reln));
+}
diff --git a/appl/cmd/install/proto.b b/appl/cmd/install/proto.b
new file mode 100644
index 00000000..c25ee220
--- /dev/null
+++ b/appl/cmd/install/proto.b
@@ -0,0 +1,320 @@
+implement Proto;
+
+include "sys.m";
+ sys: Sys;
+ Dir : import Sys;
+include "draw.m";
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+include "string.m";
+ str: String;
+include "readdir.m";
+ readdir : Readdir;
+include "proto.m";
+include "protocaller.m";
+
+NAMELEN: con 8192;
+
+WARN, ERROR, FATAL : import Protocaller;
+
+File: adt {
+ new: string;
+ elem: string;
+ old: string;
+ uid: string;
+ gid: string;
+ mode: int;
+};
+
+indent: int;
+lineno := 0;
+newfile: string;
+oldfile: string;
+oldroot : string;
+b: ref Iobuf;
+cmod : Protocaller;
+
+rdproto(proto : string, root : string, pcmod : Protocaller) : int
+{
+ if (sys == nil) {
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ str = load String String->PATH;
+ readdir = load Readdir Readdir->PATH;
+ }
+ cmod = pcmod;
+ oldroot = root;
+ b = bufio->open(proto, Sys->OREAD);
+ if(b == nil){
+ cmod->protoerr(FATAL, lineno, sys->sprint("can't open %s: %r: skipping\n", proto));
+ b.close();
+ return -1;
+ }
+ lineno = 0;
+ indent = 0;
+ file := ref File;
+ file.mode = 0;
+ mkfs(file, -1);
+ b.close();
+ return 0;
+}
+
+mkfs(me: ref File, level: int)
+{
+ (child, fp) := getfile(me);
+ if(child == nil)
+ return;
+ if(child.elem == "+" || child.elem == "*" || child.elem == "%"){
+ rec := child.elem[0] == '+';
+ filesonly := child.elem[0] == '%';
+ child.new = me.new;
+ setnames(child);
+ mktree(child, rec, filesonly);
+ (child, fp) = getfile(me);
+ }
+ while(child != nil && indent > level){
+ if(mkfile(child))
+ mkfs(child, indent);
+ (child, fp) = getfile(me);
+ }
+ if(child != nil){
+ b.seek(big fp, 0);
+ lineno--;
+ }
+}
+
+mktree(me: ref File, rec: int, filesonly: int)
+{
+ fd := sys->open(oldfile, Sys->OREAD);
+ if(fd == nil){
+ cmod->protoerr(WARN, lineno, sys->sprint("can't open %s: %r", oldfile));
+ return;
+ }
+ child := ref *me;
+ (d, n) := readdir->init(oldfile, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ if (filesonly && (d[i].mode & Sys->DMDIR))
+ continue;
+ child.new = mkpath(me.new, d[i].name);
+ if(me.old != nil)
+ child.old = mkpath(me.old, d[i].name);
+ child.elem = d[i].name;
+ setnames(child);
+ if(copyfile(child, d[i]) && rec)
+ mktree(child, rec, filesonly);
+ }
+}
+
+mkfile(f: ref File): int
+{
+ (i, dir) := sys->stat(oldfile);
+ if(i < 0){
+ cmod->protoerr(WARN, lineno, sys->sprint("can't stat file %s: %r", oldfile));
+ skipdir();
+ return 0;
+ }
+ return copyfile(f, ref dir);
+}
+
+copyfile(f: ref File, d: ref Dir): int
+{
+ d.name = f.elem;
+ if(f.mode != ~0){
+ if((d.mode&Sys->DMDIR) != (f.mode&Sys->DMDIR))
+ cmod->protoerr(WARN, lineno, sys->sprint("inconsistent mode for %s", f.new));
+ else
+ d.mode = f.mode;
+ }
+ cmod->protofile(newfile, oldfile, d);
+ return (d.mode & Sys->DMDIR) != 0;
+}
+
+setnames(f: ref File)
+{
+ newfile = f.new;
+ if(f.old != nil){
+ if(f.old[0] == '/')
+ oldfile = mkpath(oldroot, f.old);
+ else
+ oldfile = f.old;
+ }else
+ oldfile = mkpath(oldroot, f.new);
+}
+
+#
+# skip all files in the proto that
+# could be in the current dir
+#
+skipdir()
+{
+ if(indent < 0)
+ return;
+ level := indent;
+ for(;;){
+ indent = 0;
+ fp := b.offset();
+ p := b.gets('\n');
+ if (p != nil && p[len p - 1] != '\n')
+ p += "\n";
+ lineno++;
+ if(p == nil){
+ indent = -1;
+ return;
+ }
+ for(j := 0; (c := p[j++]) != '\n';)
+ if(c == ' ')
+ indent++;
+ else if(c == '\t')
+ indent += 8;
+ else
+ break;
+ if(indent <= level){
+ b.seek(fp, 0);
+ lineno--;
+ return;
+ }
+ }
+}
+
+getfile(old: ref File): (ref File, int)
+{
+ f: ref File;
+ p, elem: string;
+ c: int;
+
+ if(indent < 0)
+ return (nil, 0);
+ fp := int b.offset();
+ do {
+ indent = 0;
+ p = b.gets('\n');
+ if (p != nil && p[len p - 1] != '\n')
+ p += "\n";
+ lineno++;
+ if(p == nil){
+ indent = -1;
+ return (nil, 0);
+ }
+ for(; (c = p[0]) != '\n'; p = p[1:])
+ if(c == ' ')
+ indent++;
+ else if(c == '\t')
+ indent += 8;
+ else
+ break;
+ } while(c == '\n' || c == '#');
+ f = ref File;
+ (elem, p) = getname(p, NAMELEN);
+ f.new = mkpath(old.new, elem);
+ (nil, f.elem) = str->splitr(f.new, "/");
+ if(f.elem == nil)
+ cmod->protoerr(ERROR, lineno, sys->sprint("can't find file name component of %s", f.new));
+ (f.mode, p) = getmode(p);
+ (f.uid, p) = getname(p, NAMELEN);
+ if(f.uid == nil)
+ f.uid = "-";
+ (f.gid, p) = getname(p, NAMELEN);
+ if(f.gid == nil)
+ f.gid = "-";
+ f.old = getpath(p);
+ if(f.old == "-")
+ f.old = nil;
+ if(f.old == nil && old.old != nil)
+ f.old = mkpath(old.old, elem);
+ setnames(f);
+ return (f, fp);
+}
+
+getpath(p: string): string
+{
+ for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:])
+ ;
+ for(n := 0; (c = p[n]) != '\n' && c != ' ' && c != '\t'; n++)
+ ;
+ return p[0:n];
+}
+
+getname(p: string, lim: int): (string, string)
+{
+ for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:])
+ ;
+ i := 0;
+ s := "";
+ for(; (c = p[0]) != '\n' && c != ' ' && c != '\t'; p = p[1:])
+ s[i++] = c;
+ if(len s >= lim){
+ cmod->protoerr(WARN, lineno, sys->sprint("name %s too long; truncated", s));
+ s = s[0:lim-1];
+ }
+ if(len s > 0 && s[0] == '$'){
+ s = getenv(s[1:]);
+ if(s == nil)
+ cmod->protoerr(ERROR, lineno, sys->sprint("can't read environment variable %s", s));
+ if(len s >= NAMELEN)
+ s = s[0:NAMELEN-1];
+ }
+ return (s, p);
+}
+
+getenv(s: string): string
+{
+ if(s == "user")
+ return getuser();
+ return nil;
+}
+
+getuser(): string
+{
+ fd := sys->open("/dev/user", Sys->OREAD);
+ if(fd != nil){
+ u := array [100] of byte;
+ n := sys->read(fd, u, len u);
+ if(n > 0)
+ return string u[0:n];
+ }
+ return nil;
+}
+
+getmode(p: string): (int, string)
+{
+ s: string;
+
+ (s, p) = getname(p, 7);
+ if(s == nil || s == "-")
+ return (~0, p);
+ m := 0;
+ if(s[0] == 'd'){
+ m |= Sys->DMDIR;
+ s = s[1:];
+ }
+ if(s[0] == 'a'){
+ #m |= CHAPPEND;
+ s = s[1:];
+ }
+ if(s[0] == 'l'){
+ #m |= CHEXCL;
+ s = s[1:];
+ }
+ for(i:=0; i<len s || i < 3; i++)
+ if(i >= len s || !(s[i]>='0' && s[i]<='7')){
+ cmod->protoerr(WARN, lineno, sys->sprint("bad mode specification %s", s));
+ return (~0, p);
+ }
+ (v, nil) := str->toint(s, 8);
+ return (m|v, p);
+}
+
+mkpath(prefix, elem: string): string
+{
+ slash1 := slash2 := 0;
+ if (len prefix > 0)
+ slash1 = prefix[len prefix - 1] == '/';
+ if (len elem > 0)
+ slash2 = elem[0] == '/';
+ if (slash1 && slash2)
+ return prefix+elem[1:];
+ if (!slash1 && !slash2)
+ return prefix+"/"+elem;
+ return prefix+elem;
+}
diff --git a/appl/cmd/install/proto.m b/appl/cmd/install/proto.m
new file mode 100644
index 00000000..07d3507f
--- /dev/null
+++ b/appl/cmd/install/proto.m
@@ -0,0 +1,6 @@
+Proto : module
+{
+ PATH : con "/dis/install/proto.dis";
+
+ rdproto: fn(proto : string, root : string, pcmod : Protocaller) : int;
+}; \ No newline at end of file
diff --git a/appl/cmd/install/proto2list.b b/appl/cmd/install/proto2list.b
new file mode 100644
index 00000000..b5997c15
--- /dev/null
+++ b/appl/cmd/install/proto2list.b
@@ -0,0 +1,209 @@
+#
+# Copyright © 2001 Vita Nuova (Holdings) Limited. All rights reserved.
+#
+
+implement Proto2list;
+
+# make a version list suitable for SDS from a series of proto files
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+include "crc.m";
+ crcm : Crc;
+include "proto.m";
+ proto : Proto;
+include "protocaller.m";
+ protocaller : Protocaller;
+
+WARN, ERROR, FATAL : import Protocaller;
+
+Proto2list: module
+{
+ init : fn(ctxt: ref Draw->Context, argv: list of string);
+ protofile: fn(new : string, old : string, d : ref Sys->Dir);
+ protoerr: fn(lev : int, line : int, err : string);
+};
+
+stderr: ref Sys->FD;
+protof: string;
+
+Element: type (string, string);
+
+List: adt{
+ as: array of Element;
+ n: int;
+ init: fn(l: self ref List);
+ add: fn(l: self ref List, e: Element);
+ end: fn(l: self ref List): array of Element;
+};
+
+flist: ref List;
+
+List.init(l: self ref List)
+{
+ l.as = array[1024] of Element;
+ l.n = 0;
+}
+
+List.add(l: self ref List, e: Element)
+{
+ if(l.n == len l.as)
+ l.as = (array[2*l.n] of Element)[0:] = l.as;
+ l.as[l.n++] = e;
+}
+
+List.end(l: self ref List): array of Element
+{
+ return l.as[0: l.n];
+}
+
+usage()
+{
+ sys->fprint(stderr, "Usage: proto2list protofile ...\n");
+ exit;
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ crcm = load Crc Crc->PATH;
+ proto = load Proto Proto->PATH;
+ protocaller = load Protocaller "$self";
+ stderr = sys->fildes(2);
+ root := "/";
+ flist = ref List;
+ flist.init();
+ for(argv = tl argv; argv != nil; argv = tl argv){
+ protof = hd argv;
+ proto->rdproto(hd argv, root, protocaller);
+ }
+ fs := flist.end();
+ sort(fs);
+ fs = uniq(fs);
+ out(fs);
+}
+
+protofile(new : string, old : string, nil : ref Sys->Dir)
+{
+ if(new == old)
+ new = "-";
+ flist.add((old, new));
+}
+
+out(fs: array of Element)
+{
+ nf := len fs;
+ for(i := 0; i < nf; i++){
+ (f, g) := fs[i];
+ (ok, d) := sys->stat(f);
+ if (ok < 0) {
+ sys->fprint(stderr, "cannot open %s\n", f);
+ continue;
+ }
+ if (d.mode & Sys->DMDIR)
+ d.length = big 0;
+ sys->print("%s %s %d %d %d %d %d\n", f, g, int d.length, d.mode, d.mtime, crc(f, d), 0);
+ }
+}
+
+protoerr(lev : int, line : int, err : string)
+{
+ s := "line " + string line + " : " + err;
+ case lev {
+ WARN => warn(s);
+ ERROR => error(s);
+ FATAL => fatal(s);
+ }
+}
+
+crc(f : string, d: Sys->Dir) : int
+{
+ crcs := crcm->init(0, int 16rffffffff);
+ if (d.mode & Sys->DMDIR)
+ return 0;
+ fd := sys->open(f, Sys->OREAD);
+ if (fd == nil) {
+ sys->fprint(stderr, "cannot open %s\n", f);
+ return 0;
+ }
+ crc := 0;
+ buf := array[Sys->ATOMICIO] of byte;
+ for (;;) {
+ nr := sys->read(fd, buf, len buf);
+ if (nr < 0) {
+ sys->fprint(stderr, "bad read on %s : %r\n", f);
+ return 0;
+ }
+ if (nr <= 0)
+ break;
+ crc = crcm->crc(crcs, buf, nr);
+ }
+ crcm->reset(crcs);
+ return crc;
+}
+
+sort(a: array of Element)
+{
+ mergesort(a, array[len a] of Element);
+}
+
+mergesort(a, b: array of Element)
+{
+ r := len a;
+ if (r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m]);
+ mergesort(a[m:], b[m:]);
+ b[0:] = a;
+ for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if (b[i].t0 > b[j].t0)
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if (i < m)
+ a[k:] = b[i:m];
+ else if (j < r)
+ a[k:] = b[j:r];
+ }
+}
+
+
+uniq(a: array of Element): array of Element
+{
+ m := n := len a;
+ for(i := 0; i < n-1; ){
+ if(a[i].t0 == a[i+1].t0){
+ if(a[i].t1 != a[i+1].t1)
+ warn(sys->sprint("duplicate %s(%s %s)", a[i].t0, a[i].t1, a[i+1].t1));
+ a[i+1:] = a[i+2: n--];
+ }
+ else
+ i++;
+ }
+ if(n == m)
+ return a;
+ return a[0: n];
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "%s: %s\n", protof, s);
+ exit;
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "fatal: %s\n", s);
+ exit;
+}
+
+warn(s: string)
+{
+ sys->fprint(stderr, "%s: %s\n", protof, s);
+}
diff --git a/appl/cmd/install/protocaller.m b/appl/cmd/install/protocaller.m
new file mode 100644
index 00000000..1e269d1f
--- /dev/null
+++ b/appl/cmd/install/protocaller.m
@@ -0,0 +1,8 @@
+Protocaller : module{
+ init: fn(ctxt : ref Draw->Context, args : list of string);
+ protofile: fn(new : string, old : string, d : ref Sys->Dir);
+
+ WARN, ERROR, FATAL : con iota;
+
+ protoerr: fn(lev : int, line : int, err : string);
+}; \ No newline at end of file
diff --git a/appl/cmd/install/updatelog.b b/appl/cmd/install/updatelog.b
new file mode 100644
index 00000000..d9c6959e
--- /dev/null
+++ b/appl/cmd/install/updatelog.b
@@ -0,0 +1,386 @@
+implement Updatelog;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "string.m";
+ str: String;
+
+include "keyring.m";
+ kr: Keyring;
+
+include "logs.m";
+ logs: Logs;
+ Db, Entry, Byname, Byseq: import logs;
+ S, mkpath: import logs;
+ Log: type Entry;
+
+include "fsproto.m";
+ fsproto: FSproto;
+ Direntry: import fsproto;
+
+include "arg.m";
+
+Updatelog: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+now: int;
+gen := 0;
+changesonly := 0;
+uid: string;
+gid: string;
+debug := 0;
+state: ref Db;
+rootdir := ".";
+scanonly: list of string;
+exclude: list of string;
+sums := 0;
+stderr: ref Sys->FD;
+Seen: con 1<<31;
+bout: ref Iobuf;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ ensure(bufio, Bufio->PATH);
+ fsproto = load FSproto FSproto->PATH;
+ ensure(fsproto, FSproto->PATH);
+ daytime = load Daytime Daytime->PATH;
+ ensure(daytime, Daytime->PATH);
+ str = load String String->PATH;
+ ensure(str, String->PATH);
+ logs = load Logs Logs->PATH;
+ ensure(logs, Logs->PATH);
+ kr = load Keyring Keyring->PATH;
+ ensure(kr, Keyring->PATH);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ error(sys->sprint("can't load %s: %r", Arg->PATH));
+
+ protofile := "/lib/proto/all";
+ arg->init(args);
+ arg->setusage("updatelog [-p proto] [-r root] [-t now gen] [-c] [-x path] x.log [path ...]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'D' =>
+ debug = 1;
+ 'p' =>
+ protofile = arg->earg();
+ 'r' =>
+ rootdir = arg->earg();
+ 'c' =>
+ changesonly = 1;
+ 'u' =>
+ uid = arg->earg();
+ 'g' =>
+ gid = arg->earg();
+ 's' =>
+ sums = 1;
+ 't' =>
+ now = int arg->earg();
+ gen = int arg->earg();
+ 'x' =>
+ s := arg->earg();
+ exclude = trimpath(s) :: exclude;
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ stderr = sys->fildes(2);
+ bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+
+ fsproto->init();
+ logs->init(bufio);
+
+ logfile := hd args;
+ while((args = tl args) != nil)
+ scanonly = trimpath(hd args) :: scanonly;
+ checkroot(rootdir, "replica root");
+
+ state = Db.new("server state");
+
+ #
+ # replay log to rebuild server state
+ #
+ logfd := sys->open(logfile, Sys->OREAD);
+ if(logfd == nil)
+ error(sys->sprint("can't open %s: %r", logfile));
+ f := bufio->fopen(logfd, Sys->OREAD);
+ if(f == nil)
+ error(sys->sprint("can't open %s: %r", logfile));
+ while((log := readlog(f)) != nil)
+ replaylog(state, log);
+
+ #
+ # walk the set of names produced by the proto file, comparing against the server state
+ #
+ now = daytime->now();
+ doproto(rootdir, protofile);
+
+ if(changesonly){
+ bout.flush();
+ exit;
+ }
+
+ #
+ # names in the original state that we didn't see in the walk must have been removed:
+ # print 'd' log entries for them, in reverse lexicographic order (children before parents)
+ #
+ state.sort(Logs->Byname);
+ for(i := state.nstate; --i >= 0;){
+ e := state.state[i];
+ if((e.x & Seen) == 0 && considered(e.path)){
+ change('d', e, e.seq, e.d, e.path, e.serverpath, e.contents); # TO DO: content
+ if(debug)
+ sys->fprint(sys->fildes(2), "remove %q\n", e.path);
+ }
+ }
+ bout.flush();
+}
+
+ensure[T](m: T, path: string)
+{
+ if(m == nil)
+ error(sys->sprint("can't load %s: %r", path));
+}
+
+checkroot(dir: string, what: string)
+{
+ (ok, d) := sys->stat(dir);
+ if(ok < 0)
+ error(sys->sprint("can't stat %s %q: %r", what, dir));
+ if((d.mode & Sys->DMDIR) == 0)
+ error(sys->sprint("%s %q: not a directory", what, dir));
+}
+
+considered(s: string): int
+{
+ if(scanonly != nil && !islisted(s, scanonly))
+ return 0;
+ return exclude == nil || !islisted(s, exclude);
+}
+
+readlog(in: ref Iobuf): ref Log
+{
+ (e, err) := Entry.read(in);
+ if(err != nil)
+ error(err);
+ return e;
+}
+
+#
+# replay a log to reach the state wrt files previously taken from the server
+#
+replaylog(db: ref Db, log: ref Log)
+{
+ e := db.look(log.path);
+ indb := e != nil && !e.removed();
+ case log.action {
+ 'a' => # add new file
+ if(indb){
+ note(sys->sprint("%q duplicate create", log.path));
+ return;
+ }
+ 'c' => # contents
+ if(!indb){
+ note(sys->sprint("%q contents but no entry", log.path));
+ return;
+ }
+ 'd' => # delete
+ if(!indb){
+ note(sys->sprint("%q deleted but no entry", log.path));
+ return;
+ }
+ if(e.d.mtime > log.d.mtime){
+ note(sys->sprint("%q deleted but it's newer", log.path));
+ return;
+ }
+ 'm' => # metadata
+ if(!indb){
+ note(sys->sprint("%q metadata but no entry", log.path));
+ return;
+ }
+ * =>
+ error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF));
+ }
+ update(db, e, log);
+}
+
+#
+# update file state e to reflect the effect of the log,
+# creating a new entry if necessary
+#
+update(db: ref Db, e: ref Entry, log: ref Entry)
+{
+ if(e == nil)
+ e = db.entry(log.seq, log.path, log.d);
+ e.update(log);
+}
+
+doproto(tree: string, protofile: string)
+{
+ entries := chan of Direntry;
+ warnings := chan of (string, string);
+ err := fsproto->readprotofile(protofile, tree, entries, warnings);
+ if(err != nil)
+ error(sys->sprint("can't read %s: %s", protofile, err));
+ for(;;)alt{
+ (old, new, d) := <-entries =>
+ if(d == nil)
+ return;
+ if(debug)
+ sys->fprint(stderr, "old=%q new=%q length=%bd\n", old, new, d.length);
+ while(new != nil && new[0] == '/')
+ new = new[1:];
+ if(!considered(new))
+ continue;
+ if(sums && (d.mode & Sys->DMDIR) == 0)
+ digests := md5sum(old) :: nil;
+ if(uid != nil)
+ d.uid = uid;
+ if(gid != nil)
+ d.gid = gid;
+ old = relative(old, rootdir);
+ db := state.look(new);
+ if(db == nil){
+ if(!changesonly){
+ db = state.entry(nextseq(), new, *d);
+ change('a', db, db.seq, db.d, db.path, old, digests);
+ }
+ }else{
+ if(!samestat(db.d, *d))
+ change('c', db, nextseq(), *d, new, old, digests);
+ if(!samemeta(db.d, *d))
+ change('m', db, nextseq(), *d, new, old, nil); # need digest?
+ }
+ if(db != nil)
+ db.x |= Seen;
+ (old, msg) := <-warnings =>
+ #if(contains(msg, "entry not found") || contains(msg, "not exist"))
+ # break;
+ sys->fprint(sys->fildes(2), "updatelog: warning[old=%s]: %s\n", old, msg);
+ }
+}
+
+change(action: int, e: ref Entry, seq: big, d: Sys->Dir, path: string, serverpath: string, digests: list of string)
+{
+ log := ref Entry;
+ log.seq = seq;
+ log.action = action;
+ log.d = d;
+ log.path = path;
+ log.serverpath = serverpath;
+ log.contents = digests;
+ e.update(log);
+ bout.puts(log.logtext()+"\n");
+}
+
+samestat(a: Sys->Dir, b: Sys->Dir): int
+{
+ # doesn't check permission/ownership, does check QTDIR/QTFILE
+ if(a.mode & Sys->DMDIR)
+ return (b.mode & Sys->DMDIR) != 0;
+ return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name?
+}
+
+samemeta(a: Sys->Dir, b: Sys->Dir): int
+{
+ return a.mode == b.mode && (uid == nil || a.uid == b.uid) && (gid == nil || a.gid == b.gid) && samestat(a, b);
+}
+
+nextseq(): big
+{
+ return (big now << 32) | big gen++;
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "updatelog: %s\n", s);
+ raise "fail:error";
+}
+
+note(s: string)
+{
+ sys->fprint(sys->fildes(2), "updatelog: note: %s\n", s);
+}
+
+contains(s: string, sub: string): int
+{
+ return str->splitstrl(s, sub).t1 != nil;
+}
+
+isprefix(a, b: string): int
+{
+ la := len a;
+ lb := len b;
+ if(la > lb)
+ return 0;
+ if(la == lb)
+ return a == b;
+ return a == b[0:la] && b[la] == '/';
+}
+
+trimpath(s: string): string
+{
+ while(len s > 1 && s[len s-1] == '/')
+ s = s[0:len s-1];
+ while(s != nil && s[0] == '/')
+ s = s[1:];
+ return s;
+}
+
+relative(name: string, root: string): string
+{
+ if(root == nil || name == nil)
+ return name;
+ if(isprefix(root, name)){
+ name = name[len root:];
+ while(name != nil && name[0] == '/')
+ name = name[1:];
+ }
+ return name;
+}
+
+islisted(s: string, l: list of string): int
+{
+ for(; l != nil; l = tl l)
+ if(isprefix(hd l, s))
+ return 1;
+ return 0;
+}
+
+md5sum(file: string): string
+{
+ fd := sys->open(file, Sys->OREAD);
+ if(fd == nil)
+ error(sys->sprint("can't open %s: %r", file));
+ ds: ref Keyring->DigestState;
+ buf := array[Sys->ATOMICIO] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0)
+ ds = kr->md5(buf, n, nil, ds);
+ if(n < 0)
+ error(sys->sprint("error reading %s: %r", file));
+ digest := array[Keyring->MD5dlen] of byte;
+ kr->md5(nil, 0, digest, ds);
+ s: string;
+ for(i := 0; i < len digest; i++)
+ s += sys->sprint("%.2ux", int digest[i]);
+ return s;
+}
diff --git a/appl/cmd/install/wdiff.b b/appl/cmd/install/wdiff.b
new file mode 100644
index 00000000..47088417
--- /dev/null
+++ b/appl/cmd/install/wdiff.b
@@ -0,0 +1,148 @@
+implement Wdiff;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "arg.m";
+ arg: Arg;
+include "wrap.m";
+ wrap : Wrap;
+include "sh.m";
+include "keyring.m";
+ keyring : Keyring;
+
+
+Wdiff: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+root := "/";
+bflag : int;
+listing : int;
+package: int;
+
+diff(w : ref Wrap->Wrapped, name : string, c : chan of int)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ wrapped := w.root+"/"+name;
+ local := root+"/"+name;
+ (ok, dir) := sys->stat(local);
+ if (ok < 0) {
+ sys->print("cannot stat %s\n", local);
+ c <-= -1;
+ return;
+ }
+ (ok, dir) = sys->stat(wrapped);
+ if (ok < 0) {
+ sys->print("cannot stat %s\n", wrapped);
+ c <-= -1;
+ return;
+ }
+ cmd := "/dis/diff.dis";
+ m := load Command cmd;
+ if(m == nil) {
+ c <-= -1;
+ return;
+ }
+ if (bflag)
+ m->init(nil, cmd :: "-b" :: wrapped :: local :: nil);
+ else
+ m->init(nil, cmd :: wrapped :: local :: nil);
+ c <-= 0;
+}
+
+fatal(err : string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", err);
+ exit;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg = load Arg Arg->PATH;
+ keyring = load Keyring Keyring->PATH;
+ wrap = load Wrap Wrap->PATH;
+ wrap->init(bufio);
+
+ arg->init(args);
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'b' =>
+ bflag = 1;
+ 'l' =>
+ listing = 1;
+ 'p' =>
+ package = 1;
+ 'r' =>
+ root = arg->arg();
+ if (root == nil)
+ fatal("missing root name");
+ * =>
+ fatal(sys->sprint("bad argument -%c", c));
+ }
+ }
+ args = arg->argv();
+ if (args == nil || tl args != nil)
+ fatal("usage: install/wdiff [-blp] [-r root] package");
+ (ok, dir) := sys->stat(hd args);
+ if (ok < 0)
+ fatal(sys->sprint("no such file %s", hd args));
+ w := wrap->openwraphdr(hd args, root, nil, !listing);
+ if (w == nil)
+ fatal("no such package found");
+
+ if(package){
+ while(w.nu > 0 && w.u[w.nu-1].typ == wrap->UPD)
+ w.nu--;
+ }
+
+ digest := array[keyring->MD5dlen] of { * => byte 0 };
+ digest0 := array[keyring->MD5dlen] of { * => byte 0 };
+
+ # loop through each md5sum file of each package in increasing time order
+ for(i := 0; i < w.nu; i++){
+ b := bufio->open(w.u[i].dir+"/md5sum", Sys->OREAD);
+ if (b == nil)
+ fatal("md5sum file not found");
+ while ((p := b.gets('\n')) != nil) {
+ (n, lst) := sys->tokenize(p, " \t\n");
+ if (n != 2)
+ fatal("error in md5sum file");
+ p = hd lst;
+ q := root+"/"+p;
+ (ok, dir) = sys->stat(q);
+ if (ok >= 0 && (dir.mode & Sys->DMDIR))
+ continue;
+ t: int;
+ (ok, t) = wrap->getfileinfo(w, p, nil, digest0, nil);
+ if(ok < 0){
+ sys->print("cannot happen\n");
+ continue;
+ }
+ if(t != w.u[i].time) # covered by later update
+ continue;
+ if (wrap->md5file(q, digest) < 0) {
+ sys->print("%s removed\n", p);
+ continue;
+ }
+ str := wrap->md5conv(digest);
+ str0 := wrap->md5conv(digest0);
+ # if (str == hd tl lst)
+ if(str == str0)
+ continue;
+ if (listing)
+ sys->print("%s modified\n", p);
+ else {
+ endc := chan of int;
+ spawn diff(w, p, endc);
+ <- endc;
+ }
+ }
+ }
+ wrap->end();
+}
diff --git a/appl/cmd/install/wfind.b b/appl/cmd/install/wfind.b
new file mode 100644
index 00000000..579fd946
--- /dev/null
+++ b/appl/cmd/install/wfind.b
@@ -0,0 +1,204 @@
+implement Wfind;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "arg.m";
+ arg: Arg;
+include "wrap.m";
+ wrap : Wrap;
+include "sh.m";
+include "keyring.m";
+ keyring : Keyring;
+include "readdir.m";
+ readdir : Readdir;
+
+Wfind: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+fatal(err : string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", err);
+ exit;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg = load Arg Arg->PATH;
+ keyring = load Keyring Keyring->PATH;
+ readdir = load Readdir Readdir->PATH;
+ wrap = load Wrap Wrap->PATH;
+ wrap->init(bufio);
+
+ pkgs: list of string;
+ indir := "/install";
+ arg->init(args);
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'p' =>
+ pkg := arg->arg();
+ if (pkg == nil)
+ fatal("missing package name");
+ pkgs = pkg :: pkgs;
+ * =>
+ fatal(sys->sprint("bad argument -%c", c));
+ }
+ }
+ args = arg->argv();
+ if (args == nil)
+ fatal("usage: install/wfind [-p package ... ] file ...");
+ # (ok, dir) := sys->stat(indir);
+ # if (ok < 0)
+ # fatal(sys->sprint("cannot open install directory %s", indir));
+ if(pkgs != nil){
+ npkgs: list of string;
+ for(pkg := pkgs; pkg != nil; pkg = tl pkg)
+ npkgs = hd pkg :: npkgs;
+ pkgs = npkgs;
+ for(pkg = pkgs; pkg != nil; pkg = tl pkg)
+ scanpkg(hd pkg, indir+"/"+hd pkg, args);
+ }
+ else
+ scanpkgs(indir, args);
+ prfiles();
+}
+
+scanpkgs(d : string, files: list of string)
+{
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ if (dir[i].mode & Sys->DMDIR)
+ scanpkg(dir[i].name, d + "/" + dir[i].name, files);
+ }
+}
+
+scanpkg(pkg : string, d : string, files: list of string)
+{
+ # base package, updates and update packages have the name
+ # <timestamp> or <timestamp.gz>
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ f := dir[i].name;
+ l := len f;
+ if (l >= 4 && f[l-3:l] == ".gz")
+ f = f[0:l-3];
+ scanfile(f, pkg, d+"/"+dir[i].name, files);
+ }
+ w := wrap->openwrap(pkg, "/", 0);
+ if(w == nil)
+ return;
+ for(i = 0; i < w.nu; i++)
+ scanw(w, i, files, WRAP, pkg);
+}
+
+scanfile(f: string, pkg: string, d: string, files: list of string)
+{
+ f = nil;
+ # sys->print("%s %s %s\n", f, pkg, d);
+ w := wrap->openwraphdr(d, "/", nil, 0);
+ if(w == nil)
+ return;
+ if(w.nu != 1)
+ fatal("strange package: more than one piece");
+ # sys->print(" %s %d %s %d %d %d\n", w.name, w.tfull, w.u[0].desc, w.u[0].time, w.u[0].utime, w.u[0].typ);
+ scanw(w, 0, files, INSTALL, pkg);
+}
+
+scanw(w: ref Wrap->Wrapped, i: int, files: list of string, where: int, pkg: string)
+{
+ w.u[i].bmd5.seek(big 0, Bufio->SEEKSTART);
+ while ((p := w.u[i].bmd5.gets('\n')) != nil){
+ # sys->print("%s", p);
+ (n, l) := sys->tokenize(p, " \n");
+ if(n != 2)
+ fatal(sys->sprint("bad md5 file in %s\n", wtype(where)+"/"+w.name+"/"+wrap->now2string(w.u[i].time, 0)));
+ file := hd l;
+ md5 := hd tl l;
+ for(fs := files; fs != nil; fs = tl fs){
+ if(strsuffix(file, hd fs)){
+ # sys->print("%s %s %s %d\n", pkg, file, md5, where);
+ addfile(file, w, i, md5, where, pkg);
+ }
+ }
+ }
+}
+
+Stat: adt{
+ name: string;
+ occs: list of (ref Wrap->Wrapped, int, string, int, string);
+ md5: string;
+};
+
+stats: list of ref Stat;
+
+addfile(file: string, w: ref Wrap->Wrapped, i: int, md5: string, where: int, pkg: string)
+{
+ for(sts := stats; sts != nil; sts = tl sts){
+ st := hd sts;
+ if(st.name == file){
+ st.occs = (w, i, md5, where, pkg) :: st.occs;
+ return;
+ }
+ }
+ digest := array[keyring->MD5dlen] of { * => byte 0 };
+ if (wrap->md5file(file, digest) < 0)
+ str := "non-existent"+blanks(32-12);
+ else
+ str = wrap->md5conv(digest);
+ st := ref Stat;
+ st.name = file;
+ st.occs = (w, i, md5, where, pkg) :: nil;
+ st.md5 = str;
+ stats = st :: stats;
+}
+
+prfiles()
+{
+ for(sts := stats; sts != nil; sts = tl sts){
+ st := hd sts;
+ sys->print("%s\n", st.name);
+ proccs(st.occs);
+ sys->print("\t%s %s\n", st.md5, st.name);
+ }
+}
+
+proccs(ocs: list of (ref Wrap->Wrapped, int, string, int, string))
+{
+ if(ocs != nil){
+ proccs(tl ocs);
+ (w, i, md5, where, pkg) := hd ocs;
+ sys->print("\t%s %s/%s(%s)\t%s\n", md5, w.name, wrap->now2string(w.u[i].time, 0), ptype(w.u[i].typ), wtype(where)+"/"+pkg);
+ }
+}
+
+ptype(p: int): string
+{
+ return (array[] of { "???", "package ", "update ", "full upd" })[p];
+}
+
+INSTALL: con 0;
+WRAP: con 1;
+
+wtype(w: int): string
+{
+ return (array[] of { "/install", "/wrap" })[w];
+}
+
+strsuffix(s: string, suf: string): int
+{
+ return (l1 := len s) >= (l2 := len suf) && s[l1-l2: l1] == suf;
+}
+
+blanks(n: int): string
+{
+ s := "";
+ for(i := 0; i < n; i++)
+ s += " ";
+ return s;
+}
diff --git a/appl/cmd/install/wrap.b b/appl/cmd/install/wrap.b
new file mode 100644
index 00000000..1b90c765
--- /dev/null
+++ b/appl/cmd/install/wrap.b
@@ -0,0 +1,684 @@
+implement Wrap;
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+include "keyring.m";
+ keyring : Keyring;
+include "sh.m";
+include "arch.m";
+ arch : Arch;
+include "wrap.m";
+include "archfs.m";
+
+archpid := -1;
+gzfd: ref Sys->FD;
+gzfile: string;
+
+init(bio: Bufio)
+{
+ sys = load Sys Sys->PATH;
+ if(bio == nil)
+ bufio = load Bufio Bufio->PATH;
+ else
+ bufio = bio;
+ keyring = load Keyring Keyring->PATH;
+ arch = load Arch Arch->PATH;
+ arch->init(bufio);
+}
+
+end()
+{
+ if(gzfile != nil)
+ sys->remove(gzfile);
+ if (archpid > 0){
+ fd := sys->open("#p/" + string archpid + "/ctl", sys->OWRITE);
+ if (fd != nil)
+ sys->fprint(fd, "killgrp");
+ }
+}
+
+archfs(f : string, mtpt : string, all : int, c : chan of int)
+{
+ sys->pctl(Sys->NEWPGRP, nil);
+ cmd := "/dis/install/archfs.dis";
+ m := load Archfs Archfs->PATH;
+ if(m == nil) {
+ c <-= -1;
+ return;
+ }
+ ch := chan of int;
+ if (all)
+ spawn m->initc(cmd :: "-m" :: mtpt :: f :: nil, ch);
+ else
+ spawn m->initc(cmd :: "-s" :: "-m" :: mtpt :: f :: "/wrap" :: nil, ch);
+ pid := <- ch;
+ c <-= pid;
+}
+
+mountarch(f : string, mtpt : string, all : int) : int
+{
+ c := chan of int;
+ spawn archfs(f, mtpt, all, c);
+ pid := <- c;
+ if (pid < 0) {
+ if(pid == -1)
+ sys->fprint(sys->fildes(2), "fatal: cannot run archfs\n");
+ # else probably not an archive file
+ return -1;
+ }
+ archpid = pid;
+ return 0;
+}
+
+openmount(f : string, d : string) : ref Wrapped
+{
+ if (f == nil) {
+ p := d+"/wrap";
+ f = getfirstdir(p);
+ if (f == nil)
+ return nil;
+ }
+ w := ref Wrapped;
+ w.name = f;
+ w.root = d;
+ # p := d + "/wrap/" + f;
+ p := pathcat(d, pathcat("wrap", f));
+ (w.u, w.nu, w.tfull) = openupdate(p);
+ if (w.nu < 0) {
+ closewrap(w);
+ return nil;
+ }
+ return w;
+}
+
+closewrap(w : ref Wrapped)
+{
+ w = nil;
+}
+
+openwraphdr(f : string, d : string, argl : list of string, all : int) : ref Wrapped
+{
+ argl = nil;
+ (ok, dir) := sys->stat(f);
+ if (ok < 0 || dir.mode & Sys->DMDIR)
+ return openwrap(f, d, all);
+ (nf, fd) := arch->openarchgz(f);
+ if (nf != nil) {
+ gzfile = nf;
+ f = nf;
+ gzfd = fd;
+ }
+ return openwrap(f, "/mnt/wrap", all);
+}
+
+openwrap(f : string, d : string, all : int) : ref Wrapped
+{
+ if (d == nil)
+ d = "/";
+ if((w := openmount(f, d)) != nil)
+ return w; # don't mess about if /wrap/ structure exists
+ (ok, dir) := sys->stat(f);
+ if (ok < 0)
+ return nil;
+ # accept root/ or root/wrap/pkgname
+ if (dir.mode & Sys->DMDIR) {
+ d = f;
+ if ((i := strstr(f, "/wrap/")) >= 0) {
+ f = f[i+6:];
+ d = d[0:i+6];
+ }
+ else
+ f = nil;
+ return openmount(f, d);
+ }
+ (ok, dir) = sys->stat(f);
+ if (ok < 0 || dir.mode & Sys->DMDIR)
+ return openmount(f, d); # ?
+ if (mountarch(f, d, all) < 0)
+ return nil;
+ return openmount(nil, d);
+}
+
+getfirstdir(d : string) : string
+{
+ if ((fd := sys->open(d, Sys->OREAD)) == nil)
+ return nil;
+ for(;;){
+ (n, dir) := sys->dirread(fd);
+ if(n <= 0)
+ break;
+ for(i:=0; i<n; i++)
+ if(dir[i].mode & Sys->DMDIR)
+ return dir[i].name;
+ }
+ return nil;
+}
+
+NONE : con 0;
+
+sniffdir(base : string, elem : string) : (int, int)
+{
+ # t := int elem;
+ t := string2now(elem, 0);
+ if (t == 0)
+ return (NONE, 0);
+ # buf := sys->sprint("%ud", t);
+ # if (buf != elem)
+ # return (NONE, 0);
+ rv := NONE;
+ p := base + "/" + elem + "/package";
+ (ok, nil) := sys->stat(p);
+ if (ok >= 0)
+ rv |= FULL;
+ p = base + "/" + elem + "/update";
+ (ok, nil) = sys->stat(p);
+ if (ok >= 0)
+ rv |= UPD;
+ return (rv, t);
+}
+
+openupdate(d : string) : (array of Update, int, int)
+{
+ u : array of Update;
+
+ if ((fd := sys->open(d, Sys->OREAD)) == nil)
+ return (nil, -1, 0);
+ #
+ # We are looking to find the most recent full
+ # package; anything before that is irrelevant.
+ # Also figure out the most recent package update.
+ # Non-package updates before that are irrelevant.
+ # If there are no packages installed,
+ # grab all the updates we can find.
+ #
+ tbase := -1;
+ tfull := -1;
+ nu := 0;
+ for(;;){
+ (n, dir) := sys->dirread(fd);
+ if(n <= 0)
+ break;
+ for(i := 0; i < n; i++){
+ (k, t) := sniffdir(d, dir[i].name);
+ case (k) {
+ FULL =>
+ nu++;
+ if (t > tfull)
+ tfull = t;
+ if (t > tbase)
+ tbase = t;
+ FULL|UPD =>
+ nu++;
+ if (t > tfull)
+ tfull = t;
+ UPD =>
+ nu++;
+ }
+ }
+ }
+ if (nu == 0)
+ return (nil, -1, 0);
+ u = nil;
+ nu = 0;
+ if ((fd = sys->open(d, Sys->OREAD)) == nil)
+ return (nil, -1, 0);
+ for(;;){
+ (n, dir) := sys->dirread(fd);
+ if(n <= 0)
+ break;
+ for(i := 0; i < n; i++){
+ (k, t) := sniffdir(d, dir[i].name);
+ if (k == 0)
+ continue;
+ if (t < tbase)
+ continue;
+ if (t < tfull && k == UPD)
+ continue;
+ if (nu%8 == 0) {
+ newu := array[nu+8] of Update;
+ newu[0:] = u[0:nu];
+ u = newu;
+ }
+ u[nu].typ = k;
+ if (readupdate(u, nu, d, dir[i].name) != nil)
+ nu++;
+ }
+ }
+ if (nu == 0)
+ return (nil, -1, 0);
+ qsort(u, nu);
+ return (u, nu, tfull);
+}
+
+readupdate(u : array of Update, ui : int, base : string, elem : string) : array of Update
+{
+ # u[ui].dir = base + "/" + elem;
+ u[ui].dir = pathcat(base, elem);
+ p := u[ui].dir + "/desc";
+ u[ui].desc = readfile(p);
+ # u[ui].time = int elem;
+ u[ui].time = string2now(elem, 0);
+ p = u[ui].dir + "/md5sum";
+ u[ui].bmd5 = bufio->open(p, Bufio->OREAD);
+ p = u[ui].dir + "/update";
+ q := readfile(p);
+ if (q != nil)
+ u[ui].utime = int q;
+ else
+ u[ui].utime = 0;
+ if (u[ui].bmd5 == nil)
+ return nil;
+ return u;
+}
+
+readfile(s : string) : string
+{
+ (ok, d) := sys->stat(s);
+ if (ok < 0)
+ return nil;
+ buf := array[int d.length] of byte;
+ if ((fd := sys->open(s, Sys->OREAD)) == nil || sys->read(fd, buf, int d.length) != int d.length)
+ return nil;
+ s = string buf;
+ ls := len s;
+ if (s[ls-1] == '\n')
+ s = s[0:ls-1];
+ return s;
+}
+
+hex(c : int) : int
+{
+ if (c >= '0' && c <= '9')
+ return c-'0';
+ if (c >= 'a' && c <= 'f')
+ return c-'a'+10;
+ if (c >= 'A' && c <= 'F')
+ return c-'A'+10;
+ return -1;
+}
+
+getfileinfo(w : ref Wrapped, f : string, rdigest : array of byte, wdigest : array of byte, ardigest: array of byte) : (int, int)
+{
+ p : string;
+
+ if (w == nil)
+ return (-1, 0);
+ digest := array[keyring->MD5dlen] of { * => byte 0 };
+ for (i := w.nu-1; i >= 0; i--){
+ if ((p = bsearch(w.u[i].bmd5, f)) == nil)
+ continue;
+ if (p == nil)
+ continue;
+ k := 0;
+ while (k < len p && p[k] != ' ')
+ k++;
+ if (k == len p)
+ continue;
+ q := p[k+1:];
+ if (q == nil)
+ continue;
+ if (len q != 2*Keyring->MD5dlen+1)
+ continue;
+ for (j := 0; j < Keyring->MD5dlen; j++) {
+ a := hex(q[2*j]);
+ b := hex(q[2*j+1]);
+ if (a < 0 || b < 0)
+ break;
+ digest[j] = byte ((a<<4)|b);
+ }
+ if(j != Keyring->MD5dlen)
+ continue;
+ if(rdigest == nil || memcmp(rdigest, digest, keyring->MD5dlen) == 0 || (ardigest != nil && memcmp(ardigest, digest, keyring->MD5dlen) == 0))
+ break;
+ else
+ return (-1, 0); # NEW
+ }
+ if(i < 0)
+ return (-1, 0);
+ if(wdigest != nil)
+ wdigest[0:] = rdigest;
+ return (0, w.u[i].time);
+
+
+}
+
+bsearch(b : ref Bufio->Iobuf, p : string) : string
+{
+ if (b == nil)
+ return nil;
+ lo := 0;
+ b.seek(big 0, Bufio->SEEKEND);
+ hi := int b.offset();
+ l := len p;
+ while (lo < hi) {
+ m := (lo+hi)/2;
+ b.seek(big m, Bufio->SEEKSTART);
+ b.gets('\n');
+ if (int b.offset() == hi) {
+ bgetbackc(b);
+ m = int b.offset();
+ while (m-- > lo) {
+ if (bgetbackc(b) == '\n') {
+ b.getc();
+ break;
+ }
+ }
+ }
+ s := b.gets('\n');
+ if (len s >= l+1 && s[0:l] == p && (s[l] == ' ' || s[l] == '\n'))
+ return s;
+ if (s < p)
+ lo = int b.offset();
+ else
+ hi = int b.offset()-len s;
+ }
+ return nil;
+}
+
+bgetbackc(b : ref Bufio->Iobuf) : int
+{
+ m := int b.offset();
+ b.seek(big (m-1), Bufio->SEEKSTART);
+ c := b.getc();
+ b.ungetc();
+ return c;
+}
+
+strstr(s : string, p : string) : int
+{
+ lp := len p;
+ ls := len s;
+ for (i := 0; i < ls-lp; i++)
+ if (s[i:i+lp] == p)
+ return i;
+ return -1;
+}
+
+qsort(a : array of Update, n : int)
+{
+ i, j : int;
+ t : Update;
+
+ while(n > 1) {
+ i = n>>1;
+ t = a[0]; a[0] = a[i]; a[i] = t;
+ i = 0;
+ j = n;
+ for(;;) {
+ do
+ i++;
+ while(i < n && a[i].time < a[0].time);
+ do
+ j--;
+ while(j > 0 && a[j].time > a[0].time);
+ if(j < i)
+ break;
+ t = a[i]; a[i] = a[j]; a[j] = t;
+ }
+ t = a[0]; a[0] = a[j]; a[j] = t;
+ n = n-j-1;
+ if(j >= n) {
+ qsort(a, j);
+ a = a[j+1:];
+ } else {
+ qsort(a[j+1:], n);
+ n = j;
+ }
+ }
+}
+
+md5file(file : string, digest : array of byte) : int
+{
+ (ok, d) := sys->stat(file);
+ if (ok < 0)
+ return -1;
+ if (d.mode & Sys->DMDIR)
+ return 0;
+ bio := bufio->open(file, Bufio->OREAD);
+ if (bio == nil)
+ return -1;
+ # return md5sum(bio, digest, d.length);
+ buff := array[Sys->ATOMICIO] of byte;
+ ds := keyring->md5(nil, 0, nil, nil);
+ while ((n := bio.read(buff, len buff)) > 0)
+ keyring->md5(buff, n, nil, ds);
+ keyring->md5(nil, 0, digest, ds);
+ bio = nil;
+ return 0;
+}
+
+md5sum(b : ref Iobuf, digest : array of byte, leng : int) : int
+{
+ ds := keyring->md5(nil, 0, nil, nil);
+ buff := array[Sys->ATOMICIO] of byte;
+ while (leng > 0) {
+ if (leng > len buff)
+ n := len buff;
+ else
+ n = leng;
+ if ((n = b.read(buff, n)) <= 0)
+ return -1;
+ keyring->md5(buff, n, nil, ds);
+ leng -= n;
+ }
+ keyring->md5(nil, 0, digest, ds);
+ return 0;
+}
+
+md5conv(d : array of byte) : string
+{
+ s : string = nil;
+
+ for (i := 0; i < keyring->MD5dlen; i++)
+ s += sys->sprint("%.2ux", int d[i]);
+ return s;
+}
+
+zd : Sys->Dir;
+
+newd(time : int, uid : string, gid : string) : ref Sys->Dir
+{
+ d := ref Sys->Dir;
+ *d = zd;
+ d.uid = uid;
+ d.gid = gid;
+ d.mtime = time;
+ return d;
+}
+
+putwrapfile(b : ref Iobuf, name : string, time : int, elem : string, file : string, uid : string, gid : string)
+{
+ d := newd(time, uid, gid);
+ d.mode = 8r444;
+ (ok, dir) := sys->stat(file);
+ if (ok < 0)
+ sys->fprint(sys->fildes(2), "cannot stat %s: %r", file);
+ d.length = dir.length;
+ # s := "/wrap/"+name+"/"+sys->sprint("%ud", time)+"/"+elem;
+ s := "/wrap/"+name+"/"+now2string(time, 0)+"/"+elem;
+ arch->puthdr(b, s, d);
+ arch->putfile(b, file, int d.length);
+}
+
+putwrap(b : ref Iobuf, name : string, time : int, desc : string, utime : int, pkg : int, uid : string, gid : string)
+{
+ if (!(utime || pkg))
+ sys->fprint(sys->fildes(2), "bad precondition in putwrap()");
+ d := newd(time, uid, gid);
+ d.mode = Sys->DMDIR|8r775;
+ s := "/wrap";
+ arch->puthdr(b, s, d);
+ s += "/"+name;
+ arch->puthdr(b, s, d);
+ # s += "/"+sys->sprint("%ud", time);
+ s += "/"+now2string(time, 0);
+ arch->puthdr(b, s, d);
+ d.mode = 8r444;
+ s += "/";
+ dir := s;
+ if (utime) {
+ s = dir+"update";
+ d.length = big 23;
+ arch->puthdr(b, s, d);
+ arch->putstring(b, sys->sprint("%22ud\n", utime));
+ }
+ if (pkg) {
+ s = dir+"package";
+ d.length = big 0;
+ arch->puthdr(b, s, d);
+ }
+ if (desc != nil) {
+ s = dir+"desc";
+ d.length = big (len desc+1);
+ d.mode = 8r444;
+ arch->puthdr(b, s, d);
+ arch->putstring(b, desc+"\n");
+ }
+}
+
+memcmp(b1, b2 : array of byte, n : int) : int
+{
+ for (i := 0; i < n; i++)
+ if (b1[i] < b2[i])
+ return -1;
+ else if (b1[i] > b2[i])
+ return 1;
+ return 0;
+}
+
+strprefix(s: string, pre: string): int
+{
+ return len s >= (l := len pre) && s[0:l] == pre;
+}
+
+match(s: string, pre: list of string): int
+{
+ if(pre == nil || s == "/wrap" || strprefix(s, "/wrap/"))
+ return 1;
+ for( ; pre != nil; pre = tl pre)
+ if(strprefix(s, hd pre))
+ return 1;
+ return 0;
+}
+
+notmatch(s: string, pre: list of string): int
+{
+ if(pre == nil || s == "/wrap" || strprefix(s, "/wrap/"))
+ return 1;
+ for( ; pre != nil; pre = tl pre)
+ if(strprefix(s, hd pre))
+ return 0;
+ return 1;
+}
+
+pathcat(s : string, t : string) : string
+{
+ if (s == nil) return t;
+ if (t == nil) return s;
+ slashs := s[len s - 1] == '/';
+ slasht := t[0] == '/';
+ if (slashs && slasht)
+ return s + t[1:];
+ if (!slashs && !slasht)
+ return s + "/" + t;
+ return s + t;
+}
+
+md5filea(file : string, digest : array of byte) : int
+{
+ n, n0: int;
+
+ (ok, d) := sys->stat(file);
+ if (ok < 0)
+ return -1;
+ if (d.mode & Sys->DMDIR)
+ return 0;
+ bio := bufio->open(file, Bufio->OREAD);
+ if (bio == nil)
+ return -1;
+ buff := array[Sys->ATOMICIO] of byte;
+ m := len buff;
+ ds := keyring->md5(nil, 0, nil, nil);
+ r := 0;
+ while(1){
+ if(r){
+ if((n = bio.read(buff[1:], m-1)) <= 0)
+ break;
+ n++;
+ }
+ else{
+ if ((n = bio.read(buff, m)) <= 0)
+ break;
+ }
+ (n0, r) = remcr(buff, n);
+ if(r){
+ keyring->md5(buff, n0-1, nil, ds);
+ buff[0] = byte '\r';
+ }
+ else
+ keyring->md5(buff, n0, nil, ds);
+ }
+ if(r)
+ keyring->md5(buff, 1, nil, ds);
+ keyring->md5(nil, 0, digest, ds);
+ bio = nil;
+ return 0;
+}
+
+remcr(b: array of byte, n: int): (int, int)
+{
+ if(n == 0)
+ return (0, 0);
+ for(i := 0; i < n; ){
+ if(b[i] == byte '\r' && i+1 < n && b[i+1] == byte '\n')
+ b[i:] = b[i+1:n--];
+ else
+ i++;
+ }
+ return (n, b[n-1] == byte '\r');
+}
+
+TEN2EIGHT: con 100000000;
+
+now2string(n: int, flag: int): string
+{
+ if(flag == 0)
+ return sys->sprint("%ud", n);
+ if(n < 0)
+ return nil;
+ q := n/TEN2EIGHT;
+ s := "0" + string (n-TEN2EIGHT*q);
+ while(len s < 9)
+ s = "0" + s;
+ if(q <= 9)
+ s[0] = '0' + q - 0;
+ else if(q <= 21)
+ s[0] = 'A' + q - 10;
+ else
+ return nil;
+ return s;
+}
+
+string2now(s: string, flag: int): int
+{
+ if(flag == 0 && s[0] != 'A')
+ return int s;
+ if(len s != 9)
+ return 0;
+ r := int s[1: ];
+ c := s[0];
+ if(c >= '0' && c <= '9')
+ q := c - '0' + 0;
+ else if(c >= 'A' && c <= 'L')
+ q = c - 'A' + 10;
+ else
+ return 0;
+ n := TEN2EIGHT*q + r;
+ if(n < 0)
+ return 0;
+ return n;
+}
diff --git a/appl/cmd/install/wrap.m b/appl/cmd/install/wrap.m
new file mode 100644
index 00000000..c15624ca
--- /dev/null
+++ b/appl/cmd/install/wrap.m
@@ -0,0 +1,41 @@
+Wrap : module
+{
+ PATH : con "/dis/install/wrap.dis";
+
+ FULL, UPD : con iota+1;
+
+ Update : adt {
+ desc : string;
+ dir : string;
+ time : int;
+ utime : int;
+ bmd5 : ref Bufio->Iobuf;
+ typ : int;
+ };
+
+ Wrapped : adt {
+ name : string;
+ root : string;
+ tfull : int;
+ u : array of Update;
+ nu : int;
+ };
+
+ init: fn(bio: Bufio);
+ openwrap: fn(f : string, d : string, all : int) : ref Wrapped;
+ openwraphdr: fn(f : string, d : string, argl : list of string, all : int) : ref Wrapped;
+ getfileinfo: fn(w : ref Wrapped, f : string, rdigest : array of byte, wdigest: array of byte, ardigest: array of byte) : (int, int);
+ putwrapfile: fn(b : ref Bufio->Iobuf, name : string, time : int, elem : string, file : string, uid : string, gid : string);
+ putwrap: fn(b : ref Bufio->Iobuf, name : string, time : int, desc : string, utime : int, pkg : int, uid : string, gid : string);
+ md5file: fn(file : string, digest : array of byte) : int;
+ md5filea: fn(file : string, digest : array of byte) : int;
+ md5sum: fn(b : ref Bufio->Iobuf, digest : array of byte, leng : int) : int;
+ md5conv: fn(d : array of byte) : string;
+ # utilities
+ match: fn(s: string, pre: list of string): int;
+ notmatch: fn(s: string, pre: list of string): int;
+ memcmp: fn(b1, b2: array of byte, n: int): int;
+ end: fn();
+ now2string: fn(n: int, flag: int): string;
+ string2now: fn(s: string, flag: int): int;
+};
diff --git a/appl/cmd/install/wrap2list.b b/appl/cmd/install/wrap2list.b
new file mode 100644
index 00000000..d2656cf5
--- /dev/null
+++ b/appl/cmd/install/wrap2list.b
@@ -0,0 +1,305 @@
+#
+# Copyright © 2001 Vita Nuova (Holdings) Limited. All rights reserved.
+#
+
+implement Wrap2list;
+
+# make a version list suitable for SDS from /wrap
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+include "crc.m";
+ crcm : Crc;
+include "wrap.m";
+ wrap: Wrap;
+
+Wrap2list: module
+{
+ init : fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+HASHSZ: con 64;
+
+Element: type string;
+
+Hash: adt{
+ elems: array of Element;
+ nelems: int;
+};
+
+List: adt{
+ tabs: array of ref Hash;
+ init: fn(l: self ref List);
+ add: fn(l: self ref List, e: Element);
+ subtract: fn(l: self ref List, e: Element);
+ end: fn(l: self ref List): array of Element;
+};
+
+flist: ref List;
+
+hash(s: string): int
+{
+ h := 0;
+ n := len s;
+ for(i := 0; i < n; i++)
+ h += s[i];
+ if(h < 0)
+ h = -h;
+ return h%HASHSZ;
+}
+
+List.init(l: self ref List)
+{
+ ts := l.tabs = array[HASHSZ] of ref Hash;
+ for(i := 0; i < HASHSZ; i++){
+ t := ts[i] = ref Hash;
+ t.elems = array[HASHSZ] of Element;
+ t.nelems = 0;
+ }
+}
+
+List.add(l: self ref List, e: Element)
+{
+ h := hash(e);
+ t := l.tabs[h];
+ n := t.nelems;
+ es := t.elems;
+ for(i := 0; i < n; i++){
+ if(e == es[i])
+ return;
+ }
+ if(n == len es)
+ es = t.elems = (array[2*n] of Element)[0:] = es;
+ es[t.nelems++] = e;
+# sys->print("+ %s\n", e);
+}
+
+List.subtract(l: self ref List, e: Element)
+{
+ h := hash(e);
+ t := l.tabs[h];
+ n := t.nelems;
+ es := t.elems;
+ for(i := 0; i < n; i++){
+ if(e == es[i]){
+ es[i] = nil;
+ break;
+ }
+ }
+# sys->print("- %s\n", e);
+}
+
+List.end(l: self ref List): array of Element
+{
+ tot := 0;
+ ts := l.tabs;
+ for(i := 0; i < HASHSZ; i++)
+ tot += ts[i].nelems;
+ a := array[tot] of Element;
+ m := 0;
+ for(i = 0; i < HASHSZ; i++){
+ t := ts[i];
+ n := t.nelems;
+ es := t.elems;
+ a[m:] = es[0: n];
+ m += n;
+ }
+ return a;
+}
+
+usage()
+{
+ sys->fprint(stderr, "Usage: wrap2list [ file ... ]\n");
+ exit;
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ crcm = load Crc Crc->PATH;
+ wrap = load Wrap Wrap->PATH;
+ wrap->init(bufio);
+ if(argv != nil)
+ argv = tl argv;
+ init := 0;
+ if(argv != nil && hd argv == "-i"){
+ init = 1;
+ argv = tl argv;
+ }
+ stderr = sys->fildes(2);
+ # root := "/";
+ flist = ref List;
+ flist.init();
+ fd := sys->open("/wrap", Sys->OREAD);
+ for(;;){
+ (nd, d) := sys->dirread(fd);
+ if(nd <= 0)
+ break;
+ for(i:=0; i<nd; i++){
+ if((d[0].mode & Sys->DMDIR) && (w := wrap->openwrap(d[i].name, "/", 1)) != nil){
+ # sys->fprint(stderr, "%s %s %d %d\n", w.name, w.root, w.tfull, w.nu);
+ for(j := 0; j < w.nu; j++){
+ addfiles(w.u[j].bmd5);
+ if((b := bufio->open(w.u[j].dir+"/remove", Bufio->OREAD)) != nil)
+ subtractfiles(b);
+ # sys->fprint(stderr, "%d: %s %s %d %d %d\n", i, w.u[j].desc, w.u[j].dir, w.u[j].time, w.u[j].utime, w.u[j].typ);
+ }
+ }
+ }
+ }
+ for( ; argv != nil; argv = tl argv){
+ if((b := bufio->open(hd argv, Bufio->OREAD)) != nil)
+ addfiles(b);
+ }
+ out(uniq(rmnil(sort(flist.end()))), init);
+}
+
+addfiles(b: ref Bufio->Iobuf)
+{
+ b.seek(big 0, Bufio->SEEKSTART);
+ while((s := b.gets('\n')) != nil){
+ (n, l) := sys->tokenize(s, " \n");
+ if(n > 0)
+ flist.add(hd l);
+ }
+}
+
+subtractfiles(b: ref Bufio->Iobuf)
+{
+ b.seek(big 0, Bufio->SEEKSTART);
+ while((s := b.gets('\n')) != nil){
+ (n, l) := sys->tokenize(s, " \n");
+ if(n > 0)
+ flist.subtract(hd l);
+ }
+}
+
+out(fs: array of Element, init: int)
+{
+ nf := len fs;
+ for(i := 0; i < nf; i++){
+ f := fs[i];
+ outl(f, nil, init);
+ l := len f;
+ if(l >= 7 && f[l-7:] == "emu.new"){
+ g := f;
+ f[l-3] = 'e';
+ f[l-2] = 'x';
+ f[l-1] = 'e';
+ outl(f, g, init); # try emu.exe
+ outl(f[0: l-4], g, init); # try emu
+# sys->fprint(sys->fildes(2), "%s %s\n", f, g);
+ }
+ }
+}
+
+outl(f: string, g: string, init: int)
+{
+ (ok, d) := sys->stat(f);
+ if(ok < 0){
+ # sys->fprint(stderr, "cannot open %s\n", f);
+ return;
+ }
+ if(g == nil)
+ g = "-";
+ if(d.mode & Sys->DMDIR)
+ d.length = big 0;
+ if(init)
+ mtime := 0;
+ else
+ mtime = d.mtime;
+ sys->print("%s %s %d %d %d %d %d\n", f, g, int d.length, d.mode, mtime, crc(f, d), 0);
+}
+
+crc(f: string, d: Sys->Dir): int
+{
+ crcs := crcm->init(0, int 16rffffffff);
+ if(d.mode & Sys->DMDIR)
+ return 0;
+ fd := sys->open(f, Sys->OREAD);
+ if(fd == nil){
+ sys->fprint(stderr, "cannot open %s\n", f);
+ return 0;
+ }
+ crc := 0;
+ buf := array[Sys->ATOMICIO] of byte;
+ for(;;){
+ nr := sys->read(fd, buf, len buf);
+ if(nr < 0){
+ sys->fprint(stderr, "bad read on %s : %r\n", f);
+ return 0;
+ }
+ if(nr <= 0)
+ break;
+ crc = crcm->crc(crcs, buf, nr);
+ }
+ crcm->reset(crcs);
+ return crc;
+}
+
+sort(a: array of Element): array of Element
+{
+ qsort(a, len a);
+ return a;
+}
+
+rmnil(a: array of Element): array of Element
+{
+ n := len a;
+ for(i := 0; i < n; i++)
+ if(a[i] != nil)
+ break;
+ return a[i: n];
+}
+
+uniq(a: array of Element): array of Element
+{
+ n := len a;
+ for(i := 0; i < n-1; ){
+ if(a[i] == a[i+1])
+ a[i+1:] = a[i+2: n--];
+ else
+ i++;
+ }
+ return a[0: n];
+}
+
+qsort(a: array of Element, n: int)
+{
+ i, j: int;
+ t: Element;
+
+ while(n > 1){
+ i = n>>1;
+ t = a[0]; a[0] = a[i]; a[i] = t;
+ i = 0;
+ j = n;
+ for(;;){
+ do
+ i++;
+ while(i < n && a[i] < a[0]);
+ do
+ j--;
+ while(j > 0 && a[j] > a[0]);
+ if(j < i)
+ break;
+ t = a[i]; a[i] = a[j]; a[j] = t;
+ }
+ t = a[0]; a[0] = a[j]; a[j] = t;
+ n = n-j-1;
+ if(j >= n){
+ qsort(a, j);
+ a = a[j+1:];
+ }else{
+ qsort(a[j+1:], n);
+ n = j;
+ }
+ }
+}
diff --git a/appl/cmd/iostats.b b/appl/cmd/iostats.b
new file mode 100644
index 00000000..c70aadc4
--- /dev/null
+++ b/appl/cmd/iostats.b
@@ -0,0 +1,635 @@
+implement Iostats;
+
+#
+# iostats - gather file system access statistics
+#
+
+include "sys.m";
+ sys: Sys;
+ Qid: import sys;
+
+include "draw.m";
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg, NOFID, NOTAG: import styx;
+
+include "workdir.m";
+ workdir: Workdir;
+
+include "sh.m";
+
+include "arg.m";
+
+Iostats: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Maxmsg: con 128*1024+Styx->IOHDRSZ;
+Ns2ms: con big 1000000;
+
+Rpc: adt
+{
+ name: string;
+ count: big;
+ time: big;
+ lo: big;
+ hi: big;
+ bin: big;
+ bout: big;
+};
+
+Stats: adt
+{
+ totread: big;
+ totwrite: big;
+ nrpc: int;
+ nproto: int;
+ rpc: array of ref Rpc; # Maxrpc
+};
+
+Fid: adt {
+ nr: int; # fid number
+ path: ref Path; # path used to open Fid
+ qid: Qid;
+ mode: int;
+ nread: big;
+ nwrite: big;
+ bread: big;
+ bwrite: big;
+ offset: big; # for directories
+};
+
+Path: adt {
+ parent: cyclic ref Path;
+ name: string;
+};
+
+Frec: adt
+{
+ op: ref Path; # first name?
+ qid: Qid;
+ nread: big;
+ nwrite: big;
+ bread: big;
+ bwrite: big;
+ opens: int;
+};
+
+Tag: adt {
+ m: ref Tmsg;
+ fid: ref Fid;
+ stime: big;
+ next: cyclic ref Tag;
+};
+
+NTAGHASH: con 1<<4; # power of 2
+NFIDHASH: con 1<<4; # power of 2
+
+tags := array[NTAGHASH] of ref Tag;
+fids := array[NFIDHASH] of list of ref Fid;
+dbg := 0;
+
+stats: Stats;
+frecs: list of ref Frec;
+
+replymap := array[tagof Rmsg.Stat+1] of {
+ tagof Rmsg.Version => tagof Tmsg.Version,
+ tagof Rmsg.Auth => tagof Tmsg.Auth,
+ tagof Rmsg.Attach => tagof Tmsg.Attach,
+ tagof Rmsg.Flush => tagof Tmsg.Flush,
+ tagof Rmsg.Clunk => tagof Tmsg.Clunk,
+ tagof Rmsg.Remove => tagof Tmsg.Remove,
+ tagof Rmsg.Wstat => tagof Tmsg.Wstat,
+ tagof Rmsg.Walk => tagof Tmsg.Walk,
+ tagof Rmsg.Create => tagof Tmsg.Create,
+ tagof Rmsg.Open => tagof Tmsg.Open,
+ tagof Rmsg.Read => tagof Tmsg.Read,
+ tagof Rmsg.Write => tagof Tmsg.Write,
+ tagof Rmsg.Stat => tagof Tmsg.Stat,
+ * => -1,
+};
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ workdir = load Workdir Workdir->PATH;
+ sh := load Sh Sh->PATH;
+ styx = load Styx Styx->PATH;
+ styx->init();
+
+ wd := workdir->init();
+
+ dbfile := "iostats.out";
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("iostats [-d] [-f debugfile] cmds [args ...]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => dbg++;
+ 'f' => dbfile = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->FORKFD|Sys->FORKNS|Sys->NEWPGRP|Sys->FORKENV, nil);
+
+ if(dbg){
+ fd := sys->create(dbfile, Sys->OWRITE, 8r666);
+ if(fd == nil)
+ fatal(sys->sprint("can't create %q: %r", dbfile));
+ sys->dup(fd.fd, 2);
+ }
+
+ if(sys->chdir("/") < 0)
+ fatal(sys->sprint("chdir /: %r"));
+
+ stats.totread = big 0;
+ stats.totwrite = big 0;
+ stats.nrpc = 0;
+ stats.nproto = 0;
+ stats.rpc = array[tagof Tmsg.Wstat + 1] of ref Rpc;
+ stats.rpc[tagof Tmsg.Version] = mkrpc("version");
+ stats.rpc[tagof Tmsg.Auth] = mkrpc("auth");
+ stats.rpc[tagof Tmsg.Flush] = mkrpc("flush");
+ stats.rpc[tagof Tmsg.Attach] = mkrpc("attach");
+ stats.rpc[tagof Tmsg.Walk] = mkrpc("walk");
+ stats.rpc[tagof Tmsg.Open] = mkrpc("open");
+ stats.rpc[tagof Tmsg.Create] = mkrpc("create");
+ stats.rpc[tagof Tmsg.Clunk] = mkrpc("clunk");
+ stats.rpc[tagof Tmsg.Read] = mkrpc("read");
+ stats.rpc[tagof Tmsg.Write] = mkrpc("write");
+ stats.rpc[tagof Tmsg.Remove] = mkrpc("remove");
+ stats.rpc[tagof Tmsg.Stat] = mkrpc("stat");
+ stats.rpc[tagof Tmsg.Wstat] = mkrpc("wstat");
+
+ mpipe := array[2] of ref Sys->FD;
+ if(sys->pipe(mpipe) < 0)
+ fatal(sys->sprint("can't create pipe: %r"));
+ pids := chan of int;
+ cmddone := chan of int;
+ spawn cmd(sh, ctxt, args, wd, mpipe[0], pids, cmddone);
+ <-pids;
+ mpipe[0] = nil;
+ epipe := array[2] of ref Sys->FD;
+ if(sys->pipe(epipe) < 0)
+ fatal(sys->sprint("can't create pipe: %r"));
+ spawn export(epipe[1], pids);
+ <-pids;
+ epipe[1] = nil;
+ iodone := chan of int;
+ spawn iostats(epipe[0], mpipe[1], pids, iodone);
+ <-pids;
+ epipe[0] = mpipe[1] = nil;
+ <-cmddone;
+ <-iodone;
+ results();
+}
+
+cmd(sh: Sh, ctxt: ref Draw->Context, args: list of string, wdir: string, fsfd: ref Sys->FD, pids: chan of int, done: chan of int)
+{
+ {
+ pids <-= sys->pctl(Sys->FORKNS|Sys->FORKFD, nil);
+ if(sys->mount(fsfd, nil, "/", Sys->MREPL, "") < 0)
+ fatal(sys->sprint("can't mount /: %r"));
+ fsfd = nil;
+ sys->bind("#e", "/env", Sys->MREPL | Sys->MCREATE);
+ sys->bind("#d", "/fd", Sys->MREPL); # better than nothing
+ if(sys->chdir(wdir) < 0)
+ fatal(sys->sprint("can't chdir to %s: %r", wdir));
+ sh->run(ctxt, args);
+ }exception{
+ "fail:*" =>
+ ; # don't mention it
+ * =>
+ raise; # cause the fault
+ }
+ done <-= 1;
+}
+
+iostats(expfd: ref Sys->FD, mountfd: ref Sys->FD, pids: chan of int, done: chan of int)
+{
+ pids <-= sys->pctl(Sys->NEWFD|Sys->NEWPGRP, 1 :: 2 :: expfd.fd :: mountfd.fd :: nil);
+ timefd := sys->open("/dev/time", Sys->OREAD);
+ if(timefd == nil)
+ fatal(sys->sprint("can't open /dev/time: %r"));
+ tmsgs := chan of (int, ref Tmsg);
+ spawn Treader(mountfd, expfd, tmsgs);
+ (tpid, nil) := <-tmsgs;
+ rmsgs := chan of (int, ref Rmsg);
+ spawn Rreader(expfd, mountfd, rmsgs);
+ (rpid, nil) := <-rmsgs;
+ expfd = mountfd = nil;
+ stderr := sys->fildes(2);
+Run:
+ for(;;)alt{
+ (n, t) := <-tmsgs => # n.b.: received on tmsgs before it goes to server
+ if(t == nil || tagof t == tagof Tmsg.Readerror)
+ break Run; # TO DO?
+ if(dbg)
+ sys->fprint(stderr, "->%s\n", t.text());
+ tag := newtag(t, nsec(timefd));
+ stats.nrpc++;
+ stats.nproto += n;
+ rpc := stats.rpc[tagof t];
+ if(rpc == nil){
+ sys->fprint(stderr, "iostats: unexpected T-msg %d\n", tagof t);
+ continue;
+ }
+ rpc.count++;
+ rpc.bin += big n;
+ pick pt := t {
+ Auth =>
+ tag.fid = newfid(pt.afid);
+ Attach =>
+ tag.fid = newfid(pt.fid);
+ Walk =>
+ tag.fid = findfid(pt.fid);
+ Open =>
+ tag.fid = findfid(pt.fid);
+ Create =>
+ tag.fid = findfid(pt.fid);
+ Read =>
+ tag.fid = findfid(pt.fid);
+ Write =>
+ tag.fid = findfid(pt.fid);
+ pt.data = nil; # don't need to keep data
+ Clunk or
+ Stat or
+ Remove =>
+ tag.fid = findfid(pt.fid);
+ Wstat =>
+ tag.fid = findfid(pt.fid);
+ }
+ (n, r) := <-rmsgs =>
+ if(r == nil || tagof r == tagof Rmsg.Readerror){
+ break Run; # TO DO
+ }
+ if(dbg)
+ sys->fprint(stderr, "<-%s\n", r.text());
+ stats.nproto += n;
+ tag := findtag(r.tag, 1);
+ if(tag == nil)
+ continue; # client or server error TO DO: account for flush
+ if(tagof r < len replymap && (tt := replymap[tagof r]) >= 0 && (rpc := stats.rpc[tt]) != nil){
+ update(rpc, nsec(timefd)-tag.stime);
+ rpc.bout += big n;
+ }
+ fid := tag.fid;
+ pick pr := r {
+ Error =>
+ pick m := tag.m {
+ Auth =>
+ if(fid != nil){
+ if(fid.nread != big 0 || fid.nwrite != big 0)
+ fidreport(fid);
+ freefid(fid);
+ }
+ }
+ Version =>
+ # could pick up message size
+ # flush fids/tags
+ tags = array[len tags] of ref Tag;
+ fids = array[len fids] of list of ref Fid;
+ Auth =>
+ # afid from fid.t, qaid from auth
+ if(fid != nil){
+ fid.qid = pr.aqid;
+ fid.path = ref Path(nil, "#auth");
+ }
+ Attach =>
+ if(fid != nil){
+ fid.qid = pr.qid;
+ fid.path = ref Path(nil, "/");
+ }
+ Walk =>
+ pick m := tag.m {
+ Walk =>
+ if(len pr.qids != len m.names)
+ break; # walk failed, no change
+ if(fid == nil)
+ break;
+ if(m.newfid != m.fid){
+ nf := newfid(m.newfid);
+ nf.path = fid.path;
+ fid = nf; # walk new fid
+ }
+ for(i := 0; i < len m.names; i++){
+ fid.qid = pr.qids[i];
+ if(m.names[i] == ".."){
+ if(fid.path.parent != nil)
+ fid.path = fid.path.parent;
+ }else
+ fid.path = ref Path(fid.path, m.names[i]);
+ }
+ }
+ Open or
+ Create =>
+ if(fid != nil)
+ fid.qid = pr.qid;
+ Read =>
+ fid.nread++;
+ nr := big len pr.data;
+ fid.bread += nr;
+ stats.totread += nr;
+ Write =>
+ # count
+ fid.nwrite++;
+ fid.bwrite += big pr.count;
+ stats.totwrite += big pr.count;
+ Flush =>
+ pick m := tag.m {
+ Flush =>
+ findtag(m.oldtag, 1); # discard if there
+ }
+ Clunk or
+ Remove =>
+ if(fid != nil){
+ if(fid.nread != big 0 || fid.nwrite != big 0)
+ fidreport(fid);
+ freefid(fid);
+ }
+ }
+ }
+ kill(rpid, "kill");
+ kill(tpid, "kill");
+ done <-= 1;
+}
+
+results()
+{
+ stderr := sys->fildes(2);
+ rpc := stats.rpc[tagof Tmsg.Read];
+ brpsec := real stats.totread / ((real rpc.time/1.0e9)+.000001);
+
+ rpc = stats.rpc[tagof Tmsg.Write];
+ bwpsec := real stats.totwrite / ((real rpc.time/1.0e9)+.000001);
+
+ ttime := big 0;
+ for(n := 0; n < len stats.rpc; n++){
+ rpc = stats.rpc[n];
+ if(rpc == nil || rpc.count == big 0)
+ continue;
+ ttime += rpc.time;
+ }
+
+ bppsec := real stats.nproto / ((real ttime/1.0e9)+.000001);
+
+ sys->fprint(stderr, "\nread %bud bytes, %g Kb/sec\n", stats.totread, brpsec/1024.0);
+ sys->fprint(stderr, "write %bud bytes, %g Kb/sec\n", stats.totwrite, bwpsec/1024.0);
+ sys->fprint(stderr, "protocol %ud bytes, %g Kb/sec\n", stats.nproto, bppsec/1024.0);
+ sys->fprint(stderr, "rpc %ud count\n\n", stats.nrpc);
+
+ sys->fprint(stderr, "%-10s %5s %5s %5s %5s %5s T R\n",
+ "Message", "Count", "Low", "High", "Time", " Avg");
+
+ for(n = 0; n < len stats.rpc; n++){
+ rpc = stats.rpc[n];
+ if(rpc == nil || rpc.count == big 0)
+ continue;
+ sys->fprint(stderr, "%-10s %5bud %5bud %5bud %5bud %5bud ms %8bud %8bud bytes\n",
+ rpc.name,
+ rpc.count,
+ rpc.lo/Ns2ms,
+ rpc.hi/Ns2ms,
+ rpc.time/Ns2ms,
+ rpc.time/Ns2ms/rpc.count,
+ rpc.bin,
+ rpc.bout);
+ }
+
+ # unclunked fids
+ for(n = 0; n < NFIDHASH; n++)
+ for(fl := fids[n]; fl != nil; fl = tl fl){
+ fid := hd fl;
+ if(fid.nread != big 0 || fid.nwrite != big 0)
+ fidreport(fid);
+ }
+ if(frecs == nil)
+ exit;
+
+ sys->fprint(stderr, "\nOpens Reads (bytes) Writes (bytes) File\n");
+ for(frl := frecs; frl != nil; frl = tl frl){
+ fr := hd frl;
+ case s := makepath(fr.op) {
+ "/fd/0" => s = "(stdin)";
+ "/fd/1" => s = "(stdout)";
+ "/fd/2" => s = "(stderr)";
+ "" => s = "/.";
+ }
+ sys->fprint(stderr, "%5ud %8bud %8bud %8bud %8bud %s\n", fr.opens, fr.nread, fr.bread,
+ fr.nwrite, fr.bwrite, s);
+ }
+}
+
+Treader(fd: ref Sys->FD, ofd: ref Sys->FD, out: chan of (int, ref Tmsg))
+{
+ out <-= (sys->pctl(0, nil), nil);
+ fd = sys->fildes(fd.fd);
+ ofd = sys->fildes(ofd.fd);
+ for(;;){
+ (a, err) := styx->readmsg(fd, Maxmsg);
+ if(err != nil){
+ out <-= (0, ref Tmsg.Readerror(0, err));
+ break;
+ }
+ if(a == nil){
+ out <-= (0, nil);
+ break;
+ }
+ (nil, m) := Tmsg.unpack(a);
+ if(m == nil){
+ out <-= (0, ref Tmsg.Readerror(0, "bad Styx T-message format"));
+ break;
+ }
+ out <-= (len a, m);
+ sys->write(ofd, a, len a); # TO DO: errors
+ }
+}
+
+Rreader(fd: ref Sys->FD, ofd: ref Sys->FD, out: chan of (int, ref Rmsg))
+{
+ out <-= (sys->pctl(0, nil), nil);
+ fd = sys->fildes(fd.fd);
+ ofd = sys->fildes(ofd.fd);
+ for(;;){
+ (a, err) := styx->readmsg(fd, Maxmsg);
+ if(err != nil){
+ out <-= (0, ref Rmsg.Readerror(0, err));
+ break;
+ }
+ if(a == nil){
+ out <-= (0, nil);
+ break;
+ }
+ (nil, m) := Rmsg.unpack(a);
+ if(m == nil){
+ out <-= (0, ref Rmsg.Readerror(0, "bad Styx R-message format"));
+ break;
+ }
+ out <-= (len a, m);
+ sys->write(ofd, a, len a); # TO DO: errors
+ }
+}
+
+reply(fd: ref Sys->FD, m: ref Rmsg)
+{
+ d := m.pack();
+ sys->write(fd, d, len d);
+}
+
+mkrpc(s: string): ref Rpc
+{
+ return ref Rpc(s, big 0, big 0, big 1 << 40, big 0, big 0, big 0);
+}
+
+newfid(nr: int): ref Fid
+{
+ h := nr%NFIDHASH;
+ for(fl := fids[h]; fl != nil; fl = tl fl)
+ if((hd fl).nr == nr)
+ return hd fl; # shouldn't happen: faulty client
+ fid := ref Fid;
+ fid.nr = nr;
+ fid.nread = big 0;
+ fid.nwrite = big 0;
+ fid.bread = big 0;
+ fid.bwrite = big 0;
+ fid.qid = Qid(big 0, 0, -1);
+ fids[h] = fid :: fids[h];
+ return fid;
+}
+
+findfid(nr: int): ref Fid
+{
+ for(fl := fids[nr%NFIDHASH]; fl != nil; fl = tl fl)
+ if((hd fl).nr == nr)
+ return hd fl;
+ return nil;
+}
+
+freefid(fid: ref Fid)
+{
+ h := fid.nr%NFIDHASH;
+ nl: list of ref Fid;
+ for(fl := fids[h]; fl != nil; fl = tl fl)
+ if((hd fl).nr != fid.nr)
+ nl = hd fl :: nl;
+ fids[h] = nl;
+}
+
+makepath(p: ref Path): string
+{
+ nl: list of string;
+ for(; p != nil; p = p.parent)
+ if(p.name != "/")
+ nl = p.name :: nl;
+ s := "";
+ for(; nl != nil; nl = tl nl)
+ if(s != nil)
+ s += "/" + hd nl;
+ else
+ s = hd nl;
+ return "/"+s;
+}
+
+fatal(s: string)
+{
+ sys->fprint(sys->fildes(2), "iostats: %s: %r\n", s);
+ raise "fatal:error";
+}
+
+nsec(fd: ref Sys->FD): big
+{
+ buf := array[100] of byte;
+ n := sys->pread(fd, buf, len buf, big 0);
+ if(n <= 0)
+ return big 0;
+ return big string buf[0:n];
+}
+
+fidreport(f: ref Fid)
+{
+ for(fl := frecs; fl != nil; fl = tl fl){
+ fr := hd fl;
+ if(eqqid(f.qid, fr.qid)){
+ # could put f.path in list of paths if aliases were interesting
+ fr.nread += f.nread;
+ fr.nwrite += f.nwrite;
+ fr.bread += f.bread;
+ fr.bwrite += f.bwrite;
+ fr.opens++;
+ return;
+ }
+ }
+
+ fr := ref Frec;
+ fr.op = f.path;
+ fr.qid = f.qid;
+ fr.nread = f.nread;
+ fr.nwrite = f.nwrite;
+ fr.bread = f.bread;
+ fr.bwrite = f.bwrite;
+ fr.opens = 1;
+ frecs = fr :: frecs;
+}
+
+update(rpc: ref Rpc, t: big)
+{
+ if(t < big 0)
+ t = big 0;
+
+ rpc.time += t;
+ if(t < rpc.lo)
+ rpc.lo = t;
+ if(t > rpc.hi)
+ rpc.hi = t;
+}
+
+newtag(m: ref Tmsg, t: big): ref Tag
+{
+ slot := m.tag & (NTAGHASH - 1);
+ tag := ref Tag(m, nil, t, tags[slot]);
+ tags[slot] = tag;
+ return tag;
+}
+
+findtag(tag: int, destroy: int): ref Tag
+{
+ slot := tag & (NTAGHASH - 1);
+ prev: ref Tag;
+ for(t := tags[slot]; t != nil; t = t.next){
+ if(t.m.tag == tag)
+ break;
+ prev = t;
+ }
+ if(t == nil || !destroy)
+ return t;
+ if(prev == nil)
+ tags[slot] = t.next;
+ else
+ prev.next = t.next;
+ return t;
+}
+
+eqqid(a, b: Qid): int
+{
+ return a.path == b.path && a.qtype == b.qtype;
+}
+
+export(fd: ref Sys->FD, pid: chan of int)
+{
+ pid <-= sys->pctl(Sys->NEWFD|Sys->FORKNS, fd.fd::0::1::2::nil);
+ sys->export(fd, "/", Sys->EXPWAIT);
+}
+
+kill(pid: int, what: string)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "%s", what);
+}
diff --git a/appl/cmd/ip/bootpd.b b/appl/cmd/ip/bootpd.b
new file mode 100644
index 00000000..bf3313b2
--- /dev/null
+++ b/appl/cmd/ip/bootpd.b
@@ -0,0 +1,662 @@
+implement Bootpd;
+
+#
+# to do:
+# DHCP
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "attrdb.m";
+ attrdb: Attrdb;
+ Attr, Db, Dbentry, Tuples: import attrdb;
+
+include "ip.m";
+ ip: IP;
+ IPaddr, Udphdr: import ip;
+
+include "ipattr.m";
+ ipattr: IPattr;
+
+include "ether.m";
+ ether: Ether;
+
+include "arg.m";
+
+Bootpd: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+debug: int;
+sniff: int;
+verbose: int;
+
+siaddr: IPaddr;
+netmask: IPaddr;
+myname: string;
+progname := "bootpd";
+net := "/net";
+ndb: ref Db;
+ndbfile := "/lib/ndb/local";
+mtime := 0;
+testing := 0;
+
+Udphdrsize: con IP->OUdphdrlen;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ loadfail(Bufio->PATH);
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ loadfail(Attrdb->PATH);
+ attrdb->init();
+ ip = load IP IP->PATH;
+ if(ip == nil)
+ loadfail(IP->PATH);
+ ip->init();
+ ipattr = load IPattr IPattr->PATH;
+ if(ipattr == nil)
+ loadfail(IPattr->PATH);
+ ipattr->init(attrdb, ip);
+ ether = load Ether Ether->PATH;
+ if(ether == nil)
+ loadfail(Ether->PATH);
+ ether->init();
+
+ verbose = 1;
+ sniff = 0;
+ debug = 0;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ raise "fail: load Arg";
+ arg->init(args);
+ arg->setusage("bootpd [-dsqv] [-f file] [-x network]");
+ progname = arg->progname();
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => debug++;
+ 's' => sniff = 1; debug = 255;
+ 'q' => verbose = 0;
+ 'v' => verbose = 1;
+ 'x' => net = arg->earg();
+ 'f' => ndbfile = arg->earg();
+ 't' => testing = 1; debug = 1; verbose = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->FORKFD|Sys->FORKNS, nil);
+
+ if(!sniff && (err := dbread()) != nil)
+ error(err);
+
+ myname = sysname();
+ if(myname == nil)
+ error("system name not set");
+ (siaddr, err) = csquery(myname);
+ if(err != nil)
+ error(sys->sprint("can't find IP address for %s: %s", myname, err));
+ if(debug)
+ sys->fprint(stderr, "bootpd: local IP address is %s\n", siaddr.text());
+
+ addr := net+"/udp!*!67";
+ if(testing)
+ addr = net+"/udp!*!499";
+ if(debug)
+ sys->fprint(stderr, "bootpd: announcing %s\n", addr);
+ (ok, c) := sys->announce(addr);
+ if(ok < 0)
+ error(sys->sprint("can't announce %s: %r", addr));
+ if(sys->fprint(c.cfd, "headers") < 0)
+ error(sys->sprint("can't set headers mode: %r"));
+ sys->fprint(c.cfd, "oldheaders");
+
+ if(debug)
+ sys->fprint(stderr, "bootpd: opening %s/data\n", c.dir);
+ c.dfd = sys->open(c.dir+"/data", sys->ORDWR);
+ if(c.dfd == nil)
+ error(sys->sprint("can't open %s/data: %r", c.dir));
+
+ spawn server(c);
+}
+
+loadfail(s: string)
+{
+ error(sys->sprint("can't load %s: %r", s));
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "bootpd: %s\n", s);
+ raise "fail:error";
+}
+
+server(c: Sys->Connection)
+{
+ buf := array[2048] of byte;
+ badread := 0;
+ for(;;) {
+ n := sys->read(c.dfd, buf, len buf);
+ if(n <0) {
+ if (badread++ > 10)
+ break;
+ continue;
+ }
+ badread = 0;
+ if(n < Udphdrsize) {
+ if(debug)
+ sys->fprint(stderr, "bootpd: short Udphdr: %d bytes\n", n);
+ continue;
+ }
+ hdr := Udphdr.unpack(buf, Udphdrsize);
+ if(debug)
+ sys->fprint(stderr, "bootpd: received request from udp!%s!%d\n", hdr.raddr.text(), hdr.rport);
+ if(n < Udphdrsize+300) {
+ if(debug)
+ sys->fprint(stderr, "bootpd: short request of %d bytes\n", n - Udphdrsize);
+ continue;
+ }
+
+ (bootp, err) := Bootp.unpack(buf[Udphdrsize:]);
+ if(err != nil) {
+ if(debug)
+ sys->fprint(stderr, "bootpd: can't unpack packet: %s\n", err);
+ continue;
+ }
+ if(debug >= 2)
+ sys->fprint(stderr, "bootpd: recvd {%s}\n", bootp.text());
+ if(sniff)
+ continue;
+ if(bootp.htype != 1 || bootp.hlen != 6) {
+ # if it isn't ether, we don't do it
+ if(debug)
+ sys->fprint(stderr, "bootpd: hardware type not ether; ignoring.\n");
+ continue;
+ }
+ if((err = dbread()) != nil) {
+ sys->fprint(stderr, "bootpd: getreply: dbread failed: %s\n", err);
+ continue;
+ }
+ rec := lookup(bootp);
+ if(rec == nil) {
+ # we can't answer this request
+ if(debug)
+ sys->fprint(stderr, "bootpd: cannot answer request.\n");
+ continue;
+ }
+ if(debug)
+ sys->fprint(stderr, "bootpd: found a matching entry: {%s}\n", rec.text());
+ mkreply(bootp, rec);
+ if(verbose)
+ sys->print("bootpd: %s -> %s %s\n", ether->text(rec.ha), rec.hostname, rec.ip.text());
+ if(debug)
+ sys->fprint(stderr, "bootpd: reply {%s}\n", bootp.text());
+ repl := bootp.pack();
+ if(!testing)
+ arpenter(rec.ip.text(), ether->text(rec.ha));
+ send(hdr, repl);
+ }
+ sys->fprint(stderr, "bootpd: %d read errors: %r\n", badread);
+}
+
+arpenter(ip, ha: string)
+{
+ if(debug)
+ sys->fprint(stderr, "bootpd: arp: %s -> %s\n", ip, ha);
+ fd := sys->open(net+"/arp", Sys->OWRITE);
+ if(fd == nil) {
+ if(debug)
+ sys->fprint(stderr, "bootpd: arp open failed: %r\n");
+ return;
+ }
+ if(sys->fprint(fd, "add %s %s", ip, ha) < 0){
+ if(debug)
+ sys->fprint(stderr, "bootpd: error writing arp: %r\n");
+ }
+}
+
+sysname(): string
+{
+ t := rf("/dev/sysname");
+ if(t != nil)
+ return t;
+ return rf("#e/sysname");
+}
+
+rf(name: string): string
+{
+ fd := sys->open(name, Sys->OREAD);
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ return nil;
+ return string buf[0:n];
+}
+
+csquery(name: string): (IPaddr, string)
+{
+ siaddr = ip->noaddr;
+ # get a local IP address by translating our sysname with cs(8)
+ csfile := net+"/cs";
+ fd := sys->open(net+"/cs", Sys->ORDWR);
+ if(fd == nil)
+ return (ip->noaddr, sys->sprint("can't open %s/cs: %r", csfile));
+ if(sys->fprint(fd, "net!%s!0", name) < 0)
+ return (ip->noaddr, sys->sprint("can't translate net!%s!0: %r", name));
+ sys->seek(fd, big 0, 0);
+ a := array[1024] of byte;
+ n := sys->read(fd, a, len a);
+ if(n <= 0)
+ return (ip->noaddr, "no result from "+csfile);
+ reply := string a[0:n];
+ (l, addr):= sys->tokenize(reply, " ");
+ if(l != 2)
+ return (ip->noaddr, "bad cs reply format");
+ (l, addr) = sys->tokenize(hd tl addr, "!");
+ if(l < 2)
+ return (ip->noaddr, "bad cs reply format");
+ (ok, ipa) := IPaddr.parse(hd addr);
+ if(ok < 0 || !ipok(siaddr))
+ return (ip->noaddr, "can't parse address: "+hd addr);
+ return (ipa, nil);
+}
+
+Hostinfo: adt {
+ hostname: string;
+
+ ha: array of byte; # hardware addr
+ ip: IPaddr; # client IP addr
+ bootf: string; # boot file path
+ netmask: IPaddr; # subnet mask
+ ipgw: IPaddr; # gateway IP addr
+ fs: IPaddr; # file server IP addr
+ auth: IPaddr; # authentication server IP addr
+
+ text: fn(inf: self ref Hostinfo): string;
+};
+
+send(hdr: ref Udphdr, msg: array of byte)
+{
+ replyaddr := net+"/udp!255.255.255.255!68"; # TO DO: gateway
+ if(testing)
+ replyaddr = sys->sprint("udp!%s!%d", hdr.raddr.text(), hdr.rport);
+ lport := "67";
+ if(testing)
+ lport = "499";
+ (n, c) := sys->dial(replyaddr, lport);
+ if(n < 0) {
+ sys->fprint(stderr, "bootpd: can't dial %s for reply: %r\n", replyaddr);
+ return;
+ }
+ n = sys->write(c.dfd, msg, len msg);
+ if(n != len msg)
+ sys->fprint(stderr, "bootpd: udp write error: %r\n");
+}
+
+mkreply(bootp: ref Bootp, rec: ref Hostinfo)
+{
+ bootp.op = 2; # boot reply
+ bootp.yiaddr = rec.ip;
+ bootp.siaddr = siaddr;
+ bootp.giaddr = ip->noaddr;
+ bootp.sname = myname;
+ bootp.file = string rec.bootf;
+ bootp.vend = array of byte sys->sprint("p9 %s %s %s %s", rec.netmask.text(), rec.fs.text(), rec.auth.text(), rec.ipgw.text());
+}
+
+dbread(): string
+{
+ if(ndb == nil){
+ ndb = Db.open(ndbfile);
+ if(ndb == nil)
+ return sys->sprint("cannot open %s: %r", ndbfile);
+ }else if(ndb.changed())
+ ndb.reopen();
+ return nil;
+}
+
+ipok(a: IPaddr): int
+{
+ return a.isv4() && !(a.eq(ip->v4noaddr) || a.eq(ip->noaddr) || a.ismulticast());
+}
+
+lookup(bootp: ref Bootp): ref Hostinfo
+{
+ if(ndb == nil)
+ return nil;
+ inf: ref Hostinfo;
+ hwaddr := ether->text(bootp.chaddr);
+ if(ipok(bootp.ciaddr)){
+ # client thinks it knows address; check match with MAC address
+ ipaddr := bootp.ciaddr.text();
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ (e, ptr) = ndb.findbyattr(ptr, "ip", ipaddr, "ether");
+ if(e == nil)
+ break;
+ # TO DO: check result
+ inf = matchandfill(e, "ip", ipaddr, "ether", hwaddr);
+ if(inf != nil)
+ return inf;
+ }
+ }
+ # look up an ip address associated with given MAC address
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ (e, ptr) = ndb.findbyattr(ptr, "ether", hwaddr, "ip");
+ if(e == nil)
+ break;
+ # TO DO: check right net etc.
+ inf = matchandfill(e, "ether", hwaddr, "ip", nil);
+ if(inf != nil)
+ return inf;
+ }
+ return nil;
+}
+
+matchandfill(e: ref Dbentry, attr: string, val: string, rattr: string, rval: string): ref Hostinfo
+{
+ matches := e.findbyattr(attr, val, rattr);
+ for(; matches != nil; matches = tl matches){
+ (line, attrs) := hd matches;
+ for(; attrs != nil; attrs = tl attrs){
+ if(rval == nil || (hd attrs).val == rval){
+ inf := fillup(line, e);
+ if(inf != nil)
+ return inf;
+ break;
+ }
+ }
+ }
+ return nil;
+}
+
+fillup(line: ref Tuples, e: ref Dbentry): ref Hostinfo
+{
+ ok: int;
+ inf := ref Hostinfo;
+ inf.netmask = ip->noaddr;
+ inf.ipgw = ip->noaddr;
+ inf.fs = ip->v4noaddr;
+ inf.auth = ip->v4noaddr;
+ inf.hostname = find(line, e, "sys");
+ s := find(line, e, "ether");
+ if(s != nil)
+ inf.ha = ether->parse(s);
+ s = find(line, e, "ip");
+ if(s == nil)
+ return nil;
+ (ok, inf.ip) = IPaddr.parse(s);
+ if(ok < 0)
+ return nil;
+ (results, err) := ipattr->findnetattrs(ndb, "ip", s, list of{"ipmask", "ipgw", "fs", "FILESERVER", "SIGNER", "auth", "bootf"});
+ if(err != nil)
+ return nil;
+ for(; results != nil; results = tl results){
+ (a, nattrs) := hd results;
+ if(!a.eq(inf.ip))
+ continue; # different network
+ for(; nattrs != nil; nattrs = tl nattrs){
+ na := hd nattrs;
+ case na.name {
+ "ipmask" =>
+ inf.netmask = takeipmask(na.pairs, inf.netmask);
+ "ipgw" =>
+ inf.ipgw = takeipattr(na.pairs, inf.ipgw);
+ "fs" or "FILESERVER" =>
+ inf.fs = takeipattr(na.pairs, inf.fs);
+ "auth" or "SIGNER" =>
+ inf.auth = takeipattr(na.pairs, inf.auth);
+ "bootf" =>
+ inf.bootf = takeattr(na.pairs, inf.bootf);
+ }
+ }
+ }
+ return inf;
+}
+
+takeattr(pairs: list of ref Attr, s: string): string
+{
+ if(s != nil || pairs == nil)
+ return s;
+ return (hd pairs).val;
+}
+
+takeipattr(pairs: list of ref Attr, a: IPaddr): IPaddr
+{
+ if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr)))
+ return a;
+ (ok, na) := parseip((hd pairs).val);
+ if(ok < 0)
+ return a;
+ return na;
+}
+
+takeipmask(pairs: list of ref Attr, a: IPaddr): IPaddr
+{
+ if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr)))
+ return a;
+ (ok, na) := IPaddr.parsemask((hd pairs).val);
+ if(ok < 0)
+ return a;
+ return na;
+}
+
+findip(line: ref Tuples, e: ref Dbentry, attr: string): (int, IPaddr)
+{
+ s := find(line, e, attr);
+ if(s == nil)
+ return (-1, ip->noaddr);
+ return parseip(s);
+}
+
+parseip(s: string): (int, IPaddr)
+{
+ (ok, a) := IPaddr.parse(s);
+ if(ok < 0){
+ # look it up if it's a system name
+ s = findbyattr("sys", s, "ip");
+ (ok, a) = IPaddr.parse(s);
+ }
+ return (ok, a);
+}
+
+find(line: ref Tuples, e: ref Dbentry, attr: string): string
+{
+ if(line != nil){
+ a := line.find(attr);
+ if(a != nil)
+ return (hd a).val;
+ }
+ if(e != nil){
+ for(matches := e.find(attr); matches != nil; matches = tl matches){
+ (nil, a) := hd matches;
+ if(a != nil)
+ return (hd a).val;
+ }
+ }
+ return nil;
+}
+
+findbyattr(attr: string, val: string, rattr: string): string
+{
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ (e, ptr) = ndb.findbyattr(ptr, attr, val, rattr);
+ if(e == nil)
+ break;
+ rvl := e.find(rattr);
+ if(rvl != nil){
+ (nil, al) := hd rvl;
+ return (hd al).val;
+ }
+ }
+ return nil;
+}
+
+missing(rec: ref Hostinfo): string
+{
+ s := "";
+ if(rec.ha == nil)
+ s += " hardware address";
+ if(rec.ip.eq(ip->noaddr))
+ s += " IP address";
+ if(rec.bootf == nil)
+ s += " bootfile";
+ if(rec.netmask.eq(ip->noaddr))
+ s += " subnet mask";
+ if(rec.ipgw.eq(ip->noaddr))
+ s += " gateway";
+ if(rec.fs.eq(ip->noaddr))
+ s += " file server";
+ if(rec.auth.eq(ip->noaddr))
+ s += " authentication server";
+ if(s != "")
+ return s[1:];
+ return nil;
+}
+
+dtoa(data: array of byte): string
+{
+ if(data == nil)
+ return nil;
+ result: string;
+ for(i:=0; i < len data; i++)
+ result += sys->sprint(".%d", int data[i]);
+ return result[1:];
+}
+
+magic(cookie: array of byte): string
+{
+ if(eqa(cookie, array[] of { byte 'p', byte '9', byte ' ', byte ' ' }))
+ return "plan9";
+ if(eqa(cookie, array[] of { byte 99, byte 130, byte 83, byte 99 }))
+ return "rfc1048";
+ if(eqa(cookie, array[] of { byte 'C', byte 'M', byte 'U', byte 0 }))
+ return "cmu";
+ return dtoa(cookie);
+}
+
+eqa(a1: array of byte, a2: array of byte): int
+{
+ if(len a1 != len a2)
+ return 0;
+ for(i := 0; i < len a1; i++)
+ if(a1[i] != a2[i])
+ return 0;
+ return 1;
+}
+
+Hostinfo.text(rec: self ref Hostinfo): string
+{
+ return sys->sprint("ha=%s ip=%s bf=%s sm=%s gw=%s fs=%s au=%s",
+ ether->text(rec.ha), rec.ip.text(), rec.bootf, rec.netmask.masktext(), rec.ipgw.text(), rec.fs.text(), rec.auth.text());
+}
+
+Bootp: adt
+{
+ op: int; # opcode [1]
+ htype: int; # hardware type[1]
+ hlen: int; # hardware address length [1]
+ hops: int; # gateway hops [1]
+ xid: int; # random number [4]
+ secs: int; # seconds elapsed since client started booting [2]
+ flags: int; # flags[2]
+ ciaddr: IPaddr; # client ip address (client->server)[4]
+ yiaddr: IPaddr; # your ip address (server->client)[4]
+ siaddr: IPaddr; # server's ip address [4]
+ giaddr: IPaddr; # gateway ip address [4]
+ chaddr: array of byte; # client hardware (mac) address [16]
+ sname: string; # server host name [64]
+ file: string; # boot file name [128]
+ vend: array of byte; # vendor-specific [128]
+
+ unpack: fn(a: array of byte): (ref Bootp, string);
+ pack: fn(bp: self ref Bootp): array of byte;
+ text: fn(bp: self ref Bootp): string;
+};
+
+Bootp.unpack(data: array of byte): (ref Bootp, string)
+{
+ if(len data < 300)
+ return (nil, "too short");
+
+ bp := ref Bootp;
+ bp.op = int data[0];
+ bp.htype = int data[1];
+ bp.hlen = int data[2];
+ if(bp.hlen > 16)
+ return (nil, "length error");
+ bp.hops = int data[3];
+ bp.xid = ip->get4(data, 4);
+ bp.secs = ip->get2(data, 8);
+ bp.flags = ip->get2(data, 10);
+ bp.ciaddr = IPaddr.newv4(data[12:16]);
+ bp.yiaddr = IPaddr.newv4(data[16:20]);
+ bp.siaddr = IPaddr.newv4(data[20:24]);
+ bp.giaddr = IPaddr.newv4(data[24:28]);
+ bp.chaddr = data[28:28+bp.hlen];
+ bp.sname = ctostr(data[44:108]);
+ bp.file = ctostr(data[108:236]);
+ bp.vend = data[236:300];
+ return (bp, nil);
+}
+
+Bootp.pack(bp: self ref Bootp): array of byte
+{
+ data := array[364] of { * => byte 0 };
+ data[0] = byte bp.op;
+ data[1] = byte bp.htype;
+ data[2] = byte bp.hlen;
+ data[3] = byte bp.hops;
+ ip->put4(data, 4, bp.xid);
+ ip->put2(data, 8, bp.secs);
+ ip->put2(data, 10, bp.flags);
+ data[12:] = bp.ciaddr.v4();
+ data[16:] = bp.yiaddr.v4();
+ data[20:] = bp.siaddr.v4();
+ data[24:] = bp.giaddr.v4();
+ data[28:] = bp.chaddr;
+ data[44:] = array of byte bp.sname;
+ data[108:] = array of byte bp.file;
+ data[236:] = bp.vend;
+ return data;
+}
+
+ctostr(cstr: array of byte): string
+{
+ for(i:=0; i<len cstr; i++)
+ if(cstr[i] == byte 0)
+ break;
+ return string cstr[0:i];
+}
+
+Bootp.text(bp: self ref Bootp): string
+{
+ s := sys->sprint("op=%d htype=%d hlen=%d hops=%d xid=%ud secs=%ud ciaddr=%s yiaddr=%s",
+ int bp.op, bp.htype, bp.hlen, bp.hops, bp.xid, bp.secs, bp.ciaddr.text(), bp.yiaddr.text());
+ s += sys->sprint(" server=%s gateway=%s hwaddr=%q host=%q file=%q magic=%q",
+ bp.siaddr.text(), bp.giaddr.text(), ether->text(bp.chaddr), bp.sname, bp.file, magic(bp.vend[0:4]));
+ if(magic(bp.vend[0:4]) == "plan9")
+ s += "("+ctostr(bp.vend)+")";
+ return s;
+}
diff --git a/appl/cmd/ip/dhcp.b b/appl/cmd/ip/dhcp.b
new file mode 100644
index 00000000..63ac2255
--- /dev/null
+++ b/appl/cmd/ip/dhcp.b
@@ -0,0 +1,162 @@
+implement Dhcp;
+
+#
+# configure an interface using DHCP
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "ip.m";
+ ip: IP;
+ IPv4off, IPaddrlen, OUdphdrlen: import IP;
+ IPaddr: import ip;
+ get2, get4, put2, put4: import ip;
+
+include "dhcp.m";
+ dhcpclient: Dhcpclient;
+ Bootconf, Lease: import dhcpclient;
+
+include "arg.m";
+
+Dhcp: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+RetryTime: con 10*1000; # msec
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ ip = load IP IP->PATH;
+ dhcpclient = load Dhcpclient Dhcpclient->PATH;
+
+ sys->pctl(Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: nil);
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("dhcp [-bdmnpr] [-g ipgw] [-h hostname] [-x /net] ifcdir [ip [ipmask]]");
+ trace := 0;
+ pcfg := 0;
+ bootp := 0;
+ monitor := 0;
+ retry := 0;
+ noctl := 0;
+ netdir := "/net";
+ cfg := Bootconf.new();
+ while((o := arg->opt()) != 0)
+ case o {
+ 'b' => bootp = 1;
+ 'd' => trace++;
+ 'g' => cfg.ipgw = arg->earg();
+ 'h' => cfg.puts(Dhcpclient->Ohostname, arg->earg());
+ 'm' => monitor = 1;
+ 'n' => noctl = 1;
+ 'p' => pcfg = 1;
+ 'r' => retry = 1;
+ 'x' => netdir = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(len args == 0)
+ arg->usage();
+
+ ifcdir := hd args;
+ args = tl args;
+ if(args != nil){
+ cfg.ip = hd args;
+ args = tl args;
+ if(args != nil){
+ cfg.ipmask = hd args;
+ args = tl args;
+ if(args != nil)
+ arg->usage();
+ }
+ }
+ arg = nil;
+
+ ifcctl: ref Sys->FD;
+ if(noctl == 0){
+ ifcctl = sys->open(ifcdir+"/ctl", Sys->OWRITE);
+ if(ifcctl == nil)
+ err(sys->sprint("cannot open %s/ctl: %r", ifcdir));
+ }
+ etherdir := finddev(ifcdir);
+ if(etherdir == nil)
+ err(sys->sprint("cannot find network device in %s/status: %r", ifcdir));
+ if(etherdir[0] != '/' && etherdir[0] != '#')
+ etherdir = netdir+"/"+etherdir;
+
+ ip->init();
+ dhcpclient->init();
+ dhcpclient->tracing(trace);
+ e: string;
+ lease: ref Lease;
+ for(;;){
+ if(bootp){
+ (cfg, e) = dhcpclient->bootp(netdir, ifcctl, etherdir+"/addr", cfg);
+ if(e == nil){
+ if(cfg != nil)
+ dhcpclient->applycfg(netdir, ifcctl, cfg);
+ if(pcfg)
+ printcfg(cfg);
+ break;
+ }
+ }else{
+ (cfg, lease, e) = dhcpclient->dhcp(netdir, ifcctl, etherdir+"/addr", cfg, nil); # last is array of int options
+ if(e == nil){
+ if(pcfg)
+ printcfg(cfg);
+ if(cfg.lease > 0 && monitor)
+ leasemon(lease.configs, pcfg);
+ break;
+ }
+ }
+ if(!retry)
+ err("failed to configure network: "+e);
+ sys->fprint(sys->fildes(2), "dhcp: failed to configure network: %s; retrying", e);
+ sys->sleep(RetryTime);
+ }
+}
+
+leasemon(configs: chan of (ref Bootconf, string), pcfg: int)
+{
+ for(;;){
+ (cfg, e) := <-configs;
+ if(e != nil)
+ sys->fprint(sys->fildes(2), "dhcp: %s", e);
+ if(pcfg)
+ printcfg(cfg);
+ }
+}
+
+printcfg(cfg: ref Bootconf)
+{
+ sys->print("ip=%s ipmask=%s ipgw=%s iplease=%d\n", cfg.ip, cfg.ipmask, cfg.ipgw, cfg.lease);
+}
+
+finddev(ifcdir: string): string
+{
+ fd := sys->open(ifcdir+"/status", Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array[1024] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return nil;
+ (nf, l) := sys->tokenize(string buf[0:n], " \n");
+ if(nf < 2){
+ sys->werrstr("unexpected format for status file");
+ return nil;
+ }
+ return hd tl l;
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "dhcp: %s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/ip/mkfile b/appl/cmd/ip/mkfile
new file mode 100644
index 00000000..a2238154
--- /dev/null
+++ b/appl/cmd/ip/mkfile
@@ -0,0 +1,30 @@
+<../../../mkconfig
+
+DIRS=\
+ ppp\
+# nppp\
+
+TARG=\
+ bootpd.dis\
+ dhcp.dis\
+ ping.dis\
+ rip.dis\
+ tftpd.dis\
+ virgild.dis\
+ obootpd.dis\
+ sntp.dis\
+
+SYSMODULES=\
+ attrdb.m\
+ bufio.m\
+ dhcp.m\
+ draw.m\
+ ether.m\
+ ip.m\
+ ipattr.m\
+ sys.m\
+
+DISBIN=$ROOT/dis/ip
+
+<$ROOT/mkfiles/mkdis
+<$ROOT/mkfiles/mksubdirs
diff --git a/appl/cmd/ip/nppp/mkfile b/appl/cmd/ip/nppp/mkfile
new file mode 100644
index 00000000..0f803acd
--- /dev/null
+++ b/appl/cmd/ip/nppp/mkfile
@@ -0,0 +1,24 @@
+<../../../../mkconfig
+
+TARG=\
+ ppplink.dis\
+ pppchat.dis\
+ modem.dis\
+ script.dis\
+# ppptest.dis\
+
+MODULES=\
+ modem.m\
+ script.m\
+
+SYSMODULES=\
+ sys.m\
+ draw.m\
+ tk.m\
+ dict.m\
+ string.m\
+ lock.m\
+
+DISBIN=$ROOT/dis/ip/nppp
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/ip/nppp/modem.b b/appl/cmd/ip/nppp/modem.b
new file mode 100644
index 00000000..f8c81396
--- /dev/null
+++ b/appl/cmd/ip/nppp/modem.b
@@ -0,0 +1,469 @@
+implement Modem;
+
+#
+# Copyright © 1998-2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "lock.m";
+ lock: Lock;
+ Semaphore: import lock;
+
+include "draw.m";
+
+include "modem.m";
+
+hangupcmd := "ATH0"; # was ATZH0 but some modem versions on Umec hung on ATZ
+
+# modem return codes
+Ok, Success, Failure, Abort, Noise, Found: con iota;
+
+maxspeed: con 115200;
+
+#
+# modem return messages
+#
+Msg: adt {
+ text: string;
+ code: int;
+};
+
+msgs: array of Msg = array [] of {
+ ("OK", Ok),
+ ("NO CARRIER", Failure),
+ ("ERROR", Failure),
+ ("NO DIALTONE", Failure),
+ ("BUSY", Failure),
+ ("NO ANSWER", Failure),
+ ("CONNECT", Success),
+};
+
+kill(pid: int)
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "kill") < 0)
+ sys->print("modem: can't kill %d: %r\n", pid);
+}
+
+#
+# prepare a modem port
+#
+openserial(d: ref Device): string
+{
+ d.data = nil;
+ d.ctl = nil;
+
+ d.data = sys->open(d.local, Sys->ORDWR);
+ if(d.data == nil)
+ return sys->sprint("can't open %s: %r", d.local);
+
+ d.ctl = sys->open(d.local+"ctl", Sys->ORDWR);
+ if(d.ctl == nil)
+ return sys->sprint("can't open %s: %r", d.local+"ctl");
+
+ d.speed = maxspeed;
+ d.avail = nil;
+ return nil;
+}
+
+#
+# shut down the monitor (if any) and return the connection
+#
+
+Device.close(m: self ref Device): ref Sys->Connection
+{
+ if(m.pid != 0){
+ kill(m.pid);
+ m.pid = 0;
+ }
+ if(m.data == nil)
+ return nil;
+ mc := ref sys->Connection(m.data, m.ctl, nil);
+ m.ctl = nil;
+ m.data = nil;
+ return mc;
+}
+
+#
+# Send a string to the modem
+#
+
+Device.send(d: self ref Device, x: string): string
+{
+ a := array of byte x;
+ f := sys->write(d.data, a, len a);
+ if(f != len a) {
+ # let's attempt to close & reopen the modem
+ d.close();
+ err := openserial(d);
+ if(err != nil)
+ return err;
+ f = sys->write(d.data,a, len a);
+ if(f < 0)
+ return sys->sprint("%r");
+ if(f != len a)
+ return "short write";
+ }
+ if(d.trace)
+ sys->print("->%s\n",x);
+ return nil;
+}
+
+#
+# apply a string of commands to modem & look for a response
+#
+
+apply(d: ref Device, s: string, substr: string, secs: int): int
+{
+ m := Ok;
+ buf := "";
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ buf[len buf] = c; # assume no Unicode
+ if(c == '\r' || i == (len s -1)){
+ if(c != '\r')
+ buf[len buf] = '\r';
+ if(d.send(buf) != nil)
+ return Abort;
+ (m, nil) = readmsg(d, secs, substr);
+ buf = "";
+ }
+ }
+ return m;
+}
+
+#
+# get modem into command mode if it isn't already
+#
+GUARDTIME: con 1100; # usual default for S12=50 in units of 1/50 sec; allow 100ms fuzz
+
+attention(d: ref Device): int
+{
+ for(i := 0; i < 3; i++){
+ if(apply(d, hangupcmd, nil, 2) == Ok)
+ return Ok;
+ sys->sleep(GUARDTIME);
+ if(d.send("+++") != nil)
+ return Abort;
+ sys->sleep(GUARDTIME);
+ (nil, msg) := readmsg(d, 0, nil);
+ if(msg != nil && d.trace)
+ sys->print("status: %s\n", msg);
+ }
+ return Failure;
+}
+
+#
+# apply a command type
+#
+
+applyspecial(d: ref Device, cmd: string): int
+{
+ if(cmd == nil)
+ return Failure;
+ return apply(d, cmd, nil, 2);
+}
+
+#
+# hang up any connections in progress and close the device
+#
+Device.onhook(d: self ref Device)
+{
+ # hang up the modem
+ monitoring(d);
+ if(attention(d) != Ok)
+ sys->print("modem: no attention\n");
+
+ # hangup the stream (eg, for ppp) and toggle the lines to the modem
+ if(d.ctl != nil) {
+ sys->fprint(d.ctl,"d0\n");
+ sys->fprint(d.ctl,"r0\n");
+ sys->fprint(d.ctl, "h\n"); # hangup on native serial
+ sys->sleep(250);
+ sys->fprint(d.ctl,"r1\n");
+ sys->fprint(d.ctl,"d1\n");
+ }
+
+ d.close();
+}
+
+#
+# does string s contain t anywhere?
+#
+
+contains(s, t: string): int
+{
+ if(t == nil)
+ return 1;
+ if(s == nil)
+ return 0;
+ n := len t;
+ for(i := 0; i+n <= len s; i++)
+ if(s[i:i+n] == t)
+ return 1;
+ return 0;
+}
+
+#
+# read till we see a message or we time out
+#
+readmsg(d: ref Device, secs: int, substr: string): (int, string)
+{
+ found := 0;
+ msecs := secs*1000;
+ limit := 1000; # pretty arbitrary
+ s := "";
+
+ for(start := sys->millisec(); sys->millisec() <= start+msecs;){
+ a := d.getinput(1);
+ if(len a == 0){
+ if(limit){
+ sys->sleep(1);
+ continue;
+ }
+ break;
+ }
+ if(a[0] == byte '\n' || a[0] == byte '\r' || limit == 0){
+ if (len s) {
+ if (s[(len s)-1] == '\r')
+ s[(len s)-1] = '\n';
+ sys->print("<-%s\n",s);
+ }
+ if(substr != nil && contains(s, substr))
+ found = 1;
+ for(k := 0; k < len msgs; k++)
+ if(len s >= len msgs[k].text &&
+ s[0:len msgs[k].text] == msgs[k].text){
+ if(found)
+ return (Found, s);
+ return (msgs[k].code, s);
+ }
+ start = sys->millisec();
+ s = "";
+ continue;
+ }
+ s[len s] = int a[0];
+ limit--;
+ }
+ s = "no response from modem";
+ if(found)
+ return (Found, s);
+
+ return (Noise, s);
+}
+
+#
+# get baud rate from a connect message
+#
+
+getspeed(msg: string, speed: int): int
+{
+ p := msg[7:]; # skip "CONNECT"
+ while(p[0] == ' ' || p[0] == '\t')
+ p = p[1:];
+ s := int p;
+ if(s <= 0)
+ return speed;
+ else
+ return s;
+}
+
+#
+# set speed and RTS/CTS modem flow control
+#
+
+setspeed(d: ref Device, baud: int)
+{
+ if(d != nil && d.ctl != nil){
+ sys->fprint(d.ctl, "b%d", baud);
+ sys->fprint(d.ctl, "m1");
+ }
+}
+
+monitoring(d: ref Device)
+{
+ # if no monitor then spawn one
+ if(d.pid == 0) {
+ pidc := chan of int;
+ spawn monitor(d, pidc, nil);
+ d.pid = <-pidc;
+ }
+}
+
+#
+# a process to read input from a modem.
+#
+monitor(d: ref Device, pidc: chan of int, errc: chan of string)
+{
+ err := openserial(d);
+ pidc <-= sys->pctl(0, nil);
+ if(err != nil && errc != nil)
+ errc <-= err;
+ a := array[Sys->ATOMICIO] of byte;
+ for(;;) {
+ d.lock.obtain();
+ d.status = "Idle";
+ d.remote = "";
+ setspeed(d, d.speed);
+ d.lock.release();
+ # shuttle bytes
+ while((n := sys->read(d.data, a, len a)) > 0){
+ d.lock.obtain();
+ if (len d.avail < Sys->ATOMICIO) {
+ na := array[len d.avail + n] of byte;
+ na[0:] = d.avail[0:];
+ na[len d.avail:] = a[0:n];
+ d.avail = na;
+ }
+ d.lock.release();
+ }
+ # on an error, try reopening the device
+ d.data = nil;
+ d.ctl = nil;
+ err = openserial(d);
+ if(err != nil && errc != nil)
+ errc <-= err;
+ }
+}
+
+#
+# return up to n bytes read from the modem by monitor()
+#
+Device.getinput(d: self ref Device, n: int): array of byte
+{
+ if(d==nil || n <= 0)
+ return nil;
+ a: array of byte;
+ d.lock.obtain();
+ if(len d.avail != 0){
+ if(n > len d.avail)
+ n = len d.avail;
+ a = d.avail[0:n];
+ d.avail = d.avail[n:];
+ }
+ d.lock.release();
+ return a;
+}
+
+Device.getc(d: self ref Device, msec: int): int
+{
+ start := sys->millisec();
+ while((b := d.getinput(1)) == nil) {
+ if (msec && sys->millisec() > start+msec)
+ return 0;
+ sys->sleep(1);
+ }
+ return int b[0];
+}
+
+init(): string
+{
+ sys = load Sys Sys->PATH;
+ lock = load Lock Lock->PATH;
+ if(lock == nil)
+ return sys->sprint("can't load %s: %r", Lock->PATH);
+ lock->init();
+ return nil;
+}
+
+Device.new(modeminfo: ref ModemInfo, trace: int): ref Device
+{
+ d := ref Device;
+ d.lock = Semaphore.new();
+ d.local = modeminfo.path;
+ d.pid = 0;
+ d.speed = 0;
+ d.t = *modeminfo;
+ if(d.t.hangup == nil)
+ d.t.hangup = hangupcmd;
+ d.trace = trace | 1; # always trace for now
+ return d;
+}
+
+#
+# dial a number
+#
+Device.dial(d: self ref Device, number: string): string
+{
+ monitoring(d);
+
+ # modem type should already be established, but just in case
+ if(d.trace)
+ sys->print("modem: attention\n");
+ x := attention(d);
+ if (x != Ok && d.trace)
+ return "bad response from modem";
+ #
+ # extended Hayes commands, meaning depends on modem
+ #
+ sys->print("modem: init\n");
+ if(d.t.country != nil)
+ applyspecial(d, d.t.country);
+
+ if(d.t.init != nil)
+ applyspecial(d, d.t.init);
+
+ if(d.t.other != nil)
+ applyspecial(d, d.t.other);
+
+ applyspecial(d, d.t.errorcorrection);
+
+ compress := Abort;
+ if(d.t.mnponly != nil)
+ compress = applyspecial(d, d.t.mnponly);
+ if(d.t.compression != nil)
+ compress = applyspecial(d, d.t.compression);
+
+ rateadjust := Abort;
+ if(compress != Ok)
+ rateadjust = applyspecial(d, d.t.rateadjust);
+ applyspecial(d, d.t.flowctl);
+
+ # finally, dialout
+ if(d.trace)
+ sys->print("modem: dial\n");
+ if((dt := d.t.dialtype) == nil)
+ dt = "ATDT";
+ err := d.send(sys->sprint("%s%s\r", dt, number));
+ if(err != nil){
+ if(d.trace)
+ sys->print("modem: can't dial %s: %s\n", number, err);
+ return err;
+ }
+
+ (i, msg) := readmsg(d, 120, nil);
+ if(i != Success){
+ if(d.trace)
+ sys->print("modem: modem error reply: %s\n", msg);
+ return msg;
+ }
+
+ connectspeed := getspeed(msg, d.speed);
+
+ # change line rate if not compressing
+ if(rateadjust == Ok)
+ setspeed(d, connectspeed);
+
+ if(d.ctl != nil){
+ if(d != nil)
+ sys->fprint(d.ctl, "s%d", connectspeed); # set DCE speed (if device implements it)
+ sys->fprint(d.ctl, "c1"); # enable CD monitoring
+ }
+
+ return nil;
+}
+
+dumpa(a: array of byte): string
+{
+ s := "";
+ for(i:=0; i<len a; i++){
+ b := int a[i];
+ if(b >= ' ' && b < 16r7f)
+ s[len s] = b;
+ else
+ s += sys->sprint("\\%.2x", b);
+ }
+ return s;
+}
diff --git a/appl/cmd/ip/nppp/modem.m b/appl/cmd/ip/nppp/modem.m
new file mode 100644
index 00000000..6e84b0e3
--- /dev/null
+++ b/appl/cmd/ip/nppp/modem.m
@@ -0,0 +1,47 @@
+Modem: module
+{
+ PATH: con "/dis/ip/nppp/modem.dis";
+
+ ModemInfo: adt {
+ path: string;
+ init: string;
+ country: string;
+ other: string;
+ errorcorrection:string;
+ compression: string;
+ flowctl: string;
+ rateadjust: string;
+ mnponly: string;
+ dialtype: string;
+ hangup: string;
+ };
+
+ Device: adt {
+ lock: ref Lock->Semaphore;
+ # modem stuff
+ ctl: ref Sys->FD;
+ data: ref Sys->FD;
+
+ local: string;
+ remote: string;
+ status: string;
+ speed: int;
+ t: ModemInfo;
+ trace: int;
+
+ # input reader
+ avail: array of byte;
+ pid: int;
+
+ new: fn(i: ref ModemInfo, trace: int): ref Device;
+ dial: fn(m: self ref Device, number: string): string;
+ getc: fn(m: self ref Device, msec: int): int;
+ getinput: fn(m: self ref Device, n: int): array of byte;
+ send: fn(m: self ref Device, x: string): string;
+ close: fn(m: self ref Device): ref Sys->Connection;
+ onhook: fn(m: self ref Device);
+ };
+
+ init: fn(): string;
+
+};
diff --git a/appl/cmd/ip/nppp/pppchat.b b/appl/cmd/ip/nppp/pppchat.b
new file mode 100644
index 00000000..77202b18
--- /dev/null
+++ b/appl/cmd/ip/nppp/pppchat.b
@@ -0,0 +1,322 @@
+implement Dialupchat;
+
+#
+# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Point, Rect: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "wmlib.m";
+ wmlib: Wmlib;
+
+include "translate.m";
+ translate: Translate;
+ Dict: import translate;
+ dict: ref Dict;
+
+Dialupchat: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# Dimension constant for ISP Connect window
+WIDTH: con 300;
+HEIGHT: con 58;
+
+LightGreen: con "#00FF80"; # colour for successful blob
+Blobx: con 8;
+Gapx: con 4;
+BARW: con (Blobx+Gapx)*10; # Progress bar width
+BARH: con 18; # Progress bar height
+DIALQUANTA : con 1000;
+ICONQUANTA : con 5000;
+
+pppquanta := DIALQUANTA;
+
+Maxstep: con 9;
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ wmlib = load Wmlib Wmlib->PATH;
+ wmlib->init();
+
+ translate = load Translate Translate->PATH;
+ if(translate != nil) {
+ translate->init();
+ dictname := translate->mkdictname("", "pppchat");
+ dicterr: string;
+ (dict, dicterr) = translate->opendict(dictname);
+ if(dicterr != nil)
+ sys->fprint(sys->fildes(2), "pppchat: can't open %s: %s\n", dictname, dicterr);
+ }else
+ sys->fprint(sys->fildes(2), "pppchat: can't load %s: %r\n", Translate->PATH);
+
+ tkargs: string;
+ if(args != nil) {
+ tkargs = hd args;
+ args = tl args;
+ }
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ pppfd := sys->open("/chan/pppctl", Sys->ORDWR);
+ if(pppfd == nil)
+ error(sys->sprint("can't open /chan/pppctl: %r"));
+
+ (t, wmctl) := wmlib->titlebar(ctxt.screen, tkargs, X("Dialup Connection"), Wmlib->Hide);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ pb := Progressbar.mk(t, ".f.prog.c", (BARW, BARH));
+
+ config_win := array[] of {
+ "frame .f",
+ "frame .f.prog",
+ "frame .f.b",
+
+ pb.tkcreate(),
+ "pack .f.prog.c -pady 6 -side top",
+
+ "label .f.stat -fg blue -text {"+X("Initialising connection...")+"}",
+ "pack .f.stat -side top -fill x -expand 1 -anchor n",
+
+ "pack .f -side top -expand 1 -padx 5 -pady 3 -fill both -anchor w",
+ "pack .f.prog -side top -expand 1 -fill x",
+ "button .f.b.done -text {"+X("Cancel")+"} -command {send cmd cancel}",
+ "pack .f.b.done -side right -padx 1 -pady 1 -anchor s",
+ "button .f.b.retry -text {"+X("Retry")+"} -command {send cmd retry} -state disabled",
+ "pack .f.b.retry -side left -padx 1 -pady 1 -anchor s",
+ "pack .f.b -side top -expand 1 -fill x",
+
+ "pack propagate . 0",
+ "update",
+ };
+
+ for(i := 0; i < len config_win; i++)
+ tkcmd(t, config_win[i]);
+
+ connected := 0;
+ winmapped := 1;
+ timecount := 0;
+ xmin := 0;
+ x := 0;
+ turn := 0;
+
+ pppquanta = DIALQUANTA;
+ ticks := chan of int;
+ spawn ppptimer(ticks);
+
+ statuslines := chan of (string, string);
+ pids := chan of int;
+ spawn ctlreader(pppfd, pids, statuslines);
+ ctlpid := <-pids;
+
+Work:
+ for(;;) alt {
+
+ s := <-wmctl =>
+ if(s == "exit")
+ s = "task";
+ if(s == "task"){
+ spawn wmlib->titlectl(t, s);
+ continue;
+ }
+ wmlib->titlectl(t, s);
+
+ press := <-cmd =>
+ case press {
+ "cancel" or "disconnect" =>
+ tkcmd(t, sys->sprint(".f.stat configure -text '%s", X("Disconnecting")));
+ tkcmd(t, "update");
+ if(sys->fprint(pppfd, "hangup") < 0){
+ err := sys->sprint("%r");
+ tkcmd(t, sys->sprint(".f.stat configure -text '%s: %s", X("Error disconnecting"), X(err)));
+ sys->fprint(sys->fildes(2), "pppchat: can't disconnect: %s\n", err);
+ }
+ break Work;
+ "retry" =>
+ if(sys->fprint(pppfd, "connect") < 0){
+ err := sys->sprint("%r");
+ }
+ }
+
+ <-ticks =>
+ ticks <-= 1;
+ if(!connected){
+ if(pb != nil){
+ if((turn ^= 1) == 0)
+ pb.setcolour("white");
+ else
+ pb.setcolour(LightGreen);
+ }
+ tkcmd(t, "raise .; update");
+ }
+
+ (status, err) := <-statuslines =>
+ if(status == nil){
+ status = "0 1 empty status";
+ if(err != nil)
+ sys->print("pppchat: !%s\n", err);
+ } else
+ sys->print("pppchat: %s\n", status);
+ (nf, flds) := sys->tokenize(status, " \t\n");
+# for(i = 0; i < len status; i++)
+# if(status[i] == ' ' || status[i] == '\t') {
+# status = status[i+1:];
+# break;
+# }
+ if(nf < 3)
+ break;
+ step := int hd flds; flds = tl flds;
+ nstep := int hd flds; flds = tl flds;
+ if(step < 0)
+ raise "pppchat: bad step";
+ case hd flds {
+ "error:" =>
+ tkcmd(t, ".f.stat configure -fg red -text '"+X(status));
+ tkcmd(t, ".f.b.retry configure -state normal");
+ tkcmd(t, "update");
+ wmlib->unhide();
+ winmapped = 1;
+ pb.stepto(step, "red");
+ #break Work;
+ * =>
+ pb.setcolour(LightGreen);
+ pb.stepto(step, LightGreen);
+ }
+ turn = 0;
+ statusmsg := X(status);
+ tkcmd(t, ".f.stat configure -text '"+statusmsg);
+ tkcmd(t, "raise .; update");
+
+ case hd flds {
+ "up" or "done" =>
+ if(!connected){
+ connected = 1;
+ }
+ pppquanta = ICONQUANTA;
+
+ # display connection speed
+ if(tl flds != nil)
+ tkcmd(t, ".f.stat configure -text {"+statusmsg+" "+"SPEED"+" hd tl flds}");
+ else
+ tkcmd(t, ".f.stat configure -text {"+statusmsg+"}");
+ tkcmd(t, ".f.b.done configure -text Disconnect -command 'send cmd disconnect");
+ tkcmd(t, "update");
+ sys->sleep(2000);
+ tkcmd(t, "pack forget .f.prog; update");
+ spawn wmlib->titlectl(t, "task");
+ winmapped = 0;
+ }
+ tkcmd(t, "update");
+ }
+ <-ticks;
+ ticks <-= 0; # stop ppptimer
+ kill(ctlpid);
+}
+
+ppptimer(ticks: chan of int)
+{
+ do{
+ sys->sleep(pppquanta);
+ ticks <-= 1;
+ }while(<-ticks);
+}
+
+ctlreader(fd: ref Sys->FD, pidc: chan of int, lines: chan of (string, string))
+{
+ pidc <-= sys->pctl(0, nil);
+ buf := array[128] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0)
+ lines <-= (string buf[0:n], nil);
+ if(n < 0)
+ lines <-= (nil, sys->sprint("%r"));
+ else
+ lines <-= (nil, nil);
+}
+
+Progressbar: adt {
+ t: ref Tk->Toplevel;
+ canvas: string;
+ csize: Point;
+ blobs: list of string;
+
+ mk: fn(t: ref Tk->Toplevel, canvas: string, csize: Point): ref Progressbar;
+ tkcreate: fn(pb: self ref Progressbar): string;
+ setcolour: fn(pb: self ref Progressbar, c: string);
+ stepto: fn(pb: self ref Progressbar, step: int, col: string);
+ destroy: fn(pb: self ref Progressbar);
+};
+
+Progressbar.mk(t: ref Tk->Toplevel, canvas: string, csize: Point): ref Progressbar
+{
+ return ref Progressbar(t, canvas, csize, nil);
+}
+
+Progressbar.tkcreate(pb: self ref Progressbar): string
+{
+ return sys->sprint("canvas %s -width %d -height %d", pb.canvas, pb.csize.x, pb.csize.y);
+}
+
+Progressbar.setcolour(pb: self ref Progressbar, colour: string)
+{
+ if(pb.blobs != nil)
+ tkcmd(pb.t, sys->sprint("%s itemconfigure %s -fill %s; update", pb.canvas, hd pb.blobs, colour));
+}
+
+Progressbar.stepto(pb: self ref Progressbar, step: int, col: string)
+{
+ for(nblob := len pb.blobs; nblob > step+1; nblob--){
+ tkcmd(pb.t, sys->sprint("%s delete %s", pb.canvas, hd pb.blobs));
+ pb.blobs = tl pb.blobs;
+ }
+ if(nblob == step+1)
+ return;
+ p := Point(step*(Blobx+Gapx), 0);
+ r := Rect(p, p.add((Blobx, pb.csize.y-2)));
+ pb.blobs = tkcmd(pb.t, sys->sprint("%s create rectangle %d %d %d %d -fill %s", pb.canvas, r.min.x,r.min.y, r.max.x,r.max.y, col)) :: pb.blobs;
+}
+
+Progressbar.destroy(pb: self ref Progressbar)
+{
+ tk->cmd(pb.t, "destroy "+pb.canvas); # ignore errors
+}
+
+tkcmd(t: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(t, s);
+ if(e != nil && e[0] == '!')
+ sys->print("pppchat: tk error: %s [%s]\n", e, s);
+ return e;
+}
+
+kill(pid: int)
+{
+ if(pid > 0 && (fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE)) != nil)
+ sys->fprint(fd, "kill");
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "pppchat: %s\n", s);
+ raise "fail:error";
+}
+
+X(s: string): string
+{
+ if(dict != nil)
+ return dict.xlate(s);
+ return s;
+}
diff --git a/appl/cmd/ip/nppp/ppplink.b b/appl/cmd/ip/nppp/ppplink.b
new file mode 100644
index 00000000..5f0e9686
--- /dev/null
+++ b/appl/cmd/ip/nppp/ppplink.b
@@ -0,0 +1,782 @@
+implement PPPlink;
+
+#
+# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+include "cfgfile.m";
+ cfg: CfgFile;
+ ConfigFile: import cfg;
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+
+include "sh.m";
+
+include "translate.m";
+ translate: Translate;
+ Dict: import translate;
+ dict: ref Dict;
+
+PPPlink: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+PPPInfo: adt {
+ ipaddr: string;
+ ipmask: string;
+ peeraddr: string;
+ maxmtu: string;
+ username: string;
+ password: string;
+};
+
+modeminfo: ref Modem->ModemInfo;
+context: ref Draw->Context;
+pppinfo: ref PPPInfo;
+scriptinfo: ref Script->ScriptInfo;
+isp_number: string;
+lastCdir: ref Sys->Dir; # state of file when last read
+netdir := "/net";
+
+Packet: adt {
+ src: array of byte;
+ dst: array of byte;
+ data: array of byte;
+};
+
+DEFAULT_ISP_DB_PATH: con "/services/ppp/isp.cfg"; # contains pppinfo & scriptinfo
+DEFAULT_MODEM_DB_PATH: con "/services/ppp/modem.cfg"; # contains modeminfo
+MODEM_DB_PATH: con "modem.cfg"; # contains modeminfo
+ISP_DB_PATH: con "isp.cfg"; # contains pppinfo & scriptinfo
+
+primary := 0;
+framing := 1;
+
+Disconnected, Modeminit, Dialling, Modemup, Scriptstart, Scriptdone, Startingppp, Startedppp, Login, Linkup: con iota;
+Error: con -1;
+
+Ignorems: con 10*1000; # time to ignore outgoing packets between dial attempts
+
+statustext := array[] of {
+Disconnected => "Disconnected",
+Modeminit => "Initializing Modem",
+Dialling => "Dialling Service Provider",
+Modemup => "Logging Into Network",
+Scriptstart => "Executing Login Script",
+Scriptdone => "Script Execution Complete",
+Startingppp => "Logging Into Network",
+Startedppp => "Logging Into Network",
+Login => "Verifying Password",
+Linkup => "Connected",
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: ppplink [-P] [-f] [-m mtu] [local [remote]]\n");
+ raise "fail:usage";
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ translate = load Translate Translate->PATH;
+ if(translate != nil) {
+ translate->init();
+ dictname := translate->mkdictname("", "pppclient");
+ (dict, nil) = translate->opendict(dictname);
+ }
+ mtu := 1450;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ error(0, sys->sprint("can't load %s: %r", Arg->PATH));
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'm' =>
+ if((s := arg->arg()) == nil || !(s[0]>='0' && s[0]<='9'))
+ usage();
+ mtu = int s;
+ 'P' =>
+ primary = 1;
+ 'f' =>
+ framing = 0;
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+ localip := "10.9.8.7"; # should be something locally unique
+ fake := 1;
+ if(args != nil){
+ fake = 0;
+ localip = hd args;
+ args = tl args;
+ }
+
+ cerr := configinit();
+ if(cerr != nil)
+ error(0, sys->sprint("can't configure: %s", cerr));
+ context = ctxt;
+
+ # make default (for now)
+ # if packet appears, start ppp and reset routing until it stops
+
+ (cfd, dir, err) := getifc();
+ if(err != nil)
+ error(0, err);
+
+ if(sys->fprint(cfd, "bind pkt") < 0)
+ error(0, sys->sprint("can't bind pkt: %r"));
+ if(sys->fprint(cfd, "add %s 255.255.255.0 10.9.8.0 %d", localip, mtu) < 0)
+ error(0, sys->sprint("can't add ppp addresses: %r"));
+ if(primary && addroute("0", "0", localip) < 0)
+ error(0, sys->sprint("can't add default route: %r"));
+ dfd := sys->open(dir+"/data", Sys->ORDWR);
+ if(dfd == nil)
+ error(0, sys->sprint("can't open %s: %r", dir));
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ packets := chan of ref Packet;
+ spawn netreader(dfd, dir, localip, fake, packets);
+
+ logger := chan of (int, string);
+ iocmd := sys->file2chan("/chan", "pppctl");
+ if(iocmd == nil)
+ error(0, sys->sprint("can't create /chan/pppctl: %r"));
+ spawn servestatus(iocmd.read, logger);
+
+ starteduser := 0;
+ lasttime := 0;
+
+ for(;;) alt{
+ (nil, data, nil, wc) := <-iocmd.write => # remote io control
+ if(wc == nil)
+ break;
+ (nil, flds) := sys->tokenize(string data, " \t");
+ if(len flds > 1){
+ case hd flds {
+ "cancel" or "disconnect" or "hangup" =>
+ ; # ignore it
+ "connect" =>
+ # start connection ...
+ ;
+ * =>
+ wreply(wc, (0, "illegal request"));
+ continue;
+ }
+ }
+ wreply(wc, (len data, nil));
+
+ pkt := <-packets =>
+ sys->print("ppplink: received packet %s->%s: %d bytes\n", ipa(pkt.src), ipa(pkt.dst), len pkt.data);
+ if(abs(sys->millisec()-lasttime) < Ignorems){
+ sys->print("ppplink: ignored, not enough time elapsed yet between dial attempts\n");
+ break;
+ }
+ (ok, stat) := sys->stat(ISP_DB_PATH);
+ if(ok < 0 || lastCdir == nil || !samefile(*lastCdir, stat)){
+ cerr = configinit();
+ if(cerr != nil){
+ sys->print("ppplink: can't reconfigure: %s\n", cerr);
+ # use existing configuration
+ }
+ }
+ if(!starteduser){
+ sync := chan of int;
+ spawn userinterface(sync);
+ starteduser = <-sync;
+ }
+ (ppperr, pppdir) := makeconnection(packets, logger, iocmd.write);
+ lasttime = sys->millisec();
+ if(ppperr == nil){
+ sys->print("ppplink: connected on %s\n", pppdir);
+ # converse ...
+sys->sleep(120*1000);
+ }else{
+ sys->print("ppplink: ppp connect error: %s\n", ppperr);
+ hangup(pppdir);
+ }
+ }
+}
+
+servestatus(reader: chan of (int, int, int, Sys->Rread), updates: chan of (int, string))
+{
+ statuspending := 0;
+ statusreq: (int, int, Sys->Rread);
+ step := Disconnected;
+ statuslist := statusline(step, step, nil) :: nil;
+
+ for(;;) alt{
+ (off, nbytes, fid, rc) := <-reader=>
+ if(rc == nil){
+ statuspending = 0;
+ if(step == Disconnected)
+ statuslist = nil;
+ break;
+ }
+ if(statuslist == nil){
+ if(statuspending){
+ alt{
+ rc <-= (nil, "pppctl file already in use") => ;
+ * => ;
+ }
+ break;
+ }
+ statusreq = (nbytes, fid, rc);
+ statuspending = 1;
+ break;
+ }
+ alt{
+ rc <-= reads(hd statuslist, 0, nbytes) =>
+ statuslist = tl statuslist;
+ * => ;
+ }
+
+ (code, arg) := <-updates =>
+ # convert to string
+ if(code != Error)
+ step = code;
+ status := statusline(step, code, arg);
+ if(code == Error)
+ step = Disconnected;
+ statuslist = appends(statuslist, status);
+ sys->print("status: %d %d %s\n", step, code, status);
+ if(statuspending){
+ (nbytes, nil, rc) := statusreq;
+ statuspending = 0;
+ alt{
+ rc <-= reads(hd statuslist, 0, nbytes) =>
+ statuslist = tl statuslist;
+ * =>
+ ;
+ }
+ }
+ }
+}
+
+makeconnection(packets: chan of ref Packet, logger: chan of (int, string), writer: chan of (int, array of byte, int, Sys->Rwrite)): (string, string)
+{
+ result := chan of (string, string);
+ sync := chan of int;
+ spawn pppconnect(result, sync, logger);
+ pid := <-sync;
+ for(;;) alt{
+ (err, pppdir) := <-result =>
+ # pppconnect finished
+ return (err, pppdir);
+
+ pkt := <-packets =>
+ # ignore packets whilst connecting
+ sys->print("ppplink: ignored packet %s->%s: %d byten", ipa(pkt.src), ipa(pkt.dst), len pkt.data);
+
+ (nil, data, nil, wc) := <-writer => # user control
+ if(wc == nil)
+ break;
+ (nil, flds) := sys->tokenize(string data, " \t");
+ if(len flds > 1){
+ case hd flds {
+ "connect" =>
+ ; # ignore it
+ "cancel" or "disconnect" or "hangup"=>
+ kill(pid, "killgrp");
+ wreply(wc, (len data, nil));
+ return ("cancelled", nil);
+ * =>
+ wreply(wc, (0, "illegal request"));
+ continue;
+ }
+ }
+ wreply(wc, (len data, nil));
+ }
+}
+
+wreply(wc: chan of (int, string), v: (int, string))
+{
+ alt{
+ wc <-= v => ;
+ * => ;
+ }
+}
+
+appends(l: list of string, s: string): list of string
+{
+ if(l == nil)
+ return s :: nil;
+ return hd l :: appends(tl l, s);
+}
+
+statusline(step: int, code: int, arg: string): string
+{
+ s: string;
+ if(code >= 0 && code < len statustext){
+ n := "step";
+ if(code == Linkup)
+ n = "connect";
+ s = sys->sprint("%d %d %s %s", step, len statustext, n, X(statustext[code]));
+ }else
+ s = sys->sprint("%d %d error", step, len statustext);
+ if(arg != nil)
+ s += sys->sprint(": %s", arg);
+ return s;
+}
+
+getifc(): (ref Sys->FD, string, string)
+{
+ clonefile := netdir+"/ipifc/clone";
+ cfd := sys->open(clonefile, Sys->ORDWR);
+ if(cfd == nil)
+ return (nil, nil, sys->sprint("can't open %s: %r", clonefile));
+ buf := array[32] of byte;
+ n := sys->read(cfd, buf, len buf);
+ if(n <= 0)
+ return (nil, nil, sys->sprint("can't read %s: %r", clonefile));
+ return (cfd, netdir+"/ipifc/" + string buf[0:n], nil);
+}
+
+addroute(addr, mask, gate: string): int
+{
+ fd := sys->open(netdir+"/iproute", Sys->OWRITE);
+ if(fd == nil)
+ return -1;
+ return sys->fprint(fd, "add %s %s %s", addr, mask, gate);
+}
+
+# uchar vihl; /* Version and header length */
+# uchar tos; /* Type of service */
+# uchar length[2]; /* packet length */
+# uchar id[2]; /* ip->identification */
+# uchar frag[2]; /* Fragment information */
+# uchar ttl; /* Time to live */
+# uchar proto; /* Protocol */
+# uchar cksum[2]; /* Header checksum */
+# uchar src[4]; /* IP source */
+# uchar dst[4]; /* IP destination */
+IPhdrlen: con 20;
+
+netreader(dfd: ref Sys->FD, dir: string, localip: string, fake: int, outc: chan of ref Packet)
+{
+ buf := array [32*1024] of byte;
+ while((n := sys->read(dfd, buf, len buf)) > 0){
+ if(n < IPhdrlen){
+ sys->print("ppplink: received short packet: %d bytes\n", n);
+ continue;
+ }
+ pkt := ref Packet;
+ if(n < 9*1024){
+ pkt.data = array[n] of byte;
+ pkt.data[0:] = buf[0:n];
+ }else{
+ pkt.data = buf[0:n];
+ buf = array[32*1024] of byte;
+ }
+ pkt.src = pkt.data[12:];
+ pkt.dst = pkt.data[16:];
+ outc <-= pkt;
+ }
+ if(n < 0)
+ error(1, sys->sprint("packet interface read error: %r"));
+ else if(n == 0)
+ error(1, "packet interface: end of file");
+}
+
+ipa(a: array of byte): string
+{
+ if(len a < 4)
+ return "???";
+ return sys->sprint("%d.%d.%d.%d", int a[0], int a[1], int a[2], int a[3]);
+}
+
+reads(str: string, off, nbytes: int): (array of byte, string)
+{
+ bstr := array of byte str;
+ slen := len bstr;
+ if(off < 0 || off >= slen)
+ return (nil, nil);
+ if(off + nbytes > slen)
+ nbytes = slen - off;
+ if(nbytes <= 0)
+ return (nil, nil);
+ return (bstr[off:off+nbytes], nil);
+}
+
+readppplog(log: chan of (int, string), errfile: string, pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil);
+ src := sys->open(errfile, Sys->OREAD);
+ if(src == nil)
+ log <-= (Error, sys->sprint("can't open %s: %r", errfile));
+
+ buf := array[1024] of byte;
+ connected := 0;
+ lasterror := "";
+
+ while((count := sys->read(src, buf, len buf)) > 0) {
+ (nil, tokens) := sys->tokenize(string buf[:count],"\n");
+ for(; tokens != nil; tokens = tl tokens) {
+ case hd tokens {
+ "no error" =>
+ log <-= (Linkup, nil);
+ lasterror = nil;
+ connected = 1;
+ "permission denied" =>
+ lasterror = X("Username or Password Incorrect");
+ log <-= (Error, lasterror);
+ "write to hungup channel" =>
+ lasterror = X("Remote Host Hung Up");
+ log <-= (Error, lasterror);
+ * =>
+ lasterror = X(hd tokens);
+ log <-= (Error, lasterror);
+ }
+ }
+ }
+ if(count == 0 && connected && lasterror == nil){ # should change ip/pppmedium.c instead?
+ #hangup(nil);
+ log <-= (Error, X("Lost Connection"));
+ }
+}
+
+dialup(mi: ref Modem->ModemInfo, number: string, scriptinfo: ref Script->ScriptInfo, logchan: chan of (int, string)): (string, ref Sys->Connection)
+{
+ logchan <-= (Modeminit, nil);
+
+ # open & init the modem
+
+ modeminfo = mi;
+ modem := load Modem Modem->PATH;
+ if(modem == nil)
+ return (sys->sprint("can't load %s: %r", Modem->PATH), nil);
+ err := modem->init();
+ if(err != nil)
+ return (sys->sprint("couldn't init modem: %s", err), nil);
+ Device: import modem;
+ d := Device.new(modeminfo, 1);
+ logchan <-= (Dialling, number);
+ err = d.dial(number);
+ if(err != nil){
+ d.close();
+ return (err, nil);
+ }
+ logchan <-= (Modemup, nil);
+
+ # login script
+
+ if(scriptinfo != nil) {
+ logchan <-= (Scriptstart, nil);
+ err = runscript(modem, d, scriptinfo);
+ if(err != nil){
+ d.close();
+ return (err, nil);
+ }
+ logchan <-= (Scriptdone, nil);
+ }
+
+ mc := d.close();
+ return (nil, mc);
+
+}
+
+startppp(logchan: chan of (int, string), pppinfo: ref PPPInfo): (string, string)
+{
+ (ifd, dir, err) := getifc();
+ if(ifd == nil)
+ return (err, nil);
+
+ sync := chan of int;
+ spawn readppplog(logchan, dir + "/err", sync); # unbind gives eof on err
+ <-sync;
+
+ if(pppinfo.ipaddr == nil)
+ pppinfo.ipaddr = "-";
+# if(pppinfo.ipmask == nil)
+# pppinfo.ipmask = "255.255.255.255";
+ if(pppinfo.peeraddr == nil)
+ pppinfo.peeraddr = "-";
+ if(pppinfo.maxmtu == nil)
+ pppinfo.maxmtu = "-";
+# if(pppinfo.maxmtu <= 0)
+# pppinfo.maxmtu = mtu;
+# if(pppinfo.maxmtu < 576)
+# pppinfo.maxmtu = 576;
+ if(pppinfo.username == nil)
+ pppinfo.username = "-";
+ if(pppinfo.password == nil)
+ pppinfo.password = "-";
+
+ ifc := "bind ppp "+modeminfo.path+" "+ pppinfo.ipaddr+" "+pppinfo.peeraddr+" "+pppinfo.maxmtu
+ +" "+string framing+" "+pppinfo.username+" "+pppinfo.password;
+
+ if(sys->fprint(ifd, "%s", ifc) < 0)
+ return (sys->sprint("can't bind ppp to %s: %r", dir), nil);
+
+ sys->print("ppplink: %s\n", ifc);
+
+ return (nil, dir);
+}
+
+runscript(modem: Modem, dev: ref Modem->Device, scriptinfo: ref Script->ScriptInfo): string
+{
+ script := load Script Script->PATH;
+ if(script == nil)
+ return sys->sprint("can't load %s: %r", Script->PATH);
+ err := script->init(modem);
+ if(err != nil)
+ return err;
+ return script->execute(dev, scriptinfo);
+}
+
+hangup(pppdir: string)
+{
+ sys->print("ppplink: hangup...\n");
+ if(pppdir != nil){ # shut down the PPP link
+ fd := sys->open(pppdir + "/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "unbind") < 0)
+ sys->print("ppplink: hangup: can't unbind ppp on %s: %r\n", pppdir);
+ fd = nil;
+ }
+ modem := load Modem Modem->PATH;
+ if(modem == nil) {
+ sys->print("ppplink: hangup: can't load %s: %r", Modem->PATH);
+ return;
+ }
+ err := modem->init();
+ if(err != nil){
+ sys->print("ppplink: hangup: couldn't init modem: %s", err);
+ return;
+ }
+ Device: import modem;
+ d := Device.new(modeminfo, 1);
+ if(d != nil){
+ d.onhook();
+ d.close();
+ }
+}
+
+kill(pid: int, msg: string)
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "%s", msg) < 0)
+ sys->print("pppclient: can't %s %d: %r\n", msg, pid);
+}
+
+error(dokill: int, s: string)
+{
+ sys->fprint(sys->fildes(2), "ppplink: %s\n", s);
+ if(dokill)
+ kill(sys->pctl(0, nil), "killgrp");
+ raise "fail:error";
+}
+
+X(s : string) : string
+{
+ if(dict != nil)
+ return dict.xlate(s);
+ return s;
+}
+
+cfile(file: string): string
+{
+ if(len file > 0 && file[0] == '/')
+ return file;
+ return "/usr/"+user()+"/config/"+file;
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", Sys->OREAD);
+ buf := array[64] of byte;
+ if(fd != nil && (n := sys->read(fd, buf, len buf)) > 0)
+ return string buf[0:n];
+ return "inferno"; # hmmm.
+}
+
+cfvalue(c: ref ConfigFile, key: string) :string
+{
+ s := "";
+ for(values := c.getcfg(key); values != nil; values = tl values){
+ if(s != "")
+ s[len s] = ' ';
+ s += hd values;
+ }
+ return s;
+}
+
+configinit(): string
+{
+ cfg = load CfgFile CfgFile->PATH;
+ if(cfg == nil)
+ return sys->sprint("can't load %s: %r", CfgFile->PATH);
+
+ # Modem Configuration
+
+ modemdb := cfile(MODEM_DB_PATH);
+ cfg->verify(DEFAULT_MODEM_DB_PATH, modemdb);
+ modemcfg := cfg->init(modemdb);
+ if(modemcfg == nil)
+ return sys->sprint("can't open %s: %r", modemdb);
+ modeminfo = ref Modem->ModemInfo;
+ modeminfo.path = cfvalue(modemcfg, "PATH");
+ modeminfo.init = cfvalue(modemcfg, "INIT");
+ modeminfo.country = cfvalue(modemcfg, "COUNTRY");
+ modeminfo.other = cfvalue(modemcfg, "OTHER");
+ modeminfo.errorcorrection = cfvalue(modemcfg,"CORRECT");
+ modeminfo.compression = cfvalue(modemcfg,"COMPRESS");
+ modeminfo.flowctl = cfvalue(modemcfg,"FLOWCTL");
+ modeminfo.rateadjust = cfvalue(modemcfg,"RATEADJ");
+ modeminfo.mnponly = cfvalue(modemcfg,"MNPONLY");
+ modeminfo.dialtype = cfvalue(modemcfg,"DIALING");
+ if(modeminfo.dialtype!="ATDP")
+ modeminfo.dialtype="ATDT";
+
+ ispdb := cfile(ISP_DB_PATH);
+ cfg->verify(DEFAULT_ISP_DB_PATH, ispdb);
+ sys->print("cfg->init(%s)\n", ispdb);
+
+ # ISP Configuration
+ pppcfg := cfg->init(ispdb);
+ if(pppcfg == nil)
+ return sys->sprint("can't read or create ISP configuration file %s: %r", ispdb);
+ (ok, stat) := sys->stat(ispdb);
+ if(ok >= 0)
+ lastCdir = ref stat;
+
+ pppinfo = ref PPPInfo;
+ isp_number = cfvalue(pppcfg, "NUMBER");
+ pppinfo.ipaddr = cfvalue(pppcfg,"IPADDR");
+ pppinfo.ipmask = cfvalue(pppcfg,"IPMASK");
+ pppinfo.peeraddr = cfvalue(pppcfg,"PEERADDR");
+ pppinfo.maxmtu = cfvalue(pppcfg,"MAXMTU");
+ pppinfo.username = cfvalue(pppcfg,"USERNAME");
+ pppinfo.password = cfvalue(pppcfg,"PASSWORD");
+
+ info := pppcfg.getcfg("SCRIPT");
+ if(info != nil) {
+ scriptinfo = ref Script->ScriptInfo;
+ scriptinfo.path = hd info;
+ scriptinfo.username = pppinfo.username;
+ scriptinfo.password = pppinfo.password;
+ } else
+ scriptinfo = nil;
+
+ info = pppcfg.getcfg("TIMEOUT");
+ if(info != nil)
+ scriptinfo.timeout = int (hd info);
+ cfg = nil; # unload it
+
+ if(modeminfo.path == nil)
+ return "no modem device configured";
+ if(isp_number == nil)
+ return "no telephone number configured for ISP";
+
+ return nil;
+}
+
+isipaddr(a: string): int
+{
+ i, c, ac, np : int = 0;
+
+ for(i = 0; i < len a; i++) {
+ c = a[i];
+ if(c >= '0' && c <= '9') {
+ np = 10*np + c - '0';
+ continue;
+ }
+ if(c == '.' && np) {
+ ac++;
+ if(np > 255)
+ return 0;
+ np = 0;
+ continue;
+ }
+ return 0;
+ }
+ return np && np < 256 && ac == 3;
+}
+
+userinterface(sync: chan of int)
+{
+ pppgui := load Command "pppchat.dis";
+ if(pppgui == nil){
+ sys->fprint(sys->fildes(2), "ppplink: can't load %s: %r\n", "/dis/svc/nppp/pppchat.dis");
+ # TO DO: should be optional
+ sync <-= 0;
+ }
+
+ sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2});
+ sync <-= sys->pctl(0, nil);
+ pppgui->init(context, "pppchat" :: nil);
+}
+
+pppconnect(result: chan of (string, string), sync: chan of int, status: chan of (int, string))
+{
+ sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2});
+ sync <-= sys->pctl(0, nil);
+ pppdir: string;
+ (err, mc) := dialup(modeminfo, isp_number, scriptinfo, status); # mc keeps connection open until startppp binds it to ppp
+ if(err == nil){
+ if(0 && (cfd := mc.cfd) != nil){
+ sys->fprint(cfd, "m1"); # cts/rts flow control/fifo's on
+ sys->fprint(cfd, "q64000"); # increase queue size to 64k
+ sys->fprint(cfd, "n1"); # nonblocking writes on
+ sys->fprint(cfd, "r1"); # rts on
+ sys->fprint(cfd, "d1"); # dtr on
+ }
+ status <-= (Startingppp, nil);
+ (err, pppdir) = startppp(status, pppinfo);
+ if(err == nil){
+ status <-= (Startedppp, nil);
+ result <-= (nil, pppdir);
+ return;
+ }
+ }
+ status <-= (Error, err);
+ result <-= (err, nil);
+}
+
+getspeed(file: string): string
+{
+ return findrate("/dev/modemstat", "rcvrate" :: "baud" :: nil);
+}
+
+findrate(file: string, opt: list of string): string
+{
+ fd := sys->open(file, sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array [1024] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 1)
+ return nil;
+ (nil, flds) := sys->tokenize(string buf[0:n], " \t\r\n");
+ for(; flds != nil; flds = tl flds)
+ for(l := opt; l != nil; l = tl l)
+ if(hd flds == hd l)
+ return hd tl flds;
+ return nil;
+}
+
+samefile(d1, d2: Sys->Dir): int
+{
+ return d1.dev==d2.dev && d1.dtype==d2.dtype &&
+ d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers &&
+ d1.mtime==d2.mtime;
+}
+
+abs(n: int): int
+{
+ if(n < 0)
+ return -n;
+ return n;
+}
diff --git a/appl/cmd/ip/nppp/ppptest.b b/appl/cmd/ip/nppp/ppptest.b
new file mode 100644
index 00000000..af8e16e0
--- /dev/null
+++ b/appl/cmd/ip/nppp/ppptest.b
@@ -0,0 +1,90 @@
+# Last change: R 24 May 2001 11:05 am
+implement PPPTest;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+include "pppclient.m";
+include "pppgui.m";
+
+PPPTest: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+usage()
+{
+ sys->print("ppptest device modem_init tel user password \n");
+ sys->print("Example: ppptest /dev/modem atw2 4125678 rome xxxxxxxx\n");
+ exit;
+
+}
+init( ctxt: ref Draw->Context, argv: list of string )
+{
+ sys = load Sys Sys->PATH;
+
+ mi: Modem->ModemInfo;
+ pi: PPPClient->PPPInfo;
+ tel : string;
+# si: Script->ScriptInfo;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ mi.path = hd argv;
+
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ mi.init = hd argv;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ tel = hd argv;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ pi.username = hd argv;
+ argv = tl argv;
+ if(argv==nil)
+ usage();
+ else
+ pi.password = hd argv;
+
+
+ #si.path = "rdid.script";
+ #si.username = "ericvh";
+ #si.password = "foobar";
+ #si.timeout = 60;
+
+
+ ppp := load PPPClient PPPClient->PATH;
+
+ logger := chan of int;
+
+ spawn ppp->connect( ref mi, tel, nil, ref pi, logger );
+
+ pppgui := load PPPGUI PPPGUI->PATH;
+ (respchan, err) := pppgui->init(ctxt, logger, ppp, nil);
+ if(err != nil){
+ sys->print("ppptest: can't %s: %s\n", PPPGUI->PATH, err);
+ exit;
+ }
+
+ event := 0;
+ while(1) {
+ event =<- respchan;
+ sys->print("GUI event received: %d\n",event);
+ if(event) {
+ sys->print("success");
+ exit;
+ } else {
+ raise "fail: Couldn't connect to ISP";
+ }
+ }
+}
diff --git a/appl/cmd/ip/nppp/script.b b/appl/cmd/ip/nppp/script.b
new file mode 100644
index 00000000..d929ff7a
--- /dev/null
+++ b/appl/cmd/ip/nppp/script.b
@@ -0,0 +1,171 @@
+implement Script;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "lock.m";
+include "modem.m";
+ modem: Modem;
+ Device: import modem;
+
+include "script.m";
+
+Scriptlim: con 32*1024; # should be enough for all
+
+init(mm: Modem): string
+{
+ sys = load Sys Sys->PATH;
+ modem = mm;
+ str = load String String->PATH;
+ if(str == nil)
+ return sys->sprint("can't load %s: %r", String->PATH);
+ return nil;
+}
+
+execute(m: ref Modem->Device, scriptinfo: ref ScriptInfo): string
+{
+ if(scriptinfo.path != nil) {
+ if(m.trace)
+ sys->print("script: using %s\n",scriptinfo.path);
+ # load the script
+ err: string;
+ (scriptinfo.content, err) = scriptload(scriptinfo.path);
+ if(err != nil)
+ return err;
+ }else{
+ if(m.trace)
+ sys->print("script: using inline script\n");
+ }
+
+ if(scriptinfo.timeout == 0)
+ scriptinfo.timeout = 20;
+
+ tend := sys->millisec() + 1000*scriptinfo.timeout;
+
+ for(conv := scriptinfo.content; conv != nil; conv = tl conv){
+ e, s: string = nil;
+ p := hd conv;
+ if(len p == 0)
+ continue;
+ if(m.trace)
+ sys->print("script: %s\n",p);
+ if(p[0] == '-') { # just send
+ if(len p == 1)
+ continue;
+ s = p[1:];
+ } else {
+ (n, esl) := sys->tokenize(p, "-");
+ if(n > 0) {
+ e = hd esl;
+ esl = tl esl;
+ if(n > 1)
+ s = hd esl;
+ }
+ }
+ if(e != nil) {
+ if(match(m, special(e,scriptinfo), tend-sys->millisec()) == 0) {
+ if(m.trace)
+ sys->print("script: match failed\n");
+ return "script failed";
+ }
+ }
+ if(s != nil)
+ m.send(special(s, scriptinfo));
+ }
+ if(m.trace)
+ sys->print("script: done\n");
+ return nil;
+}
+
+match(m: ref Modem->Device, s: string, msec: int): int
+{
+ for(;;) {
+ c := m.getc(msec);
+ if(c == '\r')
+ c = '\n';
+ if(m.trace)
+ sys->print("%c",c);
+ if(c == 0)
+ return 0;
+ head:
+ while(c == s[0]) {
+ i := 1;
+ while(i < len s) {
+ c = m.getc(msec);
+ if(c == '\r')
+ c = '\n';
+ if(m.trace)
+ sys->print("%c",c);
+ if(c == 0)
+ return 0;
+ if(c != s[i])
+ continue head;
+ i++;
+ }
+ return 1;
+ }
+ if(c == '~')
+ return 1; # assume PPP for now
+ }
+}
+
+#
+# Expand special script sequences
+#
+special(s: string, scriptinfo: ref ScriptInfo): string
+{
+ if(s == "$username") # special variable
+ s = scriptinfo.username;
+ else if(s == "$password")
+ s = scriptinfo.password;
+ return deparse(s);
+}
+
+deparse(s: string): string
+{
+ r: string = "";
+ for(i:=0; i < len s; i++) {
+ c := s[i];
+ if(c == '\\' && i+1 < len s) {
+ c = s[++i];
+ case c {
+ 't' => c = '\t';
+ 'n' => c = '\n';
+ 'r' => c = '\r';
+ 'b' => c = '\b';
+ 'a' => c = '\a';
+ 'v' => c = '\v';
+ '0' => c = '\0';
+ '$' => c = '$';
+ 'u' =>
+ if(i+4 < len s) {
+ i++;
+ (c, nil) = str->toint(s[i:i+4], 16);
+ i+=3;
+ }
+ }
+ }
+ r[len r] = c;
+ }
+ return r;
+}
+
+scriptload(path: string): (list of string, string)
+{
+ dfd := sys->open(path, Sys->OREAD);
+ if(dfd == nil)
+ return (nil, sys->sprint("can't open script %s: %r", path));
+
+ b := array[Scriptlim] of byte;
+ n := sys->read(dfd, b, len b);
+ if(n < 0)
+ return (nil, sys->sprint("can't read script %s: %r", path));
+
+ (nil, script) := sys->tokenize(string b[0:n], "\n");
+ return (script, nil);
+}
diff --git a/appl/cmd/ip/nppp/script.m b/appl/cmd/ip/nppp/script.m
new file mode 100644
index 00000000..a1f66e06
--- /dev/null
+++ b/appl/cmd/ip/nppp/script.m
@@ -0,0 +1,15 @@
+Script: module
+{
+ PATH: con "/dis/ip/nppp/script.dis";
+
+ ScriptInfo: adt {
+ path: string;
+ content: list of string;
+ timeout: int;
+ username: string;
+ password: string;
+ };
+
+ init: fn(m: Modem): string;
+ execute: fn(m: ref Modem->Device, scriptinfo: ref ScriptInfo): string;
+};
diff --git a/appl/cmd/ip/obootpd.b b/appl/cmd/ip/obootpd.b
new file mode 100644
index 00000000..8795d672
--- /dev/null
+++ b/appl/cmd/ip/obootpd.b
@@ -0,0 +1,777 @@
+implement Bootpd;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "attrdb.m";
+ attrdb: Attrdb;
+ Db, Dbentry: import attrdb;
+
+include "ip.m";
+ ip: IP;
+ IPaddr, Udphdr: import ip;
+
+include "ether.m";
+ ether: Ether;
+
+include "arg.m";
+
+Bootpd: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+debug: int;
+sniff: int;
+verbose: int;
+
+siaddr: array of byte;
+sysname: string;
+progname := "bootpd";
+net := "/net";
+
+Udphdrsize: con IP->OUdphdrlen;
+
+NEED_HA: con 1;
+NEED_IP: con 0;
+NEED_BF: con 0;
+NEED_SM: con 0;
+NEED_GW: con 0;
+NEED_FS: con 0;
+NEED_AU: con 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ loadfail(Bufio->PATH);
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ loadfail(Attrdb->PATH);
+ attrdb->init();
+ ip = load IP IP->PATH;
+ if(ip == nil)
+ loadfail(IP->PATH);
+ ip->init();
+ ether = load Ether Ether->PATH;
+ if(ether == nil)
+ loadfail(Ether->PATH);
+ ether->init();
+
+ fname := "/services/bootp/db";
+ verbose = 1;
+ sniff = 0;
+ debug = 0;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ raise "fail: load Arg";
+ arg->init(args);
+ arg->setusage("bootpd [-dsqv] [-f file] [-x network]");
+ progname = arg->progname();
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => debug++;
+ 's' => sniff = 1; debug = 255;
+ 'q' => verbose = 0;
+ 'v' => verbose = 1;
+ 'x' => net = arg->earg();
+ 'f' => fname = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->FORKFD|Sys->FORKNS, nil);
+ if(tabopen(fname))
+ raise "fail: open database";
+
+ if(!sniff && (err := dbread()) != nil)
+ error(sys->sprint("error in %s: %s", fname, err));
+
+ addr := net+"/udp!*!67";
+ if(debug)
+ sys->fprint(stderr, "bootp: announcing %s\n", addr);
+ (ok, c) := sys->announce(addr);
+ if(ok < 0)
+ error(sys->sprint("can't announce %s: %r", addr));
+ get_sysname();
+ get_ip();
+
+ if(sys->fprint(c.cfd, "headers") < 0)
+ error(sys->sprint("can't set headers mode: %r"));
+ sys->fprint(c.cfd, "oldheaders");
+
+ if(debug)
+ sys->fprint(stderr, "bootp: opening %s/data\n", c.dir);
+ c.dfd = sys->open(c.dir+"/data", sys->ORDWR);
+ if(c.dfd == nil)
+ error(sys->sprint("can't open %s/data: %r", c.dir));
+
+ spawn server(c);
+}
+
+loadfail(s: string)
+{
+ error(sys->sprint("can't load %s: %r", s));
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "bootp: %s\n", s);
+ raise "fail:error";
+}
+
+server(c: Sys->Connection)
+{
+ buf := array[2048] of byte;
+ badread := 0;
+ for(;;) {
+ if(debug)
+ sys->fprint(stderr, "bootp: listening for bootp requests...\n");
+ n := sys->read(c.dfd, buf, len buf);
+ if(n <0) {
+ if (badread++ > 10)
+ break;
+ continue;
+ }
+ badread = 0;
+ if(n < Udphdrsize) {
+ if(debug)
+ sys->fprint(stderr, "bootp: short Udphdr: %d bytes\n", n);
+ continue;
+ }
+ hdr := Udphdr.unpack(buf, Udphdrsize);
+ if(debug)
+ sys->fprint(stderr, "bootp: received request from udp!%s!%d\n", hdr.raddr.text(), hdr.rport);
+ if(n < Udphdrsize+300) {
+ if(debug)
+ sys->fprint(stderr, "bootp: short request of %d bytes\n", n - Udphdrsize);
+ continue;
+ }
+
+ (err, bootp) := M2S(buf[Udphdrsize:]);
+ if(err != nil) {
+ if(debug)
+ sys->fprint(stderr, "bootp: M2S failed: %s\n", err);
+ continue;
+ }
+ if(debug >= 2)
+ ppkt(bootp);
+ if(sniff)
+ continue;
+ if(bootp.htype != byte 1 || bootp.hlen != byte 6) {
+ # if it isn't ether, we don't do it
+ if(debug)
+ sys->fprint(stderr, "bootp: hardware type not ether; ignoring.\n");
+ continue;
+ }
+ if((err = dbread()) != nil) {
+ sys->fprint(stderr, "bootp: getreply: dbread failed: %s\n", err);
+ continue;
+ }
+ rec := lookup(bootp);
+ if(rec == nil) {
+ # we can't answer this request
+ if(debug)
+ sys->fprint(stderr, "bootp: cannot answer request.\n");
+ continue;
+ }
+ if(debug){
+ sys->fprint(stderr, "bootp: found a matching entry:\n");
+ pinfbp(rec);
+ }
+ mkreply(bootp, rec);
+ if(verbose) sys->print("bootp: %s -> %s %s\n", ether->text(rec.ha), rec.hostname, iptoa(rec.ip));
+ if(debug >= 2) {
+ sys->fprint(stderr, "bootp: reply message:\n");
+ ppkt(bootp);
+ }
+ repl:= S2M(bootp);
+
+ if(debug)
+ sys->fprint(stderr, "bootp: sending reply.\n");
+ arpenter(iptoa(rec.ip), ether->text(rec.ha));
+ send(repl);
+ }
+ sys->fprint(stderr, "bootp: %d read errors: %r\n", badread);
+}
+
+arpenter(ip, ha: string)
+{
+ if(debug) sys->fprint(stderr, "bootp: arp: %s -> %s\n", ip, ha);
+ fd := sys->open(net+"/arp", Sys->OWRITE);
+ if(fd == nil) {
+ if(debug)
+ sys->fprint(stderr, "bootp: arp open failed: %r\n");
+ return;
+ }
+ if(sys->fprint(fd, "add %s %s", ip, ha) < 0){
+ if(debug)
+ sys->fprint(stderr, "bootp: error writing arp: %r\n");
+ }
+}
+
+get_sysname()
+{
+ fd := sys->open("/dev/sysname", sys->OREAD);
+ if(fd == nil) {
+ sysname = "anon";
+ return;
+ }
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0) {
+ sysname = "anon";
+ return;
+ }
+ sysname = string buf[0:n];
+}
+
+get_ip()
+{
+ siaddr = array[4] of { * => byte 0 };
+ # get a local IP address by translating our sysname with cs(8)
+ fd := sys->open(net+"/cs", Sys->ORDWR);
+ if(fd == nil){
+ if(debug)
+ sys->fprint(stderr, "bootp: cannot open %s/cs for reading: %r.\n", net);
+ return;
+ }
+ if(sys->fprint(fd, "net!%s!0", sysname) < 0){
+ if(debug)
+ sys->fprint(stderr, "bootp: can't translate net!%s!0 via %s/cs: %r\n", sysname, net);
+ return;
+ }
+ sys->seek(fd, big 0, 0);
+ a := array[1024] of byte;
+ n := sys->read(fd, a, len a);
+ if(n < 0) {
+ if(debug) sys->fprint(stderr, "bootp: read from /net/cs: %r.\n");
+ return;
+ }
+ reply := string a[0:n];
+ if(debug) sys->fprint(stderr, "bootp: read %s from /net/cs\n", reply);
+
+ (l, addr):= sys->tokenize(reply, " ");
+ if(l != 2) {
+ if(debug) sys->fprint(stderr, "bootp: bad format from cs\n");
+ return;
+ }
+ (l, addr) = sys->tokenize(hd tl addr, "!");
+ if(l < 2) {
+ if(debug) sys->fprint(stderr, "bootp: short addr from cs\n");
+ return;
+ }
+ err:= "";
+ (err, siaddr) = get_ipaddr(hd addr);
+ if(err != nil || siaddr == nil) {
+ if(debug) sys->fprint(stderr, "bootp: invalid local IP addr %s.\n", hd tl addr);
+ siaddr = array[4] of { * => byte 0 };
+ };
+ if(debug) sys->fprint(stderr, "bootp: local IP address is %s.\n", iptoa(siaddr));
+}
+
+# byte op; /* opcode */
+# byte htype; /* hardware type */
+# byte hlen; /* hardware address len */
+# byte hops; /* hops */
+# byte xid[4]; /* a random number */
+# byte secs[2]; /* elapsed snce client started booting */
+# byte pad[2];
+# byte ciaddr[4]; /* client IP address (client tells server) */
+# byte yiaddr[4]; /* client IP address (server tells client) */
+# byte siaddr[4]; /* server IP address */
+# byte giaddr[4]; /* gateway IP address */
+# byte chaddr[16]; /* client hardware address */
+# byte sname[64]; /* server host name (optional) */
+# byte file[128]; /* boot file name */
+# byte vend[128]; /* vendor-specific goo */
+
+BootpPKT: adt
+{
+ op: byte; # Start of udp datagram
+ htype: byte;
+ hlen: byte;
+ hops: byte;
+ xid: int;
+ secs: int;
+ ciaddr: array of byte;
+ yiaddr: array of byte;
+ siaddr: array of byte;
+ giaddr: array of byte;
+ chaddr: array of byte;
+ sname: string;
+ file: string;
+ vend: array of byte;
+};
+
+InfBP: adt {
+ hostname: string;
+
+ ha: array of byte; # hardware addr
+ ip: array of byte; # client IP addr
+ bf: array of byte; # boot file path
+ sm: array of byte; # subnet mask
+ gw: array of byte; # gateway IP addr
+ fs: array of byte; # file server IP addr
+ au: array of byte; # authentication server IP addr
+};
+
+records: array of ref InfBP;
+
+tabbio: ref Bufio->Iobuf;
+tabname: string;
+mtime: int;
+
+tabopen(fname: string): int
+{
+ if(sniff) return 0;
+ tabname = fname;
+ if((tabbio = bufio->open(tabname, bufio->OREAD)) == nil) {
+ sys->fprint(stderr, "bootp: cannot open %s: %r\n", tabname);
+ return 1;
+ }
+ return 0;
+}
+
+send(msg: array of byte)
+{
+ if(debug) sys->fprint(stderr, "bootp: dialing udp!broadcast!68\n");
+ (n, c) := sys->dial(net+"/udp!255.255.255.255!68", "67");
+# (n, c) := sys->dial(net+"/udp!255.255.255.255!68", "192.168.129.1!67");
+ if(n < 0) {
+ sys->fprint(stderr, "bootp: send: error calling dial: %r\n");
+ return;
+ }
+ if(debug) sys->fprint(stderr, "bootp: writing to %s/data\n", c.dir);
+ n = sys->write(c.dfd, msg, len msg);
+ if(n <=0) {
+ sys->fprint(stderr, "bootp: send: error writing to %s/data: %r\n", c.dir);
+ return;
+ }
+ if(debug) sys->fprint(stderr, "bootp: successfully wrote %d bytes to %s/data\n", n, c.dir);
+}
+
+mkreply(bootp: ref BootpPKT, rec: ref InfBP)
+{
+ bootp.op = byte 2; # boot reply
+ bootp.yiaddr = rec.ip;
+ bootp.siaddr = siaddr;
+ bootp.giaddr = array[4] of { * => byte 0 };
+ bootp.sname = sysname;
+ bootp.file = string rec.bf;
+ bootp.vend = array of byte sys->sprint("p9 %s %s %s %s", iptoa(rec.sm), iptoa(rec.fs), iptoa(rec.au), iptoa(rec.gw));
+}
+
+lookup(bootp: ref BootpPKT): ref InfBP
+{
+ for(i := 0; i < len records; i++)
+ if(eqa(bootp.chaddr[0:6], records[i].ha) || eqa(bootp.ciaddr, records[i].ip))
+ return records[i];
+ return nil;
+}
+
+dbread(): string
+{
+ (n, dir) := sys->fstat(tabbio.fd);
+ if(n < 0)
+ return sys->sprint("cannot fstat %s: %r", tabname);
+ if(mtime == 0 || mtime != dir.mtime) {
+ if(bufio->tabbio.seek(big 0, Sys->SEEKSTART) < big 0)
+ return sys->sprint("error seeking to start of %s.", tabname);
+ mtime = dir.mtime;
+ lnum: int = 0;
+ trecs: list of ref InfBP;
+LINES: while((line := bufio->tabbio.gets('\n')) != nil) {
+ lnum++;
+ if(line[0] == '#') # comment
+ continue LINES;
+ fields: list of string;
+ (n, fields) = sys->tokenize(line, ":\r\n");
+ if(n <= 0) { # blank line or colons
+ if(len line > 0) {
+ sys->fprint(stderr, "bootp: %s: %d empty entry.\n", tabname, lnum);
+ }
+ continue LINES;
+ }
+ rec := ref InfBP;
+ rec.hostname = hd fields;
+ fields = tl fields;
+ err: string;
+FIELDS: for(; fields != nil; fields = tl fields) {
+ field := hd fields;
+ if(len field <= len "xx=") {
+ sys->fprint(stderr, "bootp: %s:%d invalid field \"%s\" in entry for %s",
+ tabname, lnum, field, rec.hostname);
+ continue FIELDS;
+ }
+ err = nil;
+ case field[0:3] {
+ "ha=" =>
+ if(rec.ha != nil) {
+ sys->fprint(stderr,
+ "bootp: warning: %s:%d hardware address redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.ha) = get_haddr(field[3:]);
+ "ip=" =>
+ if(rec.ip != nil) {
+ sys->fprint(stderr, "bootp: warning: %s:%d IP address redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.ip) = get_ipaddr(field[3:]);
+ "bf=" =>
+ if(rec.bf != nil) {
+ sys->fprint(stderr, "bootp: warning: %s:%d bootfile redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.bf) = get_path(field[3:]);
+ "sm=" =>
+ if(rec.sm != nil) {
+ sys->fprint(stderr, "bootp: warning: %s:%d subnet mask redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.sm) = get_ipaddr(field[3:]);
+ "gw=" =>
+ if(rec.gw != nil) {
+ sys->fprint(stderr, "bootp: warning: %s:%d gateway redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.gw) = get_ipaddr(field[3:]);
+ "fs=" =>
+ if(rec.fs != nil) {
+ sys->fprint(stderr, "bootp: warning: %s:%d file server redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.fs) = get_ipaddr(field[3:]);
+ "au=" =>
+ if(rec.au != nil) {
+ sys->fprint(stderr,
+ "bootp: warning: %s:%d authentication server redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.au) = get_ipaddr(field[3:]);
+ * =>
+ sys->fprint(stderr,
+ "bootp: %s:%d invalid or unsupported tag \"%s\" in entry for %s.\n",
+ tabname, lnum, field[0:2], rec.hostname);
+ continue FIELDS;
+ }
+ if(err != nil) {
+ sys->fprint(stderr,
+ "bootp: %s:%d %s for %s.\nbootp: skipping entry for %s.\n",
+ tabname, lnum, err, rec.hostname,
+ rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.ha == nil) {
+ if(NEED_HA) {
+ sys->fprint(stderr, "bootp: %s:%d no hardware address defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.ip == nil) {
+ if(NEED_IP) {
+ sys->fprint(stderr, "bootp: %s:%d no IP address defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.bf == nil) {
+ if(NEED_BF) {
+ sys->fprint(stderr, "bootp: %s:%d no bootfile defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.sm == nil) {
+ if(NEED_SM) {
+ sys->fprint(stderr, "bootp: %s:%d no subnet mask defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.gw == nil) {
+ if(NEED_GW) {
+ sys->fprint(stderr, "bootp: %s:%d no gateway defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.fs == nil) {
+ if(NEED_FS) {
+ sys->fprint(stderr, "bootp: %s:%d no file server defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.au == nil) {
+ if(NEED_AU) {
+ sys->fprint(stderr,
+ "bootp: %s:%d no authentication server defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(debug) pinfbp(rec);
+ trecs = rec :: trecs;
+ }
+ if(trecs == nil) {
+ sys->fprint(stderr, "bootp: no valid entries in %s.\n", tabname);
+ if(records != nil) {
+ sys->fprint(stderr, "bootp: reverting to previous state.\n");
+ return nil;
+ }
+ return "no entries.";
+ }
+ records = array[len trecs] of ref InfBP;
+ for(n = len records; n > 0; trecs = tl trecs)
+ records[--n] = hd trecs;
+ }
+ return nil;
+}
+
+get_haddr(str: string): (string, array of byte)
+{
+ addr := ether->parse(str);
+ if(addr == nil)
+ return (sys->sprint("invalid hardware address \"%s\"", str), nil);
+ return (nil, addr);
+}
+
+get_ipaddr(str: string): (string, array of byte)
+{
+ (ok, a) := IPaddr.parse(str);
+ if(ok < 0)
+ return (sys->sprint("invalid address: %s", str), nil);
+ return (nil, a.v4());
+}
+
+get_path(str: string): (string, array of byte)
+{
+ if(str == nil) {
+ return ("nil path", nil);
+ }
+ path := array of byte str;
+ if(len path > 128)
+ return (sys->sprint("path too long (>128 bytes) \"%s...\"", string path[0:16]), nil);
+ return (nil, path);
+}
+
+iptoa(addr: array of byte): string
+{
+ if(len addr != 4)
+ return "0.0.0.0";
+ return sys->sprint("%d.%d.%d.%d",
+ int addr[0],
+ int addr[1],
+ int addr[2],
+ int addr[3]);
+}
+
+dtoa(data: array of byte): string
+{
+ if(data == nil)
+ return nil;
+ result: string;
+ for(i:=0; i < len data; i++)
+ result += sys->sprint(".%d", int data[i]);
+ return result[1:];
+}
+
+bptohw(bp: ref BootpPKT): string
+{
+ l := int bp.hlen;
+ if(l > 0 && l < len bp.chaddr)
+ return ether->text(bp.chaddr[0:l]);
+ return "";
+}
+
+ctostr(cstr: array of byte): string
+{
+ for(i:=0; i<len cstr; i++)
+ if(cstr[i] == byte 0)
+ break;
+ return string cstr[0:i];
+}
+
+strtoc(s: string): array of byte
+{
+ as := array of byte s;
+ cs := array[1 + len as] of byte;
+ cs[0:] = as;
+ cs[len cs - 1] = byte 0;
+ return cs;
+}
+
+ppkt(bootp: ref BootpPKT)
+{
+ sys->fprint(stderr, "BootpPKT {\n");
+ sys->fprint(stderr, "\top == %d\n", int bootp.op);
+ sys->fprint(stderr, "\thtype == %d\n", int bootp.htype);
+ sys->fprint(stderr, "\thlen == %d\n", int bootp.hlen);
+ sys->fprint(stderr, "\thops == %d\n", int bootp.hops);
+ sys->fprint(stderr, "\txid == %d\n", bootp.xid);
+ sys->fprint(stderr, "\tsecs == %d\n", bootp.secs);
+ sys->fprint(stderr, "\tC client == %s\n", dtoa(bootp.ciaddr));
+ sys->fprint(stderr, "\tY client == %s\n", dtoa(bootp.yiaddr));
+ sys->fprint(stderr, "\tserver == %s\n", dtoa(bootp.siaddr));
+ sys->fprint(stderr, "\tgateway == %s\n", dtoa(bootp.giaddr));
+ sys->fprint(stderr, "\thwaddr == %s\n", bptohw(bootp));
+ sys->fprint(stderr, "\thost == %s\n", bootp.sname);
+ sys->fprint(stderr, "\tfile == %s\n", bootp.file);
+ sys->fprint(stderr, "\tmagic == %s\n", magic(bootp.vend[0:4]));
+ if(magic(bootp.vend[0:4]) == "plan9") {
+ (n, strs) := sys->tokenize(string bootp.vend[4:], " \r\n");
+ if(strs != nil) {
+ sys->fprint(stderr, "\t\tsm == %s\n", hd strs);
+ strs = tl strs;
+ }
+ if(strs != nil) {
+ sys->fprint(stderr, "\t\tfs == %s\n", hd strs);
+ strs = tl strs;
+ }
+ if(strs != nil) {
+ sys->fprint(stderr, "\t\tau == %s\n", hd strs);
+ strs = tl strs;
+ }
+ if(strs != nil) {
+ sys->fprint(stderr, "\t\tgw == %s\n", hd strs);
+ strs = tl strs;
+ }
+ }
+ sys->fprint(stderr, "}\n\n");
+}
+
+eqa(a1: array of byte, a2: array of byte): int
+{
+ if(len a1 != len a2)
+ return 0;
+ for(i := 0; i < len a1; i++)
+ if(a1[i] != a2[i])
+ return 0;
+ return 1;
+}
+
+magic(cookie: array of byte): string
+{
+ if(eqa(cookie, array[] of { byte 'p', byte '9', byte ' ', byte ' ' }))
+ return "plan9";
+ if(eqa(cookie, array[] of { byte 99, byte 130, byte 83, byte 99 }))
+ return "rfc1048";
+ if(eqa(cookie, array[] of { byte 'C', byte 'M', byte 'U', byte 0 }))
+ return "cmu";
+ return dtoa(cookie);
+}
+
+pinfbp(rec: ref InfBP)
+{
+ sys->fprint(stderr, "Bootp entry {\n");
+ sys->fprint(stderr, "\tha == %s\n", ether->text(rec.ha));
+ sys->fprint(stderr, "\tip == %s\n", dtoa(rec.ip));
+ sys->fprint(stderr, "\tbf == %s\n", string rec.bf);
+ sys->fprint(stderr, "\tsm == %s\n", dtoa(rec.sm));
+ sys->fprint(stderr, "\tgw == %s\n", dtoa(rec.gw));
+ sys->fprint(stderr, "\tfs == %s\n", dtoa(rec.fs));
+ sys->fprint(stderr, "\tau == %s\n", dtoa(rec.au));
+ sys->fprint(stderr, "}\n");
+}
+
+M2S(data: array of byte): (string, ref BootpPKT)
+{
+ if(len data < 300)
+ return ("too short", nil);
+
+ bootp := ref BootpPKT;
+
+ bootp.op = data[0];
+ bootp.htype = data[1];
+ bootp.hlen = data[2];
+ bootp.hops = data[3];
+ bootp.xid = nhgetl(data[4:8]);
+ bootp.secs = nhgets(data[8:10]);
+ # data[10:12] unused
+ bootp.ciaddr = data[12:16];
+ bootp.yiaddr = data[16:20];
+ bootp.siaddr = data[20:24];
+ bootp.giaddr = data[24:28];
+ bootp.chaddr = data[28:44];
+ bootp.sname = ctostr(data[44:108]);
+ bootp.file = ctostr(data[108:236]);
+ bootp.vend = data[236:300];
+
+ return (nil, bootp);
+}
+
+S2M(bootp: ref BootpPKT): array of byte
+{
+ data := array[364] of { * => byte 0 };
+
+ data[0] = bootp.op;
+ data[1] = bootp.htype;
+ data[2] = bootp.hlen;
+ data[3] = bootp.hops;
+ data[4:] = nhputl(bootp.xid);
+ data[8:] = nhputs(bootp.secs);
+ # data[10:12] unused
+ data[12:] = bootp.ciaddr;
+ data[16:] = bootp.yiaddr;
+ data[20:] = bootp.siaddr;
+ data[24:] = bootp.giaddr;
+ data[28:] = bootp.chaddr;
+ data[44:] = array of byte bootp.sname;
+ data[108:] = array of byte bootp.file;
+ data[236:] = bootp.vend;
+
+ return data;
+}
+
+nhgetl(data: array of byte): int
+{
+ return (int data[0]<<24) | (int data[1]<<16) |
+ (int data[2]<<8) | int data[3];
+}
+
+nhgets(data: array of byte): int
+{
+ return (int data[0]<<8) | int data[1];
+}
+
+nhputl(value: int): array of byte
+{
+ return array[] of {
+ byte (value >> 24),
+ byte (value >> 16),
+ byte (value >> 8),
+ byte (value >> 0),
+ };
+}
+
+nhputs(value: int): array of byte
+{
+ return array[] of {
+ byte (value >> 8),
+ byte (value >> 0),
+ };
+}
+
diff --git a/appl/cmd/ip/ping.b b/appl/cmd/ip/ping.b
new file mode 100644
index 00000000..a148c1e6
--- /dev/null
+++ b/appl/cmd/ip/ping.b
@@ -0,0 +1,369 @@
+implement Ping;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "ip.m";
+ ip: IP;
+ IPaddr: import ip;
+
+include "timers.m";
+ timers: Timers;
+ Timer: import timers;
+
+include "rand.m";
+ rand: Rand;
+
+include "arg.m";
+
+Ping: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Icmp: adt
+{
+ ttl: int; # time to live
+ src: IPaddr;
+ dst: IPaddr;
+ ptype: int;
+ code: int;
+ seq: int;
+ munged: int;
+ time: big;
+
+ unpack: fn(b: array of byte): ref Icmp;
+};
+
+# packet types
+EchoReply: con 0;
+Unreachable: con 3;
+SrcQuench: con 4;
+EchoRequest: con 8;
+TimeExceed: con 11;
+Timestamp: con 13;
+TimestampReply: con 14;
+InfoRequest: con 15;
+InfoReply: con 16;
+
+Nmsg: con 32;
+Interval: con 1000; # ms
+
+Req: adt
+{
+ seq: int; # sequence number
+ time: big; # time sent
+ rtt: big;
+ ttl: int;
+ replied: int;
+};
+
+debug := 0;
+quiet := 0;
+lostonly := 0;
+lostmsgs := 0;
+rcvdmsgs := 0;
+sum := big 0;
+firstseq := 0;
+addresses := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ rand = load Rand Rand->PATH;
+ timers = load Timers Timers->PATH;
+ ip = load IP IP->PATH;
+ ip->init();
+
+
+ msglen := interval := 0;
+ nmsg := Nmsg;
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("ip/ping [-alq] [-s msgsize] [-i millisecs] [-n #pings] destination");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'l' =>
+ lostonly++;
+ 'd' =>
+ debug++;
+ 's' =>
+ msglen = int arg->earg();
+ 'i' =>
+ interval = int arg->earg();
+ 'n' =>
+ nmsg = int arg->earg();
+ 'a' =>
+ addresses = 1;
+ 'q' =>
+ quiet = 1;
+ }
+ if(msglen < 32)
+ msglen = 64;
+ if(msglen >= 65*1024)
+ msglen = 65*1024-1;
+ if(interval <= 0)
+ interval = Interval;
+
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+ opentime();
+ rand->init(int(nsec()/big 1000));
+
+ addr := netmkaddr(hd args, "icmp", "1");
+ (ok, c) := sys->dial(addr, nil);
+ if(ok < 0){
+ sys->fprint(sys->fildes(2), "ip/ping: can't dial %s: %r\n", addr);
+ raise "fail:dial";
+ }
+
+ sys->print("sending %d %d byte messages %d ms apart\n", nmsg, msglen, interval);
+
+ done := chan of int;
+ reqs := chan of ref Req;
+
+ spawn sender(c.dfd, msglen, interval, nmsg, done, reqs);
+ spid := <-done;
+
+ pids := chan of int;
+ replies := chan [8] of ref Icmp;
+ spawn reader(c.dfd, msglen, replies, pids);
+ rpid := <-pids;
+
+ tpid := 0;
+ timeout := chan of int;
+ requests: list of ref Req;
+Work:
+ for(;;) alt{
+ r := <-reqs =>
+ requests = r :: requests;
+ ic := <-replies =>
+ if(ic == nil){
+ rpid = 0;
+ break Work;
+ }
+ if(ic.munged)
+ sys->print("corrupted reply\n");
+ if(ic.ptype != EchoReply || ic.code != 0){
+ sys->print("bad type/code %d/%d seq %d\n",
+ ic.ptype, ic.code, ic.seq);
+ continue;
+ }
+ requests = clean(requests, ic);
+ if(lostmsgs+rcvdmsgs == nmsg)
+ break Work;
+ <-done =>
+ spid = 0;
+ # must be at least one message outstanding; wait for it
+ tpid = timers->init(Timers->Sec);
+ timeout = Timer.start((nmsg-lostmsgs-rcvdmsgs)*interval+5*Timers->Sec).timeout;
+ <-timeout =>
+ break Work;
+ }
+ kill(rpid);
+ kill(spid);
+ kill(tpid);
+
+ for(; requests != nil; requests = tl requests)
+ if((hd requests).replied == 0)
+ lostmsgs++;
+
+ if(lostmsgs){
+ sys->print("%d out of %d message(s) lost\n", lostmsgs, lostmsgs+rcvdmsgs);
+ raise "fail:lost messages";
+ }
+}
+
+kill(pid: int)
+{
+ if(pid)
+ sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill");
+}
+
+SECOND: con big 1000000000; # nanoseconds
+MINUTE: con big 60*SECOND;
+
+clean(l: list of ref Req, ip: ref Icmp): list of ref Req
+{
+ left: list of ref Req;
+ for(; l != nil; l = tl l){
+ r := hd l;
+ if(ip.seq == r.seq){
+ r.rtt = ip.time-r.time;
+ r.ttl = ip.ttl;
+ reply(r, ip);
+ }
+ if(ip.time-r.time > MINUTE){
+ r.rtt = ip.time-r.time;
+ r.ttl = ip.ttl;
+ if(!r.replied)
+ lost(r, ip);
+ }else
+ left = r :: left;
+ }
+ return left;
+}
+
+sender(fd: ref Sys->FD, msglen: int, interval: int, n: int, done: chan of int, reqs: chan of ref Req)
+{
+
+ done <-= sys->pctl(0, nil);
+
+ firstseq = rand->rand(65536) - n; # -n to ensure we don't exceed 16 bits
+ if(firstseq < 0)
+ firstseq = 0;
+
+ buf := array[64*1024+512] of {* => byte 0};
+ for(i := Odata; i < msglen; i++)
+ buf[i] = byte i;
+ buf[Otype] = byte EchoRequest;
+ buf[Ocode] = byte 0;
+
+ seq := firstseq;
+ for(i = 0; i < n; i++){
+ if(i != 0)
+ sys->sleep(interval);
+ ip->put2(buf, Oseq, seq); # order?
+ r := ref Req;
+ r.seq = seq;
+ r.replied = 0;
+ r.time = nsec();
+ reqs <-= r;
+ if(sys->write(fd, buf, msglen) < msglen){
+ sys->fprint(sys->fildes(2), "ping: write failed: %r\n");
+ break;
+ }
+ seq++;
+ }
+ done <-= 1;
+}
+
+reader(fd: ref Sys->FD, msglen: int, out: chan of ref Icmp, pid: chan of int)
+{
+ pid <-= sys->pctl(0, nil);
+ buf := array[64*1024+512] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0){
+ now := nsec();
+ if(n < msglen){
+ sys->print("bad len %d/%d\n", n, msglen);
+ continue;
+ }
+ ic := Icmp.unpack(buf[0:n]);
+ ic.munged = 0;
+ for(i := Odata; i < msglen; i++)
+ if(buf[i] != byte i)
+ ic.munged++;
+ ic.time = now;
+ out <-= ic;
+ }
+ sys->print("read: %r\n");
+ out <-= nil;
+}
+
+reply(r: ref Req, ic: ref Icmp)
+{
+ rcvdmsgs++;
+ r.rtt /= big 1000;
+ sum += r.rtt;
+ if(!quiet && !lostonly){
+ if(addresses)
+ sys->print("%ud: %s->%s rtt %bd µs, avg rtt %bd µs, ttl = %d\n",
+ r.seq-firstseq,
+ ic.src.text(), ic.dst.text(),
+ r.rtt, sum/big rcvdmsgs, r.ttl);
+ else
+ sys->print("%ud: rtt %bd µs, avg rtt %bd µs, ttl = %d\n",
+ r.seq-firstseq,
+ r.rtt, sum/big rcvdmsgs, r.ttl);
+ }
+ r.replied = 1; # TO DO: duplicates might be interesting
+}
+
+lost(r: ref Req, ic: ref Icmp)
+{
+ if(!quiet){
+ if(addresses)
+ sys->print("lost %ud: %s->%s avg rtt %bd µs\n",
+ r.seq-firstseq,
+ ic.src.text(), ic.dst.text(),
+ sum/big rcvdmsgs);
+ else
+ sys->print("lost %ud: avg rtt %bd µs\n",
+ r.seq-firstseq,
+ sum/big rcvdmsgs);
+ }
+ lostmsgs++;
+}
+
+Ovihl: con 0;
+Otos: con 1;
+Olength: con 2;
+Oid: con Olength+2;
+Ofrag: con Oid+2;
+Ottl: con Ofrag+2;
+Oproto: con Ottl+1;
+Oipcksum: con Oproto+1;
+Osrc: con Oipcksum+2;
+Odst: con Osrc+4;
+Otype: con Odst+4;
+Ocode: con Otype+1;
+Ocksum: con Ocode+1;
+Oicmpid: con Ocksum+2;
+Oseq: con Oicmpid+2;
+Odata: con Oseq+2;
+
+Icmp.unpack(b: array of byte): ref Icmp
+{
+ ic := ref Icmp;
+ ic.ttl = int b[Ottl];
+ ic.src = IPaddr.newv4(b[Osrc:]);
+ ic.dst = IPaddr.newv4(b[Odst:]);
+ ic.ptype = int b[Otype];
+ ic.code = int b[Ocode];
+ ic.seq = ip->get2(b, Oseq);
+ ic.munged = 0;
+ ic.time = big 0;
+ return ic;
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
+
+timefd: ref Sys->FD;
+
+opentime()
+{
+ timefd = sys->open("/dev/time", Sys->OREAD);
+ if(timefd == nil){
+ sys->fprint(sys->fildes(2), "ping: can't open /dev/time: %r\n");
+ raise "fail:no time";
+ }
+}
+
+nsec(): big
+{
+ buf := array[64] of byte;
+ n := sys->pread(timefd, buf, len buf, big 0);
+ if(n <= 0)
+ return big 0;
+ return big string buf[0:n] * big 1000;
+}
diff --git a/appl/cmd/ip/ppp/mkfile b/appl/cmd/ip/ppp/mkfile
new file mode 100644
index 00000000..193b8faf
--- /dev/null
+++ b/appl/cmd/ip/ppp/mkfile
@@ -0,0 +1,27 @@
+<../../../../mkconfig
+
+TARG=\
+ pppclient.dis\
+ pppdial.dis\
+ pppgui.dis\
+ ppptest.dis\
+ modem.dis\
+ script.dis\
+
+MODULES=\
+ modem.m\
+ pppclient.m\
+ pppgui.m\
+ script.m\
+
+SYSMODULES=\
+ sys.m\
+ draw.m\
+ tk.m\
+ dict.m\
+ string.m\
+ lock.m\
+
+DISBIN=$ROOT/dis/ip/ppp
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/ip/ppp/modem.b b/appl/cmd/ip/ppp/modem.b
new file mode 100644
index 00000000..6085524a
--- /dev/null
+++ b/appl/cmd/ip/ppp/modem.b
@@ -0,0 +1,468 @@
+implement Modem;
+
+include "sys.m";
+ sys: Sys;
+
+include "lock.m";
+ lock: Lock;
+ Semaphore: import lock;
+
+include "draw.m";
+
+include "modem.m";
+
+hangupcmd := "ATH0"; # was ATZH0 but some modem versions on Umec hung on ATZ (BUG: should be in modeminfo)
+
+# modem return codes
+Ok, Success, Failure, Abort, Noise, Found: con iota;
+
+maxspeed: con 115200;
+
+#
+# modem return messages
+#
+Msg: adt {
+ text: string;
+ code: int;
+};
+
+msgs: array of Msg = array [] of {
+ ("OK", Ok),
+ ("NO CARRIER", Failure),
+ ("ERROR", Failure),
+ ("NO DIALTONE", Failure),
+ ("BUSY", Failure),
+ ("NO ANSWER", Failure),
+ ("CONNECT", Success),
+};
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "kill") < 0)
+ sys->print("modem: can't kill %d: %r\n", pid);
+}
+
+#
+# prepare a modem port
+#
+openserial(d: ref Device)
+{
+ if (d==nil) {
+ raise "fail: device not initialized";
+ return;
+ }
+
+ d.data = nil;
+ d.ctl = nil;
+
+ d.data = sys->open(d.local, Sys->ORDWR);
+ if(d.data == nil) {
+ raise "fail: can't open "+d.local;
+ return;
+ }
+
+ d.ctl = sys->open(d.local+"ctl", Sys->ORDWR);
+ if(d.ctl == nil) {
+ raise "can't open "+d.local+"ctl";
+ return;
+ }
+
+ d.speed = maxspeed;
+ d.avail = nil;
+}
+
+#
+# shut down the monitor (if any) and return the connection
+#
+
+close(m: ref Device): ref Sys->Connection
+{
+ if(m == nil)
+ return nil;
+ if(m.pid != 0){
+ kill(m.pid);
+ m.pid = 0;
+ }
+ if(m.data == nil)
+ return nil;
+ mc := ref sys->Connection(m.data, m.ctl, nil);
+ m.ctl = nil;
+ m.data = nil;
+ return mc;
+}
+
+#
+# Send a string to the modem
+#
+
+send(d: ref Device, x: string): int
+{
+ if (d == nil)
+ return -1;
+
+ a := array of byte x;
+ f := sys->write(d.data, a, len a);
+ if (f < 0) {
+ # let's attempt to close & reopen the modem
+ close(d);
+ openserial(d);
+ f = sys->write(d.data,a, len a);
+ }
+ sys->print("->%s\n",x);
+ return f;
+}
+
+#
+# apply a string of commands to modem & look for a response
+#
+
+apply(d: ref Device, s: string, substr: string, secs: int): int
+{
+ m := Ok;
+ buf := "";
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ buf[len buf] = c; # assume no Unicode
+ if(c == '\r' || i == (len s -1)){
+ if(c != '\r')
+ buf[len buf] = '\r';
+ if(send(d, buf) < 0)
+ return Abort;
+ (m, nil) = readmsg(d, secs, substr);
+ buf = "";
+ }
+ }
+ return m;
+}
+
+#
+# get modem into command mode if it isn't already
+#
+GUARDTIME: con 1100; # usual default for S12=50 in units of 1/50 sec; allow 100ms fuzz
+
+attention(d: ref Device): int
+{
+ for(i := 0; i < 3; i++){
+ if(apply(d, hangupcmd, nil, 2) == Ok)
+ return Ok;
+ sys->sleep(GUARDTIME);
+ if(send(d, "+++") < 0)
+ return Abort;
+ sys->sleep(GUARDTIME);
+ (nil, msg) := readmsg(d, 0, nil);
+ if(msg != nil)
+ sys->print("status: %s\n", msg);
+ }
+ return Failure;
+}
+
+#
+# apply a command type
+#
+
+applyspecial(d: ref Device, cmd: string): int
+{
+ if(cmd == nil)
+ return Failure;
+ return apply(d, cmd, nil, 2);
+}
+
+#
+# hang up any connections in progress and close the device
+#
+onhook(d: ref Device)
+{
+ if(d == nil)
+ return;
+
+ # hang up the modem
+ monitoring(d);
+ if(attention(d) != Ok)
+ sys->print("modem: no attention\n");
+
+ # hangup the stream (eg, for ppp) and toggle the lines to the modem
+ if(d.ctl != nil) {
+ sys->fprint(d.ctl,"d0\n");
+ sys->fprint(d.ctl,"r0\n");
+ sys->fprint(d.ctl, "h\n"); # hangup on native serial
+ sys->sleep(250);
+ sys->fprint(d.ctl,"r1\n");
+ sys->fprint(d.ctl,"d1\n");
+ }
+
+ close(d);
+}
+
+#
+# does string s contain t anywhere?
+#
+
+contains(s, t: string): int
+{
+ if(t == nil)
+ return 1;
+ if(s == nil)
+ return 0;
+ n := len t;
+ for(i := 0; i+n <= len s; i++)
+ if(s[i:i+n] == t)
+ return 1;
+ return 0;
+}
+
+#
+# read till we see a message or we time out
+#
+readmsg(d: ref Device, secs: int, substr: string): (int, string)
+{
+ if (d == nil)
+ return (Abort, "device not initialized");
+ found := 0;
+ secs *= 1000;
+ limit := 1000; # pretty arbitrary
+ s := "";
+
+ for(start := sys->millisec(); sys->millisec() <= start+secs;){
+ a := getinput(d,1);
+ if(len a == 0){
+ if(limit){
+ sys->sleep(1);
+ continue;
+ }
+ break;
+ }
+ if(a[0] == byte '\n' || a[0] == byte '\r' || limit == 0){
+ if (len s) {
+ if (s[(len s)-1] == '\r')
+ s[(len s)-1] = '\n';
+ sys->print("<-%s\n",s);
+ }
+ if(substr != nil && contains(s, substr))
+ found = 1;
+ for(k := 0; k < len msgs; k++)
+ if(len s >= len msgs[k].text &&
+ s[0:len msgs[k].text] == msgs[k].text){
+ if(found)
+ return (Found, s);
+ return (msgs[k].code, s);
+ }
+ start = sys->millisec();
+ s = "";
+ continue;
+ }
+ s[len s] = int a[0];
+ limit--;
+ }
+ s = "No response from modem";
+ if(found)
+ return (Found, s);
+
+ return (Noise, s);
+}
+
+#
+# get baud rate from a connect message
+#
+
+getspeed(msg: string, speed: int): int
+{
+ p := msg[7:]; # skip "CONNECT"
+ while(p[0] == ' ' || p[0] == '\t')
+ p = p[1:];
+ s := int p;
+ if(s <= 0)
+ return speed;
+ else
+ return s;
+}
+
+#
+# set speed and RTS/CTS modem flow control
+#
+
+setspeed(d: ref Device, baud: int)
+{
+ if(d != nil && d.ctl != nil){
+ sys->fprint(d.ctl, "b%d", baud);
+ sys->fprint(d.ctl, "m1");
+ }
+}
+
+dumpa(a: array of byte): string
+{
+ s := "";
+ for(i:=0; i<len a; i++){
+ b := int a[i];
+ if(b >= ' ' && b < 16r7f)
+ s[len s] = b;
+ else
+ s += sys->sprint("\\%.2x", b);
+ }
+ return s;
+}
+
+monitoring(d: ref Device)
+{
+ # if no monitor then spawn one
+ if(d.pid == 0) {
+ pidc := chan of int;
+ spawn monitor(d, pidc);
+ d.pid = <-pidc;
+ }
+}
+
+#
+# a process to read input from a modem.
+#
+monitor(d: ref Device, pidc: chan of int)
+{
+ openserial(d);
+ pidc <-= sys->pctl(0, nil); # pidc can be written once only.
+ a := array[Sys->ATOMICIO] of byte;
+ for(;;) {
+ d.lock.obtain();
+ d.status = "Idle";
+ d.remote = "";
+ setspeed(d, d.speed);
+ d.lock.release();
+ # shuttle bytes
+ while((n := sys->read(d.data, a, len a)) > 0){
+ d.lock.obtain();
+ if (len d.avail < Sys->ATOMICIO) {
+ na := array[len d.avail + n] of byte;
+ na[0:] = d.avail[0:];
+ na[len d.avail:] = a[0:n];
+ d.avail = na;
+ }
+ d.lock.release();
+ }
+ # on an error, try reopening the device
+ d.data = nil;
+ d.ctl = nil;
+ openserial(d);
+ }
+}
+
+#
+# return up to n bytes read from the modem by monitor()
+#
+getinput(d: ref Device, n: int): array of byte
+{
+ if(d==nil || n <= 0)
+ return nil;
+ a: array of byte;
+ d.lock.obtain();
+ if(len d.avail != 0){
+ if(n > len d.avail)
+ n = len d.avail;
+ a = d.avail[0:n];
+ d.avail = d.avail[n:];
+ }
+ d.lock.release();
+ return a;
+}
+
+getc(m: ref Device, timo: int): int
+{
+ start := sys->millisec();
+ while((b := getinput(m, 1)) == nil) {
+ if (timo && sys->millisec() > start+timo)
+ return 0;
+ sys->sleep(1);
+ }
+ return int b[0];
+}
+
+init(modeminfo: ref ModemInfo): ref Device
+{
+ if (sys == nil) {
+ sys = load Sys Sys->PATH;
+ lock = load Lock Lock->PATH;
+ if (lock == nil) {
+ raise "fail: Couldn't load lock module";
+ return nil;
+ }
+ lock->init();
+ }
+
+ newdev := ref Device;
+ newdev.lock = Semaphore.new();
+ newdev.local = modeminfo.path;
+ newdev.pid = 0;
+ newdev.t = modeminfo;
+
+ return newdev;
+}
+
+
+#
+# dial a number
+#
+dial(d: ref Device, number: string)
+{
+ if (d==nil) {
+ raise "fail: Device not initialized";
+ return;
+ }
+
+ monitoring(d);
+
+ # modem type should already be established, but just in case
+ sys->print("Attention\n");
+ x := attention(d);
+ if (x != Ok)
+ sys->print("Attention failed\n");
+ #
+ # extended Hayes commands, meaning depends on modem (VGA all over again)
+ #
+ sys->print("Init\n");
+ if(d.t.country != nil)
+ applyspecial(d, d.t.country);
+
+ if(d.t.init != nil)
+ applyspecial(d, d.t.init);
+
+ if(d.t.other != nil)
+ applyspecial(d, d.t.other);
+
+ applyspecial(d, d.t.errorcorrection);
+
+ compress := Abort;
+ if(d.t.mnponly != nil)
+ compress = applyspecial(d, d.t.mnponly);
+ if(d.t.compression != nil)
+ compress = applyspecial(d, d.t.compression);
+
+ rateadjust := Abort;
+ if(compress != Ok)
+ rateadjust = applyspecial(d, d.t.rateadjust);
+ applyspecial(d, d.t.flowctl);
+
+ # finally, dialout
+ sys->print("Dialing\n");
+ if((dt := d.t.dialtype) == nil)
+ dt = "ATDT";
+ if(send(d, sys->sprint("%s%s\r", dt, number)) < 0) {
+ raise "can't dial "+number;
+ return;
+ }
+
+ (i, msg) := readmsg(d, 120, nil);
+ if(i != Success) {
+ raise "fail: "+msg;
+ return;
+ }
+
+ connectspeed := getspeed(msg, d.speed);
+
+ # change line rate if not compressing
+ if(rateadjust == Ok)
+ setspeed(d, connectspeed);
+
+ if(d.ctl != nil){
+ if(d != nil)
+ sys->fprint(d.ctl, "s%d", connectspeed); # set DCE speed (if device implements it)
+ sys->fprint(d.ctl, "c1"); # enable CD monitoring
+ }
+}
diff --git a/appl/cmd/ip/ppp/modem.m b/appl/cmd/ip/ppp/modem.m
new file mode 100644
index 00000000..9a99acf8
--- /dev/null
+++ b/appl/cmd/ip/ppp/modem.m
@@ -0,0 +1,41 @@
+Modem: module
+{
+ PATH: con "/dis/ip/ppp/modem.dis";
+
+ ModemInfo: adt {
+ path: string;
+ init: string;
+ country: string;
+ other: string;
+ errorcorrection:string;
+ compression: string;
+ flowctl: string;
+ rateadjust: string;
+ mnponly: string;
+ dialtype: string;
+ };
+
+ Device: adt {
+ lock: ref Lock->Semaphore;
+ # modem stuff
+ ctl: ref Sys->FD;
+ data: ref Sys->FD;
+
+ local: string;
+ remote: string;
+ status: string;
+ speed: int;
+ t: ref ModemInfo;
+ # input reader
+ avail: array of byte;
+ pid: int;
+ };
+
+ init: fn(i: ref ModemInfo): ref Device;
+ dial: fn( m: ref Device, number: string);
+ getc: fn(m: ref Device, timout: int): int;
+ getinput: fn(m: ref Device, n: int ): array of byte;
+ send: fn(m: ref Device, x: string): int;
+ close: fn(m: ref Device): ref Sys->Connection;
+ onhook: fn(m: ref Device);
+};
diff --git a/appl/cmd/ip/ppp/pppclient.b b/appl/cmd/ip/ppp/pppclient.b
new file mode 100644
index 00000000..be321b59
--- /dev/null
+++ b/appl/cmd/ip/ppp/pppclient.b
@@ -0,0 +1,216 @@
+implement PPPClient;
+
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+
+include "pppclient.m";
+
+include "translate.m";
+ translate : Translate;
+ Dict : import translate;
+ dict : ref Dict;
+
+#
+# Globals (these will have to be removed if we are going multithreaded)
+#
+
+pid := 0;
+modeminfo: ref Modem->ModemInfo;
+pppdir: string;
+
+ppplog(log: chan of int, errfile: string, pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil); # set reset pid to our pid
+ src := sys->open(errfile, Sys->OREAD);
+ if (src == nil)
+ raise sys->sprint("fail: Couldn't open %s: %r", errfile);
+
+ LOGBUFMAX: con 1024;
+ buf := array[LOGBUFMAX] of byte;
+ connected := 0;
+
+ while ((count := sys->read(src, buf, LOGBUFMAX)) > 0) {
+ (n, toklist) := sys->tokenize(string buf[:count],"\n");
+ for (;toklist != nil;toklist = tl toklist) {
+ case hd toklist {
+ "no error" =>
+ log <-= s_SuccessPPP;
+ lasterror = nil;
+ connected = 1;
+ "permission denied" =>
+ lasterror = X("Username or Password Incorrect");
+ log <-= s_Error;
+ "write to hungup channel" =>
+ lasterror = X("Remote Host Hung Up");
+ log <-= s_Error;
+ * =>
+ lasterror = X(hd toklist);
+ log <-= s_Error;
+ }
+ }
+ }
+ if(count == 0 && connected && lasterror == nil){ # should change ip/pppmedium.c instead?
+ lasterror = X("Lost Connection");
+ log <-= s_Error;
+ }
+}
+
+startppp(logchan: chan of int, pppinfo: ref PPPInfo)
+{
+ ifd := sys->open("/net/ipifc/clone", Sys->ORDWR);
+ if (ifd == nil)
+ raise "fail: Couldn't open /net/ipifc/clone";
+
+ buf := array[32] of byte;
+ n := sys->read(ifd, buf, len buf);
+ if(n <= 0)
+ raise "fail: can't read from /net/ipifc/clone";
+
+ pppdir = "/net/ipifc/" + string buf[0:n];
+ pidc := chan of int;
+ spawn ppplog(logchan, pppdir + "/err", pidc);
+ pid = <-pidc;
+ logchan <-= s_StartPPP;
+
+ if (pppinfo.ipaddr == nil)
+ pppinfo.ipaddr = "-";
+# if (pppinfo.ipmask == nil)
+# pppinfo.ipmask = "255.255.255.255";
+ if (pppinfo.peeraddr == nil)
+ pppinfo.peeraddr = "-";
+ if (pppinfo.maxmtu == nil)
+ pppinfo.maxmtu = "512";
+ if (pppinfo.username == nil)
+ pppinfo.username = "-";
+ if (pppinfo.password == nil)
+ pppinfo.password = "-";
+ framing := "1";
+
+ ifc := "bind ppp "+modeminfo.path+" "+ pppinfo.ipaddr+" "+pppinfo.peeraddr+" "+pppinfo.maxmtu
+ +" "+framing+" "+pppinfo.username+" "+pppinfo.password;
+
+ # send the add command
+ if (sys->fprint(ifd, "%s", ifc) < 0) {
+ sys->print("pppclient: couldn't write %s/ctl: %r\n", pppdir);
+ raise "fail: Couldn't write /net/ipifc";
+ return;
+ }
+}
+
+connect(mi: ref Modem->ModemInfo, number: string,
+ scriptinfo: ref Script->ScriptInfo, pppinfo: ref PPPInfo, logchan: chan of int)
+{
+ sys = load Sys Sys->PATH;
+
+ translate = load Translate Translate->PATH;
+ if (translate != nil) {
+ translate->init();
+ dictname := translate->mkdictname("", "pppclient");
+ (dict, nil) = translate->opendict(dictname);
+ }
+ if (pid != 0) # yikes we are already running
+ reset();
+
+ # create a new process group
+ pid = sys->pctl( Sys->NEWPGRP, nil);
+
+ {
+ logchan <-= s_Initialized;
+
+ # open & init the modem
+ modeminfo = mi;
+ modem := load Modem Modem->PATH;
+ if (modem == nil) {
+ raise "fail: Couldn't load modem module";
+ return;
+ }
+
+ modemdev := modem->init(modeminfo);
+ logchan <-= s_StartModem;
+ modem->dial(modemdev, number);
+ logchan <-= s_SuccessModem;
+
+ # if script
+ if (scriptinfo != nil) {
+ script := load Script Script->PATH;
+ if (script == nil) {
+ raise "fail: Couldn't load script module";
+ return;
+ }
+ logchan <-= s_StartScript;
+ script->execute(modem, modemdev, scriptinfo);
+ logchan <-= s_SuccessScript;
+ }
+
+ mc := modem->close(modemdev); # keep connection open for ppp mode
+ modemdev = nil;
+ modem = nil; # unload modem module
+
+ # if ppp
+ if (pppinfo != nil)
+ startppp(logchan, pppinfo);
+ else
+ logchan <-= s_Done;
+ }
+ exception e{
+ "fail*" =>
+ lasterror = e;
+ sys->print("PPPclient: fatal exception: %s\n", e);
+ logchan <-= s_Error;
+ kill(pid, "killgrp");
+ exit;
+ }
+}
+
+reset()
+{
+ sys->print("reset...");
+ if(pid != 0){
+ kill(pid, "killgrp");
+ pid = 0;
+ }
+
+ if(pppdir != nil){ # shut down the PPP link
+ fd := sys->open(pppdir + "/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "unbind") < 0)
+ sys->print("pppclient: can't unbind: %r\n");
+ fd = nil;
+ pppdir = nil;
+ }
+
+ modem := load Modem Modem->PATH;
+ if (modem == nil) {
+ raise "fail: Couldn't load modem module";
+ return;
+ }
+ modemdev := modem->init(modeminfo);
+ if(modemdev != nil)
+ modem->onhook(modemdev);
+ modem = nil;
+
+ # clear error buffer
+ lasterror = nil;
+}
+
+kill(pid: int, msg: string)
+{
+ a := array of byte msg;
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->write(fd, a, len a) < 0)
+ sys->print("pppclient: can't %s %d: %r\n", msg, pid);
+}
+
+# Translate a string
+
+X(s : string) : string
+{
+ if (dict== nil) return s;
+ return dict.xlate(s);
+}
+
diff --git a/appl/cmd/ip/ppp/pppclient.m b/appl/cmd/ip/ppp/pppclient.m
new file mode 100644
index 00000000..23396af4
--- /dev/null
+++ b/appl/cmd/ip/ppp/pppclient.m
@@ -0,0 +1,31 @@
+
+PPPClient: module {
+ PATH: con "/dis/ip/ppp/pppclient.dis";
+
+ PPPInfo: adt {
+ ipaddr: string;
+ ipmask: string;
+ peeraddr: string;
+ maxmtu: string;
+ username: string;
+ password: string;
+ };
+
+ connect: fn( mi: ref Modem->ModemInfo, number: string,
+ scriptinfo: ref Script->ScriptInfo,
+ pppinfo: ref PPPInfo, logchan: chan of int);
+ reset: fn();
+
+ lasterror :string;
+
+ s_Error: con -666;
+ s_Initialized, # Module Initialized
+ s_StartModem, # Modem Initialized
+ s_SuccessModem, # Modem Connected
+ s_StartScript, # Script Executing
+ s_SuccessScript, # Script Executed Sucessfully
+ s_StartPPP, # PPP Started
+ s_LoginPPP, # CHAP/PAP Authentication
+ s_SuccessPPP, # PPP Session Established
+ s_Done: con iota; # PPPClient Cleaningup & Exiting
+};
diff --git a/appl/cmd/ip/ppp/pppdial.b b/appl/cmd/ip/ppp/pppdial.b
new file mode 100644
index 00000000..ec689dc1
--- /dev/null
+++ b/appl/cmd/ip/ppp/pppdial.b
@@ -0,0 +1,283 @@
+implement PPPdial;
+
+#
+# Module: ispservice
+# Purpose: Simple PPP Dial-on-Demand
+# Author: Eric Van Hensbergen (ericvh@lucent.com)
+#
+# Copyright © 1998-1999 Lucent Technologies Inc. All rights reserved.
+# Revisions copyright © 2000-2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+
+include "cfgfile.m";
+ cfg: CfgFile;
+ ConfigFile: import cfg;
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+include "pppclient.m";
+ ppp: PPPClient;
+include "pppgui.m";
+
+PPPdial: module
+{
+ init: fn(nil: ref Draw->Context): string;
+ connect: fn(): string;
+};
+
+context: ref Draw->Context;
+modeminfo: ref Modem->ModemInfo;
+pppinfo: ref PPPClient->PPPInfo;
+scriptinfo: ref Script->ScriptInfo;
+isp_number: string; # should be part of pppinfo
+lastCdir: ref Sys->Dir; # state of file when last read
+
+DEFAULT_ISP_DB_PATH: con "/services/ppp/isp.cfg"; # contains pppinfo & scriptinfo
+DEFAULT_MODEM_DB_PATH: con "/services/ppp/modem.cfg"; # contains modeminfo
+MODEM_DB_PATH: con "/usr/inferno/config/modem.cfg"; # contains modeminfo
+ISP_DB_PATH: con "/usr/inferno/config/isp.cfg"; # contains pppinfo & scriptinfo
+ISP_RETRIES: con 5;
+
+getcfgstring(c: ref ConfigFile, key: string) :string
+{
+ l := c.getcfg(key);
+ if (l == nil)
+ return nil;
+ for(ret := ""; l != nil; l = tl l)
+ ret+= " " + hd l;
+
+ return ret[1:]; # trim the first space
+}
+
+configinit()
+{
+ mi: Modem->ModemInfo;
+ pppi: PPPClient->PPPInfo;
+ info: list of string;
+
+ cfg = load CfgFile CfgFile->PATH;
+ if (cfg == nil)
+ raise "fail: load CfgFile";
+
+ # Modem Configuration
+
+ cfg->verify(DEFAULT_MODEM_DB_PATH, MODEM_DB_PATH);
+ modemcfg := cfg->init(MODEM_DB_PATH);
+ if (modemcfg == nil)
+ raise "fail: read: "+MODEM_DB_PATH;
+ modeminfo = ref mi;
+
+ modeminfo.path = getcfgstring(modemcfg, "PATH");
+ modeminfo.init = getcfgstring(modemcfg, "INIT");
+ modeminfo.country = getcfgstring(modemcfg, "COUNTRY");
+ modeminfo.other = getcfgstring(modemcfg, "OTHER");
+ modeminfo.errorcorrection = getcfgstring(modemcfg,"CORRECT");
+ modeminfo.compression = getcfgstring(modemcfg,"COMPRESS");
+ modeminfo.flowctl = getcfgstring(modemcfg,"FLOWCTL");
+ modeminfo.rateadjust = getcfgstring(modemcfg,"RATEADJ");
+ modeminfo.mnponly = getcfgstring(modemcfg,"MNPONLY");
+ modeminfo.dialtype = getcfgstring(modemcfg,"DIALING");
+ if(modeminfo.dialtype!="ATDP")
+ modeminfo.dialtype="ATDT";
+
+ cfg->verify(DEFAULT_ISP_DB_PATH, ISP_DB_PATH);
+ (ok, stat) := sys->stat(ISP_DB_PATH);
+ if(ok >= 0)
+ lastCdir = ref stat;
+ sys->print("cfg->init(%s)\n", ISP_DB_PATH);
+
+ # ISP Configuration
+ pppcfg := cfg->init(ISP_DB_PATH);
+ if (pppcfg == nil)
+ raise "fail: Couldn't load ISP configuration file: "+ISP_DB_PATH;
+ pppinfo = ref pppi;
+ isp_number = getcfgstring(pppcfg, "NUMBER");
+ pppinfo.ipaddr = getcfgstring(pppcfg,"IPADDR");
+ pppinfo.ipmask = getcfgstring(pppcfg,"IPMASK");
+ pppinfo.peeraddr = getcfgstring(pppcfg,"PEERADDR");
+ pppinfo.maxmtu = getcfgstring(pppcfg,"MAXMTU");
+ pppinfo.username = getcfgstring(pppcfg,"USERNAME");
+ pppinfo.password = getcfgstring(pppcfg,"PASSWORD");
+
+ info = pppcfg.getcfg("SCRIPT");
+ if (info != nil) {
+ scriptinfo = ref Script->ScriptInfo;
+ scriptinfo.path = hd info;
+ scriptinfo.username = pppinfo.username;
+ scriptinfo.password = pppinfo.password;
+ } else
+ scriptinfo = nil;
+
+ info = pppcfg.getcfg("TIMEOUT");
+ if (info != nil)
+ scriptinfo.timeout = int (hd info);
+
+ cfg = nil; # might as well unload it
+}
+
+#
+# Parts of the following two functions could be generalized
+#
+
+isipaddr(a: string): int
+{
+ i, c, ac, np: int = 0;
+
+ for(i = 0; i < len a; i++) {
+ c = a[i];
+ if(c >= '0' && c <= '9') {
+ np = 10*np + c - '0';
+ continue;
+ }
+ if (c == '.' && np) {
+ ac++;
+ if (np > 255)
+ return 0;
+ np = 0;
+ continue;
+ }
+ return 0;
+ }
+ return np && np < 256 && ac == 3;
+}
+
+# check if there is an existing PPP connection
+connected(): int
+{
+ ifd := sys->open("/net/ipifc", Sys->OREAD);
+ if(ifd == nil)
+ return 0;
+
+ buf := array[1024] of byte;
+
+ for(;;) {
+ (n, d) := sys->dirread(ifd);
+ if (n <= 0)
+ return 0;
+ for(i := 0; i < n; i++)
+ if(d[i].name[0] <= '9') {
+ sfd := sys->open("/net/ipifc/"+d[i].name+"/status", Sys->OREAD);
+ if (sfd == nil)
+ continue;
+ ns := sys->read(sfd, buf, len buf);
+ if (ns <= 0)
+ continue;
+ (nflds, flds) := sys->tokenize(string buf[0:ns], " \t\r\n");
+ if(nflds < 4)
+ continue;
+ if (isipaddr(hd tl tl flds))
+ return 1;
+ }
+ }
+}
+
+#
+# called once when loaded
+#
+init(ctxt: ref Draw->Context): string
+{
+ sys = load Sys Sys->PATH;
+ {
+ ppp = load PPPClient PPPClient->PATH;
+ if (ppp == nil)
+ raise "fail: Couldn't load ppp module";
+
+ # Contruct Config Tables During Init - may want to change later
+ # for multiple configs (Software Download Server versus ISP)
+ configinit();
+ context = ctxt;
+ }exception e {
+ "fail:*" =>
+ return e;
+ }
+ return nil;
+}
+
+dialup_cancelled := 0;
+connecting := 0;
+
+#
+# called each time a translation is needed, to check that we're on line(!)
+# eventually this will be replaced by a packet interface that does dial-on-demand
+#
+connect(): string
+{
+ {
+ dialup_cancelled = 0;
+ (ok, stat) := sys->stat(ISP_DB_PATH);
+ if (ok < 0 || lastCdir == nil || !samefile(*lastCdir, stat))
+ configinit();
+ errc := chan of string;
+ while(!connected()){
+ if(!connecting) {
+ connecting = 1;
+ sync := chan of int;
+ spawn pppconnect(errc, sync);
+ <- sync;
+ return <-errc;
+ }else{
+ sys->sleep(2500);
+ if (dialup_cancelled)
+ return "fail: dialup cancelled";
+ }
+ }
+ }exception e{
+ "fail:*" =>
+ return e;
+ "*" =>
+ sys->print("pppdial: caught exception: %s\n", e);
+ return "fail: internal error: "+e;
+ }
+ return nil;
+}
+
+pppconnect(errc: chan of string, sync: chan of int)
+{
+ connecting = 1;
+ sys->pctl(Sys->NEWPGRP, nil);
+ sync <-= 0;
+ resp_chan: chan of int;
+ logger := chan of int;
+ pppgui := load PPPGUI PPPGUI->PATH;
+ for (count :=0; count < ISP_RETRIES; count++) {
+ resp_chan = pppgui->init(context, logger, ppp, nil);
+ spawn ppp->connect(modeminfo, isp_number, scriptinfo, pppinfo, logger);
+ x := <-resp_chan;
+ if (x > 0) {
+ if (x == 1) {
+ # alt needed in case calling process has been killed
+ alt {
+ errc <-= nil => ;
+ * => ;
+ }
+ } else { # user cancelled dial-in
+ dialup_cancelled = 1;
+ alt {
+ errc <-= "fail: dialup cancelled" => ;
+ * => ;
+ }
+ }
+ connecting = 0;
+ return;
+ }
+ # else connect failed, go around loop to try again
+ }
+ alt {
+ errc <-= "fail: dialup failed" => ;
+ * => ;
+ }
+ connecting = 0;
+}
+
+samefile(d1, d2: Sys->Dir): int
+{
+ return d1.dev==d2.dev && d1.dtype==d2.dtype &&
+ d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers &&
+ d1.mtime==d2.mtime;
+}
diff --git a/appl/cmd/ip/ppp/pppgui.b b/appl/cmd/ip/ppp/pppgui.b
new file mode 100644
index 00000000..40e7e3b4
--- /dev/null
+++ b/appl/cmd/ip/ppp/pppgui.b
@@ -0,0 +1,373 @@
+#
+# Copyright © 1998 Lucent Technologies Inc. All rights reserved.
+# Revisions copyright © 2000,2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+# Originally Written by N. W. Knauft
+# Adapted by E. V. Hensbergen (ericvh@lucent.com)
+# Further adapted by Vita Nuova
+#
+
+implement PPPGUI;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "translate.m";
+ translate: Translate;
+ Dict: import translate;
+ dict: ref Dict;
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+include "pppclient.m";
+ ppp: PPPClient;
+
+include "pppgui.m";
+
+#Screen constants
+BBG: con "#C0C0C0"; # Background color for button
+PBG: con "#808080"; # Background color for progress bar
+LTGRN: con "#00FF80"; # Color for progress bar
+BARW: con 216; # Progress bar width
+BARH: con " 9"; # Progress bar height
+INCR: con 30; # Progress bar increment size
+N_INCR: con 7; # Number of increments in progress bar width
+BSIZE: con 25; # Icon button size
+ISIZE: con BSIZE + 4; # Icon window size
+DIALQUANTA : con 1000;
+ICONQUANTA : con 5000;
+
+#Globals
+pppquanta := DIALQUANTA;
+
+#Font
+FONT: con "/fonts/lucidasans/unicode.6.font";
+
+#Messages
+stat_msgs := array[] of {
+ "Initializing Modem",
+ "Dialling Service Provider",
+ "Logging Into Network",
+ "Executing Login Script",
+ "Script Execution Complete",
+ "Logging Into Network",
+ "Verifying Password",
+ "Connected",
+ "",
+};
+
+config_icon := array[] of {
+ "button .btn -text X -width "+string BSIZE+" -height "+string BSIZE+" -command {send tsk open} -bg "+BBG,
+ "pack .btn",
+
+ "pack propagate . no",
+ ". configure -bd 0",
+ ". unmap",
+ "update",
+};
+
+
+# Create internet connect window, spawn event handler
+init(ctxt: ref Draw->Context, stat: chan of int, pppmod: PPPClient, args: list of string): chan of int
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+
+ if (draw == nil || tk == nil || tkclient == nil) {
+ sys->fprint(sys->fildes(2), "pppgui: can't load Draw or Tk: %r\n");
+ return nil;
+ }
+
+ translate = load Translate Translate->PATH;
+ if(translate != nil) {
+ translate->init();
+ dictname := translate->mkdictname("", "pppgui");
+ dicterr: string;
+ (dict, dicterr) = translate->opendict(dictname);
+ if(dicterr != nil)
+ sys->fprint(sys->fildes(2), "pppgui: can't open %s: %s\n", dictname, dicterr);
+ }else
+ sys->fprint(sys->fildes(2), "pppgui: can't load %s: %r\n", Translate->PATH);
+ ppp = pppmod; # set the global
+
+ tkargs := "";
+
+ if (args != nil) {
+ tkargs = hd args;
+ args = tl args;
+ } else
+ tkargs="-x 340 -y 4";
+
+ tkclient->init();
+
+ (t, wmctl) := tkclient->toplevel(ctxt, tkargs, "PPP", Tkclient->Plain);
+
+ config_win := array[] of {
+ "frame .f",
+ "frame .fprog",
+
+ "canvas .cprog -bg "+PBG+" -bd 2 -width "+string BARW+" -height "+BARH+" -relief ridge",
+ "pack .cprog -in .fprog -pady 6",
+
+ "label .stat -text {"+X("Initializing connection...")+"} -width 164 -font "+FONT,
+ "pack .stat -in .f -side left -fill y -anchor w",
+
+ "button .done -text {"+X("Cancel")+"} -width 60 -command {send cmd cancel} -bg "+BBG+" -font "+FONT,
+ "pack .fprog -side bottom -expand 1 -fill x",
+ "pack .done -side right -padx 1 -pady 1 -fill y -anchor e",
+ "pack .f -side left -expand 1 -padx 5 -pady 3 -fill both -anchor w",
+
+ "pack propagate . no",
+ ". configure -bd 2 -relief raised -width "+string WIDTH,
+ "update",
+ };
+
+ for(i := 0; i < len config_win; i++)
+ tk->cmd(t, config_win[i]);
+
+ itkargs := "";
+ if (args != nil) {
+ itkargs = hd args;
+ args = tl args;
+ }
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "ptr" :: nil);
+
+ if (itkargs == "") {
+ x := int tk->cmd(t, ". cget x");
+ y := int tk->cmd(t, ". cget y");
+ x += WIDTH - ISIZE;
+ itkargs = "-x "+string x+" -y "+string y;
+ }
+
+ (ticon, iconctl) := tkclient->toplevel(ctxt, itkargs, "PPP", Tkclient->Plain);
+
+ for( i = 0; i < len config_icon; i++)
+ tk->cmd(ticon, config_icon[i]);
+
+ tk->cmd(ticon, "image create bitmap Network -file network.bit -maskfile network.bit");
+ tk->cmd(ticon, ".btn configure -image Network");
+ tkclient->startinput(ticon, "ptr"::nil);
+
+ chn := chan of int;
+ spawn handle_events(t, wmctl, ticon, iconctl, stat, chn);
+ return chn;
+}
+
+ppp_timer(sync: chan of int, stat: chan of int)
+{
+ for(;;) {
+ sys->sleep(pppquanta);
+ alt {
+ <-sync =>
+ return;
+ stat <-= -1 =>
+ ;
+ }
+ }
+}
+
+send(cmd: chan of string, msg: string)
+{
+ cmd <-= msg;
+}
+
+# Process events and pass disconnect cmd to calling app
+handle_events(t: ref Tk->Toplevel, wmctl: chan of string, ticon: ref Tk->Toplevel, iconctl: chan of string, stat, chn: chan of int)
+{
+ sys->pctl(Sys->NEWPGRP, nil);
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ tsk := chan of string;
+ tk->namechan(ticon, tsk, "tsk");
+
+ connected := 0;
+ winmapped := 1;
+ timecount := 0;
+ xmin := 0;
+ x := 0;
+
+ iocmd := sys->file2chan("/chan", "pppgui");
+ if (iocmd == nil) {
+ sys->print("fail: pppgui: file2chan: /chan/pppgui: %r\n");
+ return;
+ }
+
+ pppquanta = DIALQUANTA;
+ sync_chan := chan of int;
+ spawn ppp_timer(sync_chan, stat);
+
+Work:
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-wmctl =>
+ tkclient->wmctl(t, s);
+
+ s := <-ticon.ctxt.kbd =>
+ tk->keyboard(ticon, s);
+ s := <-ticon.ctxt.ptr =>
+ tk->pointer(ticon, *s);
+ s := <-ticon.ctxt.ctl or
+ s = <-ticon.wreq or
+ s = <-iconctl =>
+ tkclient->wmctl(ticon, s);
+
+ (off, data, fid, wc) := <-iocmd.write => # remote io control
+ if (wc == nil)
+ break;
+ spawn send(cmd, string data[0:len data]);
+ wc <-= (len data, nil);
+
+ (nil, nbytes, fid, rc) := <-iocmd.read =>
+ if (rc != nil)
+ rc <-= (nil, "not readable");
+
+ press := <-cmd =>
+ case press {
+ "cancel" or "disconnect" =>
+ tk->cmd(t, ".stat configure -text 'Disconnecting...");
+ tk->cmd(t, "update");
+ ppp->reset();
+ if (!connected) {
+ # other end may have gone away
+ alt {
+ chn <-= 666 => ;
+ * => ;
+ }
+ }
+ break Work;
+ * => ;
+ }
+
+ prs := <-tsk =>
+ case prs {
+ "open" =>
+ tk->cmd(ticon, ". unmap; update");
+ tk->cmd(t, ". map; raise .; update");
+ winmapped = 1;
+ timecount = 0;
+ * => ;
+ }
+
+ s := <-stat =>
+ if (s == -1) { # just an update event
+ if(winmapped){
+ if(!connected) { # increment status bar
+ if (x < xmin+INCR) {
+ x++;
+ tk->cmd(t, ".cprog create rectangle 0 0 "+string x + BARH+" -fill "+LTGRN);
+ }
+ }else{
+ timecount++;
+ if(timecount > 1){
+ winmapped = 0;
+ timecount = 0;
+ tk->cmd(t, ". unmap; update");
+ tk->cmd(ticon, ". map; raise .; update");
+ continue;
+ }
+ }
+ tk->cmd(t, "raise .; update");
+ } else {
+ tk->cmd(ticon, "raise .; update");
+ timecount = 0;
+ }
+ continue;
+ }
+ if (s == ppp->s_Error) {
+ tk->cmd(t, ".stat configure -text '"+ppp->lasterror);
+ if (!winmapped) {
+ tk->cmd(ticon, ". unmap; update");
+ tk->cmd(t, ". map; raise .");
+ }
+ tk->cmd(t, "update");
+ sys->sleep(3000);
+ ppp->reset();
+ if (!connected)
+ chn <-= 0; # Failure
+ break Work;
+ }
+
+ if (s == ppp->s_Initialized)
+ tk->cmd(t,".cprog create rectangle 0 0 "+string BARW + BARH+" -fill "+PBG);
+
+ x = xmin = s * INCR;
+ if (xmin > BARW)
+ xmin = BARW;
+ tk->cmd(t, ".cprog create rectangle 0 0 "+string xmin + BARH+" -fill "+LTGRN);
+ tk->cmd(t, "raise .; update");
+ tk->cmd(t, ".stat configure -text '"+X(stat_msgs[s]));
+
+ if (s == ppp->s_SuccessPPP || s == ppp->s_Done) {
+ if(!connected){
+ chn <-= 1;
+ connected = 1;
+ }
+ pppquanta = ICONQUANTA;
+
+ # find and display connection speed
+ speed := findrate("/dev/modemstat", "rcvrate" :: "baud" :: nil);
+ if(speed != nil)
+ tk->cmd(t, ".stat configure -text {"+X(stat_msgs[s])+" "+speed+" bps}");
+ else
+ tk->cmd(t, ".stat configure -text {"+X(stat_msgs[s])+"}");
+ tk->cmd(t, ".done configure -text Disconnect -command 'send cmd disconnect");
+ tk->cmd(t, "update");
+ sys->sleep(2000);
+ tk->cmd(t, ". unmap; pack forget .fprog; update");
+ winmapped = 0;
+ tk->cmd(ticon, ". map; raise .; update");
+ }
+
+ tk->cmd(t, "update");
+ }
+ sync_chan <-= 1; # stop ppp_timer
+}
+
+findrate(file: string, opt: list of string): string
+{
+ fd := sys->open(file, sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array [1024] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 1)
+ return nil;
+ (nil, flds) := sys->tokenize(string buf[0:n], " \t\r\n");
+ for(; flds != nil; flds = tl flds)
+ for(l := opt; l != nil; l = tl l)
+ if (hd flds == hd l)
+ return hd tl flds;
+ return nil;
+}
+
+
+
+# Translate a string
+
+X(s : string) : string
+{
+ if (dict== nil) return s;
+ return dict.xlate(s);
+}
+
diff --git a/appl/cmd/ip/ppp/pppgui.m b/appl/cmd/ip/ppp/pppgui.m
new file mode 100644
index 00000000..af9ec574
--- /dev/null
+++ b/appl/cmd/ip/ppp/pppgui.m
@@ -0,0 +1,21 @@
+#
+# Copyright © 1998 Lucent Technologies Inc. All rights reserved.
+# Revisions copyright © 2000,2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+# Originally Written by N. W. Knauft
+# Adapted by E. V. Hensbergen (ericvh@lucent.com)
+# Further adapted by Vita Nuova
+#
+
+PPPGUI: module
+{
+ PATH: con "/dis/ip/ppp/pppgui.dis";
+
+ # Dimension constant for ISP Connect window
+ WIDTH: con 300;
+ HEIGHT: con 58;
+
+ init: fn(ctxt: ref Draw->Context, stat: chan of int,
+ ppp: PPPClient, args: list of string): chan of int;
+};
+
diff --git a/appl/cmd/ip/ppp/ppptest.b b/appl/cmd/ip/ppp/ppptest.b
new file mode 100644
index 00000000..e5dfced0
--- /dev/null
+++ b/appl/cmd/ip/ppp/ppptest.b
@@ -0,0 +1,86 @@
+# Last change: R 24 May 2001 11:05 am
+implement PPPTest;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+include "pppclient.m";
+include "pppgui.m";
+
+PPPTest: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+usage()
+{
+ sys->print("ppptest device modem_init tel user password \n");
+ sys->print("Example: ppptest /dev/modem atw2 4125678 rome xxxxxxxx\n");
+ exit;
+
+}
+init( ctxt: ref Draw->Context, argv: list of string )
+{
+ sys = load Sys Sys->PATH;
+
+ mi: Modem->ModemInfo;
+ pi: PPPClient->PPPInfo;
+ tel : string;
+# si: Script->ScriptInfo;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ mi.path = hd argv;
+
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ mi.init = hd argv;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ tel = hd argv;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ pi.username = hd argv;
+ argv = tl argv;
+ if(argv==nil)
+ usage();
+ else
+ pi.password = hd argv;
+
+
+ #si.path = "rdid.script";
+ #si.username = "ericvh";
+ #si.password = "foobar";
+ #si.timeout = 60;
+
+
+ ppp := load PPPClient PPPClient->PATH;
+
+ logger := chan of int;
+
+ spawn ppp->connect( ref mi, tel, nil, ref pi, logger );
+
+ pppgui := load PPPGUI PPPGUI->PATH;
+ respchan := pppgui->init( ctxt, logger,ppp, nil);
+
+ event := 0;
+ while (1) {
+ event =<- respchan;
+ sys->print("GUI event received: %d\n",event);
+ if (event) {
+ sys->print("success");
+ exit;
+ } else {
+ raise "fail: Couldn't connect to ISP";
+ }
+ }
+}
diff --git a/appl/cmd/ip/ppp/script.b b/appl/cmd/ip/ppp/script.b
new file mode 100644
index 00000000..8be184a4
--- /dev/null
+++ b/appl/cmd/ip/ppp/script.b
@@ -0,0 +1,168 @@
+implement Script;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "lock.m";
+include "modem.m";
+ modem: Modem;
+
+include "script.m";
+
+delim: con "-"; # expect-send delimiter
+BUFSIZE: con (1024 * 32);
+
+execute( modmod: Modem, m: ref Modem->Device, scriptinfo: ref ScriptInfo )
+{
+ sys= load Sys Sys->PATH;
+ str= load String String->PATH;
+ if (str == nil) {
+ raise "fail: couldn't load string module";
+ return;
+ }
+ modem = modmod;
+
+ if (scriptinfo.path != nil) {
+ sys->print("Executing Script %s\n",scriptinfo.path);
+ # load the script
+ scriptinfo.content = scriptload(scriptinfo.path);
+ } else {
+ sys->print("Executing Inline Script\n");
+ }
+
+ # Check for timeout variable
+
+ if (scriptinfo.timeout == 0)
+ scriptinfo.timeout = 20;
+
+ tend := sys->millisec() + 1000*scriptinfo.timeout;
+
+ conv := scriptinfo.content;
+
+ while (conv != nil) {
+ e, s: string = nil;
+ p := hd conv;
+ conv = tl conv;
+ if (len p == 0)
+ continue;
+ sys->print("script: %s\n",p);
+ if (p[0] == '-') { # just send
+ if (len p == 1)
+ continue;
+ s = p[1:];
+ } else {
+ (n, esl) := sys->tokenize(p, delim);
+ if (n > 0) {
+ e = hd esl;
+ esl = tl esl;
+ if (n > 1)
+ s = hd esl;
+ }
+ }
+ if (e != nil) {
+ if (match(m, special(e,scriptinfo), tend-sys->millisec()) == 0) {
+ sys->print("script: match failed\n");
+ raise "fail: Script Failed";
+ return;
+ }
+ }
+ if (s != nil)
+ modem->send(m, special(s, scriptinfo));
+ }
+
+ sys->print("script: done!\n");
+}
+
+match(m: ref Modem->Device, s: string, timo: int): int
+{
+ for(;;) {
+ c := modem->getc(m, timo);
+ if (c == '\r')
+ c = '\n';
+ sys->print("%c",c);
+ if (c == 0)
+ return 0;
+ head:
+ while(c == s[0]) {
+ i := 1;
+ while(i < len s) {
+ c = modem->getc(m, timo);
+ if (c == '\r')
+ c = '\n';
+ sys->print("%c",c);
+ if(c == 0)
+ return 0;
+ if(c != s[i])
+ continue head;
+ i++;
+ }
+ return 1;
+ }
+ if(c == '~')
+ return 1; # assume PPP for now
+ }
+}
+
+#
+# Expand special script sequences
+#
+special(s: string, scriptinfo: ref ScriptInfo ): string
+{
+ if (s == "$username") # special variable
+ s = scriptinfo.username;
+ else if (s == "$password")
+ s = scriptinfo.password;
+
+ return deparse(s);
+}
+
+deparse(s : string) : string
+{
+ r: string = "";
+ for(i:=0; i < len s; i++) {
+ c := s[i];
+ if (c == '\\' && i+1 < len s) {
+ c = s[++i];
+ case c {
+ 't' => c = '\t';
+ 'n' => c = '\n';
+ 'r' => c = '\r';
+ 'b' => c = '\b';
+ 'a' => c = '\a';
+ 'v' => c = '\v';
+ '0' => c = '\0';
+ '$' => c = '$';
+ 'u' =>
+ if (i+4 < len s) {
+ i++;
+ (c, nil) = str->toint(s[i:i+4], 16);
+ i+=3;
+ }
+ }
+ }
+ r[len r] = c;
+ }
+ return r;
+}
+
+scriptload( path: string) :list of string
+{
+ dfd := sys->open(path, Sys->OREAD);
+ if (dfd == nil) {
+ raise "fail: Script file ("+path+") not found";
+ return nil;
+ }
+
+ scriptbuf := array[BUFSIZE] of byte;
+ scriptlen := sys->read(dfd, scriptbuf, len scriptbuf);
+ if(scriptlen < 0)
+ raise "fail: can't read script: "+sys->sprint("%r");
+
+ (nil, scriptlist) := sys->tokenize(string scriptbuf[0:scriptlen], "\n");
+ return scriptlist;
+}
diff --git a/appl/cmd/ip/ppp/script.m b/appl/cmd/ip/ppp/script.m
new file mode 100644
index 00000000..342d4d79
--- /dev/null
+++ b/appl/cmd/ip/ppp/script.m
@@ -0,0 +1,14 @@
+Script: module {
+ PATH: con "/dis/ip/ppp/script.dis";
+
+ ScriptInfo: adt {
+ path: string;
+ content: list of string;
+ timeout: int;
+ username: string;
+ password: string;
+ };
+
+ execute: fn( modem: Modem, m: ref Modem->Device,
+ scriptinfo: ref ScriptInfo );
+};
diff --git a/appl/cmd/ip/rip.b b/appl/cmd/ip/rip.b
new file mode 100644
index 00000000..90c1b6ce
--- /dev/null
+++ b/appl/cmd/ip/rip.b
@@ -0,0 +1,620 @@
+implement Rip;
+
+# basic RIP implementation
+# understands v2, sends v1
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "ip.m";
+ ip: IP;
+ IPaddr, Ifcaddr, Udphdr: import ip;
+
+include "attrdb.m";
+ attrdb: Attrdb;
+
+include "arg.m";
+
+Rip: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# rip header:
+# op[1] version[1] pad[2]
+
+Oop: con 0; # op: byte
+Oversion: con 1; # version: byte
+Opad: con 2; # 2 byte pad
+Riphdrlen: con Opad+2; # op[1] version[1] mbz[2]
+
+# rip route entry:
+# type[2] tag[2] addr[4] mask[4] nexthop[4] metric[4]
+
+Otype: con 0; # type[2]
+Otag: con Otype+2; # tag[2] v2 or mbz v1
+Oaddr: con Otag+2; # addr[4]
+Omask: con Oaddr+4; # mask[4] v2 or mbz v1
+Onexthop: con Omask+4;
+Ometric: con Onexthop+4; # metric[4]
+Ipdestlen: con Ometric+4;
+
+Maxripmsg: con 512;
+
+# operations
+OpRequest: con 1; # want route
+OpReply: con 2; # all or part of route table
+
+HopLimit: con 16; # defined by protocol as `infinity'
+RoutesInPkt: con 25; # limit defined by protocol
+RIPport: con 520;
+
+Expired: con 180;
+Discard: con 240;
+
+OutputRate: con 60; # seconds between routing table transmissions
+
+NetworkCost: con 1; # assume the simple case
+
+Gateway: adt {
+ dest: IPaddr;
+ mask: IPaddr;
+ gateway: IPaddr;
+ metric: int;
+ valid: int;
+ changed: int;
+ local: int;
+ time: int;
+
+ contains: fn(g: self ref Gateway, a: IPaddr): int;
+};
+
+netfd: ref Sys->FD;
+routefd: ref Sys->FD;
+AF_INET: con 2;
+
+routes: array of ref Gateway;
+Routeinc: con 50;
+defroute: ref Gateway;
+debug := 0;
+nochange := 0;
+quiet := 1;
+myversion := 1; # default protocol version
+logfile := "iproute";
+netdir := "/net";
+now: int;
+nets: list of ref Ifcaddr;
+addrs: list of IPaddr;
+
+syslog(nil: int, nil: string, s: string)
+{
+ sys->print("rip: %s\n", s);
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ ip = load IP IP->PATH;
+ ip->init();
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("ip/rip [-d] [-r]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => debug++;
+ 'b' => quiet = 0;
+ '2' => myversion = 2;
+ 'n' => nochange = 1;
+ 'x' => netdir = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ quiet = 0;
+ for(; args != nil; args = tl args){
+ (ok, a) := IPaddr.parse(hd args);
+ if(ok < 0)
+ fatal(sys->sprint("invalid address: %s", hd args));
+ addrs = a :: addrs;
+ }
+ arg = nil;
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKNS, nil);
+
+ whereami();
+ addlocal();
+
+ routefd = sys->open(sys->sprint("%s/iproute", netdir), Sys->ORDWR);
+ if(routefd == nil)
+ fatal(sys->sprint("can't open %s/iproute: %r", netdir));
+ readroutes();
+
+ syslog(0, logfile, "started");
+
+ netfd = riplisten();
+
+ # broadcast request for all routes
+
+ if(!quiet){
+ sendall(OpRequest, 0);
+ spawn sender();
+ }
+
+ # read routing requests
+
+ buf := array[8192] of byte;
+ while((nb := sys->read(netfd, buf, len buf)) > 0){
+ nb -= Riphdrlen + IP->Udphdrlen;
+ if(nb < 0)
+ continue;
+ uh := Udphdr.unpack(buf, IP->Udphdrlen);
+ hdr := buf[IP->Udphdrlen:];
+ version := int hdr[Oversion];
+ if(version < 1)
+ continue;
+ bp := buf[IP->Udphdrlen + Riphdrlen:];
+ case int hdr[Oop] {
+ OpRequest =>
+ # TO DO: transmit in response to request? only if something interesting to say...
+ ;
+
+ OpReply =>
+ # wrong source port?
+ if(uh.rport != RIPport)
+ continue;
+ # my own broadcast?
+ if(ismyaddr(uh.raddr))
+ continue;
+ now = daytime->now();
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "from %s:\n", uh.raddr.text());
+ for(; (nb -= Ipdestlen) >= 0; bp = bp[Ipdestlen:])
+ unpackroute(bp, version, uh.raddr);
+ * =>
+ if(debug)
+ sys->print("rip: unexpected op: %d\n", int hdr[Oop]);
+ }
+ }
+}
+
+whereami()
+{
+ for(ifcs := ip->readipifc(netdir, -1).t0; ifcs != nil; ifcs = tl ifcs)
+ for(al := (hd ifcs).addrs; al != nil; al = tl al){
+ ifa := hd al;
+ if(!ifa.ip.isv4())
+ continue;
+ # how to tell broadcast? must be told? actually, it's in /net/iproute
+ nets = ifa :: nets;
+ }
+}
+
+ismyaddr(a: IPaddr): int
+{
+ for(l := nets; l != nil; l = tl l)
+ if((hd l).ip.eq(a))
+ return 1;
+ return 0;
+}
+
+addlocal()
+{
+ for(l := nets; l != nil; l = tl l){
+ ifc := hd l;
+ g := lookup(ifc.net);
+ g.valid = 1;
+ g.local = 1;
+ g.gateway = ifc.ip;
+ g.mask = ifc.mask;
+ g.metric = NetworkCost;
+ g.time = 0;
+ g.changed = 1;
+ if(debug)
+ syslog(0, logfile, sys->sprint("Existing: %s & %s -> %s", g.dest.text(), g.mask.masktext(), g.gateway.text()));
+ }
+}
+
+#
+# record any existing routes
+#
+readroutes()
+{
+ now = daytime->now();
+ b := bufio->fopen(routefd, Sys->OREAD);
+ while((l := b.gets('\n')) != nil){
+ (nf, flds) := sys->tokenize(l, " \t");
+ if(nf >= 5){
+ flags := hd tl tl tl flds;
+ if(flags == nil || flags[0] != '4' || contains(flags, "ibum"))
+ continue;
+ g := lookup(parseip(hd flds));
+ g.mask = parsemask(hd tl flds);
+ g.gateway = parseip(hd tl tl flds);
+ g.metric = HopLimit;
+ g.time = now;
+ g.changed = 1;
+ if(debug)
+ syslog(0, logfile, sys->sprint("Existing: %s & %s -> %s", g.dest.text(), g.mask.masktext(), g.gateway.text()));
+ if(iszero(g.dest) && iszero(g.mask)){
+ defroute = g;
+ g.local = 1;
+ }else if(defroute != nil && g.dest.eq(defroute.gateway))
+ continue;
+ else
+ g.local = !ismyaddr(g.gateway);
+ }
+ }
+}
+
+unpackroute(b: array of byte, version: int, gwa: IPaddr)
+{
+ # check that it's an IP route, valid metric, MBZ fields zero
+
+ if(b[0] != byte 0 || b[1] != byte AF_INET){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t-- unknown address type %x,%x\n", int b[0], int b[1]);
+ return;
+ }
+ dest := IPaddr.newv4(b[Oaddr:]);
+ mask: IPaddr;
+ if(version == 1){
+ # check MBZ fields
+ if(ip->get2(b, 2) | ip->get4(b, Omask) | ip->get4(b, Onexthop)){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t-- non-zero MBZ\n");
+ return;
+ }
+ mask = maskgen(dest);
+ }else if(version == 2){
+ if(ip->get4(b, Omask))
+ mask = IPaddr.newv4(b[Omask:]);
+ else
+ mask = maskgen(dest);
+ if(ip->get4(b, Onexthop))
+ gwa = IPaddr.newv4(b[Onexthop:]);
+ }
+ metric := ip->get4(b, Ometric);
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t%s %d\n", dest.text(), metric);
+ if(metric <= 0 || metric > HopLimit)
+ return;
+
+ # 1058/3.4.2: response processing
+ # ignore route if IP address is:
+ # class D or E
+ # net 0 (except perhaps 0.0.0.0)
+ # net 127
+ # broadcast address (all 1s host part)
+ # we allow host routes
+
+ if(dest.ismulticast() || dest.a[0] == byte 0 || dest.a[0] == byte 16r7F){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t%s %d invalid addr\n", dest.text(), metric);
+ return;
+ }
+ if(isbroadcast(dest, mask)){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t%s & %s -> broadcast\n", dest.text(), mask.masktext());
+ return;
+ }
+
+ # update the metric min(metric+NetworkCost, HopLimit)
+
+ metric += NetworkCost;
+ if(metric > HopLimit)
+ metric = HopLimit;
+
+ updateroute(dest, mask, gwa, metric);
+}
+
+updateroute(dest, mask, gwa: IPaddr, metric: int)
+{
+ # RFC1058 rules page 27-28, with optional replacement of expiring routes
+ r := lookup(dest);
+ if(r.valid){
+ if(r.local)
+ return; # local, don't touch
+ if(r.gateway.eq(gwa)){
+ if(metric != HopLimit){
+ r.metric = metric;
+ r.time = now;
+ }else{
+ # metric == HopLimit
+ if(r.metric != HopLimit){
+ r.metric = metric;
+ r.changed = 1;
+ r.time = now - (Discard-120);
+ delroute(r); # don't use it for routing
+ # route remains valid but advertised with metric HopLimit
+ } else if(now >= r.time+Discard){
+ delroute(r); # finally dead
+ r.valid = 0;
+ r.changed = 1;
+ }
+ }
+ }else if(metric < r.metric ||
+ metric != HopLimit && metric == r.metric && now > r.time+Expired/2){
+ delroute(r);
+ r.metric = metric;
+ r.gateway = gwa;
+ r.time = now;
+ addroute(r);
+ }
+ } else if(metric < HopLimit){ # new entry
+
+ # 1058/3.4.2: don't add route-to-host if host is on net/subnet
+ # for which we have at least as good a route
+
+ if(!mask.eq(ip->allbits) ||
+ ((pr := findroute(dest)) == nil || metric <= pr.metric)){
+ r.valid = 1;
+ r.changed = 1;
+ r.time = now;
+ r.metric = metric;
+ r.dest = dest;
+ r.mask = mask;
+ r.gateway = gwa;
+ addroute(r);
+ }
+ }
+}
+
+sender()
+{
+ for(;;){
+ sys->sleep(OutputRate*1000); # could add some random fizz
+ sendall(OpReply, 1);
+ }
+}
+
+onlist(a: IPaddr, l: list of IPaddr): int
+{
+ for(; l != nil; l = tl l)
+ if(a.eq(hd l))
+ return 1;
+ return 0;
+}
+
+sendall(op: int, changes: int)
+{
+ for(l := nets; l != nil; l = tl l){
+ if(addrs != nil && !onlist((hd l).net, addrs))
+ continue;
+ a := (hd l).net.copy();
+ b := (ip->allbits).maskn((hd l).mask);
+ for(i := 0; i < len a.a; i++)
+ a.a[i] |= b.a[i];
+ sendroutes(hd l, a, op, changes);
+ }
+ for(i := 0; i < len routes; i++)
+ if((r := routes[i]) != nil)
+ r.changed = 0;
+}
+
+zeroentry := array[Ipdestlen] of {* => byte 0};
+
+sendroutes(ifc: ref Ifcaddr, dst: IPaddr, op: int, changes: int)
+{
+ if(debug > 1)
+ sys->print("rip: send %s\n", dst.text());
+ buf := array[Maxripmsg+IP->Udphdrlen] of byte;
+ hdr := Udphdr.new();
+ hdr.lport = hdr.rport = RIPport;
+ hdr.raddr = dst; # needn't copy
+ hdr.pack(buf, IP->Udphdrlen);
+ o := IP->Udphdrlen;
+ buf[o] = byte op;
+ buf[o+1] = byte myversion;
+ buf[o+2] = byte 0;
+ buf[o+3] = byte 0;
+ o += Riphdrlen;
+ rips := buf[IP->Udphdrlen+Riphdrlen:];
+ if(op == OpRequest){
+ buf[o:] = zeroentry;
+ ip->put4(buf, o+Ometric, HopLimit);
+ o += Ipdestlen;
+ } else {
+ # send routes
+ for(i:=0; i<len routes; i++){
+ r := routes[i];
+ if(r == nil || !r.valid || changes && !r.changed)
+ continue;
+ if(r == defroute)
+ continue;
+ if(r.dest.eq(ifc.net) || isonnet(r.dest, ifc))
+ continue;
+ netmask := r.dest.classmask();
+ subnet := !r.mask.eq(netmask);
+ if(myversion < 2 && !r.mask.eq(ip->allbits)){
+ # if not a host route, don't let a subnet route leave its net
+ if(subnet && !netmask.eq(ifc.ip.classmask()))
+ continue;
+ }
+ if(o+Ipdestlen > IP->Udphdrlen+Maxripmsg){
+ if(sys->write(netfd, buf, o) < 0)
+ sys->fprint(sys->fildes(2), "RIP write failed: %r\n");
+ o = IP->Udphdrlen + Riphdrlen;
+ }
+ buf[o:] = zeroentry;
+ ip->put2(buf, o+Otype, AF_INET);
+ buf[o+Oaddr:] = r.dest.v4();
+ ip->put4(buf, o+Ometric, r.metric);
+ if(myversion == 2 && subnet)
+ buf[o+Omask:] = r.mask.v4();
+ o += Ipdestlen;
+ }
+ }
+ if(o > IP->Udphdrlen+Riphdrlen && sys->write(netfd, buf, o) < 0)
+ sys->fprint(sys->fildes(2), "rip: network write to %s failed: %r\n", dst.text());
+}
+
+lookup(addr: IPaddr): ref Gateway
+{
+ avail := -1;
+ for(i:=0; i<len routes; i++){
+ g := routes[i];
+ if(g == nil || !g.valid){
+ if(avail < 0)
+ avail = i;
+ continue;
+ }
+ if(g.dest.eq(addr))
+ return g;
+ }
+ if(avail < 0){
+ avail = len routes;
+ a := array[len routes+Routeinc] of ref Gateway;
+ a[0:] = routes;
+ routes = a;
+ }
+ if((g := routes[avail]) == nil){
+ g = ref Gateway;
+ routes[avail] = g;
+ g.valid = 0;
+ }
+ g.dest = addr;
+ return g;
+}
+
+findroute(a: IPaddr): ref Gateway
+{
+ pr: ref Gateway;
+ for(i:=0; i<len routes; i++){
+ r := routes[i];
+ if(r == nil || !r.valid)
+ continue;
+ if(r.contains(a) && (pr == nil || !maskle(r.mask, pr.mask)))
+ pr = r; # more specific mask
+ }
+ return pr;
+}
+
+maskgen(addr: IPaddr): IPaddr
+{
+ net: ref Ifcaddr;
+ for(l := nets; l != nil; l = tl l){
+ ifc := hd l;
+ if(isonnet(addr, ifc) &&
+ (net == nil || maskle(ifc.mask, net.mask))) # less specific mask?
+ net = ifc;
+ }
+ if(net != nil)
+ return net.mask;
+ return addr.classmask();
+}
+
+isonnet(a: IPaddr, n: ref Ifcaddr): int
+{
+ return a.mask(n.mask).eq(n.net);
+}
+
+isbroadcast(a: IPaddr, mask: IPaddr): int
+{
+ h := a.maskn(mask); # host part
+ hm := (ip->allbits).maskn(mask); # host part of mask
+ return h.eq(hm);
+}
+
+iszero(a: IPaddr): int
+{
+ return a.eq(ip->v4noaddr) || a.eq(ip->noaddr);
+}
+
+maskle(a, b: IPaddr): int
+{
+ return a.mask(b).eq(a);
+}
+
+#
+# add ipdest mask gateway
+# add 0.0.0.0 0.0.0.0 gateway (default)
+# delete ipdest mask
+#
+addroute(g: ref Gateway)
+{
+ if(iszero(g.mask) && iszero(g.dest))
+ g.valid = 0; # don't change default route
+ else if(defroute != nil && defroute.gateway.eq(g.gateway)){
+ if(debug)
+ syslog(0, logfile, sys->sprint("default %s %s", g.dest.text(), g.mask.text())); # don't need a new entry
+ g.valid = 1;
+ g.changed = 1;
+ } else {
+ if(debug)
+ syslog(0, logfile, sys->sprint("add %s %s %s", g.dest.text(), g.mask.text(), g.gateway.text()));
+ if(nochange || sys->fprint(routefd, "add %s %s %s", g.dest.text(), g.mask.text(), g.gateway.text()) > 0){
+ g.valid = 1;
+ g.changed = 1;
+ }
+ }
+}
+
+delroute(g: ref Gateway)
+{
+ if(debug)
+ syslog(0, logfile, sys->sprint("delete %s %s", g.dest.text(), g.mask.text()));
+ if(!nochange)
+ sys->fprint(routefd, "delete %s %s", g.dest.text(), g.mask.text());
+}
+
+parseip(s: string): IPaddr
+{
+ (ok, a) := IPaddr.parse(s);
+ if(ok < 0)
+ raise "bad route";
+ return a;
+}
+
+parsemask(s: string): IPaddr
+{
+ (ok, a) := IPaddr.parsemask(s);
+ if(ok < 0)
+ raise "bad route";
+ return a;
+}
+
+contains(s: string, t: string): int
+{
+ for(i := 0; i < len s; i++)
+ for(j := 0; j < len t; j++)
+ if(s[i] == t[j])
+ return 1;
+ return 0;
+}
+
+Gateway.contains(g: self ref Gateway, a: IPaddr): int
+{
+ return g.dest.eq(a.mask(g.mask));
+}
+
+riplisten(): ref Sys->FD
+{
+ addr := sys->sprint("%s/udp!*!rip", netdir);
+ (ok, c) := sys->announce(addr);
+ if(ok < 0)
+ fatal(sys->sprint("can't announce %s: %r", addr));
+ if(sys->fprint(c.cfd, "headers") < 0)
+ fatal(sys->sprint("can't set udp headers: %r"));
+ fd := sys->open(c.dir+"/data", Sys->ORDWR);
+ if(fd == nil)
+ fatal(sys->sprint("can't open %s: %r", c.dir+"/data"));
+ return fd;
+}
+
+fatal(s: string)
+{
+ syslog(0, logfile, s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/ip/sntp.b b/appl/cmd/ip/sntp.b
new file mode 100644
index 00000000..067d857d
--- /dev/null
+++ b/appl/cmd/ip/sntp.b
@@ -0,0 +1,313 @@
+implement Sntp;
+
+#
+# rfc1361 (simple network time protocol)
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "ip.m";
+ ip: IP;
+ IPaddr: import ip;
+
+include "timers.m";
+ timers: Timers;
+ Timer: import timers;
+
+include "arg.m";
+
+Sntp: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+debug := 0;
+
+Retries: con 4;
+Delay: con 3*1000; # milliseconds
+
+SNTP: adt {
+ li: int;
+ vn: int;
+ mode: int;
+ stratum: int; # level of local clock
+ poll: int; # log2(maximum interval in seconds between successive messages)
+ precision: int; # log2(seconds precision of local clock) [eg, -6 for mains, -18 for microsec]
+ rootdelay: int; # round trip delay in seconds to reference (16:16 fraction)
+ dispersion: int; # maximum error relative to primary reference
+ clockid: string; # reference clock identifier
+ reftime: big; # local time at which clock last set/corrected
+ orgtime: big; # local time at which client transmitted request
+ rcvtime: big; # time at which request arrived at server
+ xmttime: big; # time server transmitted reply
+ auth: array of byte; # auth field (ignored by this implementation)
+
+ new: fn(vn, mode: int): ref SNTP;
+ pack: fn(s: self ref SNTP): array of byte;
+ unpack: fn(a: array of byte): ref SNTP;
+};
+SNTPlen: con 4+3*4+4*8;
+
+Version: con 1; # accepted by version 2 and version 3 servers
+Stratum: con 0;
+Poll: con 0;
+LI: con 0;
+Symmetric: con 2;
+ClientMode: con 3;
+ServerMode: con 4;
+Epoch: con big 86400*big (365*70 + 17); # seconds between 1 Jan 1900 and 1 Jan 1970
+
+Microsec: con big 100000;
+
+server := "$ntp";
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ ip = load IP IP->PATH;
+ timers = load Timers Timers->PATH;
+
+ ip->init();
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("sntp [-d] [server]");
+
+ doset := 1;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => debug++;
+ 'i' => doset = 0;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(len args > 1)
+ arg->usage();
+ arg = nil;
+
+ if(args != nil)
+ server = hd args;
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+ stderr = sys->fildes(2);
+ timers->init(100);
+
+ (ok, conn) := sys->dial(netmkaddr(server, "udp", "ntp"), nil);
+ if(ok < 0){
+ sys->fprint(stderr, "sntp: can't dial %s: %r\n", server);
+ raise "fail:dial";
+ }
+
+ replies := chan of ref SNTP;
+ spawn reader(conn.dfd, replies);
+
+ for(i:=0; i<Retries; i++){
+ request := SNTP.new(Version, ClientMode);
+ request.poll = 6;
+ request.orgtime = (big time() + Epoch)<<32;
+ b := request.pack();
+ if(sys->write(conn.dfd, b, len b) != len b){
+ sys->fprint(stderr, "sntp: UDP write failed: %r\n");
+ continue;
+ }
+ t := Timer.start(Delay);
+ alt{
+ reply := <-replies =>
+ t.stop();
+ if(reply == nil)
+ quit("read error");
+ if(debug){
+ sys->fprint(stderr, "LI = %d, version = %d, mode = %d\n", reply.li, reply.vn, reply.mode);
+ if(reply.stratum == 1)
+ sys->fprint(stderr, "stratum = 1 (%s), ", reply.clockid);
+ else
+ sys->fprint(stderr, "stratum = %d, ", reply.stratum);
+ sys->fprint(stderr, "poll = %d, prec = %d\n", reply.poll, reply.precision);
+ sys->fprint(stderr, "rootdelay = %d, dispersion = %d\n", reply.rootdelay, reply.dispersion);
+ }
+ if(reply.vn == 0 || reply.vn > 3)
+ continue; # unsupported version, ignored
+ if(reply.mode >= 6 || reply.mode == ClientMode)
+ continue;
+ now := ((reply.xmttime>>32)&16rFFFFFFFF) - Epoch;
+ if(now <= big 1120000000)
+ continue;
+ if(reply.li == 3 || reply.stratum == 0) # unsynchronised
+ sys->fprint(stderr, "sntp: time server not synchronised to reference time\n");
+ if(debug)
+ sys->print("%bd\n", now);
+ if(doset){
+ settime("#r/rtc", now);
+ settime("/dev/time", now * Microsec);
+ }
+ quit(nil);
+ <-t.timeout =>
+ continue;
+ }
+ }
+ sys->fprint(sys->fildes(2), "sntp: no response from server %s\n", server);
+ quit("timeout");
+}
+
+reader(fd: ref Sys->FD, replies: chan of ref SNTP)
+{
+ for(;;){
+ buf := array[512] of byte;
+ nb := sys->read(fd, buf, len buf);
+ if(nb <= 0)
+ break;
+ reply := SNTP.unpack(buf[0:nb]);
+ if(reply == nil){
+ # ignore bad replies
+ if(debug)
+ sys->fprint(stderr, "sntp: invalid reply (len %d)\n", nb);
+ continue;
+ }
+ replies <-= reply;
+ }
+ if(debug)
+ sys->fprint(stderr, "sntp: UDP read failed: %r\n");
+ replies <-= nil;
+}
+
+quit(s: string)
+{
+ pid := sys->pctl(0, nil);
+ timers->shutdown();
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+ if(s != nil)
+ raise "fail:"+s;
+ exit;
+}
+
+time(): int
+{
+ fd := sys->open("#r/rtctime", Sys->OREAD);
+ if(fd == nil){
+ fd = sys->open("/dev/time", Sys->OREAD);
+ if(fd == nil)
+ return 0;
+ }
+ b := array[128] of byte;
+ n := sys->read(fd, b, len b);
+ if(n <= 0)
+ return 0;
+ return int (big string b[0:n] / big 1000000);
+}
+
+settime(f: string, t: big)
+{
+ fd := sys->open(f, Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "%bd", t);
+}
+
+get8(a: array of byte, i: int): big
+{
+ b := big ip->get4(a, i+4) & 16rFFFFFFFF;
+ return (big ip->get4(a, i) << 32) | b;
+}
+
+put8(a: array of byte, o: int, v: big)
+{
+ ip->put4(a, o, int (v>>32));
+ ip->put4(a, o+4, int v);
+}
+
+SNTP.unpack(a: array of byte): ref SNTP
+{
+ if(len a < SNTPlen)
+ return nil;
+ s := ref SNTP;
+ mode := int a[0];
+ s.li = mode>>6;
+ s.vn = (mode>>3);
+ s.mode = mode & 3;
+ s.stratum = int a[1];
+ s.poll = int a[2];
+ if(s.poll & 16r80)
+ s.poll |= ~0 << 8;
+ s.precision = int a[3];
+ if(s.precision & 16r80)
+ s.precision |= ~0 << 8;
+ s.rootdelay = ip->get4(a, 4);
+ s.dispersion = ip->get4(a, 8);
+ if(s.stratum <= 1){
+ for(i := 12; i < 16; i++)
+ if(a[i] == byte 0)
+ break;
+ s.clockid = string a[12:i];
+ }else
+ s.clockid = sys->sprint("%d.%d.%d.%d", int a[12], int a[13], int a[14], int a[15]);
+ s.reftime = get8(a, 16);
+ s.orgtime = get8(a, 24);
+ s.rcvtime = get8(a, 32);
+ s.xmttime = get8(a, 40);
+ if(len a > SNTPlen)
+ s.auth = a[48:];
+ return s;
+}
+
+SNTP.pack(s: self ref SNTP): array of byte
+{
+ a := array[SNTPlen + len s.auth] of byte;
+ a[0] = byte ((s.li<<6) | (s.vn<<3) | s.mode);
+ a[1] = byte s.stratum;
+ a[2] = byte s.poll;
+ a[3] = byte s.precision;
+ ip->put4(a, 4, s.rootdelay);
+ ip->put4(a, 8, s.dispersion);
+ ip->put4(a, 12, 0); # clockid field
+ if(s.clockid != nil){
+ if(s.stratum <= 1){
+ b := array of byte s.clockid;
+ for(i := 0; i < len b && i < 4; i++)
+ a[12+i] = b[i];
+ }else
+ a[12:] = IPaddr.parse(s.clockid).t1.v4();
+ }
+ put8(a, 16, s.reftime);
+ put8(a, 24, s.orgtime);
+ put8(a, 32, s.rcvtime);
+ put8(a, 40, s.xmttime);
+ if(s.auth != nil)
+ a[48:] = s.auth;
+ return a;
+}
+
+SNTP.new(vn, mode: int): ref SNTP
+{
+ s := ref SNTP;
+ s.vn = vn;
+ s.mode = mode;
+ s.li = 0;
+ s.stratum = 0;
+ s.poll = 0;
+ s.precision = 0;
+ s.clockid = nil;
+ s.reftime = big 0;
+ s.orgtime = big 0;
+ s.rcvtime = big 0;
+ s.xmttime = big 0;
+ return s;
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, nil) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/cmd/ip/tftpd.b b/appl/cmd/ip/tftpd.b
new file mode 100644
index 00000000..12411078
--- /dev/null
+++ b/appl/cmd/ip/tftpd.b
@@ -0,0 +1,514 @@
+implement Tftpd;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+
+include "draw.m";
+
+include "arg.m";
+
+include "ip.m";
+ ip: IP;
+ IPaddr, Udphdr: import ip;
+
+Tftpd: module
+{
+ init: fn (nil: ref Draw->Context, argv: list of string);
+};
+
+dir:= "/services/tftpd";
+net:= "/net";
+
+Tftp_READ: con 1;
+Tftp_WRITE: con 2;
+Tftp_DATA: con 3;
+Tftp_ACK: con 4;
+Tftp_ERROR: con 5;
+
+Segsize: con 512;
+
+dbg := 0;
+restricted := 0;
+port := 69;
+
+Udphdrsize: con IP->OUdphdrlen;
+
+tftpcon: Sys->Connection;
+tftpreq: ref Sys->FD;
+
+dokill(pid: int, scope: string)
+{
+ fd := sys->open("/prog/" + string pid + "/ctl", sys->OWRITE);
+ if(fd == nil)
+ fd = sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill%s", scope);
+}
+
+kill(pid: int) { dokill(pid, ""); }
+killgrp(pid: int) { dokill(pid, "grp"); }
+killme() { kill(sys->pctl(0,nil)); }
+killus() { killgrp(sys->pctl(0,nil)); }
+
+DBG(s: string)
+{
+ if(dbg)
+ sys->fprint(stderr, "tfptd: %d: %s\n", sys->pctl(0,nil), s);
+}
+
+false, true: con iota;
+
+Timer: adt {
+ KILL: con -1;
+ ALARM: con -2;
+ RETRY: con -3;
+ sig: chan of int;
+ create: fn(): ref Timer;
+ destroy: fn(t: self ref Timer);
+ set: fn(t: self ref Timer, msec, nretry: int);
+
+ ticker: fn(t: self ref Timer);
+ ticking: int;
+ wakeup: int;
+ timeout: int;
+ nretry: int;
+};
+
+Timer.create(): ref Timer
+{
+ t := ref Timer;
+ t.wakeup = 0;
+ t.ticking = false;
+ t.sig = chan of int;
+ return t;
+}
+
+Timer.destroy(t: self ref Timer)
+{
+ DBG("Timer.destroy");
+ alt {
+ t.sig <-= t.KILL =>
+ DBG("sent final msg");
+ * =>
+ DBG("couldn't send final msg");
+ }
+ DBG("Timer.destroy done");
+}
+
+Timer.ticker(t: self ref Timer)
+{
+ DBG("spawn: ticker");
+ t.ticking = true;
+ while(t.wakeup > sys->millisec()) {
+ DBG("Timer.ticker sleeping for "
+ +string (t.wakeup-sys->millisec()));
+ sys->sleep(t.wakeup-sys->millisec());
+ }
+ if(t.wakeup) {
+ DBG("Timer.ticker wakeup");
+ if(t.nretry) {
+ alt { t.sig <-= t.RETRY => ; }
+ t.ticking = false;
+ t.set(t.timeout, t.nretry-1);
+ } else
+ alt { t.sig <-= t.ALARM => ; }
+ }
+ t.ticking = false;
+ DBG("unspawn: ticker");
+}
+
+Timer.set(t: self ref Timer, msec, nretry: int)
+{
+ DBG(sys->sprint("Timer.set(%d, %d)", msec, nretry));
+ if(msec == 0) {
+ t.wakeup = 0;
+ t.timeout = 0;
+ t.nretry = 0;
+ } else {
+ t.wakeup = sys->millisec()+msec;
+ t.timeout = msec;
+ t.nretry = nretry;
+ if(!t.ticking)
+ spawn t.ticker();
+ }
+}
+
+killer(c: chan of int, pgid: int)
+{
+ DBG("spawn: killer");
+ cmd := <- c;
+ DBG(sys->sprint("killer has awakened (flag=%d)", cmd));
+ if(cmd == Timer.ALARM) {
+ killgrp(pgid);
+ DBG(sys->sprint("group %d has been killed", pgid));
+ }
+ DBG("unspawn killer");
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKNS, nil);
+ stderr = sys->fildes(2);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ fatal("can't load Arg");
+
+ arg->init(args);
+ arg->setusage("tftpd [-dr] [-p port] [-h homedir] [-x network-dir]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => dbg++;
+ 'h' => dir = arg->earg();
+ 'r' => restricted = 1;
+ 'p' => port = int arg->earg();
+ 'x' => net = arg->earg();
+ * => arg->usage();
+ }
+ args =arg->argv();
+ if(args != nil){
+ net = hd args;
+ args = tl args;
+ }
+ if(args != nil)
+ arg->usage();
+ arg = nil;
+
+ ip = load IP IP->PATH;
+ if(ip == nil)
+ fatal(sys->sprint("can't load %s: %r", IP->PATH));
+ ip->init();
+
+ if(sys->chdir(dir) < 0)
+ fatal("can't chdir to " + dir);
+
+ spawn mainthing();
+}
+
+mainthing()
+{
+ DBG("spawn: mainthing");
+ bigbuf := array[32768] of byte;
+
+ openlisten();
+ setuser();
+ for(;;) {
+ dlen := sys->read(tftpreq, bigbuf, len bigbuf);
+ if(dlen < 0)
+ fatal("listen");
+ if(dlen < Udphdrsize)
+ continue;
+
+ hdr := Udphdr.unpack(bigbuf, Udphdrsize);
+
+ raddr := sys->sprint("%s/udp!%s!%d", net, hdr.raddr.text(), hdr.rport);
+
+ DBG(sys->sprint("raddr=%s", raddr));
+ (err, cx) := sys->dial(raddr, nil);
+ if(err < 0)
+ fatal("dialing "+raddr);
+
+# showbuf("bigbuf", bigbuf[0:dlen]);
+
+ op := ip->get2(bigbuf, Udphdrsize);
+ mbuf := bigbuf[Udphdrsize+2:dlen]; # get past Udphdr and op
+ dlen -= 14;
+
+ case op {
+ Tftp_READ or Tftp_WRITE =>
+ ;
+ Tftp_ERROR =>
+ DBG("tftp error");
+ continue;
+ * =>
+ nak(cx.dfd, 4, "Illegal TFTP operation");
+ continue;
+ }
+
+# showbuf("mbuf", mbuf[0:dlen]);
+
+ i := 0;
+ while(dlen > 0 && mbuf[i] != byte 0) {
+ dlen--;
+ i++;
+ }
+
+ p := i++;
+ dlen--;
+ while(dlen > 0 && mbuf[i] != byte 0) {
+ dlen--;
+ i++;
+ }
+
+ path := string mbuf[0:p];
+ mode := string mbuf[p+1:i];
+ DBG(sys->sprint("path = %s, mode = %s", path, mode));
+
+ if(dlen == 0) {
+ nak(cx.dfd, 0, "bad tftpmode");
+ continue;
+ }
+
+ if(restricted && dodgy(path)){
+ nak(cx.dfd, 4, "Permission denied");
+ continue;
+ }
+
+ if(op == Tftp_READ)
+ spawn sendfile(cx.dfd, path, mode);
+ else
+ spawn recvfile(cx.dfd, path, mode);
+ }
+}
+
+dodgy(path: string): int
+{
+ n := len path;
+ nd := len dir;
+ if(n == 0 ||
+ path[0] == '#' ||
+ path[0] == '/' && (n < nd+1 || path[0:nd] != dir || path[nd] != '/'))
+ return 1;
+ (nil, flds) := sys->tokenize(path, "/");
+ for(; flds != nil; flds = tl flds)
+ if(hd flds == "..")
+ return 1;
+ return 0;
+}
+
+showbuf(msg: string, b: array of byte)
+{
+ sys->fprint(stderr, "%s: size %d: ", msg, len b);
+ for(i:=0; i<len b; i++)
+ sys->fprint(stderr, "%.2ux ", int b[i]);
+ sys->fprint(stderr, "\n");
+ for(i=0; i<len b; i++)
+ if(int b[i] >= 32 && int b[i] <= 126)
+ sys->fprint(stderr, " %c", int b[i]);
+ else
+ sys->fprint(stderr, " .");
+ sys->fprint(stderr, "\n");
+}
+
+sendblock(sig: chan of int, buf: array of byte, net: ref sys->FD, ksig: chan of int)
+{
+ DBG("spawn: sendblocks");
+ nbytes := 0;
+ loop: for(;;) {
+ DBG("sendblock: waiting for cmd");
+ cmd := <- sig;
+ DBG(sys->sprint("sendblock: cmd=%d", cmd));
+ case cmd {
+ Timer.KILL =>
+ DBG("sendblock: killed");
+ return;
+ Timer.RETRY =>
+ ;
+ Timer.ALARM =>
+ DBG("too many retries");
+ break loop;
+ * =>
+ nbytes = cmd;
+ }
+# showbuf("sendblock", buf[0:nbytes]);
+ ret := sys->write(net, buf, 4+nbytes);
+ DBG(sys->sprint("ret=%d", ret));
+
+ if(ret < 0) {
+ ksig <-= Timer.ALARM;
+ fatal("tftp: network write error");
+ }
+ if(ret != 4+nbytes)
+ return;
+ }
+ DBG("sendblock: exiting");
+ alt { ksig <-= Timer.ALARM => ; }
+ DBG("unspawn: sendblocks");
+}
+
+sendfile(net: ref sys->FD, name: string, mode: string)
+{
+
+ DBG(sys->sprint("spawn: sendfile: name=%s mode=%s", name, mode));
+
+ pgrp := sys->pctl(Sys->NEWPGRP, nil);
+ ack := array[1024] of byte;
+ if(name == "") {
+ nak(net, 0, "not in our database");
+ return;
+ }
+
+ file := sys->open(name, Sys->OREAD);
+ if(file == nil) {
+ DBG(sys->sprint("open failed: %s", name));
+ errbuf := sys->sprint("%r");
+ nak(net, 0, errbuf);
+ return;
+ }
+ DBG(sys->sprint("opened %s", name));
+
+ block := 0;
+ timer := Timer.create();
+ ksig := chan of int;
+ buf := array[4+Segsize] of byte;
+
+ spawn killer(ksig, pgrp);
+ spawn sendblock(timer.sig, buf, net, ksig);
+
+ mainloop: for(;;) {
+ block++;
+ buf[0:] = array[] of {byte 0, byte Tftp_DATA,
+ byte (block>>8), byte block};
+ n := sys->read(file, buf[4:], len buf-4);
+ DBG(sys->sprint("n=%d", n));
+ if(n < 0) {
+ errbuf := sys->sprint("%r");
+ nak(net, 0, errbuf);
+ break;
+ }
+ DBG(sys->sprint("signalling write of %d to block %d", n, block));
+ timer.sig <-= n;
+ for(rxl := 0; rxl < 10; rxl++) {
+
+ timer.set(1000, 15);
+ al := sys->read(net, ack, len ack);
+ timer.set(0, 0);
+ if(al < 0) {
+ timer.sig <-= Timer.ALARM;
+ break;
+ }
+ op := (int ack[0]<<8) | int ack[1];
+ if(op == Tftp_ERROR)
+ break mainloop;
+ ackblock := (int ack[2]<<8) | int ack[3];
+ DBG(sys->sprint("got ack: block=%d ackblock=%d",
+ block, ackblock));
+ if(ackblock == block)
+ break;
+ if(ackblock == 16rffff) {
+ block--;
+ break;
+ }
+ }
+ if(n < len buf-4)
+ break;
+ }
+ timer.destroy();
+ ksig <-= Timer.KILL;
+}
+
+recvfile(fd: ref sys->FD, name: string, mode: string)
+{
+ DBG(sys->sprint("spawn: recvfile: name=%s mode=%s", name, mode));
+
+ pgrp := sys->pctl(Sys->NEWPGRP, nil);
+
+ file := sys->create(name, sys->OWRITE, 8r666);
+ if(file == nil) {
+ errbuf := sys->sprint("%r");
+ nak(fd, 0, errbuf);
+ return;
+ }
+
+ block := 0;
+ ack(fd, block);
+ block++;
+
+ buf := array[8+Segsize] of byte;
+ timer := Timer.create();
+ spawn killer(timer.sig, pgrp);
+
+ for(;;) {
+ timer.set(15000, 0);
+ DBG(sys->sprint("reading block %d", block));
+ n := sys->read(fd, buf, len buf);
+ DBG(sys->sprint("read %d bytes", n));
+ timer.set(0, 0);
+
+ if(n < 0)
+ break;
+ op := int buf[0]<<8 | int buf[1];
+ if(op == Tftp_ERROR)
+ break;
+
+# showbuf("got", buf[0:n]);
+ n -= 4;
+ inblock := int buf[2]<<8 | int buf[3];
+# showbuf("hdr", buf[0:4]);
+ if(op == Tftp_DATA) {
+ if(inblock == block) {
+ ret := sys->write(file, buf[4:], n);
+ if(ret < 0) {
+ errbuf := sys->sprint("%r");
+ nak(fd, 0, errbuf);
+ break;
+ }
+ block++;
+ }
+ if(inblock < block) {
+ ack(fd, inblock);
+ DBG(sys->sprint("ok: inblock=%d block=%d",
+ inblock, block));
+ } else
+ DBG(sys->sprint("FAIL: inblock=%d block=%d",
+ inblock, block));
+ ack(fd, 16rffff);
+ if(n < 512)
+ break;
+ }
+ }
+ timer.destroy();
+}
+
+ack(fd: ref Sys->FD, block: int)
+{
+ buf := array[] of {byte 0, byte Tftp_ACK, byte (block>>8), byte block};
+# showbuf("ack", buf);
+ if(sys->write(fd, buf, 4) < 0)
+ fatal("write ack");
+}
+
+
+nak(fd: ref Sys->FD, code: int, msg: string)
+{
+sys->print("nak: %s\n", msg);
+ buf := array[128] of {byte 0, byte Tftp_ERROR, byte 0, byte code};
+ bmsg := array of byte msg;
+ buf[4:] = bmsg;
+ buf[4+len bmsg] = byte 0;
+ if(sys->write(fd, buf, 4+len bmsg+1) < 0)
+ fatal("write nak");
+}
+
+fatal(msg: string)
+{
+ sys->fprint(stderr, "tftpd: %s: %r\n", msg);
+ killus();
+ raise "fail:error";
+}
+
+openlisten()
+{
+ name := net+"/udp!*!" + string port;
+ err := 0;
+ (err, tftpcon) = sys->announce(name);
+ if(err < 0)
+ fatal("can't announce "+name);
+ if(sys->fprint(tftpcon.cfd, "headers") < 0)
+ fatal("can't set header mode");
+ sys->fprint(tftpcon.cfd, "oldheaders");
+
+ tftpreq = sys->open(tftpcon.dir+"/data", sys->ORDWR);
+ if(tftpreq == nil)
+ fatal("open udp data");
+}
+
+setuser()
+{
+ f := sys->open("/dev/user", sys->OWRITE);
+ if(f != nil)
+ sys->fprint(f, "none");
+}
+
diff --git a/appl/cmd/ip/virgild.b b/appl/cmd/ip/virgild.b
new file mode 100644
index 00000000..29ebba67
--- /dev/null
+++ b/appl/cmd/ip/virgild.b
@@ -0,0 +1,127 @@
+implement Virgild;
+
+include "sys.m";
+sys: Sys;
+
+include "draw.m";
+
+include "ip.m";
+
+Virgild: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+Udphdrsize: con IP->OUdphdrlen;
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ stderr = sys->fildes(2);
+
+ sys->pctl(Sys->FORKNS|Sys->FORKFD, nil);
+ if(sys->chdir("/lib/ndb") < 0){
+ sys->fprint(stderr, "virgild: no database\n");
+ return;
+ }
+
+ for(;;sys->sleep(10*1000)){
+ fd := openlisten();
+ if(fd == nil)
+ return;
+
+ buf := array[512] of byte;
+ for(;;){
+ n := sys->read(fd, buf, len buf);
+ if(n <= Udphdrsize){
+ break;
+ }
+ if(n <= Udphdrsize+1)
+ continue;
+
+ # dump any cruft after the question
+ for(i := Udphdrsize; i < n; i++){
+ c := int buf[i];
+ if(c == ' ' || c == 0 || c == '\n')
+ break;
+ }
+
+ answer := query(string buf[Udphdrsize:i]);
+ if(answer == nil)
+ continue;
+
+ # reply
+ r := array of byte answer;
+ if(len r > len buf - Udphdrsize)
+ continue;
+ buf[Udphdrsize:] = r;
+ sys->write(fd, buf, Udphdrsize+len r);
+ }
+ fd = nil;
+ }
+}
+
+openlisten(): ref Sys->FD
+{
+ (ok, c) := sys->announce("udp!*!virgil");
+ if(ok < 0){
+ sys->fprint(stderr, "virgild: can't open port: %r\n");
+ return nil;
+ }
+
+ if(sys->fprint(c.cfd, "headers") <= 0){
+ sys->fprint(stderr, "virgild: can't set headers: %r\n");
+ return nil;
+ }
+ sys->fprint(c.cfd, "oldheaders");
+
+ c.dfd = sys->open(c.dir+"/data", Sys->ORDWR);
+ if(c.dfd == nil) {
+ sys->fprint(stderr, "virgild: can't open data file\n");
+ return nil;
+ }
+ return c.dfd;
+}
+
+#
+# query is userid?question
+#
+# for now, we're ignoring userid
+#
+query(request: string): string
+{
+ (n, l) := sys->tokenize(request, "?");
+ if(n < 2){
+ sys->fprint(stderr, "virgild: bad request %s %d\n", request, n);
+ return nil;
+ }
+
+ #
+ # until we have something better, ask cs
+ # to translate, make the request look cs-like
+ #
+ fd := sys->open("/net/cs", Sys->ORDWR);
+ if(fd == nil){
+ sys->fprint(stderr, "virgild: can't open /net/cs - %r\n");
+ return nil;
+ }
+ q := array of byte ("tcp!" + hd(tl l) + "!1000");
+ if(sys->write(fd, q, len q) < 0){
+ sys->fprint(stderr, "virgild: can't write /net/cs - %r: %s\n", string q);
+ return nil;
+ }
+ sys->seek(fd, big 0, 0);
+ buf := array[512-Udphdrsize-len request-1] of byte;
+ n = sys->read(fd, buf, len buf);
+ if(n <= 0){
+ sys->fprint(stderr, "virgild: can't read /net/cs - %r\n");
+ return nil;
+ }
+
+ (nil, l) = sys->tokenize(string buf[0:n], " \t");
+ (nil, l) = sys->tokenize(hd(tl l), "!");
+ return request + "=" + hd l;
+}
diff --git a/appl/cmd/irtest.b b/appl/cmd/irtest.b
new file mode 100644
index 00000000..3d0260c7
--- /dev/null
+++ b/appl/cmd/irtest.b
@@ -0,0 +1,70 @@
+implement Irtest;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "ir.m";
+ ir: Ir;
+
+Irtest: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ x := chan of int;
+ p := chan of int;
+
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ ir = load Ir Ir->PATH;
+ if(ir == nil)
+ ir = load Ir Ir->SIMPATH;
+ if(ir == nil) {
+ sys->fprint(stderr, "load ir: %r\n");
+ return;
+ }
+
+ if(ir->init(x,p) != 0) {
+ sys->fprint(stderr, "Ir->init: %r\n");
+ return;
+ }
+ <-p;
+
+ names := array[] of {
+ "Zero",
+ "One",
+ "Two",
+ "Three",
+ "Four",
+ "Five",
+ "Six",
+ "Seven",
+ "Eight",
+ "Nine",
+ "ChanUP",
+ "ChanDN",
+ "VolUP",
+ "VolDN",
+ "FF",
+ "Rew",
+ "Up",
+ "Dn",
+ "Select",
+ "Power",
+ };
+
+ while((c := <-x) != Ir->EOF){
+ c = ir->translate(c);
+ if(c == ir->Error)
+ sys->print("Error\n");
+ else if(c >= len names)
+ sys->print("unknown %d\n", c);
+ else
+ sys->print("%s\n", names[c]);
+ }
+}
diff --git a/appl/cmd/itest.b b/appl/cmd/itest.b
new file mode 100644
index 00000000..aa24e4de
--- /dev/null
+++ b/appl/cmd/itest.b
@@ -0,0 +1,478 @@
+implement Itest;
+
+include "sys.m";
+ sys: Sys;
+include "string.m";
+ str: String;
+include "draw.m";
+include "daytime.m";
+ daytime: Daytime;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "readdir.m";
+ readdir: Readdir;
+include "arg.m";
+include "itslib.m";
+ S_INFO, S_WARN, S_ERROR, S_FATAL, S_STIME, S_ETIME: import Itslib;
+include "env.m";
+ env: Env;
+include "sh.m";
+
+SUMFILE: con "summary";
+MSGFILE: con "msgs";
+README: con "README";
+
+configfile := "";
+cflag := -1;
+verbosity := 3;
+repcount := 1;
+recroot := "";
+display_stderr := 0;
+display_stdout := 0;
+now := 0;
+
+stdout: ref Sys->FD;
+stderr: ref Sys->FD;
+context: ref Draw->Context;
+
+Test: adt {
+ spec: string;
+ fullspec: string;
+ cmd: Command;
+ recdir: string;
+ stdout: string;
+ stderr: string;
+ nruns: int;
+ nwarns: int;
+ nerrors: int;
+ nfatals: int;
+ failed: int;
+};
+
+
+Itest: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+ context = ctxt;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ nomod(Daytime->PATH);
+ str = load String String->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ nomod(Bufio->PATH);
+ if(str == nil)
+ nomod(String->PATH);
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ nomod(Readdir->PATH);
+ env = load Env Env->PATH;
+ if(env == nil)
+ nomod(Env->PATH);
+ arg->init(args);
+ while((o := arg->opt()) != 0)
+ case o {
+ 'c' => cflag = toint("c", arg->arg(), 0, 9);
+ 'e' => display_stderr++;
+ 'o' => display_stdout++;
+ 'r' => repcount = toint("r", arg->arg(), 0, -1);
+ 'v' => verbosity = toint("v", arg->arg(), 0, 9);
+ 'C' => configfile = arg->arg();
+ 'R' => recroot = arg->arg();
+ * => usage();
+ }
+ args = arg->argv();
+ arg = nil;
+ testlist : array of ref Test;
+ if (args != nil)
+ testlist = arg_tests(args);
+ else if (configfile != "")
+ testlist = config_tests(configfile);
+ if (testlist == nil)
+ fatal("No tests to run");
+ sys->pctl(Sys->FORKENV, nil);
+ if (env->setenv(Itslib->ENV_VERBOSITY, string verbosity))
+ fatal("Failed to set environment variable " + Itslib->ENV_VERBOSITY);
+ if (repcount)
+ reps := string repcount;
+ else
+ reps = "infinite";
+ if (len testlist == 1) ts := "";
+ else ts = "s";
+ if (repcount == 1) rs := "";
+ else rs = "s";
+ mreport(0, S_INFO, 2, sys->sprint("Starting tests - %s run%s of %d test%s", reps, rs, len testlist, ts));
+ run := big 1;
+ tlist := testlist;
+ if (recroot != nil)
+ recn := highest(recroot) + 1;
+ while (repcount == 0 || run <= big repcount) {
+ mreport(1, S_INFO, 3, sys->sprint("Starting run %bd", run));
+ for (i:=0; i<len testlist; i++) {
+ t := testlist[i];
+ if (recroot != nil) {
+ t.recdir = sys->sprint("%s/%d", recroot, recn++);
+ mreport(2, S_INFO, 3, sys->sprint("Recording in %s", t.recdir));
+ rfd := sys->create(t.recdir, Sys->OREAD, Sys->DMDIR | 8r770);
+ if (rfd == nil)
+ fatal(sys->sprint("Failed to create directory %s: %r\n", t.recdir));
+ rfd = nil;
+ }
+ runtest(t);
+ }
+ mreport(1, S_INFO, 3, sys->sprint("Finished run %bd", run));
+ run++;
+ }
+ mreport(0, S_INFO, 2, "Finished tests");
+}
+
+usage()
+{
+ sys->fprint(stderr, "Usage itest [-eo] [-c cflag] [-r count] [-v vlevel] [-C cfile] [-R recroot] [testdir ...]\n");
+ raise "fail: usage";
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "%s\n", s);
+ raise "fail: error";
+}
+
+nomod(mod: string)
+{
+ sys->fprint(stderr, "Failed to load %s\n", mod);
+ raise "fail: module";
+}
+
+toint(opt, s: string, min, max: int): int
+{
+ if (len s == 0 || str->take(s, "[0-9]+-") != s)
+ fatal(sys->sprint("no value specified for option %s", opt));
+ v := int s;
+ if (v < min)
+ fatal(sys->sprint("option %s value is less than minimum of %d: %d", opt, v, min));
+ if (max != -1 && v > max)
+ fatal(sys->sprint("option %s value is greater than maximum of %d: %d", opt, v, max));
+ return v;
+}
+
+arg_tests(args: list of string): array of ref Test
+{
+ al := len args;
+ ta := array[al] of ref Test;
+ for (i:=0; i<al; i++) {
+ tspec := hd args;
+ args = tl args;
+ ta[i] = ref Test(tspec, "", nil, "", "", "", 0, 0, 0, 0, 0);
+ tcheck(ta[i]);
+ }
+ return ta;
+}
+
+config_tests(cf: string): array of ref Test
+{
+ cl := linelist(cf);
+ if (cl == nil)
+ fatal("No tests in config file");
+ al := len cl;
+ ta := array[al] of ref Test;
+ for (i:=0; i<al; i++) {
+ tspec := hd cl;
+ cl = tl cl;
+ ta[i] = ref Test(tspec, "", nil, "", "", "", 0, 0, 0, 0, 0);
+ tcheck(ta[i]);
+ }
+ return ta;
+
+}
+
+highest(path: string): int
+{
+ (da, nd) := readdir->init(path, Readdir->NAME);
+ high := 0;
+ for (i:=0; i<nd; i++) {
+ n := int da[i].name;
+ if (n > high)
+ high = n;
+ }
+ return high;
+}
+
+tcheck(t: ref Test): int
+{
+ td := t.spec;
+ if (!checkdir(td)) {
+ fatal(sys->sprint("Failed to find test %s\n", td));
+ return 0;
+ }
+ tf1 := t.spec + "/t.sh";
+ tf2 := t.spec + "/t.dis";
+ if (checkexec(tf1)) {
+ t.fullspec = tf1;
+ return 1;
+ }
+ if (checkexec(tf2)) {
+ t.fullspec = tf2;
+ return 1;
+ }
+ fatal(sys->sprint("Could not find executable files %s or %s\n", tf1, tf2));
+ return 0;
+}
+
+checkdir(d: string): int
+{
+ (ok, dir) := sys->stat(d);
+ if (ok != 0 || ! dir.qid.qtype & Sys->QTDIR)
+ return 0;
+ return 1;
+}
+
+checkexec(d: string): int
+{
+ (ok, dir) := sys->stat(d);
+ if (ok != 0 || ! dir.mode & 8r100)
+ return 0;
+ return 1;
+}
+
+
+set_cflag(f: int)
+{
+ wfile("/dev/jit", string f, 0);
+
+}
+
+runtest(t: ref Test)
+{
+ if (t.failed)
+ return;
+ path := t.fullspec;
+ if (cflag != -1) {
+ mreport(0, S_INFO, 7, sys->sprint("Setting cflag to %d", cflag));
+ set_cflag(cflag);
+ }
+ readme := t.spec + "/" + README;
+ mreport(2, S_INFO, 3, sys->sprint("Starting test %s cflag=%s", t.spec, rfile("/dev/jit")));
+ if (verbosity > 8)
+ display_file(readme);
+ sync := chan of int;
+ spawn monitor(t, sync);
+ pid := <-sync;
+}
+
+monitor(t: ref Test, sync: chan of int)
+{
+ pid := sys->pctl(Sys->FORKFD|Sys->FORKNS|Sys->FORKENV|Sys->NEWPGRP, nil);
+ pa := array[2] of ref Sys->FD;
+ if (sys->pipe(pa))
+ fatal("Failed to set up pipe");
+ if (env->setenv(Itslib->ENV_MFD, string pa[0].fd))
+ fatal("Failed to set environment variable " + Itslib->ENV_MFD);
+ mlfd: ref Sys->FD;
+ if (t.recdir != nil) {
+ mfile := t.recdir+"/"+MSGFILE;
+ mlfd = sys->create(mfile, Sys->OWRITE, 8r660);
+ if (mlfd == nil)
+ fatal(sys->sprint("Failed to create %s: %r'\n", mfile));
+ t.stdout = t.recdir+"/stdout";
+ t.stderr = t.recdir+"/stderr";
+ } else {
+ t.stdout = "/tmp/itest.stdout";
+ t.stderr = "/tmp/itest.stderr";
+ }
+ cf := int rfile("/dev/jit");
+ stime := sys->millisec();
+ swhen := daytime->now();
+ etime := -1;
+ rsync := chan of int;
+ spawn runit(t.fullspec, t.stdout, t.stderr, t.spec, pa[0], rsync);
+ rpid := <-rsync;
+ pa[0] = nil;
+ (nwarns, nerrors, nfatals) := (0, 0, 0);
+ while (1) {
+ mbuf := array[Sys->ATOMICIO] of byte;
+ n := sys->read(pa[1], mbuf, len mbuf);
+ if (n <= 0) break;
+ msg := string mbuf[:n];
+ sev := int msg[0:1];
+ verb := int msg[1:2];
+ body := msg[2:];
+ if (sev == S_STIME)
+ stime = int body;
+ else if (sev == S_ETIME)
+ etime = int body;
+ else {
+ if (sev == S_WARN) {
+ nwarns++;
+ t.nwarns++;
+ }
+ else if (sev == S_ERROR) {
+ nerrors++;
+ t.nerrors++;
+ }
+ else if (sev == S_FATAL) {
+ nfatals++;
+ t.nfatals++;
+ }
+ mreport(3, sev, verb, sys->sprint("%s: %s", severs(sev), body));
+ }
+ if (mlfd != nil)
+ sys->fprint(mlfd, "%d:%s", now, msg);
+ }
+ if (etime < 0) {
+ etime = sys->millisec();
+ if (mlfd != nil)
+ sys->fprint(mlfd, "%d:%s", now, sys->sprint("%d0%d\n", S_ETIME, etime));
+ }
+ elapsed := etime-stime;
+ errsum := sys->sprint("WRN:%d ERR:%d FTL:%d", nwarns, nerrors, nfatals);
+ mreport(2, S_INFO, 3, sys->sprint("Finished test %s after %dms - %s", t.spec, elapsed, errsum));
+ if (t.recdir != "") {
+ wfile(t.recdir+"/"+SUMFILE, sys->sprint("%d %d %d %s\n", swhen, elapsed, cf, t.fullspec), 1);
+ }
+ if (display_stdout) {
+ mreport(2, 0, 0, "Stdout from test:");
+ display_file(t.stdout);
+ }
+ if (display_stderr) {
+ mreport(2, 0, 0, "Stderr from test:");
+ display_file(t.stderr);
+ }
+ sync <-= pid;
+}
+
+runit(fullspec, sofile, sefile, tpath: string, mfd: ref Sys->FD, sync: chan of int)
+{
+ pid := sys->pctl(Sys->NEWFD|Sys->FORKNS, mfd.fd::nil);
+ o, e: ref Sys->FD;
+ o = sys->create(sofile, Sys->OWRITE, 8r660);
+ if (o == nil)
+ treport(mfd, S_ERROR, 0, "Failed to open stdout: %r\n");
+ else
+ sys->dup(o.fd, 1);
+ o = nil;
+ e = sys->create(sefile, Sys->OWRITE, 8r660);
+ if (e == nil)
+ treport(mfd, S_ERROR, 0, "Failed to open stderr: %r\n");
+ else
+ sys->dup(e.fd, 2);
+ e = nil;
+ sync <-= pid;
+ args := list of {fullspec};
+ if (fullspec[len fullspec-1] == 's')
+ cmd := load Command fullspec;
+ else {
+ cmd = load Command "/dis/sh.dis";
+ args = fullspec :: args;
+ }
+ if (cmd == nil) {
+ treport(mfd, S_FATAL, 0, sys->sprint("Failed to load Command from %s", "/dis/sh.dis"));
+ return;
+ }
+ if (sys->chdir(tpath))
+ treport(mfd, S_FATAL, 0, "Failed to cd to " + tpath);
+ {
+ cmd->init(context, args);
+ } exception ex {
+ "*" =>
+ treport(mfd, S_FATAL, 0, sys->sprint("Exception %s in test %s", ex, fullspec));
+ }
+}
+
+severs(sevs: int): string
+{
+ SEVMAP := array[] of {"INF", "WRN", "ERR", "FTL"};
+ if (sevs >= len SEVMAP)
+ sstr := "UNK";
+ else
+ sstr = SEVMAP[sevs];
+ return sstr;
+}
+
+
+rfile(file: string): string
+{
+ fd := sys->open(file, Sys->OREAD);
+ if (fd == nil) return nil;
+ buf := array[Sys->ATOMICIO] of byte;
+ n := sys->read(fd, buf, len buf);
+ return string buf[:n];
+}
+
+
+wfile(file: string, text: string, create: int): int
+{
+ if (create)
+ fd := sys->create(file, Sys->OWRITE, 8r660);
+ else
+ fd = sys->open(file, Sys->OWRITE);
+ if (fd == nil) {
+ sys->fprint(stderr, "Failed to open %s: %r\n", file);
+ return 0;
+ }
+ a := array of byte text;
+ al := len a;
+ if (sys->write(fd, a, al) != al) {
+ sys->fprint(stderr, "Failed to write to %s: %r\n", file);
+ return 0;
+ }
+ fd = nil;
+ return 1;
+}
+
+linelist(file: string): list of string
+{
+ bf := bufio->open(file, Bufio->OREAD);
+ if (bf == nil)
+ return nil;
+ cl : list of string;
+ while ((line := bf.gets('\n')) != nil) {
+ if (line[len line -1] == '\n')
+ line = line[:len line - 1];
+ cl = line :: cl;
+ }
+ bf = nil;
+ return cl;
+}
+
+display_file(file: string)
+{
+ bf := bufio->open(file, Bufio->OREAD);
+ if (bf == nil)
+ return;
+ while ((line := bf.gets('\n')) != nil) {
+ sys->print(" %s", line);
+ }
+}
+
+mreport(indent: int, sev: int, verb: int, msg: string)
+{
+ now = daytime->now();
+ tm := daytime->local(now);
+ time := sys->sprint("%4d%02d%02d %02d:%02d:%02d", tm.year+1900, tm.mon-1, tm.mday, tm.hour, tm.min, tm.sec);
+ pad := "---"[:indent];
+ term := "";
+ if (len msg && msg[len msg-1] != '\n')
+ term = "\n";
+ if (sev || verb <= verbosity)
+ sys->print("%s %s%s%s", time, pad, msg, term);
+}
+
+
+treport(mfd: ref Sys->FD, sev: int, verb: int, msg: string)
+{
+ sys->fprint(mfd, "%d%d%s\n", sev, verb, msg);
+}
diff --git a/appl/cmd/itreplay.b b/appl/cmd/itreplay.b
new file mode 100644
index 00000000..bba2f591
--- /dev/null
+++ b/appl/cmd/itreplay.b
@@ -0,0 +1,230 @@
+implement Itreplay;
+
+include "sys.m";
+ sys: Sys;
+include "string.m";
+ str: String;
+include "draw.m";
+include "daytime.m";
+ daytime: Daytime;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "readdir.m";
+ readdir: Readdir;
+include "arg.m";
+include "itslib.m";
+ S_INFO, S_WARN, S_ERROR, S_FATAL, S_STIME, S_ETIME: import Itslib;
+
+SUMFILE: con "summary";
+MSGFILE: con "msgs";
+
+verbosity := 3;
+display_stderr := 0;
+display_stdout := 0;
+
+stderr: ref Sys->FD;
+
+
+Itreplay: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ nomod(Daytime->PATH);
+ str = load String String->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ nomod(Bufio->PATH);
+ if(str == nil)
+ nomod(String->PATH);
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ nomod(Readdir->PATH);
+ arg->init(args);
+ while((o := arg->opt()) != 0)
+ case o {
+ 'e' => display_stderr++;
+ 'o' => display_stdout++;
+ 'v' => verbosity = toint("v", arg->arg(), 0, 9);
+ * => usage();
+ }
+ recdirl := arg->argv();
+ arg = nil;
+ if (recdirl == nil)
+ usage();
+ while (recdirl != nil) {
+ dir := hd recdirl;
+ recdirl = tl recdirl;
+ replay(dir);
+ }
+}
+
+usage()
+{
+ sys->fprint(stderr, "Usage: itreplay [-eo] [-v verbosity] recorddir ...\n");
+ raise "fail: usage";
+ exit;
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "%s\n", s);
+ raise "fail: error";
+ exit;
+}
+
+nomod(mod: string)
+{
+ sys->fprint(stderr, "Failed to load %s\n", mod);
+ raise "fail: module";
+ exit;
+}
+
+toint(opt, s: string, min, max: int): int
+{
+ if (len s == 0 || str->take(s, "[0-9]+-") != s)
+ fatal(sys->sprint("no value specified for option %s", opt));
+ v := int s;
+ if (v < min)
+ fatal(sys->sprint("option %s value is less than minimum of %d: %d", opt, v, min));
+ if (max != -1 && v > max)
+ fatal(sys->sprint("option %s value is greater than maximum of %d: %d", opt, v, max));
+ return v;
+}
+
+replay(dir: string)
+{
+ sl := linelist(dir+"/"+SUMFILE);
+ if (sl == nil) {
+ sys->fprint(stderr, "No summary file in %s\n", dir);
+ return;
+ }
+ sline := hd sl;
+ (n, toks) := sys->tokenize(sline, " ");
+ if (n < 4) {
+ sys->fprint(stderr, "Bad summary file in %s\n", dir);
+ return;
+ }
+ when := int hd toks;
+ toks = tl toks;
+ elapsed := int hd toks;
+ toks = tl toks;
+ cflag := int hd toks;
+ toks = tl toks;
+ testspec := hd toks;
+ mreport(1, when, 0, 2, sys->sprint("Processing %s: test %s ran in %dms with cflag=%d\n", dir, testspec, elapsed, cflag));
+ replay_msgs(dir+"/"+MSGFILE, testspec, cflag);
+ if (display_stdout) {
+ mreport(2, 0, 0, 0, "Stdout from test:");
+ display_file(dir+"/stdout");
+ }
+ if (display_stderr) {
+ mreport(2, 0, 0, 0, "Stderr from test:");
+ display_file(dir+"/stderr");
+ }
+}
+
+
+replay_msgs(mfile: string, tspec: string, cflag: int)
+{
+ mf := bufio->open(mfile, Bufio->OREAD);
+ if (mf == nil)
+ return;
+ (nwarns, nerrors, nfatals) := (0, 0, 0);
+ stime := 0;
+ etime := 0;
+ while ((line := mf.gets('\n')) != nil) {
+ (whens, rest) := str->splitl(line, ":");
+ when := int whens;
+ msg := rest[1:];
+ sev := int msg[0:1];
+ verb := int msg[1:2];
+ body := msg[2:];
+ if (sev == S_STIME) {
+ stime = int body;
+ mreport(2, when, 0, 3, sys->sprint("Starting test %s cflag=%d", tspec, cflag));
+ }
+ else if (sev == S_ETIME) {
+ uetime := int body;
+ elapsed := uetime-stime;
+ errsum := sys->sprint("WRN:%d ERR:%d FTL:%d", nwarns, nerrors, nfatals);
+ mreport(2, when+(int body-stime)/1000, 0, 3, sys->sprint("Finished test %s after %dms - %s", tspec, elapsed, errsum));
+ }
+ else {
+ if (sev == S_WARN) {
+ nwarns++;
+ }
+ else if (sev == S_ERROR) {
+ nerrors++;
+ }
+ else if (sev == S_FATAL) {
+ nfatals++;
+ }
+ mreport(3, when, sev, verb, sys->sprint("%s: %s", severs(sev), body));
+ }
+ }
+}
+
+linelist(file: string): list of string
+{
+ bf := bufio->open(file, Bufio->OREAD);
+ if (bf == nil)
+ return nil;
+ cl : list of string;
+ while ((line := bf.gets('\n')) != nil) {
+ if (line[len line -1] == '\n')
+ line = line[:len line - 1];
+ cl = line :: cl;
+ }
+ bf = nil;
+ return cl;
+}
+
+display_file(file: string)
+{
+ bf := bufio->open(file, Bufio->OREAD);
+ if (bf == nil)
+ return;
+ while ((line := bf.gets('\n')) != nil) {
+ sys->print(" %s", line);
+ }
+}
+
+
+severs(sevs: int): string
+{
+ SEVMAP := array[] of {"INF", "WRN", "ERR", "FTL"};
+ if (sevs >= len SEVMAP)
+ sstr := "UNK";
+ else
+ sstr = SEVMAP[sevs];
+ return sstr;
+}
+
+
+mreport(indent: int, when: int, sev: int, verb: int, msg: string)
+{
+ time := "";
+ if (when) {
+ tm := daytime->local(when);
+ time = sys->sprint("%4d%02d%02d %02d:%02d:%02d", tm.year+1900, tm.mon-1, tm.mday, tm.hour, tm.min, tm.sec);
+ }
+ pad := "---"[:indent];
+ term := "";
+ if (len msg && msg[len msg-1] != '\n')
+ term = "\n";
+ if (sev || verb <= verbosity)
+ sys->print("%-17s %s%s%s", time, pad, msg, term);
+}
diff --git a/appl/cmd/kill.b b/appl/cmd/kill.b
new file mode 100644
index 00000000..7ff5a39a
--- /dev/null
+++ b/appl/cmd/kill.b
@@ -0,0 +1,146 @@
+implement Kill;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+
+Kill: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+stderr: ref Sys->FD;
+
+usage()
+{
+ sys->fprint(stderr, "usage: kill [-g] pid|module [...]\n");
+ raise "fail: usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil){
+ sys->fprint(stderr, "kill: cannot load %s: %r\n", Arg->PATH);
+ raise "fail:load";
+ }
+
+ msg := array of byte "kill";
+ arg->init(args);
+ while((o := arg->opt()) != 0)
+ case o {
+ 'g' =>
+ msg = array of byte "killgrp";
+ * =>
+ usage();
+ }
+
+ argv := arg->argv();
+ arg = nil;
+ if(argv == nil)
+ usage();
+ n := 0;
+ for(v := argv; v != nil; v = tl v) {
+ s := hd v;
+ if (s == nil)
+ usage();
+ if(s[0] >= '0' && s[0] <= '9')
+ n += killpid(s, msg, 1);
+ else
+ n += killmod(s, msg);
+ }
+ if (n == 0 && argv != nil)
+ raise "fail:nothing killed";
+}
+
+killpid(pid: string, msg: array of byte, sbok: int): int
+{
+ fd := sys->open("/prog/"+pid+"/ctl", sys->OWRITE);
+ if(fd == nil) {
+ err := sys->sprint("%r");
+ elen := len err;
+ if(sbok || err != "thread exited" && elen >= 14 && err[elen-14:] != "does not exist")
+ sys->fprint(stderr, "kill: cannot open /prog/%s/ctl: %r\n", pid);
+ return 0;
+ }
+
+ n := sys->write(fd, msg, len msg);
+ if(n < 0) {
+ err := sys->sprint("%r");
+ elen := len err;
+ if(sbok || err != "thread exited")
+ sys->fprint(stderr, "kill: cannot kill %s: %r\n", pid);
+ return 0;
+ }
+ return 1;
+}
+
+killmod(mod: string, msg: array of byte): int
+{
+ fd := sys->open("/prog", sys->OREAD);
+ if(fd == nil) {
+ sys->fprint(stderr, "kill: open /prog: %r\n");
+ return 0;
+ }
+
+ pids: list of string;
+ for(;;) {
+ (n, d) := sys->dirread(fd);
+ if(n <= 0) {
+ if (n < 0)
+ sys->fprint(stderr, "kill: read /prog: %r\n");
+ break;
+ }
+
+ for(i := 0; i < n; i++)
+ if (killmatch(d[i].name, mod))
+ pids = d[i].name :: pids;
+ }
+ if (pids == nil) {
+ sys->fprint(stderr, "kill: cannot find %s\n", mod);
+ return 0;
+ }
+ n := 0;
+ for (; pids != nil; pids = tl pids)
+ if (killpid(hd pids, msg, 0)) {
+ sys->print("%s ", hd pids);
+ n++;
+ }
+ if (n > 0)
+ sys->print("\n");
+ return n;
+}
+
+killmatch(dir, mod: string): int
+{
+ status := "/prog/"+dir+"/status";
+ fd := sys->open(status, sys->OREAD);
+ if(fd == nil)
+ return 0;
+ buf := array[512] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0) {
+ err := sys->sprint("%r");
+ if(err != "thread exited")
+ sys->fprint(stderr, "kill: cannot read %s: %s\n", status, err);
+ return 0;
+ }
+
+ # module name is last field
+ (nil, fields) := sys->tokenize(string buf[0:n], " ");
+ for(s := ""; fields != nil; fields = tl fields)
+ s = hd fields;
+
+ # strip builtin module, e.g. Sh[$Sys]
+ for(i := 0; i < len s; i++) {
+ if(s[i] == '[') {
+ s = s[0:i];
+ break;
+ }
+ }
+
+ return s == mod;
+}
diff --git a/appl/cmd/lc.b b/appl/cmd/lc.b
new file mode 100644
index 00000000..de5ec579
--- /dev/null
+++ b/appl/cmd/lc.b
@@ -0,0 +1,156 @@
+implement Lc;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "readdir.m";
+ readdir: Readdir;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Lc: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+t_FILE, t_DIR, t_NUMTYPES: con iota;
+columns := 65;
+stderr: ref Sys->FD;
+stdout: ref Iobuf;
+
+usage()
+{
+ sys->fprint(stderr, "usage: lc [-df] [-c columns] [file ...]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ readdir = load Readdir Readdir->PATH;
+ if (readdir == nil) {
+ sys->fprint(stderr, "lc: cannot load %s: %r\n", Readdir->PATH);
+ raise "fail:bad module";
+ }
+ bufio = load Bufio Bufio->PATH;
+ stdout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if (bufio == nil) {
+ sys->fprint(stderr, "lc: cannot load %s: %r\n", Bufio->PATH);
+ raise "fail:bad module";
+ }
+ if (argv == nil)
+ return;
+ argv = tl argv;
+ flags := 0;
+loop: while (argv != nil && hd argv != nil && (hd argv)[0] == '-') {
+ s := (hd argv)[1:];
+ argv = tl argv;
+ flagloop: for (; s != nil; s = s[1:]) {
+ case s[0] {
+ '-' =>
+ break loop;
+ 'd' =>
+ flags |= 1 << t_DIR;
+ 'f' =>
+ flags |= 1 << t_FILE;
+ 'c' =>
+ if (len s > 1) {
+ columns = int s[1:];
+ break flagloop;
+ }
+ if (argv == nil)
+ usage();
+ columns = int hd argv;
+ argv = tl argv;
+ * =>
+ usage();
+ }
+ }
+ }
+
+ headings := 0;
+ if (flags == 0) {
+ flags = (1<<t_DIR)|(1<<t_FILE);
+ headings = 1;
+ }
+ if (argv == nil)
+ argv = "." :: nil;
+ multi := tl argv != nil;
+ nondir: list of string;
+ for (; argv != nil; argv = tl argv) {
+ dname := hd argv;
+ (ok, dir) := sys->stat(dname);
+ if(ok < 0) {
+ sys->fprint(stderr, "lc: can't stat %s: %r\n", hd argv);
+ continue;
+ }
+ if (dir.mode & Sys->DMDIR) {
+ (d, n) := readdir->init(hd argv, Readdir->NAME | Readdir->COMPACT);
+ if (n < 0)
+ sys->fprint(stderr, "lc: cannot read %s: %r\n", hd argv);
+ else {
+ indent := 0;
+ if (multi && headings) {
+ stdout.puts(hd argv + "/\n");
+ indent = 2;
+ }
+ l: list of string = nil;
+ for (i := 0; i < n; i++) {
+ s := d[i].name;
+ if (!headings && dname != ".")
+ s = dname + "/" + s;
+ if (d[i].mode & Sys->DMDIR) {
+ if (flags & (1<<t_DIR))
+ l = s + "/" :: l;
+ } else if (flags & (1<<t_FILE))
+ l = s :: l;
+ }
+ d = nil;
+ lc(l, indent);
+ }
+ } else if (flags & (1 << t_FILE))
+ nondir = dname :: nondir;
+ }
+ lc(nondir, 0);
+ stdout.close();
+}
+
+lc(dl: list of string, indent: int)
+{
+ a := array[len dl] of string;
+ j := len a - 1;
+ maxwidth := 0;
+ for (; dl != nil; dl = tl dl) {
+ s := hd dl;
+ a[j--] = s;
+ if (len s > maxwidth)
+ maxwidth = len s;
+ }
+ outcols(a, maxwidth, indent);
+}
+
+outcols(stuff: array of string, maxwidth, indent: int)
+{
+ num := len stuff;
+ cols := columns - indent;
+ numcols := cols / (maxwidth + 1);
+ colwidth: int;
+ if (numcols == 0) {
+ numcols = 1;
+ colwidth = maxwidth;
+ } else
+ colwidth = cols / numcols;
+ numrows := (num + numcols - 1) / numcols;
+
+ for (i := 0; i < numrows; i++) {
+ if (indent)
+ stdout.puts(sys->sprint("%*s", indent, ""));
+ for (j := i; j < num; j += numrows) {
+ if (j + numrows < num)
+ stdout.puts(sys->sprint("%*.*s", -colwidth, colwidth, stuff[j]));
+ else
+ stdout.puts(sys->sprint("%.*s\n", colwidth, stuff[j]));
+ }
+ }
+}
diff --git a/appl/cmd/lego/clock.b b/appl/cmd/lego/clock.b
new file mode 100644
index 00000000..3b3c3e50
--- /dev/null
+++ b/appl/cmd/lego/clock.b
@@ -0,0 +1,214 @@
+implement Clock;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Point, Rect: import draw;
+
+include "math.m";
+ math: Math;
+ sqrt, atan2, hypot, Degree: import math;
+
+include "tk.m";
+ tk: Tk;
+ top: ref Tk->Toplevel;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+Clock: module {
+ init: fn(ctxt: ref Draw->Context, argl: list of string);
+};
+
+cmds := array[] of {
+ "bind . <Configure> {send win resize}",
+ "canvas .face -height 200 -width 200 -bg yellow",
+ "bind .face <ButtonPress> {send ptr %x %y}",
+ "bind .face <ButtonRelease> {send ptr release}",
+ "pack .face -expand yes -fill both",
+ "button .reset -text Reset -command {send win reset}",
+ "pack .reset -after .Wm_t.title -side right -fill y",
+ "pack propagate . no",
+};
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ math = load Math Math->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ tkclient->init();
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ clockface := sys->open("/chan/clockface", Sys->ORDWR);
+ if (clockface == nil) {
+ sys->print("open /chan/clockface failed: %r\n");
+ raise "fail:clockface";
+ }
+ tock := chan of string;
+ spawn readme(clockface, tock);
+
+ titlech: chan of string;
+ (top, titlech) = tkclient->toplevel(ctxt, "hh:mm", "", Tkclient->Appl);
+ win := chan of string;
+ ptr := chan of string;
+ tk->namechan(top, win, "win");
+ tk->namechan(top, ptr, "ptr");
+ for(i:=0; i<len cmds; i++)
+ tk->cmd(top, cmds[i]);
+ tkclient->onscreen(top, nil);
+ tkclient->startinput(top, "ptr"::nil);
+ drawface();
+ spawn hands(ptr, clockface);
+
+ for (;;) alt {
+ s := <-top.ctxt.kbd =>
+ tk->keyboard(top, s);
+ s := <-top.ctxt.ptr =>
+ tk->pointer(top, *s);
+ s := <-top.ctxt.ctl or
+ s = <-top.wreq or
+ s = <-titlech =>
+ tkclient->wmctl(top, s);
+ msg := <-win =>
+ case msg {
+ "resize" => drawface();
+ "reset" => sys->fprint(clockface, "reset");
+ }
+ nowis := <-tock =>
+ (n, toks) := sys->tokenize(nowis, ":");
+ if (n == 2) {
+ (hour, minute) = (int hd toks, int hd tl toks);
+ setclock();
+ }
+ }
+}
+
+readme(fd: ref Sys->FD, ch: chan of string)
+{
+ buf := array[64] of byte;
+ while ((n := sys->read(fd, buf, len buf)) > 0) {
+ if (buf[n-1] == byte '\n')
+ n--;
+ ch <-= string buf[:n];
+ }
+ ch <-= "99:99";
+}
+
+hour, minute: int;
+center, focus: Point;
+major: int;
+
+Frim: con .98;
+Fminute: con .90;
+Fhour: con .45;
+Fnub: con .05;
+
+hands(ptr: chan of string, fd: ref Sys->FD)
+{
+ for (;;) {
+ pos := <-ptr;
+ p := s2p(pos);
+ hand := "";
+ if (elinside(p, Fnub))
+ hand = nil;
+ else if (elinside(p, Fhour))
+ hand = "hour";
+ else if (elinside(p, Fminute))
+ hand = "minute";
+
+ do {
+ p = s2p(pos).sub(center);
+ angle := int (atan2(real -p.y, real p.x) / Degree);
+ if (hand != nil)
+ tkc(".face itemconfigure "+hand+" -start "+string angle+"; update");
+ case hand {
+ "hour" => hour = ((360+90-angle) / 30) % 12;
+ "minute" => minute = ((360+90-angle) / 6) % 60;
+ }
+ } while ((pos = <-ptr) != "release");
+ if (hand != nil)
+ sys->fprint(fd, "%d:%d\n", hour, minute);
+ }
+}
+
+drawface()
+{
+ elparms();
+ tkc(sys->sprint(".face configure -scrollregion {0 0 %d %d}", 2*center.x, 2*center.y));
+ tkc(".face delete all");
+ tkc(".face create oval "+elrect(Frim)+" -fill fuchsia -outline aqua -width 2");
+ for (a := 0; a < 360; a += 30)
+ tkc(".face create arc "+elrect(Frim)+" -fill aqua -outline aqua -width 2 -extent 1 -start "+string a);
+ tkc(".face create oval "+elrect(Fminute)+" -fill fuchsia -outline fuchsia");
+ tkc(".face create oval "+elrect(Fnub)+" -fill aqua -outline aqua");
+ tkc(".face create arc "+elrect(Fhour)+" -fill aqua -outline aqua -width 6 -extent 1 -tags hour");
+ tkc(".face create arc "+elrect(Fminute)+" -fill aqua -outline aqua -width 2 -extent 1 -tags minute");
+ setclock();
+}
+
+setclock()
+{
+ tkc(".face itemconfigure hour -start "+string (90 - 30*(hour%12) - minute/2));
+ tkc(".face itemconfigure minute -start "+string (90 - 6*minute));
+ tkc(sys->sprint(".Wm_t.title configure -text {%d:%.2d}", (hour+11)%12+1, minute));
+ tkc("update");
+}
+
+elparms()
+{
+ center = (int tkc(".face cget actwidth") / 2, int tkc(".face cget actheight") / 2);
+ dist := center.x*center.x - center.y*center.y;
+ if (dist > 0) {
+ major = 2 * center.x;
+ focus = (int sqrt(real dist), 0);
+ } else {
+ major = 2 * center.y;
+ focus = (0, int sqrt(real -dist));
+ }
+}
+
+elinside(p: Point, frac: real): int
+{
+ foc := mulf(focus, frac);
+ d := dist(p, center.add(foc)) + dist(p, center.sub(foc));
+ return (d < frac * real major);
+}
+
+elrect(frac: real): string
+{
+ inset := mulf(center, 1.-frac);
+ r := Rect(inset, center.mul(2).sub(inset));
+ return sys->sprint("%d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
+}
+
+mulf(p: Point, f: real): Point
+{
+ return (int (f * real p.x), int (f * real p.y));
+}
+
+dist(p, q: Point): real
+{
+ p = p.sub(q);
+ return hypot(real p.x, real p.y);
+}
+
+s2p(s: string): Point
+{
+ (nil, xy) := sys->tokenize(s, " ");
+ if (len xy != 2)
+ return (0, 0);
+ return (int hd xy, int hd tl xy);
+}
+
+tkc(msg: string): string
+{
+ ret := tk->cmd(top, msg);
+ if (ret != nil && ret[0] == '!')
+ sys->print("tk error? %s → %s\n", msg, ret);
+ return ret;
+}
diff --git a/appl/cmd/lego/clockface.b b/appl/cmd/lego/clockface.b
new file mode 100644
index 00000000..6ba6069b
--- /dev/null
+++ b/appl/cmd/lego/clockface.b
@@ -0,0 +1,384 @@
+# Model 1
+implement Clockface;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+Clockface: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+hmpath: con "motor/0"; # hour-hand motor
+mmpath: con "motor/2"; # minute-hand motor
+allmpath: con "motor/012"; # all motors (for stopall msg)
+
+hbpath: con "sensor/0"; # hour-hand sensor
+mbpath: con "sensor/2"; # minute-hand sensor
+lspath: con "sensor/1"; # light sensor;
+
+ONTHRESH: con 780; # light sensor thresholds
+OFFTHRESH: con 740;
+NCLICKS: con 120;
+MINCLICKS: con 2; # min number of clicks required to stop a motor
+
+Hand: adt {
+ motor: ref Sys->FD;
+ sensor: ref Sys->FD;
+ fwd: array of byte;
+ rev: array of byte;
+ stop: array of byte;
+ pos: int;
+ time: int;
+};
+
+lightsensor: ref Sys->FD;
+allmotors: ref Sys->FD;
+hourhand: ref Hand;
+minutehand: ref Hand;
+timedata: array of byte;
+readq: list of Sys->Rread;
+verbose := 0;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ argv = tl argv;
+ if (len argv > 0 && hd argv == "-v") {
+ verbose++;
+ argv = tl argv;
+ }
+ if (len argv != 1) {
+ sys->print("usage: [-v] legodir\n");
+ raise "fail:usage";
+ }
+ legodir := hd argv + "/";
+
+ # set up our control file
+ f2c := sys->file2chan("/chan", "clockface");
+ if (f2c == nil) {
+ sys->print("cannot create clockface channel: %r\n");
+ return;
+ }
+
+ # get the motor files
+ log("opening motor files");
+ hm := sys->open(legodir + hmpath, Sys->OWRITE);
+ mm := sys->open(legodir +mmpath, Sys->OWRITE);
+ allmotors = sys->open(legodir + allmpath, Sys->OWRITE);
+ if (hm == nil || mm == nil || allmotors == nil) {
+ sys->print("cannot open motor files\n");
+ raise "fail:error";
+ }
+
+ # get the sensor files
+ log("opening sensor files");
+ hb := sys->open(legodir + hbpath, Sys->ORDWR);
+ mb := sys->open(legodir + mbpath, Sys->ORDWR);
+ lightsensor = sys->open(legodir + lspath, Sys->ORDWR);
+
+ if (hb == nil || mb == nil) {
+ sys->print("cannot open sensor files\n");
+ raise "fail:error";
+ }
+
+ hourhand = ref Hand(hm, hb, array of byte "r7", array of byte "f7", array of byte "s7", 0, 00);
+ minutehand = ref Hand(mm, mb, array of byte "f7", array of byte "r7", array of byte "s7", 0, 00);
+
+ log("setting sensor types");
+ setsensortypes(hourhand, minutehand, lightsensor);
+
+ # get the hands to 12 o'clock
+ reset();
+ log(sys->sprint("H %d, M %d", hourhand.pos, minutehand.pos));
+ spawn srvlink(f2c);
+}
+
+srvlink(f2c: ref Sys->FileIO)
+{
+ tick := chan of int;
+ spawn eggtimer(tick);
+
+ for (;;) alt {
+ (nil, count, fid, rc) := <-f2c.read =>
+ if (rc == nil) {
+ close(fid);
+ continue;
+ }
+ if (count < len timedata) {
+ rc <-= (nil, "read too small");
+ continue;
+ }
+ if (open(fid))
+ readq = rc :: readq;
+ else
+ rc <-= (timedata, nil);
+
+ (nil, data, fid, wc) := <-f2c.write =>
+ if (wc == nil) {
+ close(fid);
+ continue;
+ }
+ (nil, toks) := sys->tokenize(string data, ": \t\n");
+ if (len toks == 2) {
+ wc <-= (len data, nil);
+ hourhand.time = int hd toks % 12;
+ minutehand.time = int hd tl toks % 60;
+ sethands();
+ } else if (len toks == 1 && hd toks == "reset") {
+ wc <-= (len data, nil);
+ reset();
+ } else
+ wc <-= (0, "syntax is hh:mm or `reset'");
+
+ <-tick =>
+ if (++minutehand.time == 60) {
+ minutehand.time = 0;
+ hourhand.time++;
+ hourhand.time %= 12;
+ }
+ sethands();
+ }
+}
+
+readers: list of int;
+
+open(fid: int): int
+{
+ for (rlist := readers; rlist != nil; rlist = tl rlist)
+ if (hd rlist == fid)
+ return 1;
+ readers = fid :: readers;
+ return 0;
+}
+
+close(fid: int)
+{
+ rlist: list of int;
+ for (; readers != nil; readers = tl readers)
+ if (hd readers != fid)
+ rlist = hd readers :: rlist;
+ readers = rlist;
+}
+
+eggtimer(tick: chan of int)
+{
+ next := sys->millisec();
+ for (;;) {
+ next += 60*1000;
+ sys->sleep(next - sys->millisec());
+ tick <-= 1;
+ }
+}
+
+clicks(): (int, int)
+{
+ h := hourhand.time;
+ m := minutehand.time;
+ h = ((h * NCLICKS) / 12) + ((m * NCLICKS) / (12 * 60));
+ m = (m * NCLICKS) / 60;
+ return (h, m);
+}
+
+sethands()
+{
+ timedata = array of byte sys->sprint("%2d:%.2d\n", (hourhand.time+11) % 12 + 1, minutehand.time);
+ for (; readq != nil; readq = tl readq)
+ alt {
+ (hd readq) <-= (timedata, nil) => ;
+ * => ;
+ }
+
+ (hclk, mclk) := clicks();
+ for (i := 0; i < 6; i++) {
+ hdelta := clickdistance(hourhand.pos, hclk, NCLICKS);
+ mdelta := clickdistance(minutehand.pos, mclk, NCLICKS);
+ if (hdelta != 0)
+ sethand(hourhand, hdelta);
+ else if (mdelta != 0)
+ sethand(minutehand, mdelta);
+ else
+ break;
+ }
+ releaseall();
+}
+
+clickdistance(start, stop, mod: int): int
+{
+ if (start > stop)
+ stop += mod;
+ d := (stop - start) % mod;
+ if (d > mod/2)
+ d -= mod;
+ return d;
+}
+
+setsensortypes(h1, h2: ref Hand, ls: ref Sys->FD)
+{
+ button := array of byte "b0";
+ light := array of byte "l0";
+ sys->write(h1.sensor, button, len button);
+ sys->write(h2.sensor, button, len button);
+ sys->write(ls, light, len light);
+}
+
+HOUR_ADJUST: con 1;
+MINUTE_ADJUST: con 2;
+
+reset()
+{
+ # run the motors until hands are well away from 12 o'clock (below threshold)
+
+ val := readsensor(lightsensor);
+ if (val > OFFTHRESH) {
+ triggered := chan of int;
+ log("wait for hands clear of light sensor");
+ spawn lightwait(triggered, lightsensor, 0);
+ forward(minutehand);
+ reverse(hourhand);
+ val = <-triggered;
+ stopall();
+ log("sensor "+string val);
+ }
+
+ resethand(hourhand);
+ hourhand.pos += HOUR_ADJUST;
+ resethand(minutehand);
+ minutehand.pos += MINUTE_ADJUST;
+ sethands();
+}
+
+sethand(hand: ref Hand, delta: int)
+{
+ triggered := chan of int;
+ dir := 1;
+ if (delta < 0) {
+ dir = -1;
+ delta = -delta;
+ }
+ if (delta > MINCLICKS) {
+ spawn handwait(triggered, hand, delta - MINCLICKS);
+ if (dir > 0)
+ forward(hand);
+ else
+ reverse(hand);
+ <-triggered;
+ stop(hand);
+ hand.pos += dir * readsensor(hand.sensor);
+ } else {
+ startval := readsensor(hand.sensor);
+ if (dir > 0)
+ forward(hand);
+ else
+ reverse(hand);
+ stop(hand);
+ hand.pos += dir * (readsensor(hand.sensor) - startval);
+ }
+ if (hand.pos < 0)
+ hand.pos += NCLICKS;
+ hand.pos %= NCLICKS;
+}
+
+resethand(hand: ref Hand)
+{
+ triggered := chan of int;
+ val: int;
+
+ # run the hand until the light sensor is above threshold
+ log("running hand until light sensor activated");
+ spawn lightwait(triggered, lightsensor, 1);
+ forward(hand);
+ val = <-triggered;
+ stop(hand);
+ log("sensor "+string val);
+
+ startclick := readsensor(hand.sensor);
+
+ # advance until light sensor drops below threshold
+ log("running hand until light sensor clear");
+ spawn lightwait(triggered, lightsensor, 0);
+ forward(hand);
+ val = <-triggered;
+ stop(hand);
+ log("sensor "+string val);
+
+ stopclick := readsensor(hand.sensor);
+ nclicks := stopclick - startclick;
+ log(sys->sprint("startpos %d, endpos %d (nclicks %d)", startclick, stopclick, nclicks));
+
+ hand.pos = nclicks/2;
+}
+
+stop(hand: ref Hand)
+{
+ sys->seek(hand.motor, big 0, Sys->SEEKSTART);
+ sys->write(hand.motor, hand.stop, len hand.stop);
+}
+
+stopall()
+{
+ msg := array of byte "s0s0s0";
+ sys->seek(allmotors, big 0, Sys->SEEKSTART);
+ sys->write(allmotors, msg, len msg);
+}
+
+releaseall()
+{
+ msg := array of byte "F0F0F0";
+ sys->seek(allmotors, big 0, Sys->SEEKSTART);
+ sys->write(allmotors, msg, len msg);
+}
+
+forward(hand: ref Hand)
+{
+ sys->seek(hand.motor, big 0, Sys->SEEKSTART);
+ sys->write(hand.motor, hand.fwd, len hand.fwd);
+}
+
+reverse(hand: ref Hand)
+{
+ sys->seek(hand.motor, big 0, Sys->SEEKSTART);
+ sys->write(hand.motor, hand.rev, len hand.rev);
+}
+
+readsensor(fd: ref Sys->FD): int
+{
+ buf := array[4] of byte;
+ sys->seek(fd, big 0, Sys->SEEKSTART);
+ n := sys->read(fd, buf, len buf);
+ if (n <= 0)
+ return -1;
+ return int string buf[:n];
+}
+
+handwait(reply: chan of int, hand: ref Hand, clicks: int)
+{
+ blk := array of byte ("b" + string clicks);
+ log("handwait "+string blk);
+ sys->seek(hand.sensor, big 0, Sys->SEEKSTART);
+ if (sys->write(hand.sensor, blk, len blk) != len blk)
+ sys->print("handwait write error: %r\n");
+ reply <-= readsensor(hand.sensor);
+}
+
+lightwait(reply: chan of int, fd: ref Sys->FD, on: int)
+{
+ thresh := "";
+ if (on)
+ thresh = "l>" + string ONTHRESH;
+ else
+ thresh = "l<" + string OFFTHRESH;
+ blk := array of byte thresh;
+ log("lightwait "+string blk);
+ sys->seek(fd, big 0, Sys->SEEKSTART);
+ sys->write(fd, blk, len blk);
+ reply <-= readsensor(fd);
+}
+
+log(msg: string)
+{
+ if (verbose)
+ sys->print("%s\n", msg);
+}
diff --git a/appl/cmd/lego/firmdl.b b/appl/cmd/lego/firmdl.b
new file mode 100644
index 00000000..718282d0
--- /dev/null
+++ b/appl/cmd/lego/firmdl.b
@@ -0,0 +1,294 @@
+implement RcxFirmdl;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+include "rcxsend.m";
+
+RcxFirmdl : module {
+ init : fn (ctxt : ref Draw->Context, argv : list of string);
+};
+
+sys : Sys;
+bufio : Bufio;
+rcx : RcxSend;
+me : int;
+
+Iobuf : import bufio;
+
+Image : adt {
+ start : int;
+ offset : int;
+ length : int;
+ data : array of byte;
+};
+
+DL_HDR : con 5; # download packet hdr size
+DL_DATA : con 16rc8; # download packet payload size
+
+init(nil : ref Draw->Context, argv : list of string)
+{
+ sys = load Sys Sys->PATH;
+ me = sys->pctl(Sys->NEWPGRP, nil);
+
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ error(sys->sprint("cannot load bufio module: %r"));
+ rcx = load RcxSend RcxSend->PATH; #"rcxsend.dis";
+ if (rcx == nil)
+ error(sys->sprint("cannot load rcx module: %r"));
+
+ argv = tl argv;
+ if (len argv != 2)
+ error("usage: portnum file");
+
+ portnum := int hd argv;
+ file := hd tl argv;
+
+ img := getimage(file);
+ cksum := sum(img.data[0:img.length]);
+ sys->print("length %.4x start %.4x \n", img.length, img.start);
+
+ err := rcx->init(portnum, 1);
+ if (err != nil)
+ error(err);
+
+ # delete firmware
+ sys->print("delete firmware\n");
+ reply : array of byte;
+ rmfirm := array [] of {byte 16r65, byte 1, byte 3, byte 5, byte 7, byte 11};
+ reply = rcx->send(rmfirm, len rmfirm, 1);
+ if (reply == nil)
+ error("delete firmware failed");
+ chkreply(reply, array [] of {byte 16r92}, "delete firmware");
+
+ # start download
+ sys->print("start download\n");
+ dlstart := array [] of {byte 16r75,
+ byte (img.start & 16rff),
+ byte ((img.start>>8) & 16rff),
+ byte (cksum & 16rff),
+ byte ((cksum>>8) & 16rff),
+ byte 0,
+ };
+ reply = rcx->send(dlstart, len dlstart, 2);
+ chkreply(reply,array [] of {byte 16r82, byte 0}, "start download");
+
+ # send the image
+ data := array [DL_HDR+DL_DATA+1] of byte; # hdr + data + 1 byte cksum
+ seqnum := 1;
+ step := DL_DATA;
+ for (i := 0; i < img.length; i += step) {
+ data[0] = byte 16r45;
+ if (seqnum & 1)
+ # alternate ops have bit 4 set
+ data[0] |= byte 16r08;
+ if (i + step > img.length) {
+ step = img.length - i;
+ seqnum = 0;
+ }
+ sys->print(".");
+ data[1] = byte (seqnum & 16rff);
+ data[2] = byte ((seqnum >> 8) & 16rff);
+ data[3] = byte (step & 16rff);
+ data[4] = byte ((step >> 8) & 16rff);
+ data[5:] = img.data[i:i+step];
+ data[5+step] = byte (sum(img.data[i:i+step]) & 16rff);
+ reply = rcx->send(data, DL_HDR+step+1, 2);
+ chkreply(reply, array [] of {byte 16rb2, byte 0}, "tx data");
+ seqnum++;
+ }
+
+ # unlock firmware
+ sys->print("\nunlock firmware\n");
+ ulfirm := array [] of {byte 16ra5, byte 'L', byte 'E', byte 'G', byte 'O', byte 174};
+ reply = rcx->send(ulfirm, len ulfirm, 26);
+ chkreply(reply, array [] of {byte 16r52}, "unlock firmware");
+ sys->print("result: %s\n", string reply[1:]);
+
+ # all done, tidy up
+ killgrp(me);
+}
+
+chkreply(got, expect : array of byte, err : string)
+{
+ if (got == nil || len got < len expect)
+ error(err + ": short reply");
+ # RCX sometimes sets bit 3 of 'opcode' byte to prevent
+ # headers with same opcode having exactly same value - mask out
+ got[0] &= byte 16rf7;
+
+ for (i := 0; i < len expect; i++)
+ if (got[i] != expect[i]) {
+ hexdump(got);
+ error(sys->sprint("%s: reply mismatch at %d", err, i));
+ }
+}
+
+error(msg : string)
+{
+ sys->print("%s\n", msg);
+ killgrp(me);
+}
+
+killgrp(pid : int)
+{
+ pctl := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE);
+ if (pctl != nil) {
+ poison := array of byte "killgrp";
+ sys->write(pctl, poison, len poison);
+ }
+ exit;
+}
+
+sum(data : array of byte) : int
+{
+ t := 0;
+ for (i := 0; i < len data; i++)
+ t += int data[i];
+ return t;
+}
+
+hexdump(data : array of byte)
+{
+ for (i := 0; i < len data; i++)
+ sys->print("%.2x ", int data[i]);
+ sys->print("\n");
+}
+
+IMGSTART : con 16r8000;
+IMGLEN : con 16r4c00;
+getimage(path : string) : ref Image
+{
+ img := ref Image (IMGSTART, IMGSTART, 0, array [IMGLEN] of {* => byte 0});
+ iob := bufio->open(path, Sys->OREAD);
+ if (iob == nil)
+ error(sys->sprint("cannot open %s: %r", path));
+
+ lnum := 0;
+ while ((s := iob.gets('\n')) != nil) {
+ lnum++;
+ slen := len s;
+ # trim trailing space
+ while (slen > 0) {
+ ch := s[slen -1];
+ if (ch == ' ' || ch == '\r' || ch == '\n') {
+ slen--;
+ continue;
+ }
+ break;
+ }
+ # ignore blank lines
+ if (slen == 0)
+ continue;
+
+ if (slen < 10)
+ # STNNAAAACC
+ error("short S-record: line " + string lnum);
+
+ s = s[0:slen];
+ t := s[1];
+ if (s[0] != 'S' || t < '0' || t > '9')
+ error("bad S-record format: line " + string lnum);
+
+ data := hex2bytes(s[2:]);
+ if (data == nil)
+ error("bad chars in S-record: line " + string lnum);
+
+ count := int data[0];
+ cksum := int data[len data - 1];
+ if (count != len data -1)
+ error("S-record length mis-match: line " + string lnum);
+
+ if (sum(data[0:len data -1]) & 16rff != 16rff)
+ error("bad S-record checksum: line " + string lnum);
+
+ alen : int;
+ case t {
+ '0' =>
+ # addr[2] mname[10] ver rev desc[18] cksum
+ continue;
+ '1' =>
+ # 16-bit address, data
+ alen = 2;
+ '2' =>
+ # 24-bit address, data
+ alen = 3;
+ '3' =>
+ # 32-bit address, data
+ alen = 4;
+ '4' =>
+ # extension record
+ error("bad S-record type: line " + string lnum);
+ '5' =>
+ # data record count - ignore
+ continue;
+ '6' =>
+ # unused - ignore
+ continue;
+ '7' =>
+ img.start = wordval(data, 1, 4);
+ continue;
+ '8' =>
+ img.start = wordval(data, 1, 3);
+ continue;
+ '9' =>
+ img.start = wordval(data, 1, 2);
+ continue;
+ }
+ addr := wordval(data, 1, alen) - img.offset;
+ if (addr < 0 || addr > len img.data)
+ error("S-record address out of range: line " + string lnum);
+ img.data[addr:] = data[1+alen:1+count];
+ img.length = max(img.length, addr + count -(alen +1));
+ }
+ iob.close();
+ return img;
+}
+
+wordval(src : array of byte, s, l : int) : int
+{
+ r := 0;
+ for (i := 0; i < l; i++) {
+ r <<= 8;
+ r += int src[s+i];
+ }
+ return r;
+}
+
+max(a, b : int) : int
+{
+ if (a > b)
+ return a;
+ return b;
+}
+
+hex2bytes(s : string) : array of byte
+{
+ slen := len s;
+ if (slen & 1)
+ # should be even
+ return nil;
+ data := array [slen/2] of byte;
+ six := 0;
+ dix := 0;
+ while (six < slen) {
+ d1 := hexdigit(s[six++]);
+ d2 := hexdigit(s[six++]);
+ if (d1 == -1 || d2 == -1)
+ return nil;
+ data[dix++] = byte ((d1 << 4) + d2);
+ }
+ return data;
+}
+
+hexdigit(h : int) : int
+{
+ if (h >= '0' && h <= '9')
+ return h - '0';
+ if (h >= 'A' && h <= 'F')
+ return 10 + h - 'A';
+ if (h >= 'a' && h <= 'f')
+ return 10 + h - 'a';
+ return -1;
+}
diff --git a/appl/cmd/lego/link.b b/appl/cmd/lego/link.b
new file mode 100644
index 00000000..5c6b30d0
--- /dev/null
+++ b/appl/cmd/lego/link.b
@@ -0,0 +1,603 @@
+implement LegoLink;
+
+include "sys.m";
+include "draw.m";
+include "timers.m";
+include "rcxsend.m";
+
+LegoLink : module {
+ init : fn (ctxt : ref Draw->Context, argv : list of string);
+};
+
+POLLDONT : con 0;
+POLLNOW : con 16r02;
+POLLDO : con 16r04;
+
+sys : Sys;
+timers : Timers;
+Timer : import timers;
+datain : chan of array of byte;
+errormsg : string;
+
+init(nil : ref Draw->Context, argv : list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ argv = tl argv;
+ if (len argv != 1) {
+ sys->print("usage: lego/link portnum\n");
+ return;
+ }
+
+ timers = load Timers Timers->PATH; #"timers.dis";
+ if (timers == nil) {
+ sys->print("cannot load timers module: %r\n");
+ return;
+ }
+ portnum := int hd argv;
+ (rdfd, wrfd, err) := serialport(portnum);
+ if (err != nil) {
+ sys->print("%s\n", err);
+ return;
+ }
+
+ # set up our mount file
+ if (sys->bind("#s", "/net", Sys->MBEFORE) == -1) {
+ sys->print("failed to bind srv device: %r\n");
+ return;
+ }
+ f2c := sys->file2chan("/net", "legolink");
+ if (f2c == nil) {
+ sys->print("cannot create legolink channel: %r\n");
+ return;
+ }
+
+ datain = chan of array of byte;
+ send := chan of array of byte;
+ recv := chan of array of byte;
+ timers->init(50);
+ spawn reader(rdfd, datain);
+ consume();
+ spawn protocol(wrfd, send, recv);
+ spawn srvlink(f2c, send, recv);
+}
+
+srvlink(f2c : ref Sys->FileIO, send, recv : chan of array of byte)
+{
+ me := sys->pctl(0, nil);
+ rdfid := -1;
+ wrfid := -1;
+ buffer := array [256] of byte;
+ bix := 0;
+
+ rdblk := chan of (int, int, int, Sys->Rread);
+ readreq := rdblk;
+ wrblk := chan of (int, array of byte, int, Sys->Rwrite);
+ writereq := f2c.write;
+ wrreply : Sys->Rwrite;
+ sendblk := chan of array of byte;
+ sendchan := sendblk;
+ senddata : array of byte;
+
+ for (;;) alt {
+ data := <- recv =>
+ # got some data from brick, nil for error
+ if (data == nil) {
+ # some sort of error
+ if (wrreply != nil) {
+ wrreply <- = (0, errormsg);
+ }
+ killgrp(me);
+ }
+ if (bix + len data > len buffer) {
+ newb := array [bix + len data + 256] of byte;
+ newb[0:] = buffer;
+ buffer = newb;
+ }
+ buffer[bix:] = data;
+ bix += len data;
+ readreq = f2c.read;
+
+ (offset, count, fid, rc) := <- readreq =>
+ if (rdfid == -1)
+ rdfid = fid;
+ if (fid != rdfid) {
+ if (rc != nil)
+ rc <- = (nil, "file in use");
+ continue;
+ }
+ if (rc == nil) {
+ rdfid = -1;
+ continue;
+ }
+ if (errormsg != nil) {
+ rc <- = (nil, errormsg);
+ killgrp(me);
+ }
+ # reply with what we've got
+ if (count > bix)
+ count = bix;
+ rdata := array [count] of byte;
+ rdata[0:] = buffer[0:count];
+ buffer[0:] = buffer[count:bix];
+ bix -= count;
+ if (bix == 0)
+ readreq = rdblk;
+ alt {
+ rc <- = (rdata, nil)=>
+ ;
+ * =>
+ ;
+ }
+
+ (offset, data, fid, wc) := <- writereq =>
+ if (wrfid == -1)
+ wrfid = fid;
+ if (fid != wrfid) {
+ if (wc != nil)
+ wc <- = (0, "file in use");
+ continue;
+ }
+ if (wc == nil) {
+ wrfid = -1;
+ continue;
+ }
+ if (errormsg != nil) {
+ wc <- = (0, errormsg);
+ killgrp(me);
+ }
+ senddata = data;
+ sendchan = send;
+ wrreply = wc;
+ writereq = wrblk;
+
+ sendchan <- = senddata =>
+ alt {
+ wrreply <- = (len senddata, nil) =>
+ ;
+ * =>
+ ;
+ }
+ wrreply = nil;
+ sendchan = sendblk;
+ writereq = f2c.write;
+ }
+}
+
+killgrp(pid : int)
+{
+ pctl := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE);
+ if (pctl != nil) {
+ poison := array of byte "killgrp";
+ sys->write(pctl, poison, len poison);
+ }
+ exit;
+}
+
+serialport(port : int) : (ref Sys->FD, ref Sys->FD, string)
+{
+ serport := "/dev/eia" + string port;
+ serctl := serport + "ctl";
+
+ rfd := sys->open(serport, Sys->OREAD);
+ if (rfd == nil)
+ return (nil, nil, sys->sprint("cannot read %s: %r", serport));
+ wfd := sys->open(serport, Sys->OWRITE);
+ if (wfd == nil)
+ return (nil, nil, sys->sprint("cannot write %s: %r", serport));
+ ctlfd := sys->open(serctl, Sys->OWRITE);
+ if (ctlfd == nil)
+ return (nil, nil, sys->sprint("cannot open %s: %r", serctl));
+
+ config := array [] of {
+ "b2400",
+ "l8",
+ "po",
+ "m0",
+ "s1",
+ "d1",
+ "r1",
+ };
+
+ for (i := 0; i < len config; i++) {
+ cmd := array of byte config[i];
+ if (sys->write(ctlfd, cmd, len cmd) <= 0)
+ return (nil, nil, sys->sprint("serial config (%s): %r", config[i]));
+ }
+ return (rfd, wfd, nil);
+}
+
+# reader and nbread as in rcxsend.b
+reader(fd : ref Sys->FD, out : chan of array of byte)
+{
+ # with buf size of 1 there is no need
+ # for overrun code in nbread()
+
+ buf := array [1] of byte;
+ for (;;) {
+ n := sys->read(fd, buf, len buf);
+ if (n <= 0)
+ break;
+ data := array [n] of byte;
+ data[0:] = buf[0:n];
+ out <- = data;
+ }
+ out <- = nil;
+}
+
+overrun : array of byte;
+
+nbread(ms, n : int) : array of byte
+{
+ ret := array[n] of byte;
+ tot := 0;
+ if (overrun != nil) {
+ if (n < len overrun) {
+ ret[0:] = overrun[0:n];
+ overrun = overrun[n:];
+ return ret;
+ }
+ ret[0:] = overrun;
+ tot += len overrun;
+ overrun = nil;
+ }
+ tmr := timers->new(ms, 0);
+loop:
+ while (tot < n) {
+ tmr.reset();
+ alt {
+ data := <- datain =>
+ if (data == nil)
+ break loop;
+ dlen := len data;
+ if (dlen > n - tot) {
+ dlen = n - tot;
+ overrun = data[dlen:];
+ }
+ ret[tot:] = data[0:dlen];
+ tot += dlen;
+ <- tmr.tick =>
+ # reply timeout;
+ break loop;
+ }
+ }
+ tmr.destroy();
+ if (tot == 0)
+ return nil;
+ return ret[0:tot];
+}
+
+consume()
+{
+ while (nbread(300, 1024) != nil)
+ ;
+}
+
+# fd: connection to remote client
+# send: from local to remote
+# recv: from remote to local
+protocol(fd : ref Sys->FD, send, recv : chan of array of byte)
+{
+ seqnum := 0;
+ towerdown := timers->new(1500, 0);
+ starttower := 1;
+ tmr := timers->new(250, 0);
+
+ for (;;) {
+ data : array of byte = nil;
+ # get data to send
+ alt {
+ data = <- send =>
+ ;
+ <- tmr.tick =>
+ data = nil;
+ <- towerdown.tick =>
+ starttower = 1;
+ continue;
+ }
+
+ poll := POLLNOW;
+ while (poll == POLLNOW) {
+ reply : array of byte;
+ (reply, poll, errormsg) = datasend(fd, seqnum++, data, starttower);
+ starttower = 0;
+ towerdown.reset();
+ if (errormsg != nil) {
+sys->print("protocol: send error: %s\n", errormsg);
+ tmr.destroy();
+ recv <- = nil;
+ return;
+ }
+ if (reply != nil) {
+ recv <- = reply;
+ }
+ if (poll == POLLNOW) {
+ # quick check to see if we have any more data
+ alt {
+ data = <- send =>
+ ;
+ * =>
+ data = nil;
+ }
+ }
+ }
+ if (poll == POLLDO)
+ tmr.reset();
+ else
+ tmr.cancel();
+ }
+}
+
+TX_HDR : con 3;
+DL_HDR : con 5; # 16r45 seqLSB seqMSB lenLSB lenMSB
+DL_CKSM : con 1;
+LN_HDR : con 1;
+LN_JUNK : con 2;
+LN_LEN : con 2;
+LN_RXLEN : con 2;
+LN_POLLMASK : con 16r06;
+LN_COMPMASK : con 16r08;
+
+
+# send a message (may be empty)
+# wait for the reply
+# returns (data, poll request, error)
+
+datasend(wrfd : ref Sys->FD, seqnum : int, data : array of byte, startup : int) : (array of byte, int, string)
+{
+if (startup) {
+ dummy := array [] of { byte 255, byte 0, byte 255, byte 0};
+ sys->write(wrfd, dummy, len dummy);
+ nbread(100, 100);
+}
+ seqnum = seqnum & 1;
+ docomp := 0;
+ if (data != nil) {
+ comp := rlencode(data);
+ if (len comp < len data) {
+ docomp = 1;
+ data = comp;
+ }
+ }
+
+ # construct the link-level data packet
+ # DL_HDR LN_HDR data cksum
+ # last byte of data is stored in cksum byte
+ llen := LN_HDR + len data;
+ blklen := LN_LEN + llen - 1; # llen includes cksum
+ ldata := array [DL_HDR + blklen + 1] of byte;
+
+ # DL_HDR
+ if (seqnum == 0)
+ ldata[0] = byte 16r45;
+ else
+ ldata[0] = byte 16r4d;
+ ldata[1] = byte 0; # blk number LSB
+ ldata[2] = byte 0; # blk number MSB
+ ldata[3] = byte (blklen & 16rff); # blk length LSB
+ ldata[4] = byte ((blklen >> 8) & 16rff); # blk length MSB
+
+ # LN_LEN
+ ldata[5] = byte (llen & 16rff);
+ ldata[6] = byte ((llen>>8) & 16rff);
+ # LN_HDR
+ lhval := byte 0;
+ if (seqnum == 1)
+ lhval |= byte 16r01;
+ if (docomp)
+ lhval |= byte 16r08;
+
+ ldata[7] = lhval;
+
+ # data (+cksum)
+ ldata[8:] = data;
+
+ # construct the rcx data packet
+ # TX_HDR (dn ~dn) cksum ~cksum
+ rcxlen := TX_HDR + 2*(len ldata + 1);
+ rcxdata := array [rcxlen] of byte;
+
+ rcxdata[0] = byte 16r55;
+ rcxdata[1] = byte 16rff;
+ rcxdata[2] = byte 16r00;
+ rcix := TX_HDR;
+ cksum := 0;
+ for (i := 0; i < len ldata; i++) {
+ b := ldata[i];
+ rcxdata[rcix++] = b;
+ rcxdata[rcix++] = ~b;
+ cksum += int b;
+ }
+ rcxdata[rcix++] = byte (cksum & 16rff);
+ rcxdata[rcix++] = byte (~cksum & 16rff);
+
+ # send it
+ err : string;
+ reply : array of byte;
+ for (try := 0; try < 8; try++) {
+ if (err != nil)
+ sys->print("Try %d (lasterr %s)\n", try, err);
+ err = "";
+ step := 8;
+ for (i = 0; err == nil && i < rcxlen; i += step) {
+ if (i + step > rcxlen)
+ step = rcxlen -i;
+ if (sys->write(wrfd, rcxdata[i:i+step], step) != step) {
+ return (nil, 0, "hangup");
+ }
+
+ # get the echo
+ reply = nbread(300, step);
+ if (reply == nil || len reply != step)
+ # short echo
+ err = "tower not responding";
+
+ # check the echo
+ for (ei := 0; err == nil && ei < step; ei++) {
+ if (reply[ei] != rcxdata[i+ei])
+ # echo mis-match
+ err = "serial comms error";
+ }
+ }
+ if (err != nil) {
+ consume();
+ continue;
+ }
+
+ # wait for a reply
+ replen := TX_HDR + LN_JUNK + 2*LN_RXLEN;
+ reply = nbread(300, replen);
+ if (reply == nil || len reply != replen) {
+ err = "brick not responding";
+ consume();
+ continue;
+ }
+ if (reply[0] != byte 16r55 || reply[1] != byte 16rff || reply[2] != byte 0
+ || reply[5] != ~reply[6] || reply[7] != ~reply[8]) {
+ err = "bad reply from brick";
+ consume();
+ continue;
+ }
+ # reply[3] and reply [4] are junk, ~junk
+ # put on front of msg by rcx rom
+ replen = int reply[5] + ((int reply[7]) << 8) + 1;
+ cksum = int reply[3] + int reply[5] + int reply[7];
+ reply = nbread(200, replen * 2);
+ if (reply == nil || len reply != replen * 2) {
+ err = "short reply from brick";
+ consume();
+ continue;
+ }
+ cksum += int reply[0];
+ for (i = 1; i < replen; i++) {
+ reply[i] = reply[2*i];
+ cksum += int reply[i];
+ }
+ cksum -= int reply[replen-1];
+ if (reply[replen-1] != byte (cksum & 16rff)) {
+ err = "bad checksum from brick";
+ consume();
+ continue;
+ }
+ if ((reply[0] & byte 1) != byte (seqnum & 1)) {
+ # seqnum error
+ # we have read everything, don't bother with consume()
+ err = "bad seqnum from brick";
+ continue;
+ }
+
+ # TADA! we have a valid message
+ mdata : array of byte;
+ lnhdr := int reply[0];
+ poll := lnhdr & LN_POLLMASK;
+ if (replen > 2) {
+ # more than just hdr and cksum
+ if (lnhdr & LN_COMPMASK) {
+ mdata = rldecode(reply[1:replen-1]);
+ if (mdata == nil) {
+ err = "bad brick msg compression";
+ continue;
+ }
+ } else {
+ mdata = array [replen - 2] of byte;
+ mdata[0:] = reply[1:replen-1];
+ }
+ }
+ return (mdata, poll, nil);
+ }
+ return (nil, 0, err);
+}
+
+
+rlencode(data : array of byte) : array of byte
+{
+ srcix := 0;
+ outix := 0;
+ out := array [64] of byte;
+ val := 0;
+ nextval := -1;
+ n0 := 0;
+
+ while (srcix < len data || nextval != -1) {
+ if (nextval != -1) {
+ val = nextval;
+ nextval = -1;
+ } else {
+ val = int data[srcix];
+ if (val == 16r88)
+ nextval = 0;
+ if (val == 0) {
+ n0++;
+ srcix++;
+ if (srcix < len data && n0 < 16rff + 2)
+ continue;
+ }
+ case n0 {
+ 0 =>
+ srcix++;
+ 1 =>
+ val = 0;
+ nextval = -1;
+ n0 = 0;
+ 2 =>
+ val = 0;
+ nextval = 0;
+ n0 = 0;
+ * =>
+ val = 16r88;
+ nextval = (n0-2);
+ n0 = 0;
+ }
+ }
+ if (outix >= len out) {
+ newout := array [2 * len out] of byte;
+ newout[0:] = out;
+ out = newout;
+ }
+ out[outix++] = byte val;
+ }
+ return out[0:outix];
+}
+
+rldecode(data : array of byte) : array of byte
+{
+ srcix := 0;
+ outix := 0;
+ out := array [64] of byte;
+
+ n0 := 0;
+ val := 0;
+ while (srcix < len data || n0 > 0) {
+ if (n0 > 0)
+ n0--;
+ else {
+ val = int data[srcix++];
+ if (val == 16r88) {
+ if (srcix >= len data)
+ # bad encoding
+ return nil;
+ n0 = int data[srcix++];
+ if (n0 > 0) {
+ n0 += 2;
+ val = 0;
+ continue;
+ }
+ }
+ }
+ if (outix >= len out) {
+ newout := array [2 * len out] of byte;
+ newout[0:] = out;
+ out = newout;
+ }
+ out[outix++] = byte val;
+ }
+ return out[0:outix];
+}
+
+hexdump(data : array of byte)
+{
+ for (i := 0; i < len data; i++)
+ sys->print("%.2x ", int data[i]);
+ sys->print("\n");
+}
diff --git a/appl/cmd/lego/mkfile b/appl/cmd/lego/mkfile
new file mode 100644
index 00000000..b0e3dddb
--- /dev/null
+++ b/appl/cmd/lego/mkfile
@@ -0,0 +1,23 @@
+<../../../mkconfig
+
+TARG=\
+ clock.dis\
+ clockface.dis\
+ firmdl.dis\
+ link.dis\
+ rcxsend.dis\
+ send.dis\
+ timers.dis\
+
+SYSMODULES=\
+ sys.m\
+ draw.m\
+ bufio.m\
+
+MODULES=\
+ rcxsend.m\
+ timers.m\
+
+DISBIN=$ROOT/dis/lego
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/lego/rcxsend.b b/appl/cmd/lego/rcxsend.b
new file mode 100644
index 00000000..402187e1
--- /dev/null
+++ b/appl/cmd/lego/rcxsend.b
@@ -0,0 +1,240 @@
+implement RcxSend;
+
+include "sys.m";
+include "timers.m";
+include "rcxsend.m";
+
+sys : Sys;
+timers : Timers;
+Timer : import timers;
+datain : chan of array of byte;
+debug : int;
+rpid : int;
+wrfd : ref Sys->FD;
+
+TX_HDR : con 3;
+TX_CKSM : con 2;
+
+init(portnum, dbg : int) : string
+{
+ debug = dbg;
+ sys = load Sys Sys->PATH;
+ timers = load Timers Timers->PATH; #"timers.dis";
+ if (timers == nil)
+ return sys->sprint("cannot load timer module: %r");
+
+ rdfd : ref Sys->FD;
+ err : string;
+ (rdfd, wrfd, err) = serialport(portnum);
+ if (err != nil)
+ return err;
+
+ timers->init(50);
+ pidc := chan of int;
+ datain = chan of array of byte;
+ spawn reader(pidc, rdfd, datain);
+ rpid = <- pidc;
+ consume();
+ return nil;
+}
+
+reader(pidc : chan of int, fd : ref Sys->FD, out : chan of array of byte)
+{
+ pidc <- = sys->pctl(0, nil);
+
+ # with buf size of 1 there is no need
+ # for overrun code in nbread()
+
+ buf := array [1] of byte;
+ for (;;) {
+ n := sys->read(fd, buf, len buf);
+ if (n <= 0)
+ break;
+ data := array [n] of byte;
+ data[0:] = buf[0:n];
+ out <- = data;
+ }
+ if (debug)
+ sys->print("Reader error\n");
+}
+
+send(data : array of byte, n, rlen: int) : array of byte
+{
+ # 16r55 16rff 16r00 (d[i] ~d[i])*n cksum ~cksum
+ obuf := array [TX_HDR + (2*n ) + TX_CKSM] of byte;
+ olen := 0;
+ obuf[olen++] = byte 16r55;
+ obuf[olen++] = byte 16rff;
+ obuf[olen++] = byte 16r00;
+ cksum := 0;
+ for (i := 0; i < n; i++) {
+ obuf[olen++] = data[i];
+ obuf[olen++] = ~data[i];
+ cksum += int data[i];
+ }
+ obuf[olen++] = byte (cksum & 16rff);
+ obuf[olen++] = byte (~cksum & 16rff);
+
+ needr := rlen;
+ if (rlen > 0)
+ needr = TX_HDR + (2 * rlen) + TX_CKSM;
+ for (try := 0; try < 5; try++) {
+ ok := 1;
+ err := "";
+ reply : array of byte;
+
+ step := 8;
+ for (i = 0; ok && i < olen; i += step) {
+ if (i + step > olen)
+ step = olen -i;
+ if (sys->write(wrfd, obuf[i:i+step], step) != step) {
+ if (debug)
+ sys->print("serial tx error: %r\n");
+ return nil;
+ }
+
+ # get the echo
+ reply = nbread(200, step);
+ if (reply == nil || len reply != step) {
+ err = "short echo";
+ ok = 0;
+ }
+
+ # check the echo
+ for (ei := 0; ok && ei < step; ei++) {
+ if (reply[ei] != obuf[i+ei]) {
+ err = "bad echo";
+ ok = 0;
+ }
+ }
+ }
+
+ # get the reply
+ if (ok) {
+ if (needr == 0)
+ return nil;
+ if (needr == -1) {
+ # just get what we can
+ needr = TX_HDR + TX_CKSM;
+ reply = nbread(300, 1024);
+ } else {
+ reply = nbread(200, needr);
+ }
+ if (len reply < needr) {
+ err = "short reply";
+ ok = 0;
+ }
+ }
+ # check the reply
+ if (ok && reply[0] == byte 16r55 && reply[1] == byte 16rff && reply[2] == byte 0) {
+ cksum := int reply[len reply -TX_CKSM];
+ val := reply[TX_HDR:len reply -TX_CKSM];
+ r := array [len val / 2] of byte;
+ sum := 0;
+ for (i = 0; i < len r; i++) {
+ r[i] = val[i*2];
+ sum += int r[i];
+ }
+ if (cksum == (sum & 16rff)) {
+ return r;
+ }
+ ok = 0;
+ err = "bad cksum";
+ } else if (ok) {
+ ok = 0;
+ err = "reply header error";
+ }
+ if (debug && ok == 0 && err != nil) {
+ sys->print("try %d %s: ", try, err);
+ hexdump(reply);
+ }
+ consume();
+ }
+ return nil;
+}
+
+overrun : array of byte;
+
+nbread(ms, n : int) : array of byte
+{
+ ret := array[n] of byte;
+ tot := 0;
+ if (overrun != nil) {
+ if (n < len overrun) {
+ ret[0:] = overrun[0:n];
+ overrun = overrun[n:];
+ return ret;
+ }
+ ret[0:] = overrun;
+ tot += len overrun;
+ overrun = nil;
+ }
+ tmr := timers->new(ms, 0);
+loop:
+ while (tot < n) {
+ tmr.reset();
+ alt {
+ data := <- datain =>
+ dlen := len data;
+ if (dlen > n - tot) {
+ dlen = n - tot;
+ overrun = data[dlen:];
+ }
+ ret[tot:] = data[0:dlen];
+ tot += dlen;
+ <- tmr.tick =>
+ # reply timeout;
+ break loop;
+ }
+ }
+ tmr.destroy();
+ if (tot == 0)
+ return nil;
+ return ret[0:tot];
+}
+
+consume()
+{
+ while (nbread(300, 1024) != nil)
+ ;
+}
+
+serialport(port : int) : (ref Sys->FD, ref Sys->FD, string)
+{
+ serport := "/dev/eia" + string port;
+ serctl := serport + "ctl";
+
+ rfd := sys->open(serport, Sys->OREAD);
+ if (rfd == nil)
+ return (nil, nil, sys->sprint("cannot read %s: %r", serport));
+ wfd := sys->open(serport, Sys->OWRITE);
+ if (wfd == nil)
+ return (nil, nil, sys->sprint("cannot write %s: %r", serport));
+ ctlfd := sys->open(serctl, Sys->OWRITE);
+ if (ctlfd == nil)
+ return (nil, nil, sys->sprint("cannot open %s: %r", serctl));
+
+ config := array [] of {
+ "b2400",
+ "l8",
+ "po",
+ "m0",
+ "s1",
+ "d1",
+ "r1",
+ };
+
+ for (i := 0; i < len config; i++) {
+ cmd := array of byte config[i];
+ if (sys->write(ctlfd, cmd, len cmd) <= 0)
+ return (nil, nil, sys->sprint("serial config (%s): %r", config[i]));
+ }
+ return (rfd, wfd, nil);
+}
+hexdump(data : array of byte)
+{
+ for (i := 0; i < len data; i++)
+ sys->print("%.2x ", int data[i]);
+ sys->print("\n");
+}
+
diff --git a/appl/cmd/lego/rcxsend.m b/appl/cmd/lego/rcxsend.m
new file mode 100644
index 00000000..f62087db
--- /dev/null
+++ b/appl/cmd/lego/rcxsend.m
@@ -0,0 +1,6 @@
+RcxSend : module {
+ PATH: con "/dis/lego/rcxsend.dis";
+
+ init: fn (pnum, dbg : int) : string;
+ send : fn (data : array of byte, slen, rlen : int) : array of byte;
+}; \ No newline at end of file
diff --git a/appl/cmd/lego/send.b b/appl/cmd/lego/send.b
new file mode 100644
index 00000000..e83861c3
--- /dev/null
+++ b/appl/cmd/lego/send.b
@@ -0,0 +1,86 @@
+implement Send;
+
+include "sys.m";
+include "draw.m";
+include "rcxsend.m";
+
+Send : module {
+ init : fn (ctxt : ref Draw->Context, argv : list of string);
+};
+
+sys : Sys;
+rcx : RcxSend;
+me : int;
+
+init(nil : ref Draw->Context, argv : list of string)
+{
+ sys = load Sys Sys->PATH;
+ me = sys->pctl(Sys->NEWPGRP, nil);
+
+ rcx = load RcxSend "rcxsend.dis";
+ if (rcx == nil)
+ error(sys->sprint("cannot load rcx module: %r"));
+
+ argv = tl argv;
+ if (len argv < 2)
+ error("usage: send portnum XX...");
+
+ portnum := int hd argv;
+ argv = tl argv;
+
+ cmd := array [len argv] of byte;
+ for (i := 0; i < len cmd; i++) {
+ arg := hd argv;
+ argv = tl argv;
+ if (arg == nil || len arg > 2)
+ error(sys->sprint("bad arg %s\n", arg));
+ d1, d2 : int = 0;
+ d2 = hexdigit(arg[0]);
+ if (len arg == 2) {
+ d1 = d2;
+ d2 = hexdigit(arg[1]);
+ }
+ if (d1 == -1 || d2 == -1)
+ error(sys->sprint("bad arg %s\n", arg));
+ cmd[i] = byte ((d1 << 4) + d2);
+ }
+
+ rcx->init(portnum, 1);
+ reply := rcx->send(cmd, len cmd, -1);
+ hexdump(reply);
+ killgrp(me);
+}
+
+hexdigit(h : int) : int
+{
+ if (h >= '0' && h <= '9')
+ return h - '0';
+ if (h >= 'A' && h <= 'F')
+ return 10 + h - 'A';
+ if (h >= 'a' && h <= 'f')
+ return 10 + h - 'a';
+ return -1;
+}
+
+error(msg : string)
+{
+ sys->print("%s\n", msg);
+ killgrp(me);
+}
+
+killgrp(pid : int)
+{
+ pctl := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE);
+ if (pctl != nil) {
+ poison := array of byte "killgrp";
+ sys->write(pctl, poison, len poison);
+ }
+ exit;
+}
+
+hexdump(data : array of byte)
+{
+ for (i := 0; i < len data; i++)
+ sys->print("%.2x ", int data[i]);
+ sys->print("\n");
+}
diff --git a/appl/cmd/lego/timers.b b/appl/cmd/lego/timers.b
new file mode 100644
index 00000000..67e08dec
--- /dev/null
+++ b/appl/cmd/lego/timers.b
@@ -0,0 +1,263 @@
+# Chris Locke. June 2000
+
+# TODO: for auto-repeat timers don't set up a new sender
+# if there is already a pending sender for that timer.
+
+implement Timers;
+
+include "sys.m";
+include "timers.m";
+
+RealTimer : adt {
+ t : ref Timer;
+ nticks : int;
+ rep : int;
+ nexttick: big;
+ tick : chan of int;
+ sender : int;
+};
+
+Sender : adt {
+ tid : int;
+ idle : int; # set by sender() when done, reset by main when about to assign work
+ ctl : chan of chan of int;
+};
+
+sys : Sys;
+acquire : chan of int;
+timers := array [4] of ref RealTimer;
+senders := array [4] of ref Sender;
+curtick := big 0;
+tickres : int;
+
+init(res : int)
+{
+ sys = load Sys Sys->PATH;
+ acquire = chan of int;
+ tickres = res;
+ spawn main();
+}
+
+new(ms, rep : int) : ref Timer
+{
+ acquire <- = 1;
+ t := do_new(ms, rep);
+ <- acquire;
+ return t;
+}
+
+Timer.destroy(t : self ref Timer)
+{
+ acquire <- = 1;
+ do_destroy(t);
+ <- acquire;
+}
+
+Timer.reset(t : self ref Timer)
+{
+ acquire <- = 1;
+ do_reset(t);
+ <- acquire;
+}
+
+Timer.cancel(t : self ref Timer)
+{
+ acquire <- = 1;
+ do_cancel(t);
+ <- acquire;
+}
+
+# only call under lock
+#
+realtimer(t : ref Timer) : ref RealTimer
+{
+ if (t.id < 0 || t.id >= len timers)
+ return nil;
+ if (timers[t.id] == nil)
+ return nil;
+ if (timers[t.id].t != t)
+ return nil;
+ return timers[t.id];
+}
+
+
+# called under lock
+#
+do_destroy(t : ref Timer)
+{
+ rt := realtimer(t);
+ if (rt == nil)
+ return;
+ clearsender(rt, t.id);
+ timers[t.id] = nil;
+}
+
+# called under lock
+#
+do_reset(t : ref Timer)
+{
+ rt := realtimer(t);
+ if (rt == nil)
+ return;
+ clearsender(rt, t.id);
+ rt.nexttick = curtick + big (rt.nticks);
+ startclk = 1;
+}
+
+# called under lock
+#
+do_cancel(t : ref Timer)
+{
+ rt := realtimer(t);
+ if (rt == nil)
+ return;
+ clearsender(rt, t.id);
+ rt.nexttick = big 0;
+}
+
+# only call under lock
+#
+clearsender(rt : ref RealTimer, tid : int)
+{
+ # check to see if there is a sender trying to deliver tick
+ if (rt.sender != -1) {
+ sender := senders[rt.sender];
+ rt.sender = -1;
+ if (sender.tid == tid && !sender.idle) {
+ # receive the tick to clear the busy state
+ alt {
+ <- rt.tick =>
+ ;
+ * =>
+ ;
+ }
+ }
+ }
+}
+
+# called under lock
+do_new(ms, rep : int) : ref Timer
+{
+ # find free slot
+ for (i := 0; i < len timers; i++)
+ if (timers[i] == nil)
+ break;
+ if (i == len timers) {
+ # grow the array
+ newtimers := array [len timers * 2] of ref RealTimer;
+ newtimers[0:] = timers;
+ timers = newtimers;
+ }
+ tick := chan of int;
+ t := ref Timer(i, tick);
+ nticks := ms / tickres;
+ if (nticks == 0)
+ nticks = 1;
+ rt := ref RealTimer(t, nticks, rep, big 0, tick, -1);
+ timers[i] = rt;
+ return t;
+}
+
+startclk : int;
+stopclk : int;
+
+main()
+{
+ clktick := chan of int;
+ clkctl := chan of int;
+ clkstopped := 1;
+ spawn ticker(tickres, clkctl, clktick);
+
+ for (;;) alt {
+ <- acquire =>
+ # Locking
+ acquire <- = 1;
+
+ if (clkstopped && startclk) {
+ clkstopped = 0;
+ startclk = 0;
+ clkctl <- = 1;
+ }
+
+ t := <- clktick =>
+ if (t == 0) {
+ stopclk = 0;
+ if (startclk) {
+ startclk = 0;
+ clkctl <- = 1;
+ } else {
+ clkstopped = 1;
+ continue;
+ }
+ }
+ curtick++;
+ npend := 0;
+ for (i := 0; i < len timers; i++) {
+ rt := timers[i];
+ if (rt == nil)
+ continue;
+ if (rt.nexttick == big 0)
+ continue;
+ if (rt.nexttick > curtick) {
+ npend++;
+ continue;
+ }
+ # Timeout - arrange to send the tick
+ if (rt.rep) {
+ rt.nexttick = curtick + big rt.nticks;
+ npend++;
+ } else
+ rt.nexttick = big 0;
+ si := getsender();
+ s := senders[si];
+ s.tid = i;
+ s.idle = 0;
+ rt.sender = si;
+ s.ctl <- = rt.tick;
+
+ }
+ if (!npend)
+ stopclk = 1;
+ }
+}
+
+getsender() : int
+{
+ for (i := 0; i < len senders; i++) {
+ s := senders[i];
+ if (s == nil || s.idle == 1)
+ break;
+ }
+ if (i == len senders) {
+ newsenders := array [len senders * 2] of ref Sender;
+ newsenders[0:] = senders;
+ senders = newsenders;
+ }
+ if (senders[i] == nil) {
+ s := ref Sender (-1, 1, chan of chan of int);
+ spawn sender(s);
+ senders[i] = s;
+ }
+ return i;
+}
+
+sender(me : ref Sender)
+{
+ for (;;) {
+ tickch := <- me.ctl;
+ tickch <- = 1;
+ me.idle = 1;
+ }
+}
+
+ticker(ms : int, start, tick : chan of int)
+{
+ for (;;) {
+ <- start;
+ while (!stopclk) {
+ sys->sleep(ms);
+ tick <- = 1;
+ }
+ tick <- = 0;
+ }
+}
diff --git a/appl/cmd/lego/timers.m b/appl/cmd/lego/timers.m
new file mode 100644
index 00000000..5cc2b731
--- /dev/null
+++ b/appl/cmd/lego/timers.m
@@ -0,0 +1,17 @@
+Timers : module{
+ PATH: con "/dis/lego/timers.dis";
+
+ Timer : adt {
+ id : int;
+ tick : chan of int;
+
+ reset : fn (t : self ref Timer);
+ cancel : fn (t : self ref Timer);
+ destroy : fn (t : self ref Timer);
+ };
+
+ init : fn (res : int);
+ new : fn(ms, rep : int) : ref Timer;
+};
+
+
diff --git a/appl/cmd/limbo/arg.m b/appl/cmd/limbo/arg.m
new file mode 100644
index 00000000..212752c1
--- /dev/null
+++ b/appl/cmd/limbo/arg.m
@@ -0,0 +1,50 @@
+Arg: adt
+{
+ argv: list of string;
+ c: int;
+ opts: string;
+
+ init: fn(argv: list of string): ref Arg;
+ opt: fn(arg: self ref Arg): int;
+ arg: fn(arg: self ref Arg): string;
+};
+
+Arg.init(argv: list of string): ref Arg
+{
+ if(argv != nil)
+ argv = tl argv;
+ return ref Arg(argv, 0, nil);
+}
+
+Arg.opt(arg: self ref Arg): int
+{
+ if(arg.opts != ""){
+ arg.c = arg.opts[0];
+ arg.opts = arg.opts[1:];
+ return arg.c;
+ }
+ if(arg.argv == nil)
+ return arg.c = 0;
+ arg.opts = hd arg.argv;
+ if(len arg.opts < 2 || arg.opts[0] != '-')
+ return arg.c = 0;
+ arg.argv = tl arg.argv;
+ if(arg.opts == "--")
+ return arg.c = 0;
+ arg.c = arg.opts[1];
+ arg.opts = arg.opts[2:];
+ return arg.c;
+}
+
+Arg.arg(arg: self ref Arg): string
+{
+ s := arg.opts;
+ arg.opts = "";
+ if(s != "")
+ return s;
+ if(arg.argv == nil)
+ return "";
+ s = hd arg.argv;
+ arg.argv = tl arg.argv;
+ return s;
+}
diff --git a/appl/cmd/limbo/asm.b b/appl/cmd/limbo/asm.b
new file mode 100644
index 00000000..3788d016
--- /dev/null
+++ b/appl/cmd/limbo/asm.b
@@ -0,0 +1,263 @@
+asmentry(e: ref Decl)
+{
+ if(e == nil)
+ return;
+ bout.puts("\tentry\t"+string e.pc.pc+", "+string e.desc.id+"\n");
+}
+
+asmmod(m: ref Decl)
+{
+ bout.puts("\tmodule\t");
+ bout.puts(m.sym.name);
+ bout.putc('\n');
+ for(m = m.ty.tof.ids; m != nil; m = m.next){
+ case m.store{
+ Dglobal =>
+ bout.puts("\tlink\t-1,-1,0x"+hex(sign(m), 0)+",\".mp\"\n");
+ Dfn =>
+ bout.puts("\tlink\t"+string m.desc.id+","+string m.pc.pc+",0x"+string hex(sign(m), 0)+",\"");
+ if(m.dot.ty.kind == Tadt)
+ bout.puts(m.dot.sym.name+".");
+ bout.puts(m.sym.name+"\"\n");
+ }
+ }
+}
+
+asmpath()
+{
+ bout.puts("\tsource\t\"" + srcpath() + "\"\n");
+}
+
+asmdesc(d: ref Desc)
+{
+ for(; d != nil; d = d.next){
+ bout.puts("\tdesc\t$"+string d.id+","+string d.size+",\"");
+ e := d.nmap;
+ m := d.map;
+ for(i := 0; i < e; i++)
+ bout.puts(hex(int m[i], 2));
+ bout.puts("\"\n");
+ }
+}
+
+asmvar(size: int, d: ref Decl)
+{
+ bout.puts("\tvar\t@mp," + string size + "\n");
+
+ for(; d != nil; d = d.next)
+ if(d.store == Dglobal && d.init != nil)
+ asminitializer(d.offset, d.init);
+}
+
+asmldt(size: int, d: ref Decl)
+{
+ bout.puts("\tldts\t@ldt," + string size + "\n");
+
+ for(; d != nil; d = d.next)
+ if(d.store == Dglobal && d.init != nil)
+ asminitializer(d.offset, d.init);
+}
+
+asminitializer(offset: int, n: ref Node)
+{
+ wild: ref Node;
+ c: ref Case;
+ lab: Label;
+ id: ref Decl;
+ i, e: int;
+
+ case n.ty.kind{
+ Tbyte =>
+ bout.puts("\tbyte\t@mp+"+string offset+","+string(int n.c.val & 16rff)+"\n");
+ Tint or
+ Tfix =>
+ bout.puts("\tword\t@mp+"+string offset+","+string(int n.c.val)+"\n");
+ Tbig =>
+ bout.puts("\tlong\t@mp+"+string offset+","+string n.c.val+" # "+string bhex(n.c.val, 16)+"\n");
+ Tstring =>
+ asmstring(offset, n.decl.sym);
+ Treal =>
+ fs := "";
+ ba := array[8] of byte;
+ export_real(ba, array[] of {n.c.rval});
+ for(i = 0; i < 8; i++)
+ fs += hex(int ba[i], 2);
+ bout.puts("\treal\t@mp+"+string offset+","+string n.c.rval+" # "+fs+"\n");
+ Tadt or
+ Tadtpick or
+ Ttuple =>
+ id = n.ty.ids;
+ for(n = n.left; n != nil; n = n.right){
+ asminitializer(offset + id.offset, n.left);
+ id = id.next;
+ }
+ Tcase =>
+ c = n.ty.cse;
+ bout.puts("\tword\t@mp+"+string offset+","+string c.nlab);
+ for(i = 0; i < c.nlab; i++){
+ lab = c.labs[i];
+ bout.puts(","+string(int lab.start.c.val)+","+string(int lab.stop.c.val+1)+","+string(lab.inst.pc));
+ }
+ if(c.iwild != nil)
+ bout.puts(","+string c.iwild.pc+"\n");
+ else
+ bout.puts(",-1\n");
+ Tcasel =>
+ c = n.ty.cse;
+ bout.puts("\tword\t@mp+"+string offset+","+string c.nlab);
+ for(i = 0; i < c.nlab; i++){
+ lab = c.labs[i];
+ bout.puts(","+string(lab.start.c.val)+","+string(lab.stop.c.val+big 1)+","+string(lab.inst.pc));
+ }
+ if(c.iwild != nil)
+ bout.puts(","+string c.iwild.pc+"\n");
+ else
+ bout.puts(",-1\n");
+ Tcasec =>
+ c = n.ty.cse;
+ bout.puts("\tword\t@mp+"+string offset+","+string c.nlab+"\n");
+ offset += IBY2WD;
+ for(i = 0; i < c.nlab; i++){
+ lab = c.labs[i];
+ asmstring(offset, lab.start.decl.sym);
+ offset += IBY2WD;
+ if(lab.stop != lab.start)
+ asmstring(offset, lab.stop.decl.sym);
+ offset += IBY2WD;
+ bout.puts("\tword\t@mp+"+string offset+","+string lab.inst.pc+"\n");
+ offset += IBY2WD;
+ }
+ if(c.iwild != nil)
+ bout.puts("\tword\t@mp+"+string offset+","+string c.iwild.pc+"\n");
+ else
+ bout.puts("\tword\t@mp+"+string offset+",-1\n");
+ Tgoto =>
+ c = n.ty.cse;
+ bout.puts("\tword\t@mp+"+string offset);
+ bout.puts(","+string(n.ty.size/IBY2WD-1));
+ for(i = 0; i < c.nlab; i++)
+ bout.puts(","+string c.labs[i].inst.pc);
+ if(c.iwild != nil)
+ bout.puts(","+string c.iwild.pc);
+ bout.puts("\n");
+ Tany =>
+ break;
+ Tarray =>
+ bout.puts("\tarray\t@mp+"+string offset+",$"+string n.ty.tof.decl.desc.id+","+string int n.left.c.val+"\n");
+ if(n.right == nil)
+ break;
+ bout.puts("\tindir\t@mp+"+string offset+",0\n");
+ c = n.right.ty.cse;
+ wild = nil;
+ if(c.wild != nil)
+ wild = c.wild.right;
+ last := 0;
+ esz := n.ty.tof.size;
+ for(i = 0; i < c.nlab; i++){
+ e = int c.labs[i].start.c.val;
+ if(wild != nil){
+ for(; last < e; last++)
+ asminitializer(esz * last, wild);
+ }
+ last = e;
+ e = int c.labs[i].stop.c.val;
+ elem := c.labs[i].node.right;
+ for(; last <= e; last++)
+ asminitializer(esz * last, elem);
+ }
+ if(wild != nil)
+ for(e = int n.left.c.val; last < e; last++)
+ asminitializer(esz * last, wild);
+ bout.puts("\tapop\n");
+ Tiface =>
+ if(LDT)
+ bout.puts("\tword\t@ldt+"+string offset+","+string int n.c.val+"\n");
+ else
+ bout.puts("\tword\t@mp+"+string offset+","+string int n.c.val+"\n");
+ offset += IBY2WD;
+ for(id = n.decl.ty.ids; id != nil; id = id.next){
+ offset = align(offset, IBY2WD);
+ if(LDT)
+ bout.puts("\text\t@ldt+"+string offset+",0x"+string hex(sign(id), 0)+",\"");
+ else
+ bout.puts("\text\t@mp+"+string offset+",0x"+string hex(sign(id), 0)+",\"");
+ dotlen := 0;
+ idlen := len array of byte id.sym.name + 1;
+ if(id.dot.ty.kind == Tadt){
+ dotlen = len array of byte id.dot.sym.name + 1;
+ bout.puts(id.dot.sym.name+".");
+ }
+ bout.puts(id.sym.name+"\"\n");
+ offset += idlen + dotlen + IBY2WD;
+ }
+ * =>
+ fatal("can't asm global "+nodeconv(n));
+ }
+}
+
+asmexc(es: ref Except)
+{
+ e: ref Except;
+
+ n := 0;
+ for(e = es; e != nil; e = e.next)
+ n++;
+ bout.puts("\texceptions\t" + string n + "\n");
+ for(e = es; e != nil; e = e.next){
+ if(!int e.p1.reach && !int e.p2.reach)
+ continue;
+ c := e.c;
+ o := e.d.offset;
+ if(e.desc != nil)
+ id := e.desc.id;
+ else
+ id = -1;
+ bout.puts("\texception\t" + string getpc(e.p1) + ", " + string getpc(e.p2) + ", " + string o + ", " + string id + ", " + string c.nlab + ", " + string e.ne + "\n");
+ for(i := 0; i < c.nlab; i++){
+ lab := c.labs[i];
+ d := lab.start.decl;
+ if(lab.start.ty.kind == Texception)
+ d = d.init.decl;
+ bout.puts("\texctab\t\"" + d.sym.name + "\", " + string lab.inst.pc + "\n");
+ }
+ if(c.iwild == nil)
+ bout.puts("\texctab\t" + "*" + ", " + string -1 + "\n");
+ else
+ bout.puts("\texctab\t" + "*" + ", " + string c.iwild.pc + "\n");
+ }
+}
+
+asmstring(offset: int, sym: ref Sym)
+{
+ bout.puts("\tstring\t@mp+"+string offset+",\"");
+ s := sym.name;
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ if(c == '\n')
+ bout.puts("\\n");
+ else if(c == '\u0000')
+ bout.puts("\\z");
+ else if(c == '"')
+ bout.puts("\\\"");
+ else if(c == '\\')
+ bout.puts("\\\\");
+ else
+ bout.putc(c);
+ }
+ bout.puts("\"\n");
+}
+
+asminst(in: ref Inst)
+{
+ for(; in != nil; in = in.next){
+ if(in.op == INOOP)
+ continue;
+ if(in.pc % 10 == 0){
+ bout.putc('#');
+ bout.puts(string in.pc);
+ bout.putc('\n');
+ }
+ bout.puts(instconv(in));
+ bout.putc('\n');
+ }
+}
diff --git a/appl/cmd/limbo/com.b b/appl/cmd/limbo/com.b
new file mode 100644
index 00000000..bc977d0a
--- /dev/null
+++ b/appl/cmd/limbo/com.b
@@ -0,0 +1,1387 @@
+# back end
+
+breaks: array of ref Inst;
+conts: array of ref Inst;
+labels: array of ref Decl;
+bcscps: array of ref Node;
+labdep: int;
+nocont: ref Inst;
+nlabel: int;
+
+scp: int;
+scps:= array[MaxScope] of ref Node;
+
+curfn: ref Decl;
+
+pushscp(n : ref Node)
+{
+ if (scp >= MaxScope)
+ fatal("scope too deep");
+ scps[scp++] = n;
+}
+
+popscp()
+{
+ scp--;
+}
+
+curscp() : ref Node
+{
+ if (scp == 0)
+ return nil;
+ return scps[scp-1];
+}
+
+zeroscopes(stop : ref Node)
+{
+ i : int;
+ cs : ref Node;
+
+ for (i = scp-1; i >= 0; i--) {
+ cs = scps[i];
+ if (cs == stop)
+ break;
+ zcom(cs.left, nil);
+ }
+}
+
+zeroallscopes(n: ref Node, nn: array of ref Node)
+{
+ if(n == nil)
+ return;
+ for(; n != nil; n = n.right){
+ case(n.op){
+ Oscope =>
+ zeroallscopes(n.right, nn);
+ zcom(n.left, nn);
+ return;
+ Olabel or
+ Odo =>
+ zeroallscopes(n.right, nn);
+ return;
+ Oif or
+ Ofor =>
+ zeroallscopes(n.right.left, nn);
+ zeroallscopes(n.right.right, nn);
+ return;
+ Oalt or
+ Ocase or
+ Opick or
+ Oexcept =>
+ for(n = n.right; n != nil; n = n.right)
+ zeroallscopes(n.left.right, nn);
+ return;
+ Oseq =>
+ zeroallscopes(n.left, nn);
+ break;
+ Oexstmt =>
+ zeroallscopes(n.left, nn);
+ zeroallscopes(n.right, nn);
+ return;
+ * =>
+ return;
+ }
+ }
+}
+
+excs: ref Except;
+
+installexc(en: ref Node, p1: ref Inst, p2: ref Inst, zn: ref Node)
+{
+ e := ref Except;
+ e.p1 = p1;
+ e.p2 = p2;
+ e.c = en.ty.cse;
+ e.d = en.left.decl;
+ e.zn = zn;
+ e.next = excs;
+ excs = e;
+
+ ne := 0;
+ c := e.c;
+ for(i := 0; i < c.nlab; i++){
+ lab := c.labs[i];
+ if(lab.start.ty.kind == Texception)
+ ne++;
+ }
+ e.ne = ne;
+}
+
+inlist(d: ref Decl, dd: ref Decl): int
+{
+ for( ; dd != nil; dd = dd.next)
+ if(d == dd)
+ return 1;
+ return 0;
+}
+
+excdesc()
+{
+ dd, nd: ref Decl;
+
+ for(e := excs; e != nil; e = e.next){
+ if(e.zn != nil){
+ dd = nil;
+ maxo := 0;
+ for(n := e.zn ; n != nil; n = n.right){
+ d := n.decl;
+ d.locals = d.next;
+ if(!inlist(d, dd)){
+ d.next = dd;
+ dd = d;
+ o := d.offset+d.ty.size;
+ if(o > maxo)
+ maxo = o;
+ }
+ }
+ e.desc = gendesc(e.d, align(maxo, MaxAlign), dd);
+ for(d := dd; d != nil; d = nd){
+ nd = d.next;
+ d.next = d.locals;
+ d.locals = nil;
+ }
+ e.zn = nil;
+ }
+ }
+}
+
+reve(e: ref Except): ref Except
+{
+ l, n: ref Except;
+
+ l = nil;
+ for( ; e != nil; e = n){
+ n = e.next;
+ e.next = l;
+ l = e;
+ }
+ return l;
+}
+
+ckinline0(n: ref Node, d: ref Decl): int
+{
+ dd: ref Decl;
+
+ if(n == nil)
+ return 1;
+ if(n.op == Oname){
+ dd = n.decl;
+ if(d == dd)
+ return 0;
+ if(int dd.inline == 1)
+ return ckinline0(dd.init.right, d);
+ return 1;
+ }
+ return ckinline0(n.left, d) && ckinline0(n.right, d);
+}
+
+ckinline(d: ref Decl)
+{
+ d.inline = byte ckinline0(d.init.right, d);
+}
+
+modcom(entry: ref Decl)
+{
+ d, m: ref Decl;
+
+ if(errors)
+ return;
+
+ if(emitcode != "" || emitstub || emittab != "" || emitsbl != ""){
+ emit(curscope());
+ popscope();
+ return;
+ }
+
+ #
+ # scom introduces global variables for case statements
+ # and unaddressable constants, so it must be done before
+ # popping the global scope
+ #
+ gent = sys->millisec();
+ nlabel = 0;
+ maxstack = MaxTemp;
+ nocont = ref Inst;
+ genstart();
+
+ for(i := 0; i < nfns; i++)
+ if(int fns[i].inline == 1)
+ ckinline(fns[i]);
+
+ ok := 0;
+ for(i = 0; i < nfns; i++){
+ d = fns[i];
+ if(d.refs > 1 && !(int d.inline == 1 && local(d) && d.iface == nil)){
+ fns[ok++] = d;
+ fncom(d);
+ }
+ }
+ fns = fns[:ok];
+ nfns = ok;
+ if(blocks != -1)
+ fatal("blocks not nested correctly");
+ firstinst = firstinst.next;
+ if(errors)
+ return;
+
+ globals := popscope();
+ checkrefs(globals);
+ if(errors)
+ return;
+ globals = vars(globals);
+ moddataref();
+
+ nils := popscope();
+ m = nil;
+ for(d = nils; d != nil; d = d.next){
+ if(debug['n'])
+ print("nil '%s' ref %d\n", d.sym.name, d.refs);
+ if(d.refs && m == nil)
+ m = dupdecl(d);
+ d.offset = 0;
+ }
+ globals = appdecls(m, globals);
+ globals = namesort(globals);
+ globals = modglobals(impdecls.d, globals);
+ vcom(globals);
+ narrowmods();
+ ldts: ref Decl;
+ if(LDT)
+ (globals, ldts) = resolveldts(globals);
+ offset := idoffsets(globals, 0, IBY2WD);
+ if(LDT)
+ ldtoff := idindices(ldts); # idoffsets(ldts, 0, IBY2WD);
+ for(d = nils; d != nil; d = d.next){
+ if(debug['n'])
+ print("nil '%s' ref %d\n", d.sym.name, d.refs);
+ if(d.refs)
+ d.offset = m.offset;
+ }
+
+ if(debug['g']){
+ print("globals:\n");
+ printdecls(globals);
+ }
+
+ ndata := 0;
+ for(d = globals; d != nil; d = d.next)
+ ndata++;
+ ndesc := resolvedesc(impdecls.d, offset, globals);
+ ninst := resolvepcs(firstinst);
+ modresolve();
+ if(impdecls.next != nil)
+ for(dl := impdecls; dl != nil; dl = dl.next)
+ resolvemod(dl.d);
+ nlink := resolvemod(impdecl);
+ gent = sys->millisec() - gent;
+
+ maxstack *= 10;
+ if(fixss != 0)
+ maxstack = fixss;
+
+ if(debug['s'])
+ print("%d instructions\n%d data elements\n%d type descriptors\n%d functions exported\n%d stack size\n",
+ ninst, ndata, ndesc, nlink, maxstack);
+
+ excs = reve(excs);
+
+ writet = sys->millisec();
+ if(gendis){
+ discon(XMAGIC);
+ hints := 0;
+ if(mustcompile)
+ hints |= MUSTCOMPILE;
+ if(dontcompile)
+ hints |= DONTCOMPILE;
+ if(LDT)
+ hints |= HASLDT;
+ if(excs != nil)
+ hints |= HASEXCEPT;
+ discon(hints); # runtime hints
+ discon(maxstack); # minimum stack extent size
+ discon(ninst);
+ discon(offset);
+ discon(ndesc);
+ discon(nlink);
+ disentry(entry);
+ disinst(firstinst);
+ disdesc(descriptors);
+ disvar(offset, globals);
+ dismod(impdecl);
+ if(LDT)
+ disldt(ldtoff, ldts);
+ if(excs != nil)
+ disexc(excs);
+ dispath();
+ }else{
+ asminst(firstinst);
+ asmentry(entry);
+ asmdesc(descriptors);
+ asmvar(offset, globals);
+ asmmod(impdecl);
+ if(LDT)
+ asmldt(ldtoff, ldts);
+ if(excs != nil)
+ asmexc(excs);
+ asmpath();
+ }
+ writet = sys->millisec() - writet;
+
+ symt = sys->millisec();
+ if(bsym != nil){
+ sblmod(impdecl);
+
+ sblfiles();
+ sblinst(firstinst, ninst);
+ sblty(adts, nadts);
+ sblfn(fns, nfns);
+ sblvar(globals);
+ }
+ symt = sys->millisec() - symt;
+
+ firstinst = nil;
+ lastinst = nil;
+
+ excs = nil;
+}
+
+fncom(decl: ref Decl)
+{
+ curfn = decl;
+ if(ispoly(decl))
+ addfnptrs(decl, 1);
+
+ #
+ # pick up the function body and compile it
+ # this code tries to clean up the parse nodes as fast as possible
+ # function is Ofunc(name, body)
+ #
+ decl.pc = nextinst();
+ tinit();
+ labdep = 0;
+ scp = 0;
+ breaks = array[maxlabdep] of ref Inst;
+ conts = array[maxlabdep] of ref Inst;
+ labels = array[maxlabdep] of ref Decl;
+ bcscps = array[maxlabdep] of ref Node;
+
+ n := decl.init;
+ if(int decl.inline == 1)
+ decl.init = dupn(0, nosrc, n);
+ else
+ decl.init = n.left;
+ src := n.right.src;
+ src.start = src.stop - 1;
+ for(n = n.right; n != nil; n = n.right){
+ if(n.op != Oseq){
+ if(n.op == Ocall && trcom(n, nil, 1))
+ break;
+ scom(n);
+ break;
+ }
+ if(n.left.op == Ocall && trcom(n.left, n.right, 1)){
+ n = n.right;
+ if(n == nil || n.op != Oseq)
+ break;
+ }
+ else
+ scom(n.left);
+ }
+ pushblock();
+ in := genrawop(src, IRET, nil, nil, nil);
+ popblock();
+ reach(decl.pc);
+ if(in.reach != byte 0 && decl.ty.tof != tnone)
+ error(src.start, "no return at end of function " + dotconv(decl));
+ # decl.endpc = lastinst;
+ if(labdep != 0)
+ fatal("unbalanced label stack");
+ breaks = nil;
+ conts = nil;
+ labels = nil;
+ bcscps = nil;
+
+ loc := declsort(appdecls(vars(decl.locals), tdecls()));
+
+ decl.offset = idoffsets(loc, decl.offset, MaxAlign);
+ for(last := decl.ty.ids; last != nil && last.next != nil; last = last.next)
+ ;
+ if(last != nil)
+ last.next = loc;
+ else
+ decl.ty.ids = loc;
+
+ if(debug['f']){
+ print("fn: %s\n", decl.sym.name);
+ printdecls(decl.ty.ids);
+ }
+
+ decl.desc = gendesc(decl, decl.offset, decl.ty.ids);
+ decl.locals = loc;
+ excdesc();
+ if(decl.offset > maxstack)
+ maxstack = decl.offset;
+ if(optims)
+ optim(decl.pc, decl);
+ if(last != nil)
+ last.next = nil;
+ else
+ decl.ty.ids = nil;
+}
+
+#
+# statement compiler
+#
+scom(n: ref Node)
+{
+ b: int;
+ p, pp: ref Inst;
+ left: ref Node;
+
+ for(; n != nil; n = n.right){
+ case n.op{
+ Ocondecl or
+ Otypedecl or
+ Ovardecl or
+ Oimport or
+ Oexdecl =>
+ return;
+ Ovardecli =>
+ break;
+ Oscope =>
+ pushscp(n);
+ scom(n.right);
+ popscp();
+ zcom(n.left, nil);
+ return;
+ Olabel =>
+ scom(n.right);
+ return;
+ Oif =>
+ pushblock();
+ left = simplify(n.left);
+ if(left.op == Oconst && left.ty == tint){
+ if(left.c.val != big 0)
+ scom(n.right.left);
+ else
+ scom(n.right.right);
+ popblock();
+ return;
+ }
+ sumark(left);
+ pushblock();
+ p = bcom(left, 1, nil);
+ tfreenow();
+ popblock();
+ scom(n.right.left);
+ if(n.right.right != nil){
+ pp = p;
+ p = genrawop(lastinst.src, IJMP, nil, nil, nil);
+ patch(pp, nextinst());
+ scom(n.right.right);
+ }
+ patch(p, nextinst());
+ popblock();
+ return;
+ Ofor =>
+ n.left = left = simplify(n.left);
+ if(left.op == Oconst && left.ty == tint){
+ if(left.c.val == big 0)
+ return;
+ left.op = Onothing;
+ left.ty = tnone;
+ left.decl = nil;
+ }
+ pp = nextinst();
+ b = pushblock();
+ sumark(left);
+ p = bcom(left, 1, nil);
+ tfreenow();
+ popblock();
+
+ if(labdep >= maxlabdep)
+ fatal("label stack overflow");
+ breaks[labdep] = nil;
+ conts[labdep] = nil;
+ labels[labdep] = n.decl;
+ bcscps[labdep] = curscp();
+ labdep++;
+ scom(n.right.left);
+ labdep--;
+
+ patch(conts[labdep], nextinst());
+ if(n.right.right != nil){
+ pushblock();
+ scom(n.right.right);
+ popblock();
+ }
+ repushblock(lastinst.block); # was b
+ patch(genrawop(lastinst.src, IJMP, nil, nil, nil), pp); # for cprof: was left.src
+ popblock();
+ patch(p, nextinst());
+ patch(breaks[labdep], nextinst());
+ return;
+ Odo =>
+ pp = nextinst();
+
+ if(labdep >= maxlabdep)
+ fatal("label stack overflow");
+ breaks[labdep] = nil;
+ conts[labdep] = nil;
+ labels[labdep] = n.decl;
+ bcscps[labdep] = curscp();
+ labdep++;
+ scom(n.right);
+ labdep--;
+
+ patch(conts[labdep], nextinst());
+
+ left = simplify(n.left);
+ if(left.op == Onothing
+ || left.op == Oconst && left.ty == tint){
+ if(left.op == Onothing || left.c.val != big 0){
+ pushblock();
+ p = genrawop(left.src, IJMP, nil, nil, nil);
+ popblock();
+ }else
+ p = nil;
+ }else{
+ pushblock();
+ p = bcom(sumark(left), 0, nil);
+ tfreenow();
+ popblock();
+ }
+ patch(p, pp);
+ patch(breaks[labdep], nextinst());
+ return;
+ Ocase or
+ Opick or
+ Oalt or
+ Oexcept =>
+ pushblock();
+ if(labdep >= maxlabdep)
+ fatal("label stack overflow");
+ breaks[labdep] = nil;
+ conts[labdep] = nocont;
+ labels[labdep] = n.decl;
+ bcscps[labdep] = curscp();
+ labdep++;
+ case n.op{
+ Oalt =>
+ altcom(n);
+ Ocase or
+ Opick =>
+ casecom(n);
+ Oexcept =>
+ excom(n);
+ }
+ labdep--;
+ patch(breaks[labdep], nextinst());
+ popblock();
+ return;
+ Obreak =>
+ pushblock();
+ bccom(n, breaks);
+ popblock();
+ Ocont =>
+ pushblock();
+ bccom(n, conts);
+ popblock();
+ Oseq =>
+ if(n.left.op == Ocall && trcom(n.left, n.right, 0)){
+ n = n.right;
+ if(n == nil || n.op != Oseq)
+ return;
+ }
+ else
+ scom(n.left);
+ Oret =>
+ if(n.left != nil && n.left.op == Ocall && trcom(n.left, nil, 1))
+ return;
+ pushblock();
+ if(n.left != nil){
+ n.left = simplify(n.left);
+ sumark(n.left);
+ ecom(n.left.src, retalloc(ref Node, n.left), n.left);
+ tfreenow();
+ }
+ genrawop(n.src, IRET, nil, nil, nil);
+ popblock();
+ return;
+ Oexit =>
+ pushblock();
+ genrawop(n.src, IEXIT, nil, nil, nil);
+ popblock();
+ return;
+ Onothing =>
+ return;
+ Ofunc =>
+ fatal("Ofunc");
+ return;
+ Oexstmt =>
+ pushblock();
+ pp = genrawop(n.right.src, IEXC0, nil, nil, nil); # marker
+ p1 := nextinst();
+ scom(n.left);
+ p2 := nextinst();
+ p3 := genrawop(n.right.src, IJMP, nil, nil, nil);
+ p = genrawop(n.right.src, IEXC, nil, nil, nil); # marker
+ p.d.decl = mkdecl(n.src, 0, n.right.ty);
+ zn := array[1] of ref Node;
+ zeroallscopes(n.left, zn);
+ scom(n.right);
+ patch(p3, nextinst());
+ installexc(n.right, p1, p2, zn[0]);
+ patch(pp, p);
+ popblock();
+ return;
+ * =>
+ pushblock();
+ n = simplify(n);
+ sumark(n);
+ ecom(n.src, nil, n);
+ tfreenow();
+ popblock();
+ return;
+ }
+ }
+}
+
+#
+# compile a break, continue
+#
+bccom(n: ref Node, bs: array of ref Inst)
+{
+ s: ref Sym;
+
+ s = nil;
+ if(n.decl != nil)
+ s = n.decl.sym;
+ ok := -1;
+ for(i := 0; i < labdep; i++){
+ if(bs[i] == nocont)
+ continue;
+ if(s == nil || labels[i] != nil && labels[i].sym == s)
+ ok = i;
+ }
+ if(ok < 0)
+ fatal("didn't find break or continue");
+ zeroscopes(bcscps[ok]);
+ p := genrawop(n.src, IJMP, nil, nil, nil);
+ p.branch = bs[ok];
+ bs[ok] = p;
+}
+
+dogoto(c: ref Case): int
+{
+ i, j, k, n, r, q, v: int;
+ l, nl: array of Label;
+ src: Src;
+
+ l = c.labs;
+ n = c.nlab;
+ if(n == 0)
+ return 0;
+ r = int l[n-1].stop.c.val - int l[0].start.c.val+1;
+ if(r >= 3 && r <= 3*n){
+ if(r != n){
+ # remove ranges, fill in gaps
+ c.nlab = r;
+ nl = c.labs = array[r] of Label;
+ k = 0;
+ v = int l[0].start.c.val-1;
+ for(i = 0; i < n; i++){
+ # p = int l[i].start.c.val;
+ q = int l[i].stop.c.val;
+ src = l[i].start.src;
+ for(j = v+1; j <= q; j++){
+ nl[k] = l[i];
+ nl[k].start = nl[k].stop = mkconst(src, big j);
+ k++;
+ }
+ v = q;
+ }
+ if(k != r)
+ fatal("bad case expansion");
+ }
+ l = c.labs;
+ for(i = 0; i < r; i++)
+ l[i].inst = nil;
+ return 1;
+ }
+ return 0;
+}
+
+fillrange(c: ref Case, nn: ref Node, in: ref Inst)
+{
+ i, j, n, p, q: int;
+ l: array of Label;
+
+ l = c.labs;
+ n = c.nlab;
+ p = int nn.left.c.val;
+ q = int nn.right.c.val;
+ for(i = 0; i < n; i++)
+ if(int l[i].start.c.val == p)
+ break;
+ if(i == n)
+ fatal("fillrange fails");
+ for(j = p; j <= q; j++)
+ l[i++].inst = in;
+}
+
+casecom(cn: ref Node)
+{
+ d: ref Decl;
+ left, p, tmp, tmpc: ref Node;
+ jmps, wild, j1, j2: ref Inst;
+
+ c := cn.ty.cse;
+
+ needwild := cn.op != Opick || c.nlab != cn.left.right.ty.tof.decl.tag;
+ igoto := cn.left.ty == tint && dogoto(c);
+
+ #
+ # generate global which has case labels
+ #
+ if(igoto){
+ d = mkids(cn.src, enter(".g"+string nlabel++, 0), cn.ty, nil);
+ cn.ty.kind = Tgoto;
+ }
+ else
+ d = mkids(cn.src, enter(".c"+string nlabel++, 0), cn.ty, nil);
+ d.init = mkdeclname(cn.src, d);
+ nto := ref znode;
+ nto.addable = Rmreg;
+ nto.left = nil;
+ nto.right = nil;
+ nto.op = Oname;
+ nto.ty = d.ty;
+ nto.decl = d;
+
+ tmp = nil;
+ left = cn.left;
+ left = simplify(left);
+ cn.left = left;
+ sumark(left);
+ if(debug['c'])
+ print("case %s\n", nodeconv(left));
+ ctype := cn.left.ty;
+ if(left.addable >= Rcant){
+ if(cn.op == Opick){
+ ecom(left.src, nil, left);
+ tfreenow();
+ left = mkunary(Oind, dupn(1, left.src, left.left));
+ left.ty = tint;
+ sumark(left);
+ ctype = tint;
+ }else{
+ (left, tmp) = eacom(left, nil);
+ tfreenow();
+ }
+ }
+
+ labs := c.labs;
+ nlab := c.nlab;
+
+ if(igoto){
+ if(labs[0].start.c.val != big 0){
+ tmpc = talloc(left.ty, nil);
+ if(left.addable == Radr || left.addable == Rmadr){
+ genrawop(left.src, IMOVW, left, nil, tmpc);
+ left = tmpc;
+ }
+ genrawop(left.src, ISUBW, sumark(labs[0].start), left, tmpc);
+ left = tmpc;
+ }
+ if(needwild){
+ j1 = genrawop(left.src, IBLTW, left, sumark(mkconst(left.src, big 0)), nil);
+ j2 = genrawop(left.src, IBGTW, left, sumark(mkconst(left.src, labs[nlab-1].start.c.val-labs[0].start.c.val)), nil);
+ }
+ j := nextinst();
+ genrawop(left.src, IGOTO, left, nil, nto);
+ j.d.reg = IBY2WD;
+ }
+ else{
+ op := ICASE;
+ if(ctype == tbig)
+ op = ICASEL;
+ else if(ctype == tstring)
+ op = ICASEC;
+ genrawop(left.src, op, left, nil, nto);
+ }
+ tfree(tmp);
+ tfree(tmpc);
+
+ jmps = nil;
+ wild = nil;
+ for(n := cn.right; n != nil; n = n.right){
+ j := nextinst();
+ for(p = n.left.left; p != nil; p = p.right){
+ if(debug['c'])
+ print("case qualifier %s\n", nodeconv(p.left));
+ case p.left.op{
+ Oconst =>
+ labs[findlab(ctype, p.left, labs, nlab)].inst = j;
+ Orange =>
+ labs[findlab(ctype, p.left.left, labs, nlab)].inst = j;
+ if(igoto)
+ fillrange(c, p.left, j);
+ Owild =>
+ if(needwild)
+ wild = j;
+ # else
+ # nwarn(p.left, "default case redundant");
+ }
+ }
+
+ if(debug['c'])
+ print("case body for %s: %s\n", expconv(n.left.left), nodeconv(n.left.right));
+
+ k := nextinst();
+ scom(n.left.right);
+
+ src := lastinst.src;
+ # if(n.left.right == nil || n.left.right.op == Onothing)
+ if(k == nextinst())
+ src = n.left.left.src;
+ j = genrawop(src, IJMP, nil, nil, nil);
+ j.branch = jmps;
+ jmps = j;
+ }
+ patch(jmps, nextinst());
+ if(wild == nil && needwild)
+ wild = nextinst();
+
+ if(igoto){
+ if(needwild){
+ patch(j1, wild);
+ patch(j2, wild);
+ }
+ for(i := 0; i < nlab; i++)
+ if(labs[i].inst == nil)
+ labs[i].inst = wild;
+ }
+
+ c.iwild = wild;
+
+ d.ty.cse = c;
+ usetype(d.ty);
+ installids(Dglobal, d);
+}
+
+altcom(nalt: ref Node)
+{
+ p, op, left: ref Node;
+ jmps, wild, j: ref Inst = nil;
+
+ talt := nalt.ty;
+ c := talt.cse;
+ nlab := c.nlab;
+ nsnd := c.nsnd;
+ comm := array[nlab] of ref Node;
+ labs := array[nlab] of Label;
+ tmps := array[nlab] of ref Node;
+ c.labs = labs;
+
+ #
+ # built the type of the alt channel table
+ # note that we lie to the garbage collector
+ # if we know that another reference exists for the channel
+ #
+ is := 0;
+ ir := nsnd;
+ i := 0;
+ for(n := nalt.left; n != nil; n = n.right){
+ for(p = n.left.right.left; p != nil; p = p.right){
+ left = simplify(p.left);
+ p.left = left;
+ if(left.op == Owild)
+ continue;
+ comm[i] = hascomm(left);
+ left = comm[i].left;
+ sumark(left);
+ isptr := left.addable >= Rcant;
+ if(comm[i].op == Osnd)
+ labs[is++].isptr = isptr;
+ else
+ labs[ir++].isptr = isptr;
+ i++;
+ }
+ }
+
+ which := talloc(tint, nil);
+ tab := talloc(talt, nil);
+
+ #
+ # build the node for the address of each channel,
+ # the values to send, and the storage fro values received
+ #
+ off := ref znode;
+ adr := ref znode;
+ add := ref znode;
+ slot := ref znode;
+ off.op = Oconst;
+ off.c = ref Const(big 0, 0.0); # jrf - added initialization
+ off.ty = tint;
+ off.addable = Rconst;
+ adr.op = Oadr;
+ adr.left = tab;
+ adr.ty = tint;
+ add.op = Oadd;
+ add.left = adr;
+ add.right = off;
+ add.ty = tint;
+ slot.op = Oind;
+ slot.left = add;
+ sumark(slot);
+
+ #
+ # compile the sending and receiving channels and values
+ #
+ is = 2*IBY2WD;
+ ir = is + nsnd*2*IBY2WD;
+ i = 0;
+ for(n = nalt.left; n != nil; n = n.right){
+ for(p = n.left.right.left; p != nil; p = p.right){
+ if(p.left.op == Owild)
+ continue;
+
+ #
+ # gen channel
+ #
+ op = comm[i];
+ if(op.op == Osnd){
+ off.c.val = big is;
+ is += 2*IBY2WD;
+ }else{
+ off.c.val = big ir;
+ ir += 2*IBY2WD;
+ }
+ left = op.left;
+
+ #
+ # this sleaze is lying to the garbage collector
+ #
+ if(left.addable < Rcant)
+ genmove(left.src, Mas, tint, left, slot);
+ else{
+ slot.ty = left.ty;
+ ecom(left.src, slot, left);
+ tfreenow();
+ slot.ty = nil;
+ }
+
+ #
+ # gen value
+ #
+ off.c.val += big IBY2WD;
+ (p.left, tmps[i]) = rewritecomm(p.left, comm[i], slot);
+
+ i++;
+ }
+ }
+
+ #
+ # stuff the number of send & receive channels into the table
+ #
+ altsrc := nalt.src;
+ altsrc.stop = (altsrc.stop & ~PosMask) | ((altsrc.stop + 3) & PosMask);
+ off.c.val = big 0;
+ genmove(altsrc, Mas, tint, sumark(mkconst(altsrc, big nsnd)), slot);
+ off.c.val += big IBY2WD;
+ genmove(altsrc, Mas, tint, sumark(mkconst(altsrc, big(nlab-nsnd))), slot);
+ off.c.val += big IBY2WD;
+
+ altop := IALT;
+ if(c.wild != nil)
+ altop = INBALT;
+ pp := genrawop(altsrc, altop, tab, nil, which);
+ pp.m.offset = talt.size; # for optimizer
+
+ d := mkids(nalt.src, enter(".g"+string nlabel++, 0), mktype(nalt.src.start, nalt.src.stop, Tgoto, nil, nil), nil);
+ d.ty.cse = c;
+ d.init = mkdeclname(nalt.src, d);
+
+ nto := ref znode;
+ nto.addable = Rmreg;
+ nto.left = nil;
+ nto.right = nil;
+ nto.op = Oname;
+ nto.decl = d;
+ nto.ty = d.ty;
+
+ me := genrawop(altsrc, IGOTO, which, nil, nto);
+ me.d.reg = IBY2WD; # skip the number of cases field
+ tfree(tab);
+ tfree(which);
+
+ #
+ # compile the guard expressions and bodies
+ #
+ i = 0;
+ is = 0;
+ ir = nsnd;
+ jmps = nil;
+ wild = nil;
+ for(n = nalt.left; n != nil; n = n.right){
+ j = nil;
+ for(p = n.left.right.left; p != nil; p = p.right){
+ tj := nextinst();
+ if(p.left.op == Owild){
+ wild = nextinst();
+ }else{
+ if(comm[i].op == Osnd)
+ labs[is++].inst = tj;
+ else{
+ labs[ir++].inst = tj;
+ tacquire(tmps[i]);
+ }
+ sumark(p.left);
+ if(debug['a'])
+ print("alt guard %s\n", nodeconv(p.left));
+ ecom(p.left.src, nil, p.left);
+ tfree(tmps[i]);
+ tfreenow();
+ i++;
+ }
+ if(p.right != nil){
+ tj = genrawop(lastinst.src, IJMP, nil, nil, nil);
+ tj.branch = j;
+ j = tj;
+ }
+ }
+
+ patch(j, nextinst());
+ if(debug['a'])
+ print("alt body %s\n", nodeconv(n.left.right));
+ scom(n.left);
+
+ j = genrawop(lastinst.src, IJMP, nil, nil, nil);
+ j.branch = jmps;
+ jmps = j;
+ }
+ patch(jmps, nextinst());
+ comm = nil;
+
+ c.iwild = wild;
+
+ usetype(d.ty);
+ installids(Dglobal, d);
+}
+
+excom(en: ref Node)
+{
+ ed: ref Decl;
+ p: ref Node;
+ jmps, wild: ref Inst;
+
+ ed = en.left.decl;
+ ed.ty = rtexception;
+ c := en.ty.cse;
+ labs := c.labs;
+ nlab := c.nlab;
+ jmps = nil;
+ wild = nil;
+ for(n := en.right; n != nil; n = n.right){
+ qt: ref Type = nil;
+ j := nextinst();
+ for(p = n.left.left; p != nil; p = p.right){
+ case p.left.op{
+ Oconst =>
+ labs[findlab(texception, p.left, labs, nlab)].inst = j;
+ Owild =>
+ wild = j;
+ }
+ if(qt == nil)
+ qt = p.left.ty;
+ else if(!tequal(qt, p.left.ty))
+ qt = texception;
+ }
+ if(qt != nil)
+ ed.ty = qt;
+ k := nextinst();
+ scom(n.left.right);
+ src := lastinst.src;
+ if(k == nextinst())
+ src = n.left.left.src;
+ j = genrawop(src, IJMP, nil, nil, nil);
+ j.branch = jmps;
+ jmps = j;
+ }
+ ed.ty = rtexception;
+ patch(jmps, nextinst());
+ c.iwild = wild;
+}
+
+#
+# rewrite the communication operand
+# allocate any temps needed for holding value to send or receive
+#
+rewritecomm(n, comm, slot: ref Node): (ref Node, ref Node)
+{
+ adr, tmp: ref Node;
+
+ if(n == nil)
+ return (nil, nil);
+ adr = nil;
+ if(n == comm){
+ if(comm.op == Osnd && sumark(n.right).addable < Rcant)
+ adr = n.right;
+ else{
+ adr = tmp = talloc(n.ty, nil);
+ tmp.src = n.src;
+ if(comm.op == Osnd){
+ ecom(n.right.src, tmp, n.right);
+ tfreenow();
+ }
+ else
+ trelease(tmp);
+ }
+ }
+ if(n.right == comm && n.op == Oas && comm.op == Orcv
+ && sumark(n.left).addable < Rcant)
+ adr = n.left;
+ if(adr != nil){
+ p := genrawop(comm.left.src, ILEA, adr, nil, slot);
+ p.m.offset = adr.ty.size; # for optimizer
+ if(comm.op == Osnd)
+ p.m.reg = 1; # for optimizer
+ return (adr, tmp);
+ }
+ (n.left, tmp) = rewritecomm(n.left, comm, slot);
+ if(tmp == nil)
+ (n.right, tmp) = rewritecomm(n.right, comm, slot);
+ return (n, tmp);
+}
+
+#
+# merge together two sorted lists, yielding a sorted list
+#
+declmerge(e, f: ref Decl): ref Decl
+{
+ d := rock := ref Decl;
+ while(e != nil && f != nil){
+ fs := f.ty.size;
+ es := e.ty.size;
+ # v := 0;
+ v := (e.link == nil) - (f.link == nil);
+ if(v == 0 && (es <= IBY2WD || fs <= IBY2WD))
+ v = fs - es;
+ if(v == 0)
+ v = e.refs - f.refs;
+ if(v == 0)
+ v = fs - es;
+ if(v == 0 && e.sym.name > f.sym.name)
+ v = -1;
+ if(v >= 0){
+ d.next = e;
+ d = e;
+ e = e.next;
+ while(e != nil && e.nid == byte 0){
+ d = e;
+ e = e.next;
+ }
+ }else{
+ d.next = f;
+ d = f;
+ f = f.next;
+ while(f != nil && f.nid == byte 0){
+ d = f;
+ f = f.next;
+ }
+ }
+ # d = d.next;
+ }
+ if(e != nil)
+ d.next = e;
+ else
+ d.next = f;
+ return rock.next;
+}
+
+#
+# recursively split lists and remerge them after they are sorted
+#
+recdeclsort(d: ref Decl, n: int): ref Decl
+{
+ if(n <= 1)
+ return d;
+ m := n / 2 - 1;
+ dd := d;
+ for(i := 0; i < m; i++){
+ dd = dd.next;
+ while(dd.nid == byte 0)
+ dd = dd.next;
+ }
+ r := dd.next;
+ while(r.nid == byte 0){
+ dd = r;
+ r = r.next;
+ }
+ dd.next = nil;
+ return declmerge(recdeclsort(d, n / 2),
+ recdeclsort(r, (n + 1) / 2));
+}
+
+#
+# sort the ids by size and number of references
+#
+declsort(d: ref Decl): ref Decl
+{
+ n := 0;
+ for(dd := d; dd != nil; dd = dd.next)
+ if(dd.nid > byte 0)
+ n++;
+ return recdeclsort(d, n);
+}
+
+nilsrc : Src;
+
+zcom1(n : ref Node, nn: array of ref Node)
+{
+ ty : ref Type;
+ d : ref Decl;
+ e : ref Node;
+
+ ty = n.ty;
+ if (!tmustzero(ty))
+ return;
+ if (n.op == Oname && n.decl.refs == 0)
+ return;
+ if (nn != nil) {
+ if(n.op != Oname)
+ error(n.src.start, "fatal: bad op in zcom1 map");
+ n.right = nn[0];
+ nn[0] = n;
+ return;
+ }
+ if (ty.kind == Tadtpick)
+ ty = ty.tof;
+ if (ty.kind == Ttuple || ty.kind == Tadt) {
+ for (d = ty.ids; d != nil; d = d.next) {
+ if (tmustzero(d.ty)) {
+ dn := n;
+ if (d.next != nil)
+ dn = dupn(0, nilsrc, n);
+ e = mkbin(Odot, dn, mkname(nilsrc, d.sym));
+ e.right.decl = d;
+ e.ty = e.right.ty = d.ty;
+ zcom1(e, nn);
+ }
+ }
+ }
+ else {
+ src := n.src;
+ n.src = nilsrc;
+ e = mkbin(Oas, n, mknil(nilsrc));
+ e.ty = e.right.ty = ty;
+ if (debug['Z'])
+ print("ecom %s\n", nodeconv(e));
+ pushblock();
+ e = simplify(e);
+ sumark(e);
+ ecom(e.src, nil, e);
+ popblock();
+ n.src = src;
+ e = nil;
+ }
+}
+
+zcom0(id : ref Decl, nn: array of ref Node)
+{
+ e := mkname(nilsrc, id.sym);
+ e.decl = id;
+ e.ty = id.ty;
+ zcom1(e, nn);
+}
+
+zcom(n : ref Node, nn: array of ref Node)
+{
+ r : ref Node;
+
+ for ( ; n != nil; n = r) {
+ r = n.right;
+ n.right = nil;
+ case (n.op) {
+ Ovardecl =>
+ last := n.left.decl;
+ for (ids := n.decl; ids != last.next; ids = ids.next)
+ zcom0(ids, nn);
+ break;
+ Oname =>
+ if (n.decl != nildecl)
+ zcom1(dupn(0, nilsrc, n), nn);
+ break;
+ Otuple =>
+ for (nt := n.left; nt != nil; nt = nt.right)
+ zcom(nt.left, nn);
+ break;
+ * =>
+ fatal("bad node in zcom()");
+ break;
+ }
+ n.right = r;
+ }
+}
+
+ret(n: ref Node, nilret: int): int
+{
+ if(n == nil)
+ return nilret;
+ if(n.op == Oseq)
+ n = n.left;
+ return n.op == Oret && n.left == nil;
+}
+
+trcom(e: ref Node, ne: ref Node, nilret: int): int
+{
+ d, id: ref Decl;
+ as, a, f, n: ref Node;
+ p: ref Inst;
+
+return 0; # TBS
+ if(e.op != Ocall || e.left.op != Oname)
+ return 0;
+ d = e.left.decl;
+ if(d != curfn || int d.handler || ispoly(d))
+ return 0;
+ if(!ret(ne, nilret))
+ return 0;
+ pushblock();
+ id = d.ty.ids;
+ # evaluate args in same order as normal calls
+ for(as = e.right; as != nil; as = as.right){
+ a = as.left;
+ if(!(a.op == Oname && id == a.decl)){
+ if(occurs(id, as.right)){
+ f = talloc(id.ty, nil);
+ f.flags |= byte TEMP;
+ }
+ else
+ f = mkdeclname(as.src, id);
+ n = mkbin(Oas, f, a);
+ n.ty = id.ty;
+ scom(n);
+ if(int f.flags&TEMP)
+ as.left = f;
+ }
+ id = id.next;
+ }
+ id = d.ty.ids;
+ for(as = e.right; as != nil; as = as.right){
+ a = as.left;
+ if(int a.flags&TEMP){
+ f = mkdeclname(as.src, id);
+ n = mkbin(Oas, f, a);
+ n.ty = id.ty;
+ scom(n);
+ tfree(a);
+ }
+ id = id.next;
+ }
+ p = genrawop(e.src, IJMP, nil, nil, nil);
+ patch(p, d.pc);
+ popblock();
+ return 1;
+}
diff --git a/appl/cmd/limbo/decls.b b/appl/cmd/limbo/decls.b
new file mode 100644
index 00000000..53d9e822
--- /dev/null
+++ b/appl/cmd/limbo/decls.b
@@ -0,0 +1,1177 @@
+
+storename := array[Dend] of
+{
+ Dtype => "type",
+ Dfn => "function",
+ Dglobal => "global",
+ Darg => "argument",
+ Dlocal => "local",
+ Dconst => "con",
+ Dfield => "field",
+ Dtag => "pick tag",
+ Dimport => "import",
+ Dunbound => "unbound",
+ Dundef => "undefined",
+ Dwundef => "undefined",
+};
+
+storeart := array[Dend] of
+{
+ Dtype => "a ",
+ Dfn => "a ",
+ Dglobal => "a ",
+ Darg => "an ",
+ Dlocal => "a ",
+ Dconst => "a ",
+ Dfield => "a ",
+ Dtag => "a ",
+ Dimport => "an ",
+ Dunbound => "",
+ Dundef => "",
+ Dwundef => "",
+};
+
+storespace := array[Dend] of
+{
+ Dtype => 0,
+ Dfn => 0,
+ Dglobal => 1,
+ Darg => 1,
+ Dlocal => 1,
+ Dconst => 0,
+ Dfield => 1,
+ Dtag => 0,
+ Dimport => 0,
+ Dunbound => 0,
+ Dundef => 0,
+ Dwundef => 0,
+};
+
+impdecl: ref Decl;
+impdecls: ref Dlist;
+scopes := array[MaxScope] of ref Decl;
+tails := array[MaxScope] of ref Decl;
+scopekind := array[MaxScope] of byte;
+scopenode := array[MaxScope] of ref Node;
+iota: ref Decl;
+zdecl: Decl;
+
+popscopes()
+{
+ d: ref Decl;
+
+ #
+ # clear out any decls left in syms
+ #
+ while(scope >= ScopeBuiltin){
+ for(d = scopes[scope--]; d != nil; d = d.next){
+ if(d.sym != nil){
+ d.sym.decl = d.old;
+ d.old = nil;
+ }
+ }
+ }
+
+ for(id := impdecls; id != nil; id = id.next){
+ for(d = id.d.ty.ids; d != nil; d = d.next){
+ d.sym.decl = nil;
+ d.old = nil;
+ }
+ }
+ impdecls = nil;
+
+ scope = ScopeBuiltin;
+ scopes[ScopeBuiltin] = nil;
+ tails[ScopeBuiltin] = nil;
+}
+
+declstart()
+{
+ iota = mkids(nosrc, enter("iota", 0), tint, nil);
+ iota.init = mkconst(nosrc, big 0);
+
+ scope = ScopeNils;
+ scopes[ScopeNils] = nil;
+ tails[ScopeNils] = nil;
+
+ nildecl = mkdecl(nosrc, Dglobal, tany);
+ nildecl.sym = enter("nil", 0);
+ installids(Dglobal, nildecl);
+ d := mkdecl(nosrc, Dglobal, tstring);
+ d.sym = enterstring("");
+ installids(Dglobal, d);
+
+ scope = ScopeGlobal;
+ scopes[ScopeGlobal] = nil;
+ tails[ScopeGlobal] = nil;
+}
+
+redecl(d: ref Decl)
+{
+ old := d.sym.decl;
+ if(old.store == Dwundef)
+ return;
+ error(d.src.start, "redeclaration of "+declconv(d)+", previously declared as "+storeconv(old)+" on line "+
+ lineconv(old.src.start));
+}
+
+checkrefs(d: ref Decl)
+{
+ id, m: ref Decl;
+ refs: int;
+
+ for(; d != nil; d = d.next){
+ if(d.das != byte 0)
+ d.refs--;
+ case d.store{
+ Dtype =>
+ refs = d.refs;
+ if(d.ty.kind == Tadt){
+ for(id = d.ty.ids; id != nil; id = id.next){
+ d.refs += id.refs;
+ if(id.store != Dfn)
+ continue;
+ if(id.init == nil && id.link == nil && d.importid == nil)
+ error(d.src.start, "function "+d.sym.name+"."+id.sym.name+" not defined");
+ if(superwarn && !id.refs && d.importid == nil)
+ warn(d.src.start, "function "+d.sym.name+"."+id.sym.name+" not referenced");
+ }
+ }
+ if(d.ty.kind == Tmodule){
+ for(id = d.ty.ids; id != nil; id = id.next){
+ refs += id.refs;
+ if(id.iface != nil)
+ id.iface.refs += id.refs;
+ if(id.store == Dtype){
+ for(m = id.ty.ids; m != nil; m = m.next){
+ refs += m.refs;
+ if(m.iface != nil)
+ m.iface.refs += m.refs;
+ }
+ }
+ }
+ d.refs = refs;
+ }
+ if(superwarn && !refs && d.importid == nil)
+ warn(d.src.start, declconv(d)+" not referenced");
+ Dglobal =>
+ if(superwarn && !d.refs && d.sym != nil && d.sym.name[0] != '.')
+ warn(d.src.start, declconv(d)+" not referenced");
+ Dlocal or
+ Darg =>
+ if(!d.refs && d.sym != nil && d.sym.name != nil && d.sym.name[0] != '.')
+ warn(d.src.start, declconv(d)+" not referenced");
+ Dconst =>
+ if(superwarn && !d.refs && d.sym != nil)
+ warn(d.src.start, declconv(d)+" not referenced");
+ Dfn =>
+ if(d.init == nil && d.importid == nil)
+ error(d.src.start, declconv(d)+" not defined");
+ if(superwarn && !d.refs)
+ warn(d.src.start, declconv(d)+" not referenced");
+ Dimport =>
+ if(superwarn && !d.refs)
+ warn(d.src.start, declconv(d)+" not referenced");
+ }
+ if(d.das != byte 0)
+ d.refs++;
+ }
+}
+
+vardecl(ids: ref Decl, t: ref Type): ref Node
+{
+ n := mkn(Ovardecl, mkn(Oseq, nil, nil), nil);
+ n.decl = ids;
+ n.ty = t;
+ return n;
+}
+
+vardecled(n: ref Node)
+{
+ store := Dlocal;
+ if(scope == ScopeGlobal)
+ store = Dglobal;
+ if(n.ty.kind == Texception && n.ty.cons == byte 1){
+ store = Dconst;
+ fatal("Texception in vardecled");
+ }
+ ids := n.decl;
+ installids(store, ids);
+ t := n.ty;
+ for(last := ids; ids != nil; ids = ids.next){
+ ids.ty = t;
+ last = ids;
+ }
+ n.left.decl = last;
+}
+
+condecl(ids: ref Decl, init: ref Node): ref Node
+{
+ n := mkn(Ocondecl, mkn(Oseq, nil, nil), init);
+ n.decl = ids;
+ return n;
+}
+
+condecled(n: ref Node)
+{
+ ids := n.decl;
+ installids(Dconst, ids);
+ for(last := ids; ids != nil; ids = ids.next){
+ ids.ty = tunknown;
+ last = ids;
+ }
+ n.left.decl = last;
+}
+
+exdecl(ids: ref Decl, tids: ref Decl): ref Node
+{
+ n: ref Node;
+ t: ref Type;
+
+ t = mktype(ids.src.start, ids.src.stop, Texception, nil, tids);
+ t.cons = byte 1;
+ n = mkn(Oexdecl, mkn(Oseq, nil, nil), nil);
+ n.decl = ids;
+ n.ty = t;
+ return n;
+}
+
+exdecled(n: ref Node)
+{
+ ids, last: ref Decl;
+ t: ref Type;
+
+ ids = n.decl;
+ installids(Dconst, ids);
+ t = n.ty;
+ for(last = ids; ids != nil; ids = ids.next){
+ ids.ty = t;
+ last = ids;
+ }
+ n.left.decl = last;
+}
+
+importdecl(m: ref Node, ids: ref Decl): ref Node
+{
+ n := mkn(Oimport, mkn(Oseq, nil, nil), m);
+ n.decl = ids;
+ return n;
+}
+
+importdecled(n: ref Node)
+{
+ ids := n.decl;
+ installids(Dimport, ids);
+ for(last := ids; ids != nil; ids = ids.next){
+ ids.ty = tunknown;
+ last = ids;
+ }
+ n.left.decl = last;
+}
+
+mkscope(body: ref Node): ref Node
+{
+ n := mkn(Oscope, nil, body);
+ if(body != nil)
+ n.src = body.src;
+ return n;
+}
+
+fndecl(n: ref Node, t: ref Type, body: ref Node): ref Node
+{
+ n = mkbin(Ofunc, n, body);
+ n.ty = t;
+ return n;
+}
+
+fndecled(n: ref Node)
+{
+ left := n.left;
+ if(left.op == Oname){
+ d := left.decl.sym.decl;
+ if(d == nil || d.store == Dimport){
+ d = mkids(left.src, left.decl.sym, n.ty, nil);
+ installids(Dfn, d);
+ }
+ left.decl = d;
+ d.refs++;
+ }
+ if(left.op == Odot)
+ pushscope(nil, Sother);
+ if(n.ty.polys != nil){
+ pushscope(nil, Sother);
+ installids(Dtype, n.ty.polys);
+ }
+ pushscope(nil, Sother);
+ installids(Darg, n.ty.ids);
+ n.ty.ids = popscope();
+ if(n.ty.val != nil)
+ mergepolydecs(n.ty);
+ if(n.ty.polys != nil)
+ n.ty.polys = popscope();
+ if(left.op == Odot)
+ popscope();
+}
+
+#
+# check the function declaration only
+# the body will be type checked later by fncheck
+#
+fnchk(n: ref Node): ref Decl
+{
+ bad := 0;
+ d := n.left.decl;
+ if(n.left.op == Odot)
+ d = n.left.right.decl;
+ if(d == nil)
+ fatal("decl() fnchk nil");
+ n.left.decl = d;
+ if(d.store == Dglobal || d.store == Dfield)
+ d.store = Dfn;
+ if(d.store != Dfn || d.init != nil){
+ nerror(n, "redeclaration of function "+dotconv(d)+", previously declared as "
+ +storeconv(d)+" on line "+lineconv(d.src.start));
+ if(d.store == Dfn && d.init != nil)
+ bad = 1;
+ }
+ d.init = n;
+
+ t := n.ty;
+ inadt := d.dot;
+ if(inadt != nil && (inadt.store != Dtype || inadt.ty.kind != Tadt))
+ inadt = nil;
+ if(n.left.op == Odot){
+ pushscope(nil, Sother);
+ adtp := outerpolys(n.left);
+ if(adtp != nil)
+ installids(Dtype, adtp);
+ if(!polyequal(adtp, n.decl))
+ nerror(n, "adt polymorphic type mismatch");
+ n.decl = nil;
+ }
+ t = validtype(t, inadt);
+ if(n.left.op == Odot)
+ popscope();
+ if(debug['d'])
+ print("declare function %s ty %s newty %s\n", dotconv(d), typeconv(d.ty), typeconv(t));
+ t = usetype(t);
+
+ if(!polyequal(d.ty.polys, t.polys))
+ nerror(n, "function polymorphic type mismatch");
+ if(!tcompat(d.ty, t, 0))
+ nerror(n, "type mismatch: "+dotconv(d)+" defined as "
+ +typeconv(t)+" declared as "+typeconv(d.ty)+" on line "+lineconv(d.src.start));
+ else if(!raisescompat(d.ty.eraises, t.eraises))
+ nerror(n, "raises mismatch: " + dotconv(d));
+ if(t.varargs != byte 0)
+ nerror(n, "cannot define functions with a '*' argument, such as "+dotconv(d));
+
+ t.eraises = d.ty.eraises;
+
+ d.ty = t;
+ d.offset = idoffsets(t.ids, MaxTemp, IBY2WD);
+ d.src = n.src;
+
+ d.locals = nil;
+
+ n.ty = t;
+
+ if(bad)
+ return nil;
+ return d;
+}
+
+globalas(dst: ref Node, v: ref Node, valok: int): ref Node
+{
+ if(v == nil)
+ return nil;
+ if(v.op == Oas || v.op == Odas){
+ v = globalas(v.left, v.right, valok);
+ if(v == nil)
+ return nil;
+ }else if(valok && !initable(dst, v, 0))
+ return nil;
+ case dst.op{
+ Oname =>
+ if(dst.decl.init != nil)
+ nerror(dst, "duplicate assignment to "+expconv(dst)+", previously assigned on line "
+ +lineconv(dst.decl.init.src.start));
+ if(valok)
+ dst.decl.init = v;
+ return v;
+ Otuple =>
+ if(valok && v.op != Otuple)
+ fatal("can't deal with "+nodeconv(v)+" in tuple case of globalas");
+ tv := v.left;
+ for(dst = dst.left; dst != nil; dst = dst.right){
+ globalas(dst.left, tv.left, valok);
+ if(valok)
+ tv = tv.right;
+ }
+ return v;
+ }
+ fatal("can't deal with "+nodeconv(dst)+" in globalas");
+ return nil;
+}
+
+needsstore(d: ref Decl): int
+{
+ if(!d.refs)
+ return 0;
+ if(d.importid != nil)
+ return 0;
+ if(storespace[d.store])
+ return 1;
+ return 0;
+}
+
+#
+# return the list of all referenced storage variables
+#
+vars(d: ref Decl): ref Decl
+{
+ while(d != nil && !needsstore(d))
+ d = d.next;
+ for(v := d; v != nil; v = v.next){
+ while(v.next != nil){
+ n := v.next;
+ if(needsstore(n))
+ break;
+ v.next = n.next;
+ }
+ }
+ return d;
+}
+
+#
+# declare variables from the left side of a := statement
+#
+recdasdecl(n: ref Node, store: int, nid: int): (int, int)
+{
+ r: int;
+
+ case n.op{
+ Otuple =>
+ ok := 1;
+ for(n = n.left; n != nil; n = n.right){
+ (r, nid) = recdasdecl(n.left, store, nid);
+ ok &= r;
+ }
+ return (ok, nid);
+ Oname =>
+ if(n.decl == nildecl)
+ return (1, -1);
+ d := mkids(n.src, n.decl.sym, nil, nil);
+ installids(store, d);
+ n.decl = d;
+ old := d.old;
+ if(old != nil
+ && old.store != Dfn
+ && old.store != Dwundef
+ && old.store != Dundef)
+ warn(d.src.start, "redeclaration of "+declconv(d)+", previously declared as "
+ +storeconv(old)+" on line "+lineconv(old.src.start));
+ d.refs++;
+ d.das = byte 1;
+ if(nid >= 0)
+ nid++;
+ return (1, nid);
+ }
+ return (0, nid);
+}
+
+recmark(n: ref Node, nid: int): int
+{
+ case(n.op){
+ Otuple =>
+ for(n = n.left; n != nil; n = n.right)
+ nid = recmark(n.left, nid);
+ Oname =>
+ n.decl.nid = byte nid;
+ nid = 0;
+ }
+ return nid;
+}
+
+dasdecl(n: ref Node): int
+{
+ ok: int;
+
+ nid := 0;
+ store := Dlocal;
+ if(scope == ScopeGlobal)
+ store = Dglobal;
+
+ (ok, nid) = recdasdecl(n, store, nid);
+ if(!ok)
+ nerror(n, "illegal declaration expression "+expconv(n));
+ if(ok && store == Dlocal && nid > 1)
+ recmark(n, nid);
+ return ok;
+}
+
+#
+# declare global variables in nested := expressions
+#
+gdasdecl(n: ref Node)
+{
+ if(n == nil)
+ return;
+
+ if(n.op == Odas){
+ gdasdecl(n.right);
+ dasdecl(n.left);
+ }else{
+ gdasdecl(n.left);
+ gdasdecl(n.right);
+ }
+}
+
+undefed(src: Src, s: ref Sym): ref Decl
+{
+ d := mkids(src, s, tnone, nil);
+ error(src.start, s.name+" is not declared");
+ installids(Dwundef, d);
+ return d;
+}
+
+# inloop() : int
+# {
+# for (i := scope; i > 0; i--)
+# if (int scopekind[i] == Sloop)
+# return 1;
+# return 0;
+# }
+
+nested() : int
+{
+ for (i := scope; i > 0; i--)
+ if (int scopekind[i] == Sscope || int scopekind[i] == Sloop)
+ return 1;
+ return 0;
+}
+
+decltozero(n : ref Node)
+{
+ if ((scop := scopenode[scope]) != nil) {
+ if (n.right != nil && errors == 0)
+ fatal("Ovardecl/Oname/Otuple has right field\n");
+ n.right = scop.left;
+ scop.left = n;
+ }
+}
+
+pushscope(scp : ref Node, kind : int)
+{
+ if(scope >= MaxScope)
+ fatal("scope too deep");
+ scope++;
+ scopes[scope] = nil;
+ tails[scope] = nil;
+ scopenode[scope] = scp;
+ scopekind[scope] = byte kind;
+}
+
+curscope(): ref Decl
+{
+ return scopes[scope];
+}
+
+#
+# revert to old declarations for each symbol in the currect scope.
+# remove the effects of any imported adt types
+# whenever the adt is imported from a module,
+# we record in the type's decl the module to use
+# when calling members. the process is reversed here.
+#
+popscope(): ref Decl
+{
+ for(id := scopes[scope]; id != nil; id = id.next){
+ if(id.sym != nil){
+ id.sym.decl = id.old;
+ id.old = nil;
+ }
+ if(id.importid != nil)
+ id.importid.refs += id.refs;
+ t := id.ty;
+ if(id.store == Dtype
+ && t.decl != nil
+ && t.decl.timport == id)
+ t.decl.timport = id.timport;
+ if(id.store == Dlocal)
+ freeloc(id);
+ }
+ return scopes[scope--];
+}
+
+#
+# make a new scope,
+# preinstalled with some previously installed identifiers
+# don't add the identifiers to the scope chain,
+# so they remain separate from any newly installed ids
+#
+# these routines assume no ids are imports
+#
+repushids(ids: ref Decl)
+{
+ if(scope >= MaxScope)
+ fatal("scope too deep");
+ scope++;
+ scopes[scope] = nil;
+ tails[scope] = nil;
+ scopenode[scope] = nil;
+ scopekind[scope] = byte Sother;
+
+ for(; ids != nil; ids = ids.next){
+ if(ids.scope != scope
+ && (ids.dot == nil || !isimpmod(ids.dot.sym)
+ || ids.scope != ScopeGlobal || scope != ScopeGlobal + 1))
+ fatal("repushids scope mismatch");
+ s := ids.sym;
+ if(s != nil && ids.store != Dtag){
+ if(s.decl != nil && s.decl.scope >= scope)
+ ids.old = s.decl.old;
+ else
+ ids.old = s.decl;
+ s.decl = ids;
+ }
+ }
+}
+
+#
+# pop a scope which was started with repushids
+# return any newly installed ids
+#
+popids(ids: ref Decl): ref Decl
+{
+ for(; ids != nil; ids = ids.next){
+ if(ids.sym != nil && ids.store != Dtag){
+ ids.sym.decl = ids.old;
+ ids.old = nil;
+ }
+ }
+ return popscope();
+}
+
+installids(store: int, ids: ref Decl)
+{
+ last : ref Decl = nil;
+ for(d := ids; d != nil; d = d.next){
+ d.scope = scope;
+ if(d.store == Dundef)
+ d.store = store;
+ s := d.sym;
+ if(s != nil){
+ if(s.decl != nil && s.decl.scope >= scope){
+ redecl(d);
+ d.old = s.decl.old;
+ }else
+ d.old = s.decl;
+ s.decl = d;
+ }
+ last = d;
+ }
+ if(ids != nil){
+ d = tails[scope];
+ if(d == nil)
+ scopes[scope] = ids;
+ else
+ d.next = ids;
+ tails[scope] = last;
+ }
+}
+
+lookup(sym: ref Sym): ref Decl
+{
+ s: int;
+ d: ref Decl;
+
+ for(s = scope; s >= ScopeBuiltin; s--){
+ for(d = scopes[s]; d != nil; d = d.next){
+ if(d.sym == sym)
+ return d;
+ }
+ }
+ return nil;
+}
+
+mkids(src: Src, s: ref Sym, t: ref Type, next: ref Decl): ref Decl
+{
+ d := ref zdecl;
+ d.src = src;
+ d.store = Dundef;
+ d.ty = t;
+ d.next = next;
+ d.sym = s;
+ d.nid = byte 1;
+ return d;
+}
+
+mkdecl(src: Src, store: int, t: ref Type): ref Decl
+{
+ d := ref zdecl;
+ d.src = src;
+ d.store = store;
+ d.ty = t;
+ d.nid = byte 1;
+ return d;
+}
+
+dupdecl(old: ref Decl): ref Decl
+{
+ d := ref *old;
+ d.next = nil;
+ return d;
+}
+
+dupdecls(old: ref Decl): ref Decl
+{
+ d, nd, first, last: ref Decl;
+
+ first = last = nil;
+ for(d = old; d != nil; d = d.next){
+ nd = dupdecl(d);
+ if(first == nil)
+ first = nd;
+ else
+ last.next = nd;
+ last = nd;
+ }
+ return first;
+}
+
+appdecls(d: ref Decl, dd: ref Decl): ref Decl
+{
+ if(d == nil)
+ return dd;
+ for(t := d; t.next != nil; t = t.next)
+ ;
+ t.next = dd;
+ return d;
+}
+
+revids(id: ref Decl): ref Decl
+{
+ next : ref Decl;
+ d : ref Decl = nil;
+ for(; id != nil; id = next){
+ next = id.next;
+ id.next = d;
+ d = id;
+ }
+ return d;
+}
+
+idoffsets(id: ref Decl, offset: int, al: int): int
+{
+ algn := 1;
+ for(; id != nil; id = id.next){
+ if(storespace[id.store]){
+usedty(id.ty);
+ if(id.store == Dlocal && id.link != nil){
+ # id.nid always 1
+ id.offset = id.link.offset;
+ continue;
+ }
+ a := id.ty.align;
+ if(id.nid > byte 1){
+ for(d := id.next; d != nil && d.nid == byte 0; d = d.next)
+ if(d.ty.align > a)
+ a = d.ty.align;
+ algn = a;
+ }
+ offset = align(offset, a);
+ id.offset = offset;
+ offset += id.ty.size;
+ if(id.nid == byte 0 && (id.next == nil || id.next.nid != byte 0))
+ offset = align(offset, algn);
+ }
+ }
+ return align(offset, al);
+}
+
+idindices(id: ref Decl): int
+{
+ i := 0;
+ for(; id != nil; id = id.next){
+ if(storespace[id.store]){
+ usedty(id.ty);
+ id.offset = i++;
+ }
+ }
+ return i;
+}
+
+declconv(d: ref Decl): string
+{
+ if(d.sym == nil)
+ return storename[d.store] + " " + "<???>";
+ return storename[d.store] + " " + d.sym.name;
+}
+
+storeconv(d: ref Decl): string
+{
+ return storeart[d.store] + storename[d.store];
+}
+
+dotconv(d: ref Decl): string
+{
+ s: string;
+
+ if(d.dot != nil && !isimpmod(d.dot.sym)){
+ s = dotconv(d.dot);
+ if(d.dot.ty != nil && d.dot.ty.kind == Tmodule)
+ s += ".";
+ else
+ s += ".";
+ }
+ s += d.sym.name;
+ return s;
+}
+
+#
+# merge together two sorted lists, yielding a sorted list
+#
+namemerge(e, f: ref Decl): ref Decl
+{
+ d := rock := ref Decl;
+ while(e != nil && f != nil){
+ if(e.sym.name <= f.sym.name){
+ d.next = e;
+ e = e.next;
+ }else{
+ d.next = f;
+ f = f.next;
+ }
+ d = d.next;
+ }
+ if(e != nil)
+ d.next = e;
+ else
+ d.next = f;
+ return rock.next;
+}
+
+#
+# recursively split lists and remerge them after they are sorted
+#
+recnamesort(d: ref Decl, n: int): ref Decl
+{
+ if(n <= 1)
+ return d;
+ m := n / 2 - 1;
+ dd := d;
+ for(i := 0; i < m; i++)
+ dd = dd.next;
+ r := dd.next;
+ dd.next = nil;
+ return namemerge(recnamesort(d, n / 2),
+ recnamesort(r, (n + 1) / 2));
+}
+
+#
+# sort the ids by name
+#
+namesort(d: ref Decl): ref Decl
+{
+ n := 0;
+ for(dd := d; dd != nil; dd = dd.next)
+ n++;
+ return recnamesort(d, n);
+}
+
+printdecls(d: ref Decl)
+{
+ for(; d != nil; d = d.next)
+ print("%d: %s %s ref %d\n", d.offset, declconv(d), typeconv(d.ty), d.refs);
+}
+
+mergepolydecs(t: ref Type)
+{
+ n, nn: ref Node;
+ id, ids, ids1: ref Decl;
+
+ for(n = t.val; n != nil; n = n.right){
+ nn = n.left;
+ for(ids = nn.decl; ids != nil; ids = ids.next){
+ id = ids.sym.decl;
+ if(id == nil){
+ undefed(ids.src, ids.sym);
+ break;
+ }
+ if(id.store != Dtype){
+ error(ids.src.start, declconv(id) + " is not a type");
+ break;
+ }
+ if(id.ty.kind != Tpoly){
+ error(ids.src.start, declconv(id) + " is not a polymorphic type");
+ break;
+ }
+ if(id.ty.ids != nil)
+ error(ids.src.start, declconv(id) + " redefined");
+ pushscope(nil, Sother);
+ fielddecled(nn.left);
+ id.ty.ids = popscope();
+ for(ids1 = id.ty.ids; ids1 != nil; ids1 = ids1.next){
+ ids1.dot = id;
+ bindtypes(ids1.ty);
+ if(ids1.ty.kind != Tfn){
+ error(ids1.src.start, "only function types expected");
+ id.ty.ids = nil;
+ }
+ }
+ }
+ }
+ t.val = nil;
+}
+
+adjfnptrs(d: ref Decl, polys1: ref Decl, polys2: ref Decl)
+{
+ n: int;
+ id, idt, idf, arg: ref Decl;
+
+ n = 0;
+ for(id = d.ty.ids; id != nil; id = id.next)
+ n++;
+ for(idt = polys1; idt != nil; idt = idt.next)
+ for(idf = idt.ty.ids; idf != nil; idf = idf.next)
+ n -= 2;
+ for(idt = polys2; idt != nil; idt = idt.next)
+ for(idf = idt.ty.ids; idf != nil; idf = idf.next)
+ n -= 2;
+ for(arg = d.ty.ids; --n >= 0; arg = arg.next)
+ ;
+ for(idt = polys1; idt != nil; idt = idt.next){
+ for(idf = idt.ty.ids; idf != nil; idf = idf.next){
+ idf.link = arg;
+ arg = arg.next.next;
+ }
+ }
+ for(idt = polys2; idt != nil; idt = idt.next){
+ for(idf = idt.ty.ids; idf != nil; idf = idf.next){
+ idf.link = arg;
+ arg = arg.next.next;
+ }
+ }
+}
+
+addptrs(polys: ref Decl, fps: ref Decl, last: ref Decl, link: int, src: Src): (ref Decl, ref Decl)
+{
+ for(idt := polys; idt != nil; idt = idt.next){
+ for(idf := idt.ty.ids; idf != nil; idf = idf.next){
+ fp := mkdecl(src, Darg, tany);
+ fp.sym = idf.sym;
+ if(link)
+ idf.link = fp;
+ if(fps == nil)
+ fps = fp;
+ else
+ last.next = fp;
+ last = fp;
+ fp = mkdecl(src, Darg, tint);
+ fp.sym = idf.sym;
+ last.next = fp;
+ last = fp;
+ }
+ }
+ return (fps, last);
+}
+
+addfnptrs(d: ref Decl, link: int)
+{
+ fps, last, polys: ref Decl;
+
+ polys = encpolys(d);
+ if(int(d.ty.flags&FULLARGS)){
+ if(link)
+ adjfnptrs(d, d.ty.polys, polys);
+ return;
+ }
+ d.ty.flags |= FULLARGS;
+ fps = last = nil;
+ (fps, last) = addptrs(d.ty.polys, fps, last, link, d.src);
+ (fps, last) = addptrs(polys, fps, last, link, d.src);
+ for(last = d.ty.ids; last != nil && last.next != nil; last = last.next)
+ ;
+ if(last != nil)
+ last.next = fps;
+ else
+ d.ty.ids = fps;
+ d.offset = idoffsets(d.ty.ids, MaxTemp, IBY2WD);
+}
+
+rmfnptrs(d: ref Decl)
+{
+ n: int;
+ id, idt, idf: ref Decl;
+
+ if(int(d.ty.flags&FULLARGS))
+ d.ty.flags &= ~FULLARGS;
+ else
+ return;
+ n = 0;
+ for(id = d.ty.ids; id != nil; id = id.next)
+ n++;
+ for(idt = d.ty.polys; idt != nil; idt = idt.next)
+ for(idf = idt.ty.ids; idf != nil; idf = idf.next)
+ n -= 2;
+ for(idt = encpolys(d); idt != nil; idt = idt.next)
+ for(idf = idt.ty.ids; idf != nil; idf = idf.next)
+ n -= 2;
+ if(n == 0){
+ d.ty.ids = nil;
+ return;
+ }
+ for(id = d.ty.ids; --n > 0; id = id.next)
+ ;
+ id.next = nil;
+ d.offset = idoffsets(d.ty.ids, MaxTemp, IBY2WD);
+}
+
+local(d: ref Decl): int
+{
+ for(d = d.dot; d != nil; d = d.dot)
+ if(d.store == Dtype && d.ty.kind == Tmodule)
+ return 0;
+ return 1;
+}
+
+lmodule(d: ref Decl): ref Decl
+{
+ for(d = d.dot; d != nil; d = d.dot)
+ if(d.store == Dtype && d.ty.kind == Tmodule)
+ return d;
+ return nil;
+}
+
+outerpolys(n: ref Node): ref Decl
+{
+ d: ref Decl;
+
+ if(n.op == Odot){
+ d = n.right.decl;
+ if(d == nil)
+ fatal("decl() outeradt nil");
+ d = d.dot;
+ if(d != nil && d.store == Dtype && d.ty.kind == Tadt)
+ return d.ty.polys;
+ }
+ return nil;
+}
+
+encpolys(d: ref Decl): ref Decl
+{
+ if((d = d.dot) == nil)
+ return nil;
+ return d.ty.polys;
+}
+
+fnlookup(s: ref Sym, t: ref Type): (ref Decl, ref Node)
+{
+ id: ref Decl;
+ mod: ref Node;
+
+ id = nil;
+ mod = nil;
+ if(t.kind == Tpoly || t.kind == Tmodule)
+ id = namedot(t.ids, s);
+ else if(t.kind == Tref){
+ t = t.tof;
+ if(t.kind == Tadt){
+ id = namedot(t.ids, s);
+ if(t.decl != nil && t.decl.timport != nil)
+ mod = t.decl.timport.eimport;
+ }
+ else if(t.kind == Tadtpick){
+ id = namedot(t.ids, s);
+ if(t.decl != nil && t.decl.timport != nil)
+ mod = t.decl.timport.eimport;
+ t = t.decl.dot.ty;
+ if(id == nil)
+ id = namedot(t.ids, s);
+ if(t.decl != nil && t.decl.timport != nil)
+ mod = t.decl.timport.eimport;
+ }
+ }
+ if(id == nil){
+ id = lookup(s);
+ if(id != nil)
+ mod = id.eimport;
+ }
+ return (id, mod);
+}
+
+isimpmod(s: ref Sym): int
+{
+ d: ref Decl;
+
+ for(d = impmods; d != nil; d = d.next)
+ if(d.sym == s)
+ return 1;
+ return 0;
+}
+
+dequal(d1: ref Decl, d2: ref Decl, full: int): int
+{
+ return d1.sym == d2.sym &&
+ d1.store == d2.store &&
+ d1.implicit == d2.implicit &&
+ d1.cyc == d2.cyc &&
+ (!full || tequal(d1.ty, d2.ty)) &&
+ (!full || d1.store == Dfn || sametree(d1.init, d2.init));
+}
+
+tzero(t: ref Type): int
+{
+ return t.kind == Texception || tmustzero(t);
+}
+
+isptr(t: ref Type): int
+{
+ return t.kind == Texception || tattr[t.kind].isptr;
+}
+
+# can d share the same stack location as another local ?
+shareloc(d: ref Decl)
+{
+ z: int;
+ t, tt: ref Type;
+ dd, res: ref Decl;
+
+ if(d.store != Dlocal || d.nid != byte 1)
+ return;
+ t = d.ty;
+ res = nil;
+ for(dd = fndecls; dd != nil; dd = dd.next){
+ if(d == dd)
+ fatal("d==dd in shareloc");
+ if(dd.store != Dlocal || dd.nid != byte 1 || dd.link != nil || dd.tref != 0)
+ continue;
+ tt = dd.ty;
+ if(t.size != tt.size || t.align != tt.align)
+ continue;
+ z = tzero(t)+tzero(tt);
+ if(z > 0)
+ continue; # for now
+ if(t == tt || tequal(t, tt))
+ res = dd;
+ else{
+ if(z == 1)
+ continue;
+ if(z == 0 || isptr(t) || isptr(tt) || mktdesc(t) == mktdesc(tt))
+ res = dd;
+ }
+ if(res != nil){
+ d.link = res;
+ res.tref = 1;
+ return;
+ }
+ }
+ return;
+}
+
+freeloc(d: ref Decl)
+{
+ if(d.link != nil)
+ d.link.tref = 0;
+}
diff --git a/appl/cmd/limbo/dis.b b/appl/cmd/limbo/dis.b
new file mode 100644
index 00000000..7d79553e
--- /dev/null
+++ b/appl/cmd/limbo/dis.b
@@ -0,0 +1,560 @@
+
+NAMELEN: con 28;
+
+cache: array of byte;
+ncached: int;
+ndatum: int;
+startoff: int;
+lastoff: int;
+lastkind: int;
+
+discon(val: int)
+{
+ if(val >= -64 && val <= 63){
+ bout.putb(byte (val & ~16r80));
+ return;
+ }
+ if(val >= -8192 && val <= 8191){
+ bout.putb(byte ((val>>8) & ~16rC0 | 16r80));
+ bout.putb(byte val);
+ return;
+ }
+ if(val < 0 && ((val >> 29) & 7) != 7
+ || val > 0 && (val >> 29) != 0)
+ fatal("overflow in constant 16r"+hex(val, 0));
+ bout.putb(byte(val>>24 | 16rC0));
+ bout.putb(byte(val>>16));
+ bout.putb(byte(val>>8));
+ bout.putb(byte val);
+}
+
+disword(w: int)
+{
+ bout.putb(byte(w >> 24));
+ bout.putb(byte(w >> 16));
+ bout.putb(byte(w >> 8));
+ bout.putb(byte w);
+}
+
+disdata(kind, n: int)
+{
+ if(n < DMAX && n != 0)
+ bout.putb(byte((kind << DBYTE) | n));
+ else{
+ bout.putb(byte kind << DBYTE);
+ discon(n);
+ }
+}
+
+dismod(m: ref Decl)
+{
+ fileoff := bout.seek(big 0, 1);
+ name := array of byte m.sym.name;
+ n := len name;
+ if(n > NAMELEN-1)
+ n = NAMELEN-1;
+ bout.write(name, n);
+ bout.putb(byte 0);
+ for(m = m.ty.tof.ids; m != nil; m = m.next){
+ case m.store{
+ Dglobal =>
+ discon(-1);
+ discon(-1);
+ disword(sign(m));
+ bout.puts(".mp");
+ bout.putb(byte 0);
+ Dfn =>
+ discon(m.pc.pc);
+ discon(m.desc.id);
+ disword(sign(m));
+ if(m.dot.ty.kind == Tadt){
+ bout.puts(m.dot.sym.name);
+ bout.putb(byte '.');
+ }
+ bout.puts(m.sym.name);
+ bout.putb(byte 0);
+ * =>
+ fatal("unknown kind in dismod: "+declconv(m));
+ }
+ }
+ if(debug['s'])
+ print("%bd linkage bytes start %bd\n", bout.seek(big 0, 1) - fileoff, fileoff);
+}
+
+dispath()
+{
+ sp := array of byte srcpath();
+ bout.write(sp, len sp);
+ bout.putb(byte 0);
+}
+
+disentry(e: ref Decl)
+{
+ if(e == nil){
+ discon(-1);
+ discon(-1);
+ return;
+ }
+ discon(e.pc.pc);
+ discon(e.desc.id);
+}
+
+disdesc(d: ref Desc)
+{
+ fileoff := bout.seek(big 0, 1);
+ for(; d != nil; d = d.next){
+ discon(d.id);
+ discon(d.size);
+ discon(d.nmap);
+ bout.write(d.map, d.nmap);
+ }
+ if(debug['s'])
+ print("%bd type descriptor bytes start %bd\n", bout.seek(big 0, 1) - fileoff, fileoff);
+}
+
+disvar(nil: int, ids: ref Decl)
+{
+ fileoff := bout.seek(big 0, 1);
+ lastkind = -1;
+ ncached = 0;
+ ndatum = 0;
+
+ for(d := ids; d != nil; d = d.next)
+ if(d.store == Dglobal && d.init != nil)
+ disdatum(d.offset, d.init);
+
+ disflush(-1, -1, 0);
+
+ bout.putb(byte 0);
+
+ if(debug['s'])
+ print("%bd data bytes start %bd\n", bout.seek(big 0, 1) - fileoff, fileoff);
+}
+
+disldt(size: int, ds: ref Decl)
+{
+ if(0){
+ discon(size);
+ disvar(size, ds);
+ return;
+ }
+
+ m := 0;
+ for(d := ds; d != nil; d = d.next)
+ if(d.store == Dglobal && d.init != nil)
+ m++;
+ discon(m);
+ n: ref Node;
+ for(d = ds; d != nil; d = d.next){
+ if(d.store == Dglobal && d.init != nil){
+ n = d.init;
+ if(n.ty.kind != Tiface)
+ nerror(n, "disldt: not Tiface");
+ discon(int n.c.val);
+ for(id := n.decl.ty.ids; id != nil; id = id.next){
+ disword(sign(id));
+ if(id.dot.ty.kind == Tadt){
+ s := array of byte id.dot.sym.name;
+ bout.write(s, len s);
+ bout.putb(byte '.');
+ }
+ s := array of byte id.sym.name;
+ bout.write(s, len s);
+ bout.putb(byte 0);
+ }
+ }
+ }
+ discon(0);
+}
+
+disdatum(offset: int, n: ref Node)
+{
+ c: ref Case;
+ lab: Label;
+ id: ref Decl;
+ wild: ref Node;
+ i, e: int;
+
+ case n.ty.kind{
+ Tbyte =>
+ disbyte(offset, byte n.c.val);
+ Tint or
+ Tfix =>
+ disint(offset, int n.c.val);
+ Tbig =>
+ disbig(offset, n.c.val);
+ Tstring =>
+ disstring(offset, n.decl.sym);
+ Treal =>
+ disreal(offset, n.c.rval);
+ Tadt or
+ Tadtpick or
+ Ttuple =>
+ id = n.ty.ids;
+ for(n = n.left; n != nil; n = n.right){
+ disdatum(offset + id.offset, n.left);
+ id = id.next;
+ }
+ Tany =>
+ break;
+ Tcase =>
+ c = n.ty.cse;
+ disint(offset, c.nlab);
+ offset += IBY2WD;
+ for(i = 0; i < c.nlab; i++){
+ lab = c.labs[i];
+ disint(offset, int lab.start.c.val);
+ offset += IBY2WD;
+ disint(offset, int lab.stop.c.val+1);
+ offset += IBY2WD;
+ disint(offset, lab.inst.pc);
+ offset += IBY2WD;
+ }
+ if(c.iwild != nil)
+ disint(offset, c.iwild.pc);
+ else
+ disint(offset, -1);
+ Tcasel =>
+ c = n.ty.cse;
+ disint(offset, c.nlab);
+ offset += 2*IBY2WD;
+ for(i = 0; i < c.nlab; i++){
+ lab = c.labs[i];
+ disbig(offset, lab.start.c.val);
+ offset += IBY2LG;
+ disbig(offset, lab.stop.c.val+big 1);
+ offset += IBY2LG;
+ disint(offset, lab.inst.pc);
+ offset += 2*IBY2WD;
+ }
+ if(c.iwild != nil)
+ disint(offset, c.iwild.pc);
+ else
+ disint(offset, -1);
+ Tcasec =>
+ c = n.ty.cse;
+ disint(offset, c.nlab);
+ offset += IBY2WD;
+ for(i = 0; i < c.nlab; i++){
+ lab = c.labs[i];
+ disstring(offset, lab.start.decl.sym);
+ offset += IBY2WD;
+ if(lab.stop != lab.start)
+ disstring(offset, lab.stop.decl.sym);
+ offset += IBY2WD;
+ disint(offset, lab.inst.pc);
+ offset += IBY2WD;
+ }
+ if(c.iwild != nil)
+ disint(offset, c.iwild.pc);
+ else
+ disint(offset, -1);
+ Tgoto =>
+ c = n.ty.cse;
+ disint(offset, n.ty.size/IBY2WD-1);
+ offset += IBY2WD;
+ for(i = 0; i < c.nlab; i++){
+ disint(offset, c.labs[i].inst.pc);
+ offset += IBY2WD;
+ }
+ if(c.iwild != nil)
+ disint(offset, c.iwild.pc);
+ Tarray =>
+ disflush(-1, -1, 0);
+ disdata(DEFA, 1); # 1 is ignored
+ discon(offset);
+ disword(n.ty.tof.decl.desc.id);
+ disword(int n.left.c.val);
+
+ if(n.right == nil)
+ break;
+
+ disdata(DIND, 1); # 1 is ignored
+ discon(offset);
+ disword(0);
+
+ c = n.right.ty.cse;
+ wild = nil;
+ if(c.wild != nil)
+ wild = c.wild.right;
+ last := 0;
+ esz := n.ty.tof.size;
+ for(i = 0; i < c.nlab; i++){
+ e = int c.labs[i].start.c.val;
+ if(wild != nil){
+ for(; last < e; last++)
+ disdatum(esz * last, wild);
+ }
+ last = e;
+ e = int c.labs[i].stop.c.val;
+ elem := c.labs[i].node.right;
+ for(; last <= e; last++)
+ disdatum(esz * last, elem);
+ }
+ if(wild != nil)
+ for(e = int n.left.c.val; last < e; last++)
+ disdatum(esz * last, wild);
+
+ disflush(-1, -1, 0);
+ disdata(DAPOP, 1); # 1 is ignored
+ discon(0);
+ Tiface =>
+ disint(offset, int n.c.val);
+ offset += IBY2WD;
+ for(id = n.decl.ty.ids; id != nil; id = id.next){
+ offset = align(offset, IBY2WD);
+ disint(offset, sign(id));
+ offset += IBY2WD;
+
+ name: array of byte;
+ if(id.dot.ty.kind == Tadt){
+ name = array of byte id.dot.sym.name;
+ disbytes(offset, name);
+ offset += len name;
+ disbyte(offset, byte '.');
+ offset++;
+ }
+ name = array of byte id.sym.name;
+ disbytes(offset, name);
+ offset += len name;
+ disbyte(offset, byte 0);
+ offset++;
+ }
+ * =>
+ fatal("can't gen global "+nodeconv(n));
+ }
+}
+
+disexc(es: ref Except)
+{
+ e: ref Except;
+
+ n := 0;
+ for(e = es; e != nil; e = e.next)
+ if(int e.p1.reach || int e.p2.reach)
+ n++;
+ discon(n);
+ for(e = es; e != nil; e = e.next){
+ if(!int e.p1.reach && !int e.p2.reach)
+ continue;
+ c := e.c;
+ discon(e.d.offset);
+ discon(getpc(e.p1));
+ discon(getpc(e.p2));
+ if(e.desc != nil)
+ discon(e.desc.id);
+ else
+ discon(-1);
+ discon(c.nlab|(e.ne<<16));
+ for(i := 0; i < c.nlab; i++){
+ lab := c.labs[i];
+ d := lab.start.decl;
+ if(lab.start.ty.kind == Texception)
+ d = d.init.decl;
+ bout.puts(d.sym.name);
+ bout.putb(byte 0);
+ discon(lab.inst.pc);
+ }
+ if(c.iwild == nil)
+ discon(-1);
+ else
+ discon(c.iwild.pc);
+ }
+ discon(0);
+}
+
+disbyte(off: int, v: byte)
+{
+ disflush(DEFB, off, 1);
+ cache[ncached++] = v;
+ ndatum++;
+}
+
+disbytes(off: int, v: array of byte)
+{
+ n := len v;
+ disflush(DEFB, off, n);
+ cache[ncached:] = v;
+ ncached += n;
+ ndatum += n;
+}
+
+disint(off, v: int)
+{
+ disflush(DEFW, off, IBY2WD);
+ cache[ncached++] = byte(v >> 24);
+ cache[ncached++] = byte(v >> 16);
+ cache[ncached++] = byte(v >> 8);
+ cache[ncached++] = byte(v);
+ ndatum++;
+}
+
+disbig(off: int, v: big)
+{
+ disflush(DEFL, off, IBY2LG);
+ iv := int(v >> 32);
+ cache[ncached++] = byte(iv >> 24);
+ cache[ncached++] = byte(iv >> 16);
+ cache[ncached++] = byte(iv >> 8);
+ cache[ncached++] = byte(iv);
+ iv = int v;
+ cache[ncached++] = byte(iv >> 24);
+ cache[ncached++] = byte(iv >> 16);
+ cache[ncached++] = byte(iv >> 8);
+ cache[ncached++] = byte(iv);
+ ndatum++;
+}
+
+disreal(off: int, v: real)
+{
+ disflush(DEFF, off, IBY2LG);
+ export_real(cache[ncached:ncached+8], array[] of {v});
+ ncached += IBY2LG;
+ ndatum++;
+}
+
+disstring(offset: int, sym: ref Sym)
+{
+ disflush(-1, -1, 0);
+ d := array of byte sym.name;
+ disdata(DEFS, len d);
+ discon(offset);
+ bout.write(d, len d);
+}
+
+disflush(kind, off, size: int)
+{
+ if(kind != lastkind || off != lastoff){
+ if(lastkind != -1 && ncached){
+ disdata(lastkind, ndatum);
+ discon(startoff);
+ bout.write(cache, ncached);
+ }
+ startoff = off;
+ lastkind = kind;
+ ncached = 0;
+ ndatum = 0;
+ }
+ lastoff = off + size;
+ while(kind >= 0 && ncached + size >= len cache){
+ c := array[ncached + 1024] of byte;
+ c[0:] = cache;
+ cache = c;
+ }
+}
+
+dismode := array[int Aend] of
+{
+ int Aimm => byte AIMM,
+ int Amp => byte AMP,
+ int Ampind => byte(AMP|AIND),
+ int Afp => byte AFP,
+ int Afpind => byte(AFP|AIND),
+ int Apc => byte AIMM,
+ int Adesc => byte AIMM,
+ int Aoff => byte AIMM,
+ int Anoff => byte AIMM,
+ int Aerr => byte AXXX,
+ int Anone => byte AXXX,
+ int Aldt => byte AIMM,
+};
+
+disregmode := array[int Aend] of
+{
+ int Aimm => byte AXIMM,
+ int Amp => byte AXINM,
+ int Ampind => byte AXNON,
+ int Afp => byte AXINF,
+ int Afpind => byte AXNON,
+ int Apc => byte AXIMM,
+ int Adesc => byte AXIMM,
+ int Aoff => byte AXIMM,
+ int Anoff => byte AXIMM,
+ int Aerr => byte AXNON,
+ int Anone => byte AXNON,
+ int Aldt => byte AXIMM,
+};
+
+MAXCON: con 4;
+MAXADDR: con 2*MAXCON;
+MAXINST: con 3*MAXADDR+2;
+NIBUF: con 1024;
+
+ibuf: array of byte;
+nibuf: int;
+
+disinst(in: ref Inst)
+{
+ fileoff := bout.seek(big 0, 1);
+ ibuf = array[NIBUF] of byte;
+ nibuf = 0;
+ for(; in != nil; in = in.next){
+ if(in.op == INOOP)
+ continue;
+ if(nibuf >= NIBUF-MAXINST){
+ bout.write(ibuf, nibuf);
+ nibuf = 0;
+ }
+ ibuf[nibuf++] = byte in.op;
+ o := dismode[int in.sm] << SRC;
+ o |= dismode[int in.dm] << DST;
+ o |= disregmode[int in.mm];
+ ibuf[nibuf++] = o;
+ if(in.mm != Anone)
+ disaddr(in.mm, in.m);
+ if(in.sm != Anone)
+ disaddr(in.sm, in.s);
+ if(in.dm != Anone)
+ disaddr(in.dm, in.d);
+ }
+ if(nibuf > 0)
+ bout.write(ibuf, nibuf);
+ ibuf = nil;
+
+ if(debug['s'])
+ print("%bd instruction bytes start %bd\n", bout.seek(big 0, 1) - fileoff, fileoff);
+}
+
+disaddr(m: byte, a: Addr)
+{
+ val := 0;
+ case int m{
+ int Aimm or
+ int Apc or
+ int Adesc =>
+ val = a.offset;
+ int Aoff =>
+ val = a.decl.iface.offset;
+ int Anoff =>
+ val = -(a.decl.iface.offset+1);
+ int Afp or
+ int Amp or
+ int Aldt =>
+ val = a.reg;
+ int Afpind or
+ int Ampind =>
+ disbcon(a.reg);
+ val = a.offset;
+ }
+ disbcon(val);
+}
+
+disbcon(val: int)
+{
+ if(val >= -64 && val <= 63){
+ ibuf[nibuf++] = byte(val & ~16r80);
+ return;
+ }
+ if(val >= -8192 && val <= 8191){
+ ibuf[nibuf++] = byte(val>>8 & ~16rC0 | 16r80);
+ ibuf[nibuf++] = byte val;
+ return;
+ }
+ if(val < 0 && ((val >> 29) & 7) != 7
+ || val > 0 && (val >> 29) != 0)
+ fatal("overflow in constant 16r"+hex(val, 0));
+ ibuf[nibuf++] = byte(val>>24 | 16rC0);
+ ibuf[nibuf++] = byte(val>>16);
+ ibuf[nibuf++] = byte(val>>8);
+ ibuf[nibuf++] = byte val;
+}
diff --git a/appl/cmd/limbo/disoptab.m b/appl/cmd/limbo/disoptab.m
new file mode 100644
index 00000000..a2e51a8b
--- /dev/null
+++ b/appl/cmd/limbo/disoptab.m
@@ -0,0 +1,355 @@
+movetab:= array [Mend]of
+{
+ Mas => array[Tend] of
+ {
+ Tadt => IMOVM,
+ Tadtpick => IMOVM,
+ Tarray => IMOVP,
+ Tbig => IMOVL,
+ Tbyte => IMOVB,
+ Tchan => IMOVP,
+ Treal => IMOVF,
+ Tint => IMOVW,
+ Tlist => IMOVP,
+ Tmodule => IMOVP,
+ Tref => IMOVP,
+ Tstring => IMOVP,
+ Ttuple => IMOVM,
+ Texception => IMOVM,
+ Tfix => IMOVW,
+ Tpoly => IMOVP,
+
+ Tany => IMOVP,
+
+ * => 0
+ },
+ Mcons => array[Tend] of
+ {
+ Tadt => ICONSM,
+ Tadtpick => 0,
+ Tarray => ICONSP,
+ Tbig => ICONSL,
+ Tbyte => ICONSB,
+ Tchan => ICONSP,
+ Treal => ICONSF,
+ Tint => ICONSW,
+ Tlist => ICONSP,
+ Tmodule => ICONSP,
+ Tref => ICONSP,
+ Tstring => ICONSP,
+ Ttuple => ICONSM,
+ Texception => ICONSM,
+ Tfix => ICONSW,
+ Tpoly => ICONSP,
+
+ Tany => ICONSP,
+
+ * => 0
+ },
+ Mhd => array[Tend] of
+ {
+ Tadt => IHEADM,
+ Tadtpick => 0,
+ Tarray => IHEADP,
+ Tbig => IHEADL,
+ Tbyte => IHEADB,
+ Tchan => IHEADP,
+ Treal => IHEADF,
+ Tint => IHEADW,
+ Tlist => IHEADP,
+ Tmodule => IHEADP,
+ Tref => IHEADP,
+ Tstring => IHEADP,
+ Ttuple => IHEADM,
+ Texception => IHEADM,
+ Tfix => IHEADW,
+ Tpoly => IHEADP,
+
+ Tany => IHEADP,
+
+ * => 0
+ },
+ Mtl => array[Tend] of
+ {
+ Tlist => ITAIL,
+
+ * => 0
+ },
+};
+
+chantab := array[Tend] of
+{
+ Tadt => INEWCM,
+ Tadtpick => 0,
+ Tarray => INEWCP,
+ Tbig => INEWCL,
+ Tbyte => INEWCB,
+ Tchan => INEWCP,
+ Treal => INEWCF,
+ Tint => INEWCW,
+ Tlist => INEWCP,
+ Tmodule => INEWCP,
+ Tref => INEWCP,
+ Tstring => INEWCP,
+ Ttuple => INEWCM,
+ Texception => INEWCM,
+ Tfix => INEWCW,
+ Tpoly => INEWCP,
+
+ Tany => INEWCP,
+
+ * => 0
+};
+
+opind := array[Tend] of
+{
+ Tbyte => 1,
+ Tint => 2,
+ Tbig => 3,
+ Treal => 4,
+ Tstring => 5,
+ Tfix => 6,
+
+ * => 0
+};
+
+disoptab := array[Oend+1] of
+{
+ # opcode default byte word big real string fixed
+ Oadd => array[7] of {0, IADDB, IADDW, IADDL, IADDF, IADDC, IADDW,},
+ Oaddas => array[7] of {0, IADDB, IADDW, IADDL, IADDF, IADDC, IADDW,},
+ Oand => array[7] of {0, IANDB, IANDW, IANDL, 0, 0, 0,},
+ Oandas => array[7] of {0, IANDB, IANDW, IANDL, 0, 0, 0,},
+ Odec => array[7] of {0, ISUBB, ISUBW, ISUBL, ISUBF, 0, ISUBW,},
+ Odiv => array[7] of {0, IDIVB, IDIVW, IDIVL, IDIVF, 0, IDIVX,},
+ Odivas => array[7] of {0, IDIVB, IDIVW, IDIVL, IDIVF, 0, IDIVX,},
+ Oeq => array[7] of {IBEQW, IBEQB, IBEQW, IBEQL, IBEQF, IBEQC, IBEQW,},
+ Oexp => array[7] of {0, 0, IEXPW, IEXPL, IEXPF, 0, 0,},
+ Oexpas => array[7] of {0, 0, IEXPW, IEXPL, IEXPF, 0, 0,},
+ Ogeq => array[7] of {0, IBGEB, IBGEW, IBGEL, IBGEF, IBGEC, IBGEW,},
+ Ogt => array[7] of {0, IBGTB, IBGTW, IBGTL, IBGTF, IBGTC, IBGTW,},
+ Oinc => array[7] of {0, IADDB, IADDW, IADDL, IADDF, 0, IADDW,},
+ Oinds => array[7] of {0, 0, IINDC, 0, 0, 0, 0,},
+ Oindx => array[7] of {0, 0, IINDX, 0, 0, 0, 0,},
+ Olen => array[7] of {ILENA, 0, 0, 0, 0, ILENC, 0,},
+ Oleq => array[7] of {0, IBLEB, IBLEW, IBLEL, IBLEF, IBLEC, IBLEW,},
+ Olsh => array[7] of {0, ISHLB, ISHLW, ISHLL, 0, 0, 0,},
+ Olshas => array[7] of {0, ISHLB, ISHLW, ISHLL, 0, 0, 0,},
+ Olt => array[7] of {0, IBLTB, IBLTW, IBLTL, IBLTF, IBLTC, IBLTW,},
+ Omod => array[7] of {0, IMODB, IMODW, IMODL, 0, 0, 0,},
+ Omodas => array[7] of {0, IMODB, IMODW, IMODL, 0, 0, 0,},
+ Omul => array[7] of {0, IMULB, IMULW, IMULL, IMULF, 0, IMULX,},
+ Omulas => array[7] of {0, IMULB, IMULW, IMULL, IMULF, 0, IMULX,},
+ Oneg => array[7] of {0, 0, 0, 0, INEGF, 0, 0, },
+ Oneq => array[7] of {IBNEW, IBNEB, IBNEW, IBNEL, IBNEF, IBNEC, IBNEW,},
+ Oor => array[7] of {0, IORB, IORW, IORL, 0, 0, 0,},
+ Ooras => array[7] of {0, IORB, IORW, IORL, 0, 0, 0,},
+ Orsh => array[7] of {0, ISHRB, ISHRW, ISHRL, 0, 0, 0,},
+ Orshas => array[7] of {0, ISHRB, ISHRW, ISHRL, 0, 0, 0,},
+ Oslice => array[7] of {ISLICEA,0, 0, 0, 0, ISLICEC, 0,},
+ Osub => array[7] of {0, ISUBB, ISUBW, ISUBL, ISUBF, 0, ISUBW,},
+ Osubas => array[7] of {0, ISUBB, ISUBW, ISUBL, ISUBF, 0, ISUBW,},
+ Oxor => array[7] of {0, IXORB, IXORW, IXORL, 0, 0, 0,},
+ Oxoras => array[7] of {0, IXORB, IXORW, IXORL, 0, 0, 0,},
+};
+
+isbyteinst := array [256] of
+{
+ IMULB => 1,
+ ISUBB => 1,
+ IADDB => 1,
+ IDIVB => 1,
+ IORB => 1,
+ IXORB => 1,
+ ISHLB => 1,
+ ISHRB => 1,
+ IMODB => 1,
+ IANDB => 1,
+ IBEQB => 1,
+ IBNEB => 1,
+ IBLTB => 1,
+ IBLEB => 1,
+ IBGTB => 1,
+ IBGEB => 1,
+
+ * => 0,
+};
+
+instname := array[256] of
+{
+ "nop",
+ "alt",
+ "nbalt",
+ "goto",
+ "call",
+ "frame",
+ "spawn",
+ "runt",
+ "load",
+ "mcall",
+ "mspawn",
+ "mframe",
+ "ret",
+ "jmp",
+ "case",
+ "exit",
+ "new",
+ "newa",
+ "newcb",
+ "newcw",
+ "newcf",
+ "newcp",
+ "newcm",
+ "newcmp",
+ "send",
+ "recv",
+ "consb",
+ "consw",
+ "consp",
+ "consf",
+ "consm",
+ "consmp",
+ "headb",
+ "headw",
+ "headp",
+ "headf",
+ "headm",
+ "headmp",
+ "tail",
+ "lea",
+ "indx",
+ "movp",
+ "movm",
+ "movmp",
+ "movb",
+ "movw",
+ "movf",
+ "cvtbw",
+ "cvtwb",
+ "cvtfw",
+ "cvtwf",
+ "cvtca",
+ "cvtac",
+ "cvtwc",
+ "cvtcw",
+ "cvtfc",
+ "cvtcf",
+ "addb",
+ "addw",
+ "addf",
+ "subb",
+ "subw",
+ "subf",
+ "mulb",
+ "mulw",
+ "mulf",
+ "divb",
+ "divw",
+ "divf",
+ "modw",
+ "modb",
+ "andb",
+ "andw",
+ "orb",
+ "orw",
+ "xorb",
+ "xorw",
+ "shlb",
+ "shlw",
+ "shrb",
+ "shrw",
+ "insc",
+ "indc",
+ "addc",
+ "lenc",
+ "lena",
+ "lenl",
+ "beqb",
+ "bneb",
+ "bltb",
+ "bleb",
+ "bgtb",
+ "bgeb",
+ "beqw",
+ "bnew",
+ "bltw",
+ "blew",
+ "bgtw",
+ "bgew",
+ "beqf",
+ "bnef",
+ "bltf",
+ "blef",
+ "bgtf",
+ "bgef",
+ "beqc",
+ "bnec",
+ "bltc",
+ "blec",
+ "bgtc",
+ "bgec",
+ "slicea",
+ "slicela",
+ "slicec",
+ "indw",
+ "indf",
+ "indb",
+ "negf",
+ "movl",
+ "addl",
+ "subl",
+ "divl",
+ "modl",
+ "mull",
+ "andl",
+ "orl",
+ "xorl",
+ "shll",
+ "shrl",
+ "bnel",
+ "bltl",
+ "blel",
+ "bgtl",
+ "bgel",
+ "beql",
+ "cvtlf",
+ "cvtfl",
+ "cvtlw",
+ "cvtwl",
+ "cvtlc",
+ "cvtcl",
+ "headl",
+ "consl",
+ "newcl",
+ "casec",
+ "indl",
+ "movpc",
+ "tcmp",
+ "mnewz",
+ "cvtrf",
+ "cvtfr",
+ "cvtws",
+ "cvtsw",
+ "lsrw",
+ "lsrl",
+ "eclr",
+ "newz",
+ "newaz",
+ "raise",
+ "casel",
+ "mulx",
+ "divx",
+ "cvtxx",
+ "mulx0",
+ "divx0",
+ "cvtxx0",
+ "mulx1",
+ "divx1",
+ "cvtxx1",
+ "cvtfx",
+ "cvtxf",
+ "expw",
+ "expl",
+ "expf",
+ "self",
+};
diff --git a/appl/cmd/limbo/ecom.b b/appl/cmd/limbo/ecom.b
new file mode 100644
index 00000000..978882ab
--- /dev/null
+++ b/appl/cmd/limbo/ecom.b
@@ -0,0 +1,2345 @@
+maxstack: int; # max size of a stack frame called
+
+precasttab := array[Tend] of array of ref Type;
+
+optabinit()
+{
+ ct := array[Tend] of ref Type;
+ for(i := 0; i < Tend; i++)
+ precasttab[i] = ct;
+ precasttab[Tstring] = array[Tend] of { Tbyte => tint, Tfix => treal, };
+ precasttab[Tbig] = array[Tend] of { Tbyte => tint, Tfix => treal, };
+ precasttab[Treal] = array[Tend] of { Tbyte => tint, };
+ precasttab[Tfix] = array[Tend] of { Tbyte => tint, Tstring => treal, Tbig => treal, };
+ precasttab[Tbyte] = array[Tend] of { Tstring => tint, Tbig => tint, Treal => tint, Tfix => tint, };
+
+ casttab = array[Tend] of { * => array[Tend] of {* => 0}};
+
+ casttab[Tint][Tint] = IMOVW;
+ casttab[Tbig][Tbig] = IMOVL;
+ casttab[Treal][Treal] = IMOVF;
+ casttab[Tbyte][Tbyte] = IMOVB;
+ casttab[Tstring][Tstring] = IMOVP;
+ casttab[Tfix][Tfix] = ICVTXX; # never same type
+
+ casttab[Tint][Tbyte] = ICVTWB;
+ casttab[Tint][Treal] = ICVTWF;
+ casttab[Tint][Tstring] = ICVTWC;
+ casttab[Tint][Tfix] = ICVTXX;
+ casttab[Tbyte][Tint] = ICVTBW;
+ casttab[Treal][Tint] = ICVTFW;
+ casttab[Tstring][Tint] = ICVTCW;
+ casttab[Tfix][Tint] = ICVTXX;
+
+ casttab[Tint][Tbig] = ICVTWL;
+ casttab[Treal][Tbig] = ICVTFL;
+ casttab[Tstring][Tbig] = ICVTCL;
+ casttab[Tbig][Tint] = ICVTLW;
+ casttab[Tbig][Treal] = ICVTLF;
+ casttab[Tbig][Tstring] = ICVTLC;
+
+ casttab[Treal][Tstring] = ICVTFC;
+ casttab[Tstring][Treal] = ICVTCF;
+
+ casttab[Treal][Tfix] = ICVTFX;
+ casttab[Tfix][Treal] = ICVTXF;
+
+ casttab[Tstring][Tarray] = ICVTCA;
+ casttab[Tarray][Tstring] = ICVTAC;
+
+ #
+ # placeholders; fixed in precasttab
+ #
+ casttab[Tbyte][Tstring] = 16rff;
+ casttab[Tstring][Tbyte] = 16rff;
+ casttab[Tbyte][Treal] = 16rff;
+ casttab[Treal][Tbyte] = 16rff;
+ casttab[Tbyte][Tbig] = 16rff;
+ casttab[Tbig][Tbyte] = 16rff;
+ casttab[Tfix][Tbyte] = 16rff;
+ casttab[Tbyte][Tfix] = 16rff;
+ casttab[Tfix][Tbig] = 16rff;
+ casttab[Tbig][Tfix] = 16rff;
+ casttab[Tfix][Tstring] = 16rff;
+ casttab[Tstring][Tfix] = 16rff;
+}
+
+#
+# global variable and constant initialization checking
+#
+vcom(ids: ref Decl): int
+{
+ ok := 1;
+ for(v := ids; v != nil; v = v.next)
+ ok &= varcom(v);
+ for(v = ids; v != nil; v = v.next)
+ v.init = simplify(v.init);
+ return ok;
+}
+
+simplify(n: ref Node): ref Node
+{
+ if(n == nil)
+ return nil;
+ if(debug['F'])
+ print("simplify %s\n", nodeconv(n));
+ n = efold(rewrite(n));
+ if(debug['F'])
+ print("simplified %s\n", nodeconv(n));
+ return n;
+}
+
+isfix(n: ref Node): int
+{
+ if(n.ty.kind == Tint || n.ty.kind == Tfix){
+ if(n.op == Ocast)
+ return n.left.ty.kind == Tint || n.left.ty.kind == Tfix;
+ return 1;
+ }
+ return 0;
+}
+
+#
+# rewrite an expression to make it easiser to compile,
+# or give the correct results
+#
+rewrite(n: ref Node): ref Node
+{
+ v: big;
+ t: ref Type;
+ d: ref Decl;
+ nn: ref Node;
+
+ if(n == nil)
+ return nil;
+
+ left := n.left;
+ right := n.right;
+
+ #
+ # rewrites
+ #
+ case n.op{
+ Oname =>
+ d = n.decl;
+ if(d.importid != nil){
+ left = mkbin(Omdot, dupn(1, n.src, d.eimport), mkdeclname(n.src, d.importid));
+ left.ty = n.ty;
+ return rewrite(left);
+ }
+ if((t = n.ty).kind == Texception){
+ if(int t.cons)
+ fatal("cons in rewrite Oname");
+ n = mkbin(Oadd, n, mkconst(n.src, big(2*IBY2WD)));
+ n = mkunary(Oind, n);
+ n.ty = t;
+ n.left.ty = n.left.left.ty = tint;
+ return rewrite(n);
+ }
+ Odas =>
+ n.op = Oas;
+ return rewrite(n);
+ Oneg =>
+ n.left = rewrite(left);
+ if(n.ty == treal)
+ break;
+ left = n.left;
+ n.right = left;
+ n.left = mkconst(n.src, big 0);
+ n.left.ty = n.ty;
+ n.op = Osub;
+ Ocomp =>
+ v = big 0;
+ v = ~v;
+ n.right = mkconst(n.src, v);
+ n.right.ty = n.ty;
+ n.left = rewrite(left);
+ n.op = Oxor;
+ Oinc or
+ Odec or
+ Opreinc or
+ Opredec =>
+ n.left = rewrite(left);
+ case n.ty.kind{
+ Treal =>
+ n.right = mkrconst(n.src, 1.0);
+ Tint or
+ Tbig or
+ Tbyte or
+ Tfix =>
+ n.right = mkconst(n.src, big 1);
+ n.right.ty = n.ty;
+ * =>
+ fatal("can't rewrite inc/dec "+nodeconv(n));
+ }
+ if(n.op == Opreinc)
+ n.op = Oaddas;
+ else if(n.op == Opredec)
+ n.op = Osubas;
+ Oslice =>
+ if(right.left.op == Onothing)
+ right.left = mkconst(right.left.src, big 0);
+ n.left = rewrite(left);
+ n.right = rewrite(right);
+ Oindex =>
+ n.op = Oindx;
+ n.left = rewrite(left);
+ n.right = rewrite(right);
+ n = mkunary(Oind, n);
+ n.ty = n.left.ty;
+ n.left.ty = tint;
+ Oload =>
+ n.right = mkn(Oname, nil, nil);
+ n.right.src = n.left.src;
+ n.right.decl = n.ty.tof.decl;
+ n.right.ty = n.ty;
+ n.left = rewrite(left);
+ Ocast =>
+ if(left.ty.kind == Texception){
+ n = rewrite(left);
+ break;
+ }
+ n.op = Ocast;
+ t = precasttab[left.ty.kind][n.ty.kind];
+ if(t != nil){
+ n.left = mkunary(Ocast, left);
+ n.left.ty = t;
+ return rewrite(n);
+ }
+ n.left = rewrite(left);
+ Oraise =>
+ if(left.ty == tstring)
+ ;
+ else if(left.ty.cons == byte 0)
+ break;
+ else if(left.op != Ocall || left.left.ty.kind == Tfn){
+ left = mkunary(Ocall, left);
+ left.ty = left.left.ty;
+ }
+ n.left = rewrite(left);
+ Ocall =>
+ t = left.ty;
+ if(t.kind == Tref)
+ t = t.tof;
+ if(t.kind == Tfn){
+ if(left.ty.kind == Tref){ # call by function reference
+ n.left = mkunary(Oind, left);
+ n.left.ty = t;
+ return rewrite(n);
+ }
+ d = nil;
+ if(left.op == Oname)
+ d = left.decl;
+ else if(left.op == Omdot && left.right.op == Odot)
+ d = left.right.right.decl;
+ else if(left.op == Omdot || left.op == Odot)
+ d = left.right.decl;
+ else if(left.op != Oind)
+ fatal("cannot deal with call " + nodeconv(n) + " in rewrite");
+ if(ispoly(d))
+ addfnptrs(d, 0);
+ n.left = rewrite(left);
+ if(right != nil)
+ n.right = rewrite(right);
+ if(d != nil && int d.inline == 1)
+ n = simplify(inline(n));
+ break;
+ }
+ case n.ty.kind{
+ Tref =>
+ n = mkunary(Oref, n);
+ n.ty = n.left.ty;
+ n.left.ty = n.left.ty.tof;
+ n.left.left.ty = n.left.ty;
+ return rewrite(n);
+ Tadt =>
+ n.op = Otuple;
+ n.right = nil;
+ if(n.ty.tags != nil){
+ n.left = nn = mkunary(Oseq, mkconst(n.src, big left.right.decl.tag));
+ if(right != nil){
+ nn.right = right;
+ nn.src.stop = right.src.stop;
+ }
+ n.ty = left.right.decl.ty.tof;
+ }else
+ n.left = right;
+ return rewrite(n);
+ Tadtpick =>
+ n.op = Otuple;
+ n.right = nil;
+ n.left = nn = mkunary(Oseq, mkconst(n.src, big left.right.decl.tag));
+ if(right != nil){
+ nn.right = right;
+ nn.src.stop = right.src.stop;
+ }
+ n.ty = left.right.decl.ty.tof;
+ return rewrite(n);
+ Texception =>
+ if(n.ty.cons == byte 0)
+ return n.left;
+ if(left.op == Omdot){
+ left.right.ty = left.ty;
+ left = left.right;
+ }
+ n.op = Otuple;
+ n.right = nil;
+ n.left = nn = mkunary(Oseq, left.decl.init);
+ nn.right = mkunary(Oseq, mkconst(n.src, big 0));
+ nn.right.right = right;
+ n.ty = mkexbasetype(n.ty);
+ n = mkunary(Oref, n);
+ n.ty = internaltype(mktype(n.src.start, n.src.stop, Tref, t, nil));
+ return rewrite(n);
+ * =>
+ fatal("can't deal with "+nodeconv(n)+" in rewrite/Ocall");
+ }
+ Omdot =>
+ #
+ # what about side effects from left?
+ #
+ d = right.decl;
+ case d.store{
+ Dfn =>
+ n.left = rewrite(left);
+ if(right.op == Odot){
+ n.right = dupn(1, left.src, right.right);
+ n.right.ty = d.ty;
+ }
+ Dconst or
+ Dtag or
+ Dtype =>
+ # handled by fold
+ return n;
+ Dglobal =>
+ right.op = Oconst;
+ right.c = ref Const(big d.offset, 0.);
+ right.ty = tint;
+
+ n.left = left = mkunary(Oind, left);
+ left.ty = tint;
+ n.op = Oadd;
+ n = mkunary(Oind, n);
+ n.ty = n.left.ty;
+ n.left.ty = tint;
+ n.left = rewrite(n.left);
+ return n;
+ Darg =>
+ return n;
+ * =>
+ fatal("can't deal with "+nodeconv(n)+" in rewrite/Omdot");
+ }
+ Odot =>
+ #
+ # what about side effects from left?
+ #
+ d = right.decl;
+ case d.store{
+ Dfn =>
+ if(right.left != nil){
+ n = mkbin(Omdot, dupn(1, left.src, right.left), right);
+ right.left = nil;
+ n.ty = d.ty;
+ return rewrite(n);
+ }
+ if(left.ty.kind == Tpoly){
+ n = mkbin(Omdot, mkdeclname(left.src, d.link), mkdeclname(left.src, d.link.next));
+ n.ty = d.ty;
+ return rewrite(n);
+ }
+ n.op = Oname;
+ n.decl = d;
+ n.right = nil;
+ n.left = nil;
+ return n;
+ Dconst or
+ Dtag or
+ Dtype =>
+ # handled by fold
+ return n;
+ }
+ if(istuple(left))
+ return n; # handled by fold
+ right.op = Oconst;
+ right.c = ref Const(big d.offset, 0.);
+ right.ty = tint;
+
+ if(left.ty.kind != Tref){
+ n.left = mkunary(Oadr, left);
+ n.left.ty = tint;
+ }
+ n.op = Oadd;
+ n = mkunary(Oind, n);
+ n.ty = n.left.ty;
+ n.left.ty = tint;
+ n.left = rewrite(n.left);
+ return n;
+ Oadr =>
+ left = rewrite(left);
+ n.left = left;
+ if(left.op == Oind)
+ return left.left;
+ Otagof =>
+ if(n.decl == nil){
+ n.op = Oind;
+ return rewrite(n);
+ }
+ return n;
+ Omul or
+ Odiv =>
+ left = n.left = rewrite(left);
+ right = n.right = rewrite(right);
+ if(n.ty.kind == Tfix && isfix(left) && isfix(right)){
+ if(left.op == Ocast && tequal(left.ty, n.ty))
+ n.left = left.left;
+ if(right.op == Ocast && tequal(right.ty, n.ty))
+ n.right = right.left;
+ }
+ Oself =>
+ if(newfnptr)
+ return n;
+ if(selfdecl == nil){
+ d = selfdecl = mkids(n.src, enter(".self", 5), tany, nil);
+ installids(Dglobal, d);
+ d.refs++;
+ }
+ nn = mkn(Oload, nil, nil);
+ nn.src = n.src;
+ nn.left = mksconst(n.src, enterstring("$self"));
+ nn.ty = impdecl.ty;
+ usetype(nn.ty);
+ usetype(nn.ty.tof);
+ nn = rewrite(nn);
+ nn.op = Oself;
+ return nn;
+ Ofnptr =>
+ if(n.flags == byte 0){
+ # module
+ if(left == nil)
+ left = mkn(Oself, nil, nil);
+ return rewrite(left);
+ }
+ right.flags = n.flags;
+ n = right;
+ d = n.decl;
+ if(int n.flags == FNPTR2){
+ if(left != nil && left.op != Oname)
+ fatal("not Oname for addiface");
+ if(left == nil){
+ addiface(nil, d);
+ if(newfnptr)
+ n.flags |= byte FNPTRN;
+ }
+ else
+ addiface(left.decl, d);
+ n.ty = tint;
+ return n;
+ }
+ if(int n.flags == FNPTRA){
+ n = mkdeclname(n.src, d.link);
+ n.ty = tany;
+ return n;
+ }
+ if(int n.flags == (FNPTRA|FNPTR2)){
+ n = mkdeclname(n.src, d.link.next);
+ n.ty = tint;
+ return n;
+ }
+ Ochan =>
+ if(left == nil)
+ left = n.left = mkconst(n.src, big 0);
+ n.left = rewrite(left);
+ * =>
+ n.left = rewrite(left);
+ n.right = rewrite(right);
+ }
+
+ return n;
+}
+
+#
+# label a node with sethi-ullman numbers and addressablity
+# genaddr interprets addable to generate operands,
+# so a change here mandates a change there.
+#
+# addressable:
+# const Rconst $value may also be Roff or Rdesc or Rnoff
+# Asmall(local) Rreg value(FP)
+# Asmall(global) Rmreg value(MP)
+# ind(Rareg) Rreg value(FP)
+# ind(Ramreg) Rmreg value(MP)
+# ind(Rreg) Radr *value(FP)
+# ind(Rmreg) Rmadr *value(MP)
+# ind(Raadr) Radr value(value(FP))
+# ind(Ramadr) Rmadr value(value(MP))
+#
+# almost addressable:
+# adr(Rreg) Rareg
+# adr(Rmreg) Ramreg
+# add(const, Rareg) Rareg
+# add(const, Ramreg) Ramreg
+# add(const, Rreg) Raadr
+# add(const, Rmreg) Ramadr
+# add(const, Raadr) Raadr
+# add(const, Ramadr) Ramadr
+# adr(Radr) Raadr
+# adr(Rmadr) Ramadr
+#
+# strangely addressable:
+# fn Rpc
+# mdot(module,exp) Rmpc
+#
+sumark(n: ref Node): ref Node
+{
+ if(n == nil)
+ return nil;
+
+ n.temps = byte 0;
+ n.addable = Rcant;
+
+ left := n.left;
+ right := n.right;
+ if(left != nil){
+ sumark(left);
+ n.temps = left.temps;
+ }
+ if(right != nil){
+ sumark(right);
+ if(right.temps == n.temps)
+ n.temps++;
+ else if(right.temps > n.temps)
+ n.temps = right.temps;
+ }
+
+ case n.op{
+ Oadr =>
+ case int left.addable{
+ int Rreg =>
+ n.addable = Rareg;
+ int Rmreg =>
+ n.addable = Ramreg;
+ int Radr =>
+ n.addable = Raadr;
+ int Rmadr =>
+ n.addable = Ramadr;
+ }
+ Oind =>
+ case int left.addable{
+ int Rreg =>
+ n.addable = Radr;
+ int Rmreg =>
+ n.addable = Rmadr;
+ int Rareg =>
+ n.addable = Rreg;
+ int Ramreg =>
+ n.addable = Rmreg;
+ int Raadr =>
+ n.addable = Radr;
+ int Ramadr =>
+ n.addable = Rmadr;
+ }
+ Oname =>
+ case n.decl.store{
+ Darg or
+ Dlocal =>
+ n.addable = Rreg;
+ Dglobal =>
+ n.addable = Rmreg;
+ if(LDT && n.decl.ty.kind == Tiface)
+ n.addable = Rldt;
+ Dtype =>
+ #
+ # check for inferface to load
+ #
+ if(n.decl.ty.kind == Tmodule)
+ n.addable = Rmreg;
+ Dfn =>
+ if(int n.flags & FNPTR){
+ if(int n.flags == FNPTR2)
+ n.addable = Roff;
+ else if(int n.flags == FNPTR2|FNPTRN)
+ n.addable = Rnoff;
+ }
+ else
+ n.addable = Rpc;
+ * =>
+ fatal("cannot deal with "+declconv(n.decl)+" in Oname in "+nodeconv(n));
+ }
+ Omdot =>
+ n.addable = Rmpc;
+ Oconst =>
+ case n.ty.kind{
+ Tint or
+ Tfix =>
+ v := int n.c.val;
+ if(v < 0 && ((v >> 29) & 7) != 7
+ || v > 0 && (v >> 29) != 0){
+ n.decl = globalconst(n);
+ n.addable = Rmreg;
+ }else
+ n.addable = Rconst;
+ Tbig =>
+ n.decl = globalBconst(n);
+ n.addable = Rmreg;
+ Tbyte =>
+ n.decl = globalbconst(n);
+ n.addable = Rmreg;
+ Treal =>
+ n.decl = globalfconst(n);
+ n.addable = Rmreg;
+ Tstring =>
+ n.decl = globalsconst(n);
+ n.addable = Rmreg;
+ * =>
+ fatal("cannot const in sumark "+typeconv(n.ty));
+ }
+ Oadd =>
+ if(right.addable == Rconst){
+ case int left.addable{
+ int Rareg =>
+ n.addable = Rareg;
+ int Ramreg =>
+ n.addable = Ramreg;
+ int Rreg or
+ int Raadr =>
+ n.addable = Raadr;
+ int Rmreg or
+ int Ramadr =>
+ n.addable = Ramadr;
+ }
+ }
+ }
+ if(n.addable < Rcant)
+ n.temps = byte 0;
+ else if(n.temps == byte 0)
+ n.temps = byte 1;
+ return n;
+}
+
+mktn(t: ref Type): ref Node
+{
+ n := mkn(Oname, nil, nil);
+ usedesc(mktdesc(t));
+ n.ty = t;
+ if(t.decl == nil)
+ fatal("mktn nil decl t "+typeconv(t));
+ n.decl = t.decl;
+ n.addable = Rdesc;
+ return n;
+}
+
+# does a tuple of the form (a, b, ...) form a contiguous block
+# of memory on the stack when offsets are assigned later
+# - only when (a, b, ...) := rhs and none of the names nil
+# can we guarantee this
+#
+tupblk0(n: ref Node, d: ref Decl): (int, ref Decl)
+{
+ ok, nid: int;
+
+ case(n.op){
+ Otuple =>
+ for(n = n.left; n != nil; n = n.right){
+ (ok, d) = tupblk0(n.left, d);
+ if(!ok)
+ return (0, nil);
+ }
+ return (1, d);
+ Oname =>
+ if(n.decl == nildecl)
+ return (0, nil);
+ if(d != nil && d.next != n.decl)
+ return (0, nil);
+ nid = int n.decl.nid;
+ if(d == nil && nid == 1)
+ return (0, nil);
+ if(d != nil && nid != 0)
+ return (0, nil);
+ return (1, n.decl);
+ }
+ return (0, nil);
+}
+
+# could force locals to be next to each other
+# - need to shuffle locals list
+# - later
+#
+tupblk(n: ref Node): ref Node
+{
+ ok: int;
+ d: ref Decl;
+
+ if(n.op != Otuple)
+ return nil;
+ d = nil;
+ (ok, d) = tupblk0(n, d);
+ if(!ok)
+ return nil;
+ while(n.op == Otuple)
+ n = n.left.left;
+ if(n.op != Oname || n.decl.nid == byte 1)
+ fatal("bad tupblk");
+ return n;
+}
+
+# for cprof
+esrc(src: Src, osrc: Src, nto: ref Node): Src
+{
+ if(nto != nil && src.start != 0 && src.stop != 0)
+ return src;
+ return osrc;
+}
+
+#
+# compile an expression with an implicit assignment
+# note: you are not allowed to use nto.src
+#
+# need to think carefully about the types used in moves
+#
+ecom(src: Src, nto, n: ref Node): ref Node
+{
+ tleft, tright, tto, ttn: ref Node;
+ t: ref Type;
+ p: ref Inst;
+
+ if(debug['e']){
+ print("ecom: %s\n", nodeconv(n));
+ if(nto != nil)
+ print("ecom nto: %s\n", nodeconv(nto));
+ }
+
+ if(n.addable < Rcant){
+ #
+ # think carefully about the type used here
+ #
+ if(nto != nil)
+ genmove(src, Mas, n.ty, n, nto);
+ return nto;
+ }
+
+ left := n.left;
+ right := n.right;
+ op := n.op;
+ case op{
+ * =>
+ fatal("can't ecom "+nodeconv(n));
+ return nto;
+ Oif =>
+ p = bcom(left, 1, nil);
+ ecom(right.left.src, nto, right.left);
+ if(right.right != nil){
+ pp := p;
+ p = genrawop(right.left.src, IJMP, nil, nil, nil);
+ patch(pp, nextinst());
+ ecom(right.right.src, nto, right.right);
+ }
+ patch(p, nextinst());
+ Ocomma =>
+ ttn = left.left;
+ ecom(left.src, nil, left);
+ ecom(right.src, nto, right);
+ tfree(ttn);
+ Oname =>
+ if(n.addable == Rpc){
+ if(nto != nil)
+ genmove(src, Mas, n.ty, n, nto);
+ return nto;
+ }
+ fatal("can't ecom "+nodeconv(n));
+ Onothing =>
+ break;
+ Oused =>
+ if(nto != nil)
+ fatal("superflous used "+nodeconv(left)+" nto "+nodeconv(nto));
+ tto = talloc(left.ty, nil);
+ ecom(left.src, tto, left);
+ tfree(tto);
+ Oas =>
+ if(right.ty == tany)
+ right.ty = n.ty;
+ if(left.op == Oname && left.decl.ty == tany){
+ if(nto == nil)
+ nto = tto = talloc(right.ty, nil);
+ left = nto;
+ nto = nil;
+ }
+ if(left.op == Oinds){
+ indsascom(src, nto, n);
+ tfree(tto);
+ break;
+ }
+ if(left.op == Oslice){
+ slicelcom(src, nto, n);
+ tfree(tto);
+ break;
+ }
+
+ if(left.op == Otuple){
+ if(!tupsaliased(right, left)){
+ if((tn := tupblk(left)) != nil){
+ tn.ty = n.ty;
+ ecom(n.right.src, tn, right);
+ if(nto != nil)
+ genmove(src, Mas, n.ty, tn, nto);
+ tfree(tto);
+ break;
+ }
+ if((tn = tupblk(right)) != nil){
+ tn.ty = n.ty;
+ tuplcom(tn, left);
+ if(nto != nil)
+ genmove(src, Mas, n.ty, tn, nto);
+ tfree(tto);
+ break;
+ }
+ if(nto == nil && right.op == Otuple && left.ty.kind != Tadtpick){
+ tuplrcom(right, left);
+ tfree(tto);
+ break;
+ }
+ }
+ if(right.addable >= Ralways
+ || right.op != Oname
+ || tupaliased(right, left)){
+ tright = talloc(n.ty, nil);
+ ecom(n.right.src, tright, right);
+ right = tright;
+ }
+ tuplcom(right, n.left);
+ if(nto != nil)
+ genmove(src, Mas, n.ty, right, nto);
+ tfree(tright);
+ tfree(tto);
+ break;
+ }
+
+ #
+ # check for left/right aliasing and build right into temporary
+ #
+ if(right.op == Otuple){
+ if(!tupsaliased(left, right) && (tn := tupblk(right)) != nil){
+ tn.ty = n.ty;
+ right = tn;
+ }
+ else if(left.op != Oname || tupaliased(left, right))
+ right = ecom(right.src, tright = talloc(right.ty, nil), right);
+ }
+
+ #
+ # think carefully about types here
+ #
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ ecom(n.src, left, right);
+ if(nto != nil)
+ genmove(src, Mas, nto.ty, left, nto);
+ tfree(tleft);
+ tfree(tright);
+ tfree(tto);
+ Ochan =>
+ if(left != nil && left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ genchan(src, left, n.ty.tof, nto);
+ tfree(tleft);
+ Oinds =>
+ if(right.addable < Ralways){
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ }else if(left.temps <= right.temps){
+ right = ecom(right.src, tright = talloc(right.ty, nil), right);
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ }else{
+ (left, tleft) = eacom(left, nil);
+ right = ecom(right.src, tright = talloc(right.ty, nil), right);
+ }
+ genop(n.src, op, left, right, nto);
+ tfree(tleft);
+ tfree(tright);
+ Osnd =>
+ if(right.addable < Rcant){
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ }else if(left.temps < right.temps){
+ (right, tright) = eacom(right, nto);
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ }else{
+ (left, tleft) = eacom(left, nto);
+ (right, tright) = eacom(right, nil);
+ }
+ p = genrawop(n.src, ISEND, right, nil, left);
+ p.m.offset = n.ty.size; # for optimizer
+ if(nto != nil)
+ genmove(src, Mas, right.ty, right, nto);
+ tfree(tleft);
+ tfree(tright);
+ Orcv =>
+ if(nto == nil){
+ ecom(n.src, tto = talloc(n.ty, nil), n);
+ tfree(tto);
+ return nil;
+ }
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ if(left.ty.kind == Tchan){
+ p = genrawop(src, IRECV, left, nil, nto);
+ p.m.offset = n.ty.size; # for optimizer
+ }else{
+ recvacom(src, nto, n);
+ }
+ tfree(tleft);
+ Ocons =>
+ #
+ # another temp which can go with analysis
+ #
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ if(!sameaddr(right, nto)){
+ ecom(right.src, tto = talloc(n.ty, nto), right);
+ genmove(src, Mcons, left.ty, left, tto);
+ if(!sameaddr(tto, nto))
+ genmove(src, Mas, nto.ty, tto, nto);
+ }else
+ genmove(src, Mcons, left.ty, left, nto);
+ tfree(tleft);
+ tfree(tto);
+ Ohd =>
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ genmove(src, Mhd, nto.ty, left, nto);
+ tfree(tleft);
+ Otl =>
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ genmove(src, Mtl, left.ty, left, nto);
+ tfree(tleft);
+ Otuple =>
+ if((tn := tupblk(n)) != nil){
+ tn.ty = n.ty;
+ genmove(src, Mas, n.ty, tn, nto);
+ break;
+ }
+ tupcom(nto, n);
+ Oadd or
+ Osub or
+ Omul or
+ Odiv or
+ Omod or
+ Oand or
+ Oor or
+ Oxor or
+ Olsh or
+ Orsh or
+ Oexp =>
+ #
+ # check for 2 operand forms
+ #
+ if(sameaddr(nto, left)){
+ if(right.addable >= Rcant)
+ (right, tright) = eacom(right, nto);
+ genop(src, op, right, nil, nto);
+ tfree(tright);
+ break;
+ }
+
+ if(opcommute[op] && sameaddr(nto, right) && n.ty != tstring){
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ genop(src, opcommute[op], left, nil, nto);
+ tfree(tleft);
+ break;
+ }
+
+ if(right.addable < left.addable
+ && opcommute[op]
+ && n.ty != tstring){
+ op = opcommute[op];
+ left = right;
+ right = n.left;
+ }
+ if(left.addable < Ralways){
+ if(right.addable >= Rcant)
+ (right, tright) = eacom(right, nto);
+ }else if(right.temps <= left.temps){
+ left = ecom(left.src, tleft = talloc(left.ty, nto), left);
+ if(right.addable >= Rcant)
+ (right, tright) = eacom(right, nil);
+ }else{
+ (right, tright) = eacom(right, nto);
+ left = ecom(left.src, tleft = talloc(left.ty, nil), left);
+ }
+
+ #
+ # check for 2 operand forms
+ #
+ if(sameaddr(nto, left))
+ genop(src, op, right, nil, nto);
+ else if(opcommute[op] && sameaddr(nto, right) && n.ty != tstring)
+ genop(src, opcommute[op], left, nil, nto);
+ else
+ genop(src, op, right, left, nto);
+ tfree(tleft);
+ tfree(tright);
+ Oaddas or
+ Osubas or
+ Omulas or
+ Odivas or
+ Omodas or
+ Oexpas or
+ Oandas or
+ Ooras or
+ Oxoras or
+ Olshas or
+ Orshas =>
+ if(left.op == Oinds){
+ indsascom(src, nto, n);
+ break;
+ }
+ if(right.addable < Rcant){
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ }else if(left.temps < right.temps){
+ (right, tright) = eacom(right, nto);
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ }else{
+ (left, tleft) = eacom(left, nto);
+ (right, tright) = eacom(right, nil);
+ }
+ genop(n.src, op, right, nil, left);
+ if(nto != nil)
+ genmove(src, Mas, left.ty, left, nto);
+ tfree(tleft);
+ tfree(tright);
+ Olen =>
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ op = -1;
+ t = left.ty;
+ if(t == tstring)
+ op = ILENC;
+ else if(t.kind == Tarray)
+ op = ILENA;
+ else if(t.kind == Tlist)
+ op = ILENL;
+ else
+ fatal("can't len "+nodeconv(n));
+ genrawop(src, op, left, nil, nto);
+ tfree(tleft);
+ Oneg =>
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ genop(n.src, op, left, nil, nto);
+ tfree(tleft);
+ Oinc or
+ Odec =>
+ if(left.op == Oinds){
+ indsascom(src, nto, n);
+ break;
+ }
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ if(nto != nil)
+ genmove(src, Mas, left.ty, left, nto);
+ if(right.addable >= Rcant)
+ fatal("inc/dec amount not addressable: "+nodeconv(n));
+ genop(n.src, op, right, nil, left);
+ tfree(tleft);
+ Ospawn =>
+ if(left.left.op == Oind)
+ fpcall(n.src, op, left, nto);
+ else
+ callcom(n.src, op, left, nto);
+ Oraise =>
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ genrawop(n.src, IRAISE, left, nil, nil);
+ tfree(tleft);
+ Ocall =>
+ if(left.op == Oind)
+ fpcall(esrc(src, n.src, nto), op, n, nto);
+ else
+ callcom(esrc(src, n.src, nto), op, n, nto);
+ Oref =>
+ t = left.ty;
+ if(left.op == Oname && left.decl.store == Dfn || left.op == Omdot && left.right.op == Oname && left.right.decl.store == Dfn){ # create a function reference
+ mod, ind: ref Node;
+
+ d := left.decl;
+ if(left.op == Omdot){
+ d = left.right.decl;
+ mod = left.left;
+ }
+ else if(d.eimport != nil)
+ mod = d.eimport;
+ else{
+ mod = rewrite(mkn(Oself, nil, nil));
+ addiface(nil, d);
+ }
+ sumark(mod);
+ tto = talloc(n.ty, nto);
+ genrawop(src, INEW, mktn(usetype(tfnptr)), nil, tto);
+ tright = ref znode;
+ tright.src = src;
+ tright.op = Oind;
+ tright.left = tto;
+ tright.right = nil;
+ tright.ty = tany;
+ sumark(tright);
+ ecom(src, tright, mod);
+ ind = mkunary(Oind, mkbin(Oadd, dupn(0, src, tto), mkconst(src, big IBY2WD)));
+ ind.ty = ind.left.ty = ind.left.right.ty = tint;
+ tright.op = Oas;
+ tright.left = ind;
+ tright.right = mkdeclname(src, d);
+ tright.ty = tright.right.ty = tint;
+ sumark(tright);
+ if(mod.op == Oself && newfnptr)
+ tright.right.addable = Rnoff;
+ else
+ tright.right.addable = Roff;
+ ecom(src, nil, tright);
+ if(!sameaddr(tto, nto))
+ genmove(src, Mas, n.ty, tto, nto);
+ tfree(tto);
+ break;
+ }
+ if(left.op == Oname && left.decl.store == Dtype){
+ genrawop(src, INEW, mktn(t), nil, nto);
+ break;
+ }
+ if(t.kind == Tadt && t.tags != nil){
+ pickdupcom(src, nto, left);
+ break;
+ }
+
+ tt := t;
+ if(left.op == Oconst && left.decl.store == Dtag)
+ t = left.decl.ty.tof;
+
+ #
+ # could eliminate temp if nto does not occur
+ # in tuple initializer
+ #
+ tto = talloc(n.ty, nto);
+ genrawop(src, INEW, mktn(t), nil, tto);
+ tright = ref znode;
+ tright.op = Oind;
+ tright.left = tto;
+ tright.right = nil;
+ tright.ty = tt;
+ sumark(tright);
+ ecom(src, tright, left);
+ if(!sameaddr(tto, nto))
+ genmove(src, Mas, n.ty, tto, nto);
+ tfree(tto);
+ Oload =>
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ tright = talloc(tint, nil);
+ if(LDT)
+ genrawop(src, ILOAD, left, right, nto);
+ else{
+ genrawop(src, ILEA, right, nil, tright);
+ genrawop(src, ILOAD, left, tright, nto);
+ }
+ tfree(tleft);
+ tfree(tright);
+ Ocast =>
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ t = left.ty;
+ if(t.kind == Tfix || n.ty.kind == Tfix){
+ op = casttab[t.kind][n.ty.kind];
+ if(op == ICVTXX)
+ genfixcastop(src, op, left, nto);
+ else{
+ ttn = sumark(mkrconst(src, scale2(t, n.ty)));
+ genrawop(src, op, left, ttn, nto);
+ }
+ }
+ else
+ genrawop(src, casttab[t.kind][n.ty.kind], left, nil, nto);
+ tfree(tleft);
+ Oarray =>
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ if(arrayz)
+ genrawop(esrc(src, left.src, nto), INEWAZ, left, mktn(n.ty.tof), nto);
+ else
+ genrawop(esrc(src, left.src, nto), INEWA, left, mktn(n.ty.tof), nto);
+ if(right != nil)
+ arraycom(nto, right);
+ tfree(tleft);
+ Oslice =>
+ tn := right.right;
+ right = right.left;
+
+ #
+ # make the left node of the slice directly addressable
+ # therefore, if it's len is taken (via tn),
+ # left's tree won't be rewritten
+ #
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+
+ if(tn.op == Onothing){
+ tn = mkn(Olen, left, nil);
+ tn.src = src;
+ tn.ty = tint;
+ sumark(tn);
+ }
+ if(tn.addable < Ralways){
+ if(right.addable >= Rcant)
+ (right, tright) = eacom(right, nil);
+ }else if(right.temps <= tn.temps){
+ tn = ecom(tn.src, ttn = talloc(tn.ty, nil), tn);
+ if(right.addable >= Rcant)
+ (right, tright) = eacom(right, nil);
+ }else{
+ (right, tright) = eacom(right, nil);
+ tn = ecom(tn.src, ttn = talloc(tn.ty, nil), tn);
+ }
+ op = ISLICEA;
+ if(nto.ty == tstring)
+ op = ISLICEC;
+
+ #
+ # overwrite the destination last,
+ # since it might be used in computing the slice bounds
+ #
+ if(!sameaddr(left, nto))
+ ecom(left.src, nto, left);
+
+ genrawop(src, op, right, tn, nto);
+ tfree(tleft);
+ tfree(tright);
+ tfree(ttn);
+ Oindx =>
+ if(right.addable < Rcant){
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ }else if(left.temps < right.temps){
+ (right, tright) = eacom(right, nto);
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ }else{
+ (left, tleft) = eacom(left, nto);
+ (right, tright) = eacom(right, nil);
+ }
+ if(nto.addable >= Ralways)
+ nto = ecom(src, tto = talloc(nto.ty, nil), nto);
+ op = IINDX;
+ case left.ty.tof.size{
+ IBY2LG =>
+ op = IINDL;
+ if(left.ty.tof == treal)
+ op = IINDF;
+ IBY2WD =>
+ op = IINDW;
+ 1 =>
+ op = IINDB;
+ }
+ genrawop(src, op, left, nto, right);
+ if(tleft != nil && tleft.decl != nil)
+ tfreelater(tleft);
+ else
+ tfree(tleft);
+ tfree(tright);
+ tfree(tto);
+ Oind =>
+ (n, tleft) = eacom(n, nto);
+ genmove(src, Mas, n.ty, n, nto);
+ tfree(tleft);
+ Onot or
+ Oandand or
+ Ooror or
+ Oeq or
+ Oneq or
+ Olt or
+ Oleq or
+ Ogt or
+ Ogeq =>
+ p = bcom(n, 1, nil);
+ genmove(src, Mas, tint, sumark(mkconst(src, big 1)), nto);
+ pp := genrawop(src, IJMP, nil, nil, nil);
+ patch(p, nextinst());
+ genmove(src, Mas, tint, sumark(mkconst(src, big 0)), nto);
+ patch(pp, nextinst());
+ Oself =>
+ if(newfnptr){
+ if(nto != nil)
+ genrawop(src, ISELF, nil, nil, nto);
+ break;
+ }
+ tn := sumark(mkdeclname(src, selfdecl));
+ p = genbra(src, Oneq, tn, sumark(mkdeclname(src, nildecl)));
+ n.op = Oload;
+ ecom(src, tn, n);
+ patch(p, nextinst());
+ genmove(src, Mas, n.ty, tn, nto);
+ }
+ return nto;
+}
+
+#
+# compile exp n to yield an addressable expression
+# use reg to build a temporary; if t is a temp, it is usable
+#
+# note that 0adr's are strange as they are only used
+# for calculating the addresses of fields within adt's.
+# therefore an Oind is the parent or grandparent of the Oadr,
+# and we pick off all of the cases where Oadr's argument is not
+# addressable by looking from the Oind.
+#
+eacom(n, t: ref Node): (ref Node, ref Node)
+{
+ reg: ref Node;
+
+ if(n.op == Ocomma){
+ tn := n.left.left;
+ ecom(n.left.src, nil, n.left);
+ nn := eacom(n.right, t);
+ tfree(tn);
+ return nn;
+ }
+
+ if(debug['e'] || debug['E'])
+ print("eacom: %s\n", nodeconv(n));
+
+ left := n.left;
+ if(n.op != Oind){
+ ecom(n.src, reg = talloc(n.ty, t), n);
+ reg.src = n.src;
+ return (reg, reg);
+ }
+
+ if(left.op == Oadd && left.right.op == Oconst){
+ if(left.left.op == Oadr){
+ (left.left.left, reg) = eacom(left.left.left, t);
+ sumark(n);
+ if(n.addable >= Rcant)
+ fatal("eacom can't make node addressable: "+nodeconv(n));
+ return (n, reg);
+ }
+ reg = talloc(left.left.ty, t);
+ ecom(left.left.src, reg, left.left);
+ left.left.decl = reg.decl;
+ left.left.addable = Rreg;
+ left.left = reg;
+ left.addable = Raadr;
+ n.addable = Radr;
+ }else if(left.op == Oadr){
+ reg = talloc(left.left.ty, t);
+ ecom(left.left.src, reg, left.left);
+
+ #
+ # sleaze: treat the temp as the type of the field, not the enclosing structure
+ #
+ reg.ty = n.ty;
+ reg.src = n.src;
+ return (reg, reg);
+ }else{
+ reg = talloc(left.ty, t);
+ ecom(left.src, reg, left);
+ n.left = reg;
+ n.addable = Radr;
+ }
+ return (n, reg);
+}
+
+#
+# compile an assignment to an array slice
+#
+slicelcom(src: Src, nto, n: ref Node): ref Node
+{
+ tleft, tright, tv: ref Node;
+
+ left := n.left.left;
+ right := n.left.right.left;
+ v := n.right;
+ if(right.addable < Ralways){
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ }else if(left.temps <= right.temps){
+ right = ecom(right.src, tright = talloc(right.ty, nto), right);
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ }else{
+ (left, tleft) = eacom(left, nil); # dangle on right and v
+ right = ecom(right.src, tright = talloc(right.ty, nil), right);
+ }
+
+ case n.op{
+ Oas =>
+ if(v.addable >= Rcant)
+ (v, tv) = eacom(v, nil);
+ }
+
+ genrawop(n.src, ISLICELA, v, right, left);
+ if(nto != nil)
+ genmove(src, Mas, n.ty, left, nto);
+ tfree(tleft);
+ tfree(tv);
+ tfree(tright);
+ return nto;
+}
+
+#
+# compile an assignment to a string location
+#
+indsascom(src: Src, nto, n: ref Node): ref Node
+{
+ tleft, tright, tv, tu, u: ref Node;
+
+ left := n.left.left;
+ right := n.left.right;
+ v := n.right;
+ if(right.addable < Ralways){
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nto);
+ }else if(left.temps <= right.temps){
+ right = ecom(right.src, tright = talloc(right.ty, nto), right);
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ }else{
+ (left, tleft) = eacom(left, nil); # dangle on right and v
+ right = ecom(right.src, tright = talloc(right.ty, nil), right);
+ }
+
+ case n.op{
+ Oas =>
+ if(v.addable >= Rcant)
+ (v, tv) = eacom(v, nil);
+ Oinc or
+ Odec =>
+ if(v.addable >= Rcant)
+ fatal("inc/dec amount not addable");
+ u = tu = talloc(tint, nil);
+ genop(n.left.src, Oinds, left, right, u);
+ if(nto != nil)
+ genmove(src, Mas, n.ty, u, nto);
+ nto = nil;
+ genop(n.src, n.op, v, nil, u);
+ v = u;
+ Oaddas or
+ Osubas or
+ Omulas or
+ Odivas or
+ Omodas or
+ Oexpas or
+ Oandas or
+ Ooras or
+ Oxoras or
+ Olshas or
+ Orshas =>
+ if(v.addable >= Rcant)
+ (v, tv) = eacom(v, nil);
+ u = tu = talloc(tint, nil);
+ genop(n.left.src, Oinds, left, right, u);
+ genop(n.src, n.op, v, nil, u);
+ v = u;
+ }
+
+ genrawop(n.src, IINSC, v, right, left);
+ tfree(tleft);
+ tfree(tv);
+ tfree(tright);
+ tfree(tu);
+ if(nto != nil)
+ genmove(src, Mas, n.ty, v, nto);
+ return nto;
+}
+
+callcom(src: Src, op: int, n, ret: ref Node)
+{
+ tmod, tind: ref Node;
+ callee: ref Decl;
+
+ args := n.right;
+ nfn := n.left;
+ case(nfn.op){
+ Odot =>
+ callee = nfn.right.decl;
+ nfn.addable = Rpc;
+ Omdot =>
+ callee = nfn.right.decl;
+ Oname =>
+ callee = nfn.decl;
+ * =>
+ callee = nil;
+ fatal("bad call op in callcom");
+ }
+ if(nfn.addable != Rpc && nfn.addable != Rmpc)
+ fatal("can't gen call addresses");
+ if(nfn.ty.tof != tnone && ret == nil){
+ ecom(src, tmod = talloc(nfn.ty.tof, nil), n);
+ tfree(tmod);
+ return;
+ }
+ if(ispoly(callee))
+ addfnptrs(callee, 0);
+ if(nfn.ty.varargs != byte 0){
+ d := dupdecl(nfn.right.decl);
+ nfn.decl = d;
+ d.desc = gendesc(d, idoffsets(nfn.ty.ids, MaxTemp, MaxAlign), nfn.ty.ids);
+ }
+
+ frame := talloc(tint, nil);
+
+ mod := nfn.left;
+ ind := nfn.right;
+ if(nfn.addable == Rmpc){
+ if(mod.addable >= Rcant)
+ (mod, tmod) = eacom(mod, nil); # dangle always
+ if(ind.op != Oname && ind.addable >= Ralways){
+ tind = talloc(ind.ty, nil);
+ ecom(ind.src, tind, ind);
+ ind = tind;
+ }
+ else if(ind.decl != nil && ind.decl.store != Darg)
+ ind.addable = Roff;
+ }
+
+ #
+ # stop nested uncalled frames
+ # otherwise exception handling very complicated
+ #
+ for(a := args; a != nil; a = a.right){
+ if(hascall(a.left)){
+ tn := talloc(a.left.ty, nil);
+ ecom(a.left.src, tn, a.left);
+ a.left = tn;
+ tn.flags |= byte TEMP;
+ }
+ }
+
+ #
+ # allocate the frame
+ #
+ if(nfn.addable == Rmpc && nfn.ty.varargs == byte 0){
+ genrawop(src, IMFRAME, mod, ind, frame);
+ }else if(nfn.op == Odot){
+ genrawop(src, IFRAME, nfn.left, nil, frame);
+ }else{
+ in := genrawop(src, IFRAME, nil, nil, frame);
+ in.sm = Adesc;
+ in.s.decl = nfn.decl;
+ }
+
+ #
+ # build a fake node for the argument area
+ #
+ toff := ref znode;
+ tadd := ref znode;
+ pass := ref znode;
+ toff.op = Oconst;
+ toff.c = ref Const(big 0, 0.0); # jrf - added initialization
+ toff.addable = Rconst;
+ toff.ty = tint;
+ tadd.op = Oadd;
+ tadd.addable = Raadr;
+ tadd.left = frame;
+ tadd.right = toff;
+ tadd.ty = tint;
+ pass.op = Oind;
+ pass.addable = Radr;
+ pass.left = tadd;
+
+ #
+ # compile all the args
+ #
+ d := nfn.ty.ids;
+ off := 0;
+ for(a = args; a != nil; a = a.right){
+ off = d.offset;
+ toff.c.val = big off;
+ if(d.ty.kind == Tpoly)
+ pass.ty = a.left.ty;
+ else
+ pass.ty = d.ty;
+ ecom(a.left.src, pass, a.left);
+ d = d.next;
+ if(int a.left.flags & TEMP)
+ tfree(a.left);
+ }
+ if(off > maxstack)
+ maxstack = off;
+
+ #
+ # pass return value
+ #
+ if(ret != nil){
+ toff.c.val = big(REGRET*IBY2WD);
+ pass.ty = nfn.ty.tof;
+ p := genrawop(src, ILEA, ret, nil, pass);
+ p.m.offset = ret.ty.size; # for optimizer
+ }
+
+ #
+ # call it
+ #
+ iop: int;
+ if(nfn.addable == Rmpc){
+ iop = IMCALL;
+ if(op == Ospawn)
+ iop = IMSPAWN;
+ genrawop(src, iop, frame, ind, mod);
+ tfree(tmod);
+ tfree(tind);
+ }else if(nfn.op == Odot){
+ iop = ICALL;
+ if(op == Ospawn)
+ iop = ISPAWN;
+ genrawop(src, iop, frame, nil, nfn.right);
+ }else{
+ iop = ICALL;
+ if(op == Ospawn)
+ iop = ISPAWN;
+ in := genrawop(src, iop, frame, nil, nil);
+ in.d.decl = nfn.decl;
+ in.dm = Apc;
+ }
+ tfree(frame);
+}
+
+#
+# initialization code for arrays
+# a must be addressable (< Rcant)
+#
+arraycom(a, elems: ref Node)
+{
+ top, out: ref Inst;
+ ri, n, wild: ref Node;
+
+ if(debug['A'])
+ print("arraycom: %s %s\n", nodeconv(a), nodeconv(elems));
+
+ # c := elems.ty.cse;
+ # don't use c.wild in case we've been inlined
+ wild = nil;
+ for(e := elems; e != nil; e = e.right)
+ for(q := e.left.left; q != nil; q = q.right)
+ if(q.left.op == Owild)
+ wild = e.left;
+ if(wild != nil)
+ arraydefault(a, wild.right);
+
+ tindex := ref znode;
+ fake := ref znode;
+ tmp := talloc(tint, nil);
+ tindex.op = Oindx;
+ tindex.addable = Rcant;
+ tindex.left = a;
+ tindex.right = nil;
+ tindex.ty = tint;
+ fake.op = Oind;
+ fake.addable = Radr;
+ fake.left = tmp;
+ fake.ty = a.ty.tof;
+
+ for(e = elems; e != nil; e = e.right){
+ #
+ # just duplicate the initializer for Oor
+ #
+ for(q = e.left.left; q != nil; q = q.right){
+ if(q.left.op == Owild)
+ continue;
+
+ body := e.left.right;
+ if(q.right != nil)
+ body = dupn(0, nosrc, body);
+ top = nil;
+ out = nil;
+ ri = nil;
+ if(q.left.op == Orange){
+ #
+ # for(i := q.left.left; i <= q.left.right; i++)
+ #
+ ri = talloc(tint, nil);
+ ri.src = q.left.src;
+ ecom(q.left.src, ri, q.left.left);
+
+ # i <= q.left.right;
+ n = mkn(Oleq, ri, q.left.right);
+ n.src = q.left.src;
+ n.ty = tint;
+ top = nextinst();
+ out = bcom(n, 1, nil);
+
+ tindex.right = ri;
+ }else{
+ tindex.right = q.left;
+ }
+
+ tindex.addable = Rcant;
+ tindex.src = q.left.src;
+ ecom(tindex.src, tmp, tindex);
+
+ ecom(body.src, fake, body);
+
+ if(q.left.op == Orange){
+ # i++
+ n = mkbin(Oinc, ri, sumark(mkconst(ri.src, big 1)));
+ n.ty = tint;
+ n.addable = Rcant;
+ ecom(n.src, nil, n);
+
+ # jump to test
+ patch(genrawop(q.left.src, IJMP, nil, nil, nil), top);
+ patch(out, nextinst());
+ tfree(ri);
+ }
+ }
+ }
+ tfree(tmp);
+}
+
+#
+# default initialization code for arrays.
+# compiles to
+# n = len a;
+# while(n){
+# n--;
+# a[n] = elem;
+# }
+#
+arraydefault(a, elem: ref Node)
+{
+ e: ref Node;
+
+ if(debug['A'])
+ print("arraydefault: %s %s\n", nodeconv(a), nodeconv(elem));
+
+ t := mkn(Olen, a, nil);
+ t.src = elem.src;
+ t.ty = tint;
+ t.addable = Rcant;
+ n := talloc(tint, nil);
+ n.src = elem.src;
+ ecom(t.src, n, t);
+
+ top := nextinst();
+ out := bcom(n, 1, nil);
+
+ t = mkbin(Odec, n, sumark(mkconst(elem.src, big 1)));
+ t.ty = tint;
+ t.addable = Rcant;
+ ecom(t.src, nil, t);
+
+ if(elem.addable >= Rcant)
+ (elem, e) = eacom(elem, nil);
+
+ t = mkn(Oindx, a, n);
+ t.src = elem.src;
+ t = mkbin(Oas, mkunary(Oind, t), elem);
+ t.ty = elem.ty;
+ t.left.ty = elem.ty;
+ t.left.left.ty = tint;
+ sumark(t);
+ ecom(t.src, nil, t);
+
+ patch(genrawop(t.src, IJMP, nil, nil, nil), top);
+
+ tfree(n);
+ tfree(e);
+ patch(out, nextinst());
+}
+
+tupcom(nto, n: ref Node)
+{
+ if(debug['Y'])
+ print("tupcom %s\nto %s\n", nodeconv(n), nodeconv(nto));
+
+ #
+ # build a fake node for the tuple
+ #
+ toff := ref znode;
+ tadd := ref znode;
+ fake := ref znode;
+ tadr := ref znode;
+ toff.op = Oconst;
+ toff.c = ref Const(big 0, 0.0); # no val => may get fatal error below (jrf)
+ toff.ty = tint;
+ tadr.op = Oadr;
+ tadr.left = nto;
+ tadr.ty = tint;
+ tadd.op = Oadd;
+ tadd.left = tadr;
+ tadd.right = toff;
+ tadd.ty = tint;
+ fake.op = Oind;
+ fake.left = tadd;
+ sumark(fake);
+ if(fake.addable >= Rcant)
+ fatal("tupcom: bad value exp "+nodeconv(fake));
+
+ #
+ # compile all the exps
+ #
+ d := n.ty.ids;
+ for(e := n.left; e != nil; e = e.right){
+ toff.c.val = big d.offset;
+ fake.ty = d.ty;
+ ecom(e.left.src, fake, e.left);
+ d = d.next;
+ }
+}
+
+tuplcom(n, nto: ref Node)
+{
+ if(debug['Y'])
+ print("tuplcom %s\nto %s\n", nodeconv(n), nodeconv(nto));
+
+ #
+ # build a fake node for the tuple
+ #
+ toff := ref znode;
+ tadd := ref znode;
+ fake := ref znode;
+ tadr := ref znode;
+ toff.op = Oconst;
+ toff.c = ref Const(big 0, 0.0); # no val => may get fatal error below (jrf)
+ toff.ty = tint;
+ tadr.op = Oadr;
+ tadr.left = n;
+ tadr.ty = tint;
+ tadd.op = Oadd;
+ tadd.left = tadr;
+ tadd.right = toff;
+ tadd.ty = tint;
+ fake.op = Oind;
+ fake.left = tadd;
+ sumark(fake);
+ if(fake.addable >= Rcant)
+ fatal("tuplcom: bad value exp for "+nodeconv(fake));
+
+ #
+ # compile all the exps
+ #
+ tas := ref znode;
+ d := nto.ty.ids;
+ if(nto.ty.kind == Tadtpick)
+ d = nto.ty.tof.ids.next;
+ for(e := nto.left; e != nil; e = e.right){
+ as := e.left;
+ if(as.op != Oname || as.decl != nildecl){
+ toff.c.val = big d.offset;
+ fake.ty = d.ty;
+ fake.src = as.src;
+ if(as.addable < Rcant)
+ genmove(as.src, Mas, d.ty, fake, as);
+ else{
+ tas.op = Oas;
+ tas.ty = d.ty;
+ tas.src = as.src;
+ tas.left = as;
+ tas.right = fake;
+ tas.addable = Rcant;
+ ecom(as.src, nil, tas);
+ }
+ }
+ d = d.next;
+ }
+}
+
+tuplrcom(n: ref Node, nto: ref Node)
+{
+ s, d, tas: ref Node;
+ de: ref Decl;
+
+ tas = ref znode;
+ de = nto.ty.ids;
+ for((s, d) = (n.left, nto.left); s != nil && d != nil; (s, d) = (s.right, d.right)){
+ if(d.left.op != Oname || d.left.decl != nildecl){
+ tas.op = Oas;
+ tas.ty = de.ty;
+ tas.src = s.left.src;
+ tas.left = d.left;
+ tas.right = s.left;
+ sumark(tas);
+ ecom(tas.src, nil, tas);
+ }
+ de = de.next;
+ }
+ if(s != nil || d != nil)
+ fatal("tuplrcom");
+}
+
+#
+# boolean compiler
+# fall through when condition == true
+#
+bcom(n: ref Node, true: int, b: ref Inst): ref Inst
+{
+ tleft, tright: ref Node;
+
+ if(n.op == Ocomma){
+ tn := n.left.left;
+ ecom(n.left.src, nil, n.left);
+ b = bcom(n.right, true, b);
+ tfree(tn);
+ return b;
+ }
+
+ if(debug['b'])
+ print("bcom %s %d\n", nodeconv(n), true);
+
+ left := n.left;
+ right := n.right;
+ op := n.op;
+ case op{
+ Onothing =>
+ return b;
+ Onot =>
+ return bcom(n.left, !true, b);
+ Oandand =>
+ if(!true)
+ return oror(n, true, b);
+ return andand(n, true, b);
+ Ooror =>
+ if(!true)
+ return andand(n, true, b);
+ return oror(n, true, b);
+ Ogt or
+ Ogeq or
+ Oneq or
+ Oeq or
+ Olt or
+ Oleq =>
+ break;
+ * =>
+ if(n.ty.kind == Tint){
+ right = mkconst(n.src, big 0);
+ right.addable = Rconst;
+ left = n;
+ op = Oneq;
+ break;
+ }
+ fatal("can't bcom "+nodeconv(n));
+ return b;
+ }
+
+ if(true)
+ op = oprelinvert[op];
+
+ if(left.addable < right.addable){
+ t := left;
+ left = right;
+ right = t;
+ op = opcommute[op];
+ }
+
+ if(right.addable < Ralways){
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ }else if(left.temps <= right.temps){
+ right = ecom(right.src, tright = talloc(right.ty, nil), right);
+ if(left.addable >= Rcant)
+ (left, tleft) = eacom(left, nil);
+ }else{
+ (left, tleft) = eacom(left, nil);
+ right = ecom(right.src, tright = talloc(right.ty, nil), right);
+ }
+ bb := genbra(n.src, op, left, right);
+ bb.branch = b;
+ tfree(tleft);
+ tfree(tright);
+ return bb;
+}
+
+andand(n: ref Node, true: int, b: ref Inst): ref Inst
+{
+ if(debug['b'])
+ print("andand %s\n", nodeconv(n));
+ b = bcom(n.left, true, b);
+ b = bcom(n.right, true, b);
+ return b;
+}
+
+oror(n: ref Node, true: int, b: ref Inst): ref Inst
+{
+ if(debug['b'])
+ print("oror %s\n", nodeconv(n));
+ bb := bcom(n.left, !true, nil);
+ b = bcom(n.right, true, b);
+ patch(bb, nextinst());
+ return b;
+}
+
+#
+# generate code for a recva expression
+# this is just a hacked up small alt
+#
+recvacom(src: Src, nto, n: ref Node)
+{
+ p: ref Inst;
+
+ left := n.left;
+
+ labs := array[1] of Label;
+ labs[0].isptr = left.addable >= Rcant;
+ c := ref Case;
+ c.nlab = 1;
+ c.nsnd = 0;
+ c.offset = 0;
+ c.labs = labs;
+ talt := mktalt(c);
+
+ which := talloc(tint, nil);
+ tab := talloc(talt, nil);
+
+ #
+ # build the node for the address of each channel,
+ # the values to send, and the storage for values received
+ #
+ off := ref znode;
+ adr := ref znode;
+ add := ref znode;
+ slot := ref znode;
+ off.op = Oconst;
+ off.c = ref Const(big 0, 0.0); # jrf - added initialization
+ off.ty = tint;
+ off.addable = Rconst;
+ adr.op = Oadr;
+ adr.left = tab;
+ adr.ty = tint;
+ add.op = Oadd;
+ add.left = adr;
+ add.right = off;
+ add.ty = tint;
+ slot.op = Oind;
+ slot.left = add;
+ sumark(slot);
+
+ #
+ # gen the channel
+ # this sleaze is lying to the garbage collector
+ #
+ off.c.val = big(2*IBY2WD);
+ if(left.addable < Rcant)
+ genmove(src, Mas, tint, left, slot);
+ else{
+ slot.ty = left.ty;
+ ecom(src, slot, left);
+ slot.ty = nil;
+ }
+
+ #
+ # gen the value
+ #
+ off.c.val += big IBY2WD;
+ p = genrawop(left.src, ILEA, nto, nil, slot);
+ p.m.offset = nto.ty.size; # for optimizer
+
+ #
+ # number of senders and receivers
+ #
+ off.c.val = big 0;
+ genmove(src, Mas, tint, sumark(mkconst(src, big 0)), slot);
+ off.c.val += big IBY2WD;
+ genmove(src, Mas, tint, sumark(mkconst(src, big 1)), slot);
+ off.c.val += big IBY2WD;
+
+ p = genrawop(src, IALT, tab, nil, which);
+ p.m.offset = talt.size; # for optimizer
+ tfree(which);
+ tfree(tab);
+}
+
+#
+# generate code to duplicate an adt with pick fields
+# this is just a hacked up small pick
+# n is Oind(exp)
+#
+pickdupcom(src: Src, nto, n: ref Node)
+{
+ jmps: ref Inst;
+
+ if(n.op != Oind)
+ fatal("pickdupcom not Oind: " + nodeconv(n));
+
+ t := n.ty;
+ nlab := t.decl.tag;
+
+ #
+ # generate global which has case labels
+ #
+ d := mkids(src, enter(".c"+string nlabel++, 0), mktype(src.start, src.stop, Tcase, nil, nil), nil);
+ d.init = mkdeclname(src, d);
+
+ clab := ref znode;
+ clab.addable = Rmreg;
+ clab.left = nil;
+ clab.right = nil;
+ clab.op = Oname;
+ clab.ty = d.ty;
+ clab.decl = d;
+
+ #
+ # generate a temp to hold the real value
+ # then generate a case on the tag
+ #
+ orig := n.left;
+ tmp := talloc(orig.ty, nil);
+ ecom(src, tmp, orig);
+ orig = mkunary(Oind, tmp);
+ orig.ty = tint;
+ sumark(orig);
+
+ dest := mkunary(Oind, nto);
+ dest.ty = nto.ty.tof;
+ sumark(dest);
+
+ genrawop(src, ICASE, orig, nil, clab);
+
+ labs := array[nlab] of Label;
+
+ i := 0;
+ jmps = nil;
+ for(tg := t.tags; tg != nil; tg = tg.next){
+ stg := tg;
+ for(; tg.next != nil; tg = tg.next)
+ if(stg.ty != tg.next.ty)
+ break;
+ start := sumark(simplify(mkdeclname(src, stg)));
+ stop := start;
+ node := start;
+ if(stg != tg){
+ stop = sumark(simplify(mkdeclname(src, tg)));
+ node = mkbin(Orange, start, stop);
+ }
+
+ labs[i].start = start;
+ labs[i].stop = stop;
+ labs[i].node = node;
+ labs[i++].inst = nextinst();
+
+ genrawop(src, INEW, mktn(tg.ty.tof), nil, nto);
+ genmove(src, Mas, tg.ty.tof, orig, dest);
+
+ j := genrawop(src, IJMP, nil, nil, nil);
+ j.branch = jmps;
+ jmps = j;
+ }
+
+ #
+ # this should really be a runtime error
+ #
+ wild := genrawop(src, IJMP, nil, nil, nil);
+ patch(wild, wild);
+
+ patch(jmps, nextinst());
+ tfree(tmp);
+
+ if(i > nlab)
+ fatal("overflowed label tab for pickdupcom");
+
+ c := ref Case;
+ c.nlab = i;
+ c.nsnd = 0;
+ c.labs = labs;
+ c.iwild = wild;
+
+ d.ty.cse = c;
+ usetype(d.ty);
+ installids(Dglobal, d);
+}
+
+#
+# see if name n occurs anywhere in e
+#
+tupaliased(n, e: ref Node): int
+{
+ for(;;){
+ if(e == nil)
+ return 0;
+ if(e.op == Oname && e.decl == n.decl)
+ return 1;
+ if(tupaliased(n, e.left))
+ return 1;
+ e = e.right;
+ }
+ return 0;
+}
+
+#
+# see if any name in n occurs anywere in e
+#
+tupsaliased(n, e: ref Node): int
+{
+ for(;;){
+ if(n == nil)
+ return 0;
+ if(n.op == Oname && tupaliased(n, e))
+ return 1;
+ if(tupsaliased(n.left, e))
+ return 1;
+ n = n.right;
+ }
+ return 0;
+}
+
+#
+# put unaddressable constants in the global data area
+#
+globalconst(n: ref Node): ref Decl
+{
+ s := enter(".i." + hex(int n.c.val, 8), 0);
+ d := s.decl;
+ if(d == nil){
+ d = mkids(n.src, s, tint, nil);
+ installids(Dglobal, d);
+ d.init = n;
+ d.refs++;
+ }
+ return d;
+}
+
+globalBconst(n: ref Node): ref Decl
+{
+ s := enter(".B." + bhex(n.c.val, 16), 0);
+ d := s.decl;
+ if(d == nil){
+ d = mkids(n.src, s, tbig, nil);
+ installids(Dglobal, d);
+ d.init = n;
+ d.refs++;
+ }
+ return d;
+}
+
+globalbconst(n: ref Node): ref Decl
+{
+ s := enter(".b." + hex(int n.c.val & 16rff, 2), 0);
+ d := s.decl;
+ if(d == nil){
+ d = mkids(n.src, s, tbyte, nil);
+ installids(Dglobal, d);
+ d.init = n;
+ d.refs++;
+ }
+ return d;
+}
+
+globalfconst(n: ref Node): ref Decl
+{
+ ba := array[8] of byte;
+ export_real(ba, array[] of {n.c.rval});
+ fs := ".f.";
+ for(i := 0; i < 8; i++)
+ fs += hex(int ba[i], 2);
+ if(fs != ".f." + bhex(math->realbits64(n.c.rval), 16))
+ fatal("bad globalfconst number");
+ s := enter(fs, 0);
+ d := s.decl;
+ if(d == nil){
+ d = mkids(n.src, s, treal, nil);
+ installids(Dglobal, d);
+ d.init = n;
+ d.refs++;
+ }
+ return d;
+}
+
+globalsconst(n: ref Node): ref Decl
+{
+ s := n.decl.sym;
+ n.decl = nil;
+ d := s.decl;
+ if(d == nil){
+ d = mkids(n.src, s, tstring, nil);
+ installids(Dglobal, d);
+ d.init = n;
+ }
+ d.refs++;
+ n.decl = d;
+ return d;
+}
+
+#
+# make a global of type t
+# used to make initialized data
+#
+globalztup(t: ref Type): ref Decl
+{
+ z := ".z." + string t.size + ".";
+ desc := t.decl.desc;
+ for(i := 0; i < desc.nmap; i++)
+ z += hex(int desc.map[i], 2);
+ s := enter(z, 0);
+ d := s.decl;
+ if(d == nil){
+ d = mkids(t.src, s, t, nil);
+ installids(Dglobal, d);
+ d.init = nil;
+ }
+ d.refs++;
+ return d;
+}
+
+subst(d: ref Decl, e: ref Node, n: ref Node): ref Node
+{
+ if(n == nil)
+ return nil;
+ if(n.op == Oname){
+ if(d == n.decl){
+ n = dupn(0, nosrc, e);
+ n.ty = d.ty;
+ }
+ return n;
+ }
+ n.left = subst(d, e, n.left);
+ n.right = subst(d, e, n.right);
+ return n;
+}
+
+inline(n: ref Node): ref Node
+{
+ e, tn: ref Node;
+ t: ref Type;
+ d: ref Decl;
+
+if(debug['z']) sys->print("inline1: %s\n", nodeconv(n));
+ if(n.left.op == Oname)
+ d = n.left.decl;
+ else
+ d = n.left.right.decl;
+ e = d.init;
+ t = e.ty;
+ e = dupn(1, n.src, e.right.left.left);
+ n = n.right;
+ for(d = t.ids; d != nil && n != nil; d = d.next){
+ if(hasside(n.left, 0) && occurs(d, e) != 1){
+ tn = talloc(d.ty, nil);
+ e = mkbin(Ocomma, mkbin(Oas, tn, n.left), subst(d, tn, e));
+ e.ty = e.right.ty;
+ e.left.ty = d.ty;
+ }
+ else
+ e = subst(d, n.left, e);
+ n = n.right;
+ }
+ if(d != nil || n != nil)
+ fatal("bad arg match in inline()");
+if(debug['z']) sys->print("inline2: %s\n", nodeconv(e));
+ return e;
+}
+
+fpcall(src: Src, op: int, n: ref Node, ret: ref Node)
+{
+ tp, e, mod, ind: ref Node;
+
+ e = n.left.left;
+ if(e.addable >= Rcant)
+ (e, tp) = eacom(e, nil);
+ mod = mkunary(Oind, e);
+ ind = mkunary(Oind, mkbin(Oadd, dupn(0, src, e), mkconst(src, big IBY2WD)));
+ n.left = mkbin(Omdot, mod, ind);
+ n.left.ty = e.ty.tof;
+ mod.ty = ind.ty = ind.left.ty = ind.left.right.ty = tint;
+ sumark(n);
+ callcom(src, op, n, ret);
+ tfree(tp);
+}
diff --git a/appl/cmd/limbo/gen.b b/appl/cmd/limbo/gen.b
new file mode 100644
index 00000000..062980fb
--- /dev/null
+++ b/appl/cmd/limbo/gen.b
@@ -0,0 +1,1012 @@
+ blocks: int; # nesting of blocks while generating code
+ zinst: Inst;
+ firstinst: ref Inst;
+ lastinst: ref Inst;
+
+include "disoptab.m";
+
+addrmode := array[int Rend] of
+{
+ int Rreg => Afp,
+ int Rmreg => Amp,
+ int Roff => Aoff,
+ int Rnoff => Anoff,
+ int Rdesc => Adesc,
+ int Rdescp => Adesc,
+ int Rconst => Aimm,
+ int Radr => Afpind,
+ int Rmadr => Ampind,
+ int Rpc => Apc,
+ int Rldt => Aldt,
+ * => Aerr,
+};
+
+wtemp: ref Decl;
+bigtemp: ref Decl;
+ntemp: int;
+retnode: ref Node;
+nilnode: ref Node;
+
+blockstack: array of int;
+blockdep: int;
+nblocks: int;
+ntoz: ref Node;
+
+#znode: Node;
+
+genstart()
+{
+ d := mkdecl(nosrc, Dlocal, tint);
+ d.sym = enter(".ret", 0);
+ d.offset = IBY2WD * REGRET;
+
+ retnode = ref znode;
+ retnode.op = Oname;
+ retnode.addable = Rreg;
+ retnode.decl = d;
+ retnode.ty = tint;
+
+ zinst.op = INOP;
+ zinst.sm = Anone;
+ zinst.dm = Anone;
+ zinst.mm = Anone;
+
+ firstinst = ref zinst;
+ lastinst = firstinst;
+
+ nilnode = ref znode;
+ nilnode.op = Oname;
+ nilnode.addable = Rmreg;
+ nilnode.decl = nildecl;
+ nilnode.ty = nildecl.ty;
+
+ blocks = -1;
+ blockdep = 0;
+ nblocks = 0;
+}
+
+#
+# manage nested control flow blocks
+#
+pushblock(): int
+{
+ if(blockdep >= len blockstack){
+ bs := array[blockdep + 32] of int;
+ bs[0:] = blockstack;
+ blockstack = bs;
+ }
+ blockstack[blockdep++] = blocks;
+ return blocks = nblocks++;
+}
+
+repushblock(b: int)
+{
+ blockstack[blockdep++] = blocks;
+ blocks = b;
+}
+
+popblock()
+{
+ blocks = blockstack[blockdep -= 1];
+}
+
+tinit()
+{
+ wtemp = nil;
+ bigtemp = nil;
+}
+
+tdecls(): ref Decl
+{
+ for(d := wtemp; d != nil; d = d.next){
+ if(d.tref != 1)
+ fatal("temporary "+d.sym.name+" has "+string(d.tref-1)+" references");
+ }
+
+ for(d = bigtemp; d != nil; d = d.next){
+ if(d.tref != 1)
+ fatal("temporary "+d.sym.name+" has "+string(d.tref-1)+" references");
+ }
+
+ return appdecls(wtemp, bigtemp);
+}
+
+talloc(t: ref Type, nok: ref Node): ref Node
+{
+ ok, d: ref Decl;
+
+ ok = nil;
+ if(nok != nil)
+ ok = nok.decl;
+ if(ok == nil || ok.tref == 0 || tattr[ok.ty.kind].isbig != tattr[t.kind].isbig || ok.ty.align != t.align)
+ ok = nil;
+ n := ref znode;
+ n.op = Oname;
+ n.addable = Rreg;
+ n.ty = t;
+ if(tattr[t.kind].isbig){
+ desc := mktdesc(t);
+ if(ok != nil && ok.desc == desc){
+ ok.tref++;
+ ok.refs++;
+ n.decl = ok;
+ return n;
+ }
+ for(d = bigtemp; d != nil; d = d.next){
+ if(d.tref == 1 && d.desc == desc && d.ty.align == t.align){
+ d.tref++;
+ d.refs++;
+ n.decl = d;
+ return n;
+ }
+ }
+ d = mkdecl(nosrc, Dlocal, t);
+ d.desc = desc;
+ d.tref = 2;
+ d.refs = 1;
+ d.sym = enter(".b"+string ntemp++, 0);
+ d.next = bigtemp;
+ bigtemp = d;
+ n.decl = d;
+ return n;
+ }
+ if(ok != nil
+ && tattr[ok.ty.kind].isptr == tattr[t.kind].isptr
+ && ok.ty.size == t.size){
+ ok.tref++;
+ n.decl = ok;
+ return n;
+ }
+ for(d = wtemp; d != nil; d = d.next){
+ if(d.tref == 1
+ && tattr[d.ty.kind].isptr == tattr[t.kind].isptr
+ && d.ty.size == t.size
+ && d.ty.align == t.align){
+ d.tref++;
+ n.decl = d;
+ return n;
+ }
+ }
+ d = mkdecl(nosrc, Dlocal, t);
+ d.tref = 2;
+ d.refs = 1;
+ d.sym = enter(".t"+string ntemp++, 0);
+ d.next = wtemp;
+ wtemp = d;
+ n.decl = d;
+ return n;
+}
+
+tfree(n: ref Node)
+{
+ if(n == nil || n.decl == nil)
+ return;
+ d := n.decl;
+ if(d.tref == 0)
+ return;
+
+ if(d.tref == 1)
+ fatal("double free of temporary " + d.sym.name);
+ if (--d.tref == 1)
+ zcom1(n, nil);
+
+ #
+ # nil out any pointers so we don't
+ # hang onto references
+ #
+#
+# costs ~7% in instruction count
+# if(d.tref != 1)
+# return;
+# if(!tattr[d.ty.kind].isbig){
+# if(tattr[d.ty.kind].isptr){ # or tmustzero()
+# nilnode.decl.refs++;
+# genmove(lastinst.src, Mas, d.ty, nilnode, n);
+# }
+# }else{
+# if(d.desc.nmap != 0){ # tmustzero() is better
+# zn := ref znode;
+# zn.op = Oname;
+# zn.addable = Rmreg;
+# zn.decl = globalztup(d.ty);
+# zn.ty = d.ty;
+# genmove(lastinst.src, Mas, d.ty, zn, n);
+# }
+# }
+}
+
+tfreelater(n: ref Node)
+{
+ if(n == nil || n.decl == nil)
+ return;
+ d := n.decl;
+ if(d.tref == 0)
+ return;
+
+ if(d.tref == 1)
+ fatal("double free of temporary " + d.sym.name);
+ if (--d.tref == 1){
+ nn := mkn(Oname, nil, nil);
+ *nn = *n;
+ nn.left = ntoz;
+ ntoz = nn;
+ d.tref++;
+ }
+}
+
+tfreenow()
+{
+ nn: ref Node;
+
+ for(n := ntoz; n != nil; n = nn){
+ nn = n.left;
+ n.left = nil;
+ if(n.decl.tref != 2)
+ fatal(sprint("bad free of temporary %s", n.decl.sym.name));
+ --n.decl.tref;
+ zcom1(n, nil);
+ }
+ ntoz = nil;
+}
+
+#
+# realloc a temporary after it's been released
+#
+tacquire(n: ref Node): ref Node
+{
+ if(n == nil || n.decl == nil)
+ return n;
+ d := n.decl;
+ if(d.tref == 0)
+ return n;
+ # if(d.tref != 1)
+ # fatal("tacquire ref != 1: "+string d.tref);
+ d.tref++;
+ return n;
+}
+
+trelease(n: ref Node)
+{
+ if(n == nil || n.decl == nil)
+ return;
+ d := n.decl;
+ if(d.tref == 0)
+ return;
+ if(d.tref == 1)
+ fatal("double release of temporary " + d.sym.name);
+ d.tref--;
+}
+
+mkinst(): ref Inst
+{
+ in := lastinst.next;
+ if(in == nil){
+ in = ref zinst;
+ lastinst.next = in;
+ }
+ lastinst = in;
+ in.block = blocks;
+ if(blocks < 0)
+ fatal("mkinst no block");
+ return in;
+}
+
+nextinst(): ref Inst
+{
+ in := lastinst.next;
+ if(in != nil)
+ return in;
+ in = ref zinst;
+ lastinst.next = in;
+ return in;
+}
+
+#
+# allocate a node for returning
+#
+retalloc(n, nn: ref Node): ref Node
+{
+ if(nn.ty == tnone)
+ return nil;
+ n = ref znode;
+ n.op = Oind;
+ n.addable = Radr;
+ n.left = dupn(1, n.src, retnode);
+ n.ty = nn.ty;
+ return n;
+}
+
+genrawop(src: Src, op: int, s, m, d: ref Node): ref Inst
+{
+ in := mkinst();
+ in.op = op;
+ in.src = src;
+ if(s != nil){
+ in.s = genaddr(s);
+ in.sm = addrmode[int s.addable];
+ }
+ if(m != nil){
+ in.m = genaddr(m);
+ in.mm = addrmode[int m.addable];
+ if(in.mm == Ampind || in.mm == Afpind)
+ fatal("illegal addressing mode in register "+nodeconv(m));
+ }
+ if(d != nil){
+ in.d = genaddr(d);
+ in.dm = addrmode[int d.addable];
+ }
+ return in;
+}
+
+genop(src: Src, op: int, s, m, d: ref Node): ref Inst
+{
+ iop := disoptab[op][opind[d.ty.kind]];
+ if(iop == 0)
+ fatal("can't deal with op "+opconv(op)+" on "+nodeconv(s)+" "+nodeconv(m)+" "+nodeconv(d)+" in genop");
+ if(iop == IMULX || iop == IDIVX)
+ return genfixop(src, iop, s, m, d);
+ in := mkinst();
+ in.op = iop;
+ in.src = src;
+ if(s != nil){
+ in.s = genaddr(s);
+ in.sm = addrmode[int s.addable];
+ }
+ if(m != nil){
+ in.m = genaddr(m);
+ in.mm = addrmode[int m.addable];
+ if(in.mm == Ampind || in.mm == Afpind)
+ fatal("illegal addressing mode in register "+nodeconv(m));
+ }
+ if(d != nil){
+ in.d = genaddr(d);
+ in.dm = addrmode[int d.addable];
+ }
+ return in;
+}
+
+genbra(src: Src, op: int, s, m: ref Node): ref Inst
+{
+ t := s.ty;
+ if(t == tany)
+ t = m.ty;
+ iop := disoptab[op][opind[t.kind]];
+ if(iop == 0)
+ fatal("can't deal with op "+opconv(op)+" on "+nodeconv(s)+" "+nodeconv(m)+" in genbra");
+ in := mkinst();
+ in.op = iop;
+ in.src = src;
+ if(s != nil){
+ in.s = genaddr(s);
+ in.sm = addrmode[int s.addable];
+ }
+ if(m != nil){
+ in.m = genaddr(m);
+ in.mm = addrmode[int m.addable];
+ if(in.mm == Ampind || in.mm == Afpind)
+ fatal("illegal addressing mode in register "+nodeconv(m));
+ }
+ return in;
+}
+
+genchan(src: Src, sz: ref Node, mt: ref Type, d: ref Node): ref Inst
+{
+ reg: Addr;
+
+ regm := Anone;
+ reg.decl = nil;
+ reg.reg = 0;
+ reg.offset = 0;
+ op := chantab[mt.kind];
+ if(op == 0)
+ fatal("can't deal with op "+string mt.kind+" in genchan");
+
+ case mt.kind{
+ Tadt or
+ Tadtpick or
+ Ttuple =>
+ td := mktdesc(mt);
+ if(td.nmap != 0){
+ op++; # sleazy
+ usedesc(td);
+ regm = Adesc;
+ reg.decl = mt.decl;
+ }else{
+ regm = Aimm;
+ reg.offset = mt.size;
+ }
+ }
+ in := mkinst();
+ in.op = op;
+ in.src = src;
+ in.s = reg;
+ in.sm = regm;
+ if(sz != nil){
+ in.m = genaddr(sz);
+ in.mm = addrmode[int sz.addable];
+ }
+ if(d != nil){
+ in.d = genaddr(d);
+ in.dm = addrmode[int d.addable];
+ }
+ return in;
+}
+
+genmove(src: Src, how: int, mt: ref Type, s, d: ref Node): ref Inst
+{
+ reg: Addr;
+
+ regm := Anone;
+ reg.decl = nil;
+ reg.reg = 0;
+ reg.offset = 0;
+ op := movetab[how][mt.kind];
+ if(op == 0)
+ fatal("can't deal with op "+string how+" on "+nodeconv(s)+" "+nodeconv(d)+" in genmove");
+
+ case mt.kind{
+ Tadt or
+ Tadtpick or
+ Ttuple or
+ Texception =>
+ if(mt.size == 0 && how == Mas)
+ return nil;
+ td := mktdesc(mt);
+ if(td.nmap != 0){
+ op++; # sleazy
+ usedesc(td);
+ regm = Adesc;
+ reg.decl = mt.decl;
+ }else{
+ regm = Aimm;
+ reg.offset = mt.size;
+ }
+ }
+ in := mkinst();
+ in.op = op;
+ in.src = src;
+ if(s != nil){
+ in.s = genaddr(s);
+ in.sm = addrmode[int s.addable];
+ }
+ in.m = reg;
+ in.mm = regm;
+ if(d != nil){
+ in.d = genaddr(d);
+ in.dm = addrmode[int d.addable];
+ }
+ if(s.addable == Rpc)
+ in.op = IMOVPC;
+ return in;
+}
+
+patch(b, dst: ref Inst)
+{
+ n: ref Inst;
+
+ for(; b != nil; b = n){
+ n = b.branch;
+ b.branch = dst;
+ }
+}
+
+getpc(i: ref Inst): int
+{
+ if(i.pc == 0 && i != firstinst && (firstinst.op != INOOP || i != firstinst.next)){
+ do
+ i = i.next;
+ while(i != nil && i.pc == 0);
+ if(i == nil || i.pc == 0)
+ fatal("bad instruction in getpc");
+ }
+ return i.pc;
+}
+
+#
+# follow all possible paths from n,
+# marking reached code, compressing branches, and reclaiming unreached insts
+#
+reach(in: ref Inst)
+{
+ foldbranch(in);
+ last := in;
+ for(in = in.next; in != nil; in = in.next){
+ if(in.reach == byte 0)
+ last.next = in.next;
+ else
+ last = in;
+ }
+ lastinst = last;
+}
+
+foldbranch(in: ref Inst)
+{
+ while(in != nil && in.reach != byte 1){
+ in.reach = byte 1;
+ if(in.branch != nil)
+ while(in.branch.op == IJMP){
+ if(in == in.branch || in.branch == in.branch.branch)
+ break;
+ in.branch = in.branch.branch;
+ }
+ case in.op{
+ IGOTO or
+ ICASE or
+ ICASEL or
+ ICASEC or
+ IEXC =>
+ foldbranch(in.d.decl.ty.cse.iwild);
+ lab := in.d.decl.ty.cse.labs;
+ n := in.d.decl.ty.cse.nlab;
+ for(i := 0; i < n; i++)
+ foldbranch(lab[i].inst);
+ if(in.op == IEXC)
+ in.op = INOOP;
+ return;
+ IEXC0 =>
+ foldbranch(in.branch);
+ in.op = INOOP;
+ break;
+ IRET or
+ IEXIT or
+ IRAISE =>
+ return;
+ IJMP =>
+ b := in.branch;
+ case b.op{
+ ICASE or
+ ICASEL or
+ ICASEC or
+ IRET or
+ IEXIT =>
+ next := in.next;
+ *in = *b;
+ in.next = next;
+ # b.reach = byte 1;
+ continue;
+ }
+ foldbranch(in.branch);
+ return;
+ * =>
+ if(in.branch != nil)
+ foldbranch(in.branch);
+ }
+
+ in = in.next;
+ }
+}
+
+#
+# convert the addressable node into an operand
+# see the comment for sumark
+#
+genaddr(n: ref Node): Addr
+{
+ a: Addr;
+
+ a.reg = 0;
+ a.offset = 0;
+ a.decl = nil;
+ case int n.addable{
+ int Rreg =>
+ if(n.decl != nil)
+ a.decl = n.decl;
+ else
+ a = genaddr(n.left);
+ int Rmreg =>
+ if(n.decl != nil)
+ a.decl = n.decl;
+ else
+ a = genaddr(n.left);
+ int Rdesc =>
+ a.decl = n.ty.decl;
+ int Roff or
+ int Rnoff =>
+ a.decl = n.decl;
+ int Rconst =>
+ a.offset = int n.c.val;
+ int Radr =>
+ a = genaddr(n.left);
+ int Rmadr =>
+ a = genaddr(n.left);
+ int Rareg or
+ int Ramreg =>
+ a = genaddr(n.left);
+ if(n.op == Oadd)
+ a.reg += int n.right.c.val;
+ int Raadr or
+ int Ramadr =>
+ a = genaddr(n.left);
+ if(n.op == Oadd)
+ a.offset += int n.right.c.val;
+ int Rldt =>
+ a.decl = n.decl;
+ int Rdescp or
+ int Rpc =>
+ a.decl = n.decl;
+ * =>
+ fatal("can't deal with "+nodeconv(n)+" in genaddr");
+ }
+ return a;
+}
+
+sameaddr(n, m: ref Node): int
+{
+ if(n.addable != m.addable)
+ return 0;
+ a := genaddr(n);
+ b := genaddr(m);
+ return a.offset == b.offset && a.reg == b.reg && a.decl == b.decl;
+}
+
+resolvedesc(mod: ref Decl, length: int, id: ref Decl): int
+{
+ last: ref Desc;
+
+ g := gendesc(mod, length, id);
+ g.used = 0;
+ last = nil;
+ for(d := descriptors; d != nil; d = d.next){
+ if(!d.used){
+ if(last != nil)
+ last.next = d.next;
+ else
+ descriptors = d.next;
+ continue;
+ }
+ last = d;
+ }
+
+ g.next = descriptors;
+ descriptors = g;
+
+ descid := 0;
+ for(d = descriptors; d != nil; d = d.next)
+ d.id = descid++;
+ if(g.id != 0)
+ fatal("bad global descriptor id");
+
+ return descid;
+}
+
+resolvemod(m: ref Decl): int
+{
+ for(id := m.ty.ids; id != nil; id = id.next){
+ case id.store{
+ Dfn =>
+ id.iface.pc = id.pc;
+ id.iface.desc = id.desc;
+ Dtype =>
+ if(id.ty.kind != Tadt)
+ break;
+ for(d := id.ty.ids; d != nil; d = d.next){
+ if(d.store == Dfn){
+ d.iface.pc = d.pc;
+ d.iface.desc = d.desc;
+ }
+ }
+ }
+ }
+ # for addiface
+ for(id = m.ty.tof.ids; id != nil; id = id.next){
+ if(id.store == Dfn){
+ if(id.pc == nil)
+ id.pc = id.iface.pc;
+ if(id.desc == nil)
+ id.desc = id.iface.desc;
+ }
+ }
+ return int m.ty.tof.decl.init.c.val;
+}
+
+#
+# place the Tiface decs in another list
+#
+resolveldts(d: ref Decl): (ref Decl, ref Decl)
+{
+ d1, ld1, d2, ld2, n: ref Decl;
+
+ d1 = d2 = nil;
+ ld1 = ld2 = nil;
+ for( ; d != nil; d = n){
+ n = d.next;
+ d.next = nil;
+ if(d.ty.kind == Tiface){
+ if(d2 == nil)
+ d2 = d;
+ else
+ ld2.next = d;
+ ld2 = d;
+ }
+ else{
+ if(d1 == nil)
+ d1 = d;
+ else
+ ld1.next = d;
+ ld1 = d;
+ }
+ }
+ return (d1, d2);
+}
+
+#
+# fix up all pc's
+# finalize all data offsets
+# fix up instructions with offsets too large
+#
+resolvepcs(inst: ref Inst): int
+{
+ d: ref Decl;
+
+ pc := 0;
+ for(in := inst; in != nil; in = in.next){
+ if(in.reach == byte 0 || in.op == INOP)
+ fatal("unreachable pc: "+instconv(in));
+ if(in.op == INOOP){
+ in.pc = pc;
+ continue;
+ }
+ d = in.s.decl;
+ if(d != nil){
+ if(in.sm == Adesc){
+ if(d.desc != nil)
+ in.s.offset = d.desc.id;
+ }else
+ in.s.reg += d.offset;
+ }
+ r := in.s.reg;
+ off := in.s.offset;
+ if((in.sm == Afpind || in.sm == Ampind)
+ && (r >= MaxReg || off >= MaxReg))
+ fatal("big offset in "+instconv(in));
+
+ d = in.m.decl;
+ if(d != nil){
+ if(in.mm == Adesc){
+ if(d.desc != nil)
+ in.m.offset = d.desc.id;
+ }else
+ in.m.reg += d.offset;
+ }
+ v := 0;
+ case int in.mm{
+ int Anone =>
+ break;
+ int Aimm or
+ int Apc or
+ int Adesc =>
+ v = in.m.offset;
+ int Aoff or
+ int Anoff =>
+ v = in.m.decl.iface.offset;
+ int Afp or
+ int Amp or
+ int Aldt =>
+ v = in.m.reg;
+ if(v < 0)
+ v = 16r8000;
+ * =>
+ fatal("can't deal with "+instconv(in)+"'s m mode");
+ }
+ if(v > 16r7fff || v < -16r8000){
+ case in.op{
+ IALT or
+ IINDX =>
+ rewritedestreg(in, IMOVW, RTemp);
+ * =>
+ op := IMOVW;
+ if(isbyteinst[in.op])
+ op = IMOVB;
+ in = rewritesrcreg(in, op, RTemp, pc++);
+ }
+ }
+
+ d = in.d.decl;
+ if(d != nil){
+ if(in.dm == Apc)
+ in.d.offset = d.pc.pc;
+ else
+ in.d.reg += d.offset;
+ }
+ r = in.d.reg;
+ off = in.d.offset;
+ if((in.dm == Afpind || in.dm == Ampind)
+ && (r >= MaxReg || off >= MaxReg))
+ fatal("big offset in "+instconv(in));
+
+ in.pc = pc;
+ pc++;
+ }
+ for(in = inst; in != nil; in = in.next){
+ d = in.s.decl;
+ if(d != nil && in.sm == Apc)
+ in.s.offset = d.pc.pc;
+ d = in.d.decl;
+ if(d != nil && in.dm == Apc)
+ in.d.offset = d.pc.pc;
+ if(in.branch != nil){
+ in.dm = Apc;
+ in.d.offset = in.branch.pc;
+ }
+ }
+ return pc;
+}
+
+#
+# fixp up a big register constant uses as a source
+# ugly: smashes the instruction
+#
+rewritesrcreg(in: ref Inst, op: int, treg: int, pc: int): ref Inst
+{
+ a := in.m;
+ am := in.mm;
+ in.mm = Afp;
+ in.m.reg = treg;
+ in.m.decl = nil;
+
+ new := ref *in;
+
+ *in = zinst;
+ in.src = new.src;
+ in.next = new;
+ in.op = op;
+ in.s = a;
+ in.sm = am;
+ in.dm = Afp;
+ in.d.reg = treg;
+ in.pc = pc;
+ in.reach = byte 1;
+ in.block = new.block;
+ return new;
+}
+
+#
+# fix up a big register constant by moving to the destination
+# after the instruction completes
+#
+rewritedestreg(in: ref Inst, op: int, treg: int): ref Inst
+{
+ n := ref zinst;
+ n.next = in.next;
+ in.next = n;
+ n.src = in.src;
+ n.op = op;
+ n.sm = Afp;
+ n.s.reg = treg;
+ n.d = in.m;
+ n.dm = in.mm;
+ n.reach = byte 1;
+ n.block = in.block;
+
+ in.mm = Afp;
+ in.m.reg = treg;
+ in.m.decl = nil;
+
+ return n;
+}
+
+instconv(in: ref Inst): string
+{
+ if(in.op == INOP)
+ return "nop";
+ op := "";
+ if(in.op >= 0 && in.op < 256)
+ op = instname[in.op];
+ if(op == nil)
+ op = "?"+string in.op+"?";
+ s := "\t" + op + "\t";
+ comma := "";
+ if(in.sm != Anone){
+ s += addrconv(in.sm, in.s);
+ comma = ",";
+ }
+ if(in.mm != Anone){
+ s += comma;
+ s += addrconv(in.mm, in.m);
+ comma = ",";
+ }
+ if(in.dm != Anone){
+ s += comma;
+ s += addrconv(in.dm, in.d);
+ }
+
+ if(!asmsym)
+ return s;
+
+ if(in.s.decl != nil && in.sm == Adesc){
+ s += "\t#";
+ s += dotconv(in.s.decl);
+ }
+ if(0 && in.m.decl != nil){
+ s += "\t#";
+ s += dotconv(in.m.decl);
+ }
+ if(in.d.decl != nil && in.dm == Apc){
+ s += "\t#";
+ s += dotconv(in.d.decl);
+ }
+ s += "\t#";
+ s += srcconv(in.src);
+ return s;
+}
+
+addrconv(am: byte, a: Addr): string
+{
+ s := "";
+ case int am{
+ int Anone =>
+ break;
+ int Aimm or
+ int Apc or
+ int Adesc =>
+ s = "$" + string a.offset;
+ int Aoff =>
+ s = "$" + string a.decl.iface.offset;
+ int Anoff =>
+ s = "-$" + string a.decl.iface.offset;
+ int Afp =>
+ s = string a.reg + "(fp)";
+ int Afpind =>
+ s = string a.offset + "(" + string a.reg + "(fp))";
+ int Amp =>
+ s = string a.reg + "(mp)";
+ int Ampind =>
+ s = string a.offset + "(" + string a.reg + "(mp))";
+ int Aldt =>
+ s = "$" + string a.reg;
+ * =>
+ s = string a.offset + "(" + string a.reg + "(?" + string am + "?))";
+ }
+ return s;
+}
+
+genstore(src: Src, n: ref Node, offset: int)
+{
+ de := mkdecl(nosrc, Dlocal, tint);
+ de.sym = nil;
+ de.offset = offset;
+
+ d := ref znode;
+ d.op = Oname;
+ d.addable = Rreg;
+ d.decl = de;
+ d.ty = tint;
+ genrawop(src, IMOVW, n, nil, d);
+}
+
+genfixop(src: Src, op: int, s, m, d: ref Node): ref Inst
+{
+ p, a: int;
+ mm: ref Node;
+
+ if(m == nil)
+ mm = d;
+ else
+ mm = m;
+ (op, p, a) = fixop(op, mm.ty, s.ty, d.ty);
+ if(op == IMOVW){ # just zero d
+ s = sumark(mkconst(src, big 0));
+ return genrawop(src, op, s, nil, d);
+ }
+ if(op != IMULX && op != IDIVX)
+ genstore(src, sumark(mkconst(src, big a)), STemp);
+ genstore(src, sumark(mkconst(src, big p)), DTemp);
+ i := genrawop(src, op, s, m, d);
+ return i;
+}
+
+genfixcastop(src: Src, op: int, s, d: ref Node): ref Inst
+{
+ p, a: int;
+ m: ref Node;
+
+ (op, p, a) = fixop(op, s.ty, tint, d.ty);
+ if(op == IMOVW){ # just zero d
+ s = sumark(mkconst(src, big 0));
+ return genrawop(src, op, s, nil, d);
+ }
+ m = sumark(mkconst(src, big p));
+ if(op != ICVTXX)
+ genstore(src, sumark(mkconst(src, big a)), STemp);
+ return genrawop(src, op, s, m, d);
+}
diff --git a/appl/cmd/limbo/isa.m b/appl/cmd/limbo/isa.m
new file mode 100644
index 00000000..9e9936d0
--- /dev/null
+++ b/appl/cmd/limbo/isa.m
@@ -0,0 +1,247 @@
+#
+# VM instruction set
+#
+ INOP,
+ IALT,
+ INBALT,
+ IGOTO,
+ ICALL,
+ IFRAME,
+ ISPAWN,
+ IRUNT,
+ ILOAD,
+ IMCALL,
+ IMSPAWN,
+ IMFRAME,
+ IRET,
+ IJMP,
+ ICASE,
+ IEXIT,
+ INEW,
+ INEWA,
+ INEWCB,
+ INEWCW,
+ INEWCF,
+ INEWCP,
+ INEWCM,
+ INEWCMP,
+ ISEND,
+ IRECV,
+ ICONSB,
+ ICONSW,
+ ICONSP,
+ ICONSF,
+ ICONSM,
+ ICONSMP,
+ IHEADB,
+ IHEADW,
+ IHEADP,
+ IHEADF,
+ IHEADM,
+ IHEADMP,
+ ITAIL,
+ ILEA,
+ IINDX,
+ IMOVP,
+ IMOVM,
+ IMOVMP,
+ IMOVB,
+ IMOVW,
+ IMOVF,
+ ICVTBW,
+ ICVTWB,
+ ICVTFW,
+ ICVTWF,
+ ICVTCA,
+ ICVTAC,
+ ICVTWC,
+ ICVTCW,
+ ICVTFC,
+ ICVTCF,
+ IADDB,
+ IADDW,
+ IADDF,
+ ISUBB,
+ ISUBW,
+ ISUBF,
+ IMULB,
+ IMULW,
+ IMULF,
+ IDIVB,
+ IDIVW,
+ IDIVF,
+ IMODW,
+ IMODB,
+ IANDB,
+ IANDW,
+ IORB,
+ IORW,
+ IXORB,
+ IXORW,
+ ISHLB,
+ ISHLW,
+ ISHRB,
+ ISHRW,
+ IINSC,
+ IINDC,
+ IADDC,
+ ILENC,
+ ILENA,
+ ILENL,
+ IBEQB,
+ IBNEB,
+ IBLTB,
+ IBLEB,
+ IBGTB,
+ IBGEB,
+ IBEQW,
+ IBNEW,
+ IBLTW,
+ IBLEW,
+ IBGTW,
+ IBGEW,
+ IBEQF,
+ IBNEF,
+ IBLTF,
+ IBLEF,
+ IBGTF,
+ IBGEF,
+ IBEQC,
+ IBNEC,
+ IBLTC,
+ IBLEC,
+ IBGTC,
+ IBGEC,
+ ISLICEA,
+ ISLICELA,
+ ISLICEC,
+ IINDW,
+ IINDF,
+ IINDB,
+ INEGF,
+ IMOVL,
+ IADDL,
+ ISUBL,
+ IDIVL,
+ IMODL,
+ IMULL,
+ IANDL,
+ IORL,
+ IXORL,
+ ISHLL,
+ ISHRL,
+ IBNEL,
+ IBLTL,
+ IBLEL,
+ IBGTL,
+ IBGEL,
+ IBEQL,
+ ICVTLF,
+ ICVTFL,
+ ICVTLW,
+ ICVTWL,
+ ICVTLC,
+ ICVTCL,
+ IHEADL,
+ ICONSL,
+ INEWCL,
+ ICASEC,
+ IINDL,
+ IMOVPC,
+ ITCMP,
+ IMNEWZ,
+ ICVTRF,
+ ICVTFR,
+ ICVTWS,
+ ICVTSW,
+ ILSRW,
+ ILSRL,
+ IECLR,
+ INEWZ,
+ INEWAZ,
+ IRAISE,
+ ICASEL,
+ IMULX,
+ IDIVX,
+ ICVTXX,
+ IMULX0,
+ IDIVX0,
+ ICVTXX0,
+ IMULX1,
+ IDIVX1,
+ ICVTXX1,
+ ICVTFX,
+ ICVTXF,
+ IEXPW,
+ IEXPL,
+ IEXPF,
+ ISELF,
+ # add new operators here
+ MAXDIS: con iota;
+
+XMAGIC: con 819248; # Normal magic
+SMAGIC: con 923426; # Signed module
+
+AMP: con 16r00; # Src/Dst op addressing
+AFP: con 16r01;
+AIMM: con 16r2;
+AXXX: con 16r03;
+AIND: con 16r04;
+AMASK: con 16r07;
+AOFF: con 16r08;
+AVAL: con 16r10;
+
+ARM: con 16rC0; # Middle op addressing
+AXNON: con 16r00;
+AXIMM: con 16r40;
+AXINF: con 16r80;
+AXINM: con 16rC0;
+
+DEFZ: con 0;
+DEFB: con 1; # Byte
+DEFW: con 2; # Word
+DEFS: con 3; # Utf-string
+DEFF: con 4; # Real value
+DEFA: con 5; # Array
+DIND: con 6; # Set index
+DAPOP: con 7; # Restore address register
+DEFL: con 8; # BIG
+
+DADEPTH: con 4; # Array address stack size
+
+REGLINK: con 0;
+REGFRAME: con 1;
+REGMOD: con 2;
+REGTYP: con 3;
+REGRET: con 4;
+NREG: con 5;
+
+IBY2WD: con 4;
+IBY2FT: con 8;
+IBY2LG: con 8;
+
+MUSTCOMPILE: con 1<<0;
+DONTCOMPILE: con 1<<1;
+SHAREMP: con 1<<2;
+DYNMOD: con 1<<3;
+HASLDT0: con 1<<4;
+HASEXCEPT: con 1<<5;
+HASLDT: con 1<<6;
+
+DMAX: con 1 << 4;
+
+#define DTYPE(x) (x>>4)
+#define DBYTE(x, l) ((x<<4)|l)
+#define DMAX (1<<4)
+#define DLEN(x) (x& (DMAX-1))
+
+DBYTE: con 4;
+SRC: con 3;
+DST: con 0;
+
+#define SRC(x) ((x)<<3)
+#define DST(x) ((x)<<0)
+#define USRC(x) (((x)>>3)&AMASK)
+#define UDST(x) ((x)&AMASK)
+#define UXSRC(x) ((x)&(AMASK<<3))
+#define UXDST(x) ((x)&(AMASK<<0))
diff --git a/appl/cmd/limbo/lex.b b/appl/cmd/limbo/lex.b
new file mode 100644
index 00000000..ae87b4a9
--- /dev/null
+++ b/appl/cmd/limbo/lex.b
@@ -0,0 +1,1146 @@
+Leof: con -1;
+Linestart: con 0;
+
+Mlower,
+Mupper,
+Munder,
+Mdigit,
+Msign,
+Mexp,
+Mhex,
+Mradix: con byte 1 << iota;
+Malpha: con Mupper|Mlower|Munder;
+
+HashSize: con 1024;
+
+Keywd: adt
+{
+ name: string;
+ token: int;
+};
+
+#
+# internals
+#
+savec: int;
+files: array of ref File; # files making up the module, sorted by absolute line
+nfiles: int;
+lastfile := 0; # index of last file looked up
+incpath := array[MaxIncPath] of string;
+symbols := array[HashSize] of ref Sym;
+strings := array[HashSize] of ref Sym;
+map := array[256] of byte;
+bins := array [MaxInclude] of ref Iobuf;
+bin: ref Iobuf;
+linestack := array[MaxInclude] of (int, int);
+lineno: int;
+linepos: int;
+bstack: int;
+lasttok: int;
+lastyylval: YYSTYPE;
+dowarn: int;
+maxerr: int;
+dosym: int;
+toterrors: int;
+fabort: int;
+srcdir: string;
+outfile: string;
+stderr: ref Sys->FD;
+dontinline: int;
+
+escmap := array[256] of
+{
+ '\'' => '\'',
+ '"' => '"',
+ '\\' => '\\',
+ 'a' => '\a',
+ 'b' => '\b',
+ 'f' => '\f',
+ 'n' => '\n',
+ 'r' => '\r',
+ 't' => '\t',
+ 'v' => '\v',
+ '0' => '\u0000',
+
+ * => -1
+};
+unescmap := array[256] of
+{
+ '\'' => '\'',
+ '"' => '"',
+ '\\' => '\\',
+ '\a' => 'a',
+ '\b' => 'b',
+ '\f' => 'f',
+ '\n' => 'n',
+ '\r' => 'r',
+ '\t' => 't',
+ '\v' => 'v',
+ '\u0000' => '0',
+
+ * => 0
+};
+
+keywords := array [] of
+{
+ Keywd("adt", Ladt),
+ Keywd("alt", Lalt),
+ Keywd("array", Larray),
+ Keywd("big", Ltid),
+ Keywd("break", Lbreak),
+ Keywd("byte", Ltid),
+ Keywd("case", Lcase),
+ Keywd("chan", Lchan),
+ Keywd("con", Lcon),
+ Keywd("continue", Lcont),
+ Keywd("cyclic", Lcyclic),
+ Keywd("do", Ldo),
+ Keywd("else", Lelse),
+ Keywd("exception", Lexcept),
+ Keywd("exit", Lexit),
+ Keywd("fixed", Lfix),
+ Keywd("fn", Lfn),
+ Keywd("for", Lfor),
+ Keywd("hd", Lhd),
+ Keywd("if", Lif),
+ Keywd("implement", Limplement),
+ Keywd("import", Limport),
+ Keywd("include", Linclude),
+ Keywd("int", Ltid),
+ Keywd("len", Llen),
+ Keywd("list", Llist),
+ Keywd("load", Lload),
+ Keywd("module", Lmodule),
+ Keywd("nil", Lnil),
+ Keywd("of", Lof),
+ Keywd("or", Lor),
+ Keywd("pick", Lpick),
+ Keywd("raise", Lraise),
+ Keywd("raises", Lraises),
+ Keywd("real", Ltid),
+ Keywd("ref", Lref),
+ Keywd("return", Lreturn),
+ Keywd("self", Lself),
+ Keywd("spawn", Lspawn),
+ Keywd("string", Ltid),
+ Keywd("tagof", Ltagof),
+ Keywd("tl", Ltl),
+ Keywd("to", Lto),
+ Keywd("type", Ltype),
+ Keywd("while", Lwhile),
+};
+
+tokwords := array[] of
+{
+ Keywd("&=", Landeq),
+ Keywd("|=", Loreq),
+ Keywd("^=", Lxoreq),
+ Keywd("<<=", Llsheq),
+ Keywd(">>=", Lrsheq),
+ Keywd("+=", Laddeq),
+ Keywd("-=", Lsubeq),
+ Keywd("*=", Lmuleq),
+ Keywd("/=", Ldiveq),
+ Keywd("%=", Lmodeq),
+ Keywd("**=", Lexpeq),
+ Keywd(":=", Ldeclas),
+ Keywd("||", Loror),
+ Keywd("&&", Landand),
+ Keywd("::", Lcons),
+ Keywd("==", Leq),
+ Keywd("!=", Lneq),
+ Keywd("<=", Lleq),
+ Keywd(">=", Lgeq),
+ Keywd("<<", Llsh),
+ Keywd(">>", Lrsh),
+ Keywd("<-", Lcomm),
+ Keywd("++", Linc),
+ Keywd("--", Ldec),
+ Keywd("->", Lmdot),
+ Keywd("=>", Llabs),
+ Keywd("**", Lexp),
+ Keywd("EOF", Leof),
+};
+
+lexinit()
+{
+ for(i := 0; i < 256; i++){
+ map[i] = byte 0;
+ if(i == '_' || i > 16ra0)
+ map[i] |= Munder;
+ if(i >= 'A' && i <= 'Z')
+ map[i] |= Mupper;
+ if(i >= 'a' && i <= 'z')
+ map[i] |= Mlower;
+ if(i >= 'A' && i <= 'F' || i >= 'a' && i <= 'f')
+ map[i] |= Mhex;
+ if(i == 'e' || i == 'E')
+ map[i] |= Mexp;
+ if(i == 'r' || i == 'R')
+ map[i] |= Mradix;
+ if(i == '-' || i == '+')
+ map[i] |= Msign;
+ if(i >= '0' && i <= '9')
+ map[i] |= Mdigit;
+ }
+
+ for(i = 0; i < len keywords; i++)
+ enter(keywords[i].name, keywords[i].token);
+}
+
+cmap(c: int): byte
+{
+ if(c<0)
+ return byte 0;
+ if(c<256)
+ return map[c];
+ return Mlower;
+}
+
+lexstart(in: string)
+{
+ savec = 0;
+ bstack = 0;
+ nfiles = 0;
+ addfile(ref File(in, 1, 0, -1, nil, 0, -1));
+ bin = bins[bstack];
+ lineno = 1;
+ linepos = Linestart;
+
+ (srcdir, nil) = str->splitr(in, "/");
+}
+
+getc(): int
+{
+ if(c := savec){
+ if(savec >= 0){
+ linepos++;
+ savec = 0;
+ }
+ return c;
+ }
+ c = bin.getc();
+ if(c < 0){
+ savec = -1;
+ return savec;
+ }
+ linepos++;
+ return c;
+}
+
+#
+# dumps '\u0000' chararcters
+#
+ungetc(c: int)
+{
+ if(c > 0)
+ linepos--;
+ savec = c;
+}
+
+addinclude(s: string)
+{
+ for(i := 0; i < MaxIncPath; i++){
+ if(incpath[i] == nil){
+ incpath[i] = s;
+ return;
+ }
+ }
+ fatal("out of include path space");
+}
+
+addfile(f: ref File): int
+{
+ if(lastfile >= nfiles)
+ lastfile = 0;
+ if(nfiles >= len files){
+ nf := array[nfiles+32] of ref File;
+ nf[0:] = files;
+ files = nf;
+ }
+ files[nfiles] = f;
+ return nfiles++;
+}
+
+#
+# include a new file
+#
+includef(file: ref Sym)
+{
+ linestack[bstack] = (lineno, linepos);
+ bstack++;
+ if(bstack >= MaxInclude)
+ fatal(lineconv(lineno<<PosBits)+": include file depth too great");
+ buf := file.name;
+ if(buf[0] != '/')
+ buf = srcdir+buf;
+ b := bufio->open(buf, Bufio->OREAD);
+ for(i := 0; b == nil && i < MaxIncPath && incpath[i] != nil && file.name[0] != '/'; i++){
+ buf = incpath[i] + "/" + file.name;
+ b = bufio->open(buf, Bufio->OREAD);
+ }
+ bins[bstack] = b;
+ if(bins[bstack] == nil){
+ yyerror("can't include "+file.name+": "+sprint("%r"));
+ bstack--;
+ }else{
+ addfile(ref File(buf, lineno+1, -lineno, lineno, nil, 0, -1));
+ lineno++;
+ linepos = Linestart;
+ }
+ bin = bins[bstack];
+}
+
+#
+# we hit eof in the current file
+# revert to the file which included it.
+#
+popinclude()
+{
+ savec = 0;
+ bstack--;
+ bin = bins[bstack];
+ (oline, opos) := linestack[bstack];
+ (f, ln) := fline(oline);
+ lineno++;
+ linepos = opos;
+ addfile(ref File(f.name, lineno, ln-lineno, f.in, f.act, f.actoff, -1));
+}
+
+#
+# convert an absolute Line into a file and line within the file
+#
+fline(absline: int): (ref File, int)
+{
+ if(absline < files[lastfile].abs
+ || lastfile+1 < nfiles && absline >= files[lastfile+1].abs){
+ lastfile = 0;
+ l := 0;
+ r := nfiles - 1;
+ while(l <= r){
+ m := (r + l) / 2;
+ s := files[m].abs;
+ if(s <= absline){
+ l = m + 1;
+ lastfile = m;
+ }else
+ r = m - 1;
+ }
+ }
+ return (files[lastfile], absline + files[lastfile].off);
+}
+
+#
+# read a comment; process #line file renamings
+#
+lexcom(): int
+{
+ i := 0;
+ buf := "";
+ while((c := getc()) != '\n'){
+ if(c == Bufio->EOF)
+ return -1;
+ buf[i++] = c;
+ }
+
+ lineno++;
+ linepos = Linestart;
+
+ if(len buf < 6
+ || buf[len buf - 1] != '"'
+ || buf[:5] != "line " && buf[:5] != "line\t")
+ return 0;
+ for(s := 5; buf[s] == ' ' || buf[s] == '\t'; s++)
+ ;
+ if((cmap(buf[s]) & Mdigit) == byte 0)
+ return 0;
+ n := 0;
+ for(; (cmap(c = buf[s]) & Mdigit) != byte 0; s++)
+ n = n * 10 + c - '0';
+ for(; buf[s] == ' ' || buf[s] == '\t'; s++)
+ ;
+ if(buf[s++] != '"')
+ return 0;
+ buf = buf[s:len buf - 1];
+ f := files[nfiles - 1];
+ if(n == f.off+lineno && buf == f.name)
+ return 1;
+ act := f.name;
+ actline := lineno + f.off;
+ if(f.act != nil){
+ actline += f.actoff;
+ act = f.act;
+ }
+ addfile(ref File(buf, lineno, n-lineno, f.in, act, actline - n, -1));
+
+ return 1;
+}
+
+curline(): Line
+{
+ return (lineno << PosBits) | (linepos & PosMask);
+}
+
+lineconv(line: Line): string
+{
+ line >>= PosBits;
+ if(line < 0)
+ return "<noline>";
+ (f, ln) := fline(line);
+ s := "";
+ if(f.in >= 0){
+ s = ": " + lineconv(f.in << PosBits);
+ }
+ if(f.act != nil)
+ s = " [ " + f.act + ":" + string(f.actoff+ln) + " ]" + s;
+ return f.name + ":" + string ln + s;
+}
+
+posconv(s: Line): string
+{
+ if(s < 0)
+ return "nopos";
+ spos := s & PosMask;
+ s >>= PosBits;
+ (f, ln) := fline(s);
+ return f.name + ":" + string ln + "." + string spos;
+}
+
+srcconv(src: Src): string
+{
+ s := posconv(src.start);
+ s[len s] = ',';
+ s += posconv(src.stop);
+ return s;
+}
+
+lexid(c: int): int
+{
+ id := "";
+ i := 0;
+ for(;;){
+ if(i < StrSize)
+ id[i++] = c;
+ c = getc();
+ if(c == Bufio->EOF
+ || (cmap(c) & (Malpha|Mdigit)) == byte 0){
+ ungetc(c);
+ break;
+ }
+ }
+ sym := enter(id, Lid);
+ t := sym.token;
+ if(t == Lid || t == Ltid)
+ yyctxt.lval.tok.v.idval = sym;
+ return t;
+}
+
+maxfast := array[37] of
+{
+ 2 => 31,
+ 4 => 15,
+ 8 => 10,
+ 10 => 9,
+ 16 => 7,
+ 32 => 6,
+ * => 0,
+};
+
+strtoi(t: string, bbase: big): big
+{
+ #
+ # do the first part in ints
+ #
+ v := 0;
+ bv: big;
+ base := int bbase;
+ n := maxfast[base];
+
+ neg := 0;
+ i := 0;
+ if(i < len t && t[i] == '-'){
+ neg = 1;
+ i++;
+ }else if(i < len t && t[i] == '+')
+ i++;
+
+ for(; i < len t; i++){
+ c := t[i];
+ if(c >= '0' && c <= '9')
+ c -= '0';
+ else if(c >= 'a' && c <= 'z')
+ c -= 'a' - 10;
+ else
+ c -= 'A' - 10;
+ if(c >= base){
+ yyerror("digit '"+t[i:i+1]+"' is not radix "+string base);
+ return big -1;
+ }
+ if(i < n)
+ v = v * base + c;
+ else{
+ if(i == n)
+ bv = big v;
+ bv = bv * bbase + big c;
+ }
+ }
+ if(i <= n)
+ bv = big v;
+ if(neg)
+ return -bv;
+ return bv;
+}
+
+digit(c: int, base: int): int
+{
+ ck: byte;
+ cc: int;
+
+ cc = c;
+ ck = cmap(c);
+ if((ck & Mdigit) != byte 0)
+ c -= '0';
+ else if((ck & Mlower) != byte 0)
+ c = c - 'a' + 10;
+ else if((ck & Mupper) != byte 0)
+ c = c - 'A' + 10;
+ else if((ck & Munder) != byte 0)
+ ;
+ else
+ return -1;
+ if(c >= base){
+ s := "z";
+ s[0] = cc;
+ yyerror("digit '" + s + "' not radix " + string base);
+ }
+ return c;
+}
+
+strtodb(t: string, base: int): real
+{
+ num, dem, rbase: real;
+ neg, eneg, dig, exp, c, d: int;
+
+ t[len t] = 0;
+
+ num = 0.0;
+ rbase = real base;
+ neg = 0;
+ dig = 0;
+ exp = 0;
+ eneg = 0;
+
+ i := 0;
+ c = t[i++];
+ if(c == '-' || c == '+'){
+ if(c == '-')
+ neg = 1;
+ c = t[i++];
+ }
+ while((d = digit(c, base)) >= 0){
+ num = num*rbase + real d;
+ c = t[i++];
+ }
+ if(c == '.')
+ c = t[i++];
+ while((d = digit(c, base)) >= 0){
+ num = num*rbase + real d;
+ dig++;
+ c = t[i++];
+ }
+ if(c == 'e' || c == 'E'){
+ c = t[i++];
+ if(c == '-' || c == '+'){
+ if(c == '-'){
+ dig = -dig;
+ eneg = 1;
+ }
+ c = t[i++];
+ }
+ while((d = digit(c, base)) >= 0){
+ exp = exp*base + d;
+ c = t[i++];
+ }
+ }
+ exp -= dig;
+ if(exp < 0){
+ exp = -exp;
+ eneg = !eneg;
+ }
+ dem = rpow(rbase, exp);
+ if(eneg)
+ num /= dem;
+ else
+ num *= dem;
+ if(neg)
+ return -num;
+ return num;
+}
+
+#
+# parse a numeric identifier
+# format [0-9]+(r[0-9A-Za-z]+)?
+# or ([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?
+#
+lexnum(c: int): int
+{
+ Int, Radix, RadixSeen, Frac, ExpSeen, ExpSignSeen, Exp, FracB: con iota;
+
+ i := 0;
+ buf := "";
+ buf[i++] = c;
+ state := Int;
+ if(c == '.')
+ state = Frac;
+ radix := "";
+
+done: for(;;){
+ c = getc();
+ if(c == Bufio->EOF){
+ yyerror("end of file in numeric constant");
+ return Leof;
+ }
+
+ ck := cmap(c);
+ case state{
+ Int =>
+ if((ck & Mdigit) != byte 0)
+ break;
+ if((ck & Mexp) != byte 0){
+ state = ExpSeen;
+ break;
+ }
+ if((ck & Mradix) != byte 0){
+ radix = buf;
+ buf = "";
+ i = 0;
+ state = RadixSeen;
+ break;
+ }
+ if(c == '.'){
+ state = Frac;
+ break;
+ }
+ break done;
+ RadixSeen or
+ Radix =>
+ if((ck & (Mdigit|Malpha)) != byte 0){
+ state = Radix;
+ break;
+ }
+ if(c == '.'){
+ state = FracB;
+ break;
+ }
+ break done;
+ Frac =>
+ if((ck & Mdigit) != byte 0)
+ break;
+ if((ck & Mexp) != byte 0)
+ state = ExpSeen;
+ else
+ break done;
+ FracB =>
+ if((ck & (Mdigit|Malpha)) != byte 0)
+ break;
+ break done;
+ ExpSeen =>
+ if((ck & Msign) != byte 0){
+ state = ExpSignSeen;
+ break;
+ }
+ if((ck & Mdigit) != byte 0){
+ state = Exp;
+ break;
+ }
+ break done;
+ ExpSignSeen or
+ Exp =>
+ if((ck & Mdigit) != byte 0){
+ state = Exp;
+ break;
+ }
+ break done;
+ }
+ buf[i++] = c;
+ }
+
+ ungetc(c);
+ v: big;
+ case state{
+ * =>
+ yyerror("malformed numerical constant '"+radix+buf+"'");
+ yyctxt.lval.tok.v.ival = big 0;
+ return Lconst;
+ Radix =>
+ v = strtoi(radix, big 10);
+ if(v < big 2 || v > big 36){
+ yyerror("radix '"+radix+"' is not between 2 and 36");
+ break;
+ }
+ v = strtoi(buf[1:], v);
+ Int =>
+ v = strtoi(buf, big 10);
+ Frac or
+ Exp =>
+ yyctxt.lval.tok.v.rval = real buf;
+ return Lrconst;
+ FracB =>
+ v = strtoi(radix, big 10);
+ if(v < big 2 || v > big 36){
+ yyerror("radix '"+radix+"' is not between 2 and 36");
+ break;
+ }
+ yyctxt.lval.tok.v.rval = strtodb(buf[1:], int v);
+ return Lrconst;
+ }
+ yyctxt.lval.tok.v.ival = v;
+ return Lconst;
+}
+
+escchar(): int
+{
+ c := getc();
+ if(c == Bufio->EOF)
+ return Bufio->EOF;
+ if(c == 'u'){
+ v := 0;
+ for(i := 0; i < 4; i++){
+ c = getc();
+ ck := cmap(c);
+ if(c == Bufio->EOF || (ck & (Mdigit|Mhex)) == byte 0){
+ yyerror("malformed \\u escape sequence");
+ ungetc(c);
+ break;
+ }
+ if((ck & Mdigit) != byte 0)
+ c -= '0';
+ else if((ck & Mlower) != byte 0)
+ c = c - 'a' + 10;
+ else if((ck & Mupper) != byte 0)
+ c = c - 'A' + 10;
+ v = v * 16 + c;
+ }
+ return v;
+ }
+ if(c < len escmap && (v := escmap[c]) >= 0)
+ return v;
+ s := "";
+ s[0] = c;
+ yyerror("unrecognized escape \\"+s);
+ return c;
+}
+
+lexstring()
+{
+ s := "";
+ i := 0;
+loop: for(;;){
+ case c := getc(){
+ '\\' =>
+ c = escchar();
+ if(c != Bufio->EOF)
+ s[i++] = c;
+ Bufio->EOF =>
+ yyerror("end of file in string constant");
+ break loop;
+ '\n' =>
+ yyerror("newline in string constant");
+ lineno++;
+ linepos = Linestart;
+ break loop;
+ '"' =>
+ break loop;
+ * =>
+ s[i++] = c;
+ }
+ }
+ yyctxt.lval.tok.v.idval = enterstring(s);
+}
+
+lex(): int
+{
+ for(;;){
+ yyctxt.lval.tok.src.start = (lineno << PosBits) | (linepos & PosMask);
+ case c := getc(){
+ Bufio->EOF =>
+ bin.close();
+ if(bstack == 0)
+ return Leof;
+ popinclude();
+ '#' =>
+ if(lexcom() < 0){
+ bin.close();
+ if(bstack == 0)
+ return Leof;
+ popinclude();
+ }
+ '\n' =>
+ lineno++;
+ linepos = Linestart;
+ ' ' or
+ '\t' or
+ '\r' or
+ '\v' =>
+ ;
+ '"' =>
+ lexstring();
+ return Lsconst;
+ '\'' =>
+ c = getc();
+ if(c == '\\')
+ c = escchar();
+ if(c == Bufio->EOF){
+ yyerror("end of file in character constant");
+ return Bufio->EOF;
+ }else
+ yyctxt.lval.tok.v.ival = big c;
+ c = getc();
+ if(c != '\''){
+ yyerror("missing closing '");
+ ungetc(c);
+ }
+ return Lconst;
+ '(' or
+ ')' or
+ '[' or
+ ']' or
+ '{' or
+ '}' or
+ ',' or
+ ';' or
+ '~' =>
+ return c;
+ ':' =>
+ c = getc();
+ if(c == ':')
+ return Lcons;
+ if(c == '=')
+ return Ldeclas;
+ ungetc(c);
+ return ':';
+ '.' =>
+ c = getc();
+ ungetc(c);
+ if(c != Bufio->EOF && (cmap(c) & Mdigit) != byte 0)
+ return lexnum('.');
+ return '.';
+ '|' =>
+ c = getc();
+ if(c == '=')
+ return Loreq;
+ if(c == '|')
+ return Loror;
+ ungetc(c);
+ return '|';
+ '&' =>
+ c = getc();
+ if(c == '=')
+ return Landeq;
+ if(c == '&')
+ return Landand;
+ ungetc(c);
+ return '&';
+ '^' =>
+ c = getc();
+ if(c == '=')
+ return Lxoreq;
+ ungetc(c);
+ return '^';
+ '*' =>
+ c = getc();
+ if(c == '=')
+ return Lmuleq;
+ if(c == '*'){
+ c = getc();
+ if(c == '=')
+ return Lexpeq;
+ ungetc(c);
+ return Lexp;
+ }
+ ungetc(c);
+ return '*';
+ '/' =>
+ c = getc();
+ if(c == '=')
+ return Ldiveq;
+ ungetc(c);
+ return '/';
+ '%' =>
+ c = getc();
+ if(c == '=')
+ return Lmodeq;
+ ungetc(c);
+ return '%';
+ '=' =>
+ c = getc();
+ if(c == '=')
+ return Leq;
+ if(c == '>')
+ return Llabs;
+ ungetc(c);
+ return '=';
+ '!' =>
+ c = getc();
+ if(c == '=')
+ return Lneq;
+ ungetc(c);
+ return '!';
+ '>' =>
+ c = getc();
+ if(c == '=')
+ return Lgeq;
+ if(c == '>'){
+ c = getc();
+ if(c == '=')
+ return Lrsheq;
+ ungetc(c);
+ return Lrsh;
+ }
+ ungetc(c);
+ return '>';
+ '<' =>
+ c = getc();
+ if(c == '=')
+ return Lleq;
+ if(c == '-')
+ return Lcomm;
+ if(c == '<'){
+ c = getc();
+ if(c == '=')
+ return Llsheq;
+ ungetc(c);
+ return Llsh;
+ }
+ ungetc(c);
+ return '<';
+ '+' =>
+ c = getc();
+ if(c == '=')
+ return Laddeq;
+ if(c == '+')
+ return Linc;
+ ungetc(c);
+ return '+';
+ '-' =>
+ c = getc();
+ if(c == '=')
+ return Lsubeq;
+ if(c == '-')
+ return Ldec;
+ if(c == '>')
+ return Lmdot;
+ ungetc(c);
+ return '-';
+ '0' to '9' =>
+ return lexnum(c);
+ * =>
+ if((cmap(c) & Malpha) != byte 0)
+ return lexid(c);
+ s := "";
+ s[0] = c;
+ yyerror("unknown character '"+s+"'");
+ }
+ }
+}
+
+YYLEX.lex(nil: self ref YYLEX): int
+{
+ t := lex();
+ yyctxt.lval.tok.src.stop = (lineno << PosBits) | (linepos & PosMask);
+ lasttok = t;
+ lastyylval = yyctxt.lval;
+ return t;
+}
+
+toksp(t: int): string
+{
+ case(t){
+ Lconst =>
+ return sprint("%bd", lastyylval.tok.v.ival);
+ Lrconst =>
+ return sprint("%f", lastyylval.tok.v.rval);
+ Lsconst =>
+ return sprint("\"%s\"", lastyylval.tok.v.idval.name);
+ Ltid or Lid =>
+ return lastyylval.tok.v.idval.name;
+ }
+ for(i := 0; i < len keywords; i++)
+ if(t == keywords[i].token)
+ return keywords[i].name;
+ for(i = 0; i < len tokwords; i++)
+ if(t == tokwords[i].token)
+ return tokwords[i].name;
+ if(t < 0 || t > 255)
+ fatal(sprint("bad token %d in toksp()", t));
+ buf := "Z";
+ buf[0] = t;
+ return buf;
+}
+
+enterstring(name: string): ref Sym
+{
+ h := 0;
+ n := len name;
+ for(i := 0; i < n; i++){
+ c := d := name[i];
+ c ^= c << 6;
+ h += (c << 11) ^ (c >> 1);
+ h ^= (d << 14) + (d << 7) + (d << 4) + d;
+ }
+
+ h &= HashSize-1;
+ for(s := strings[h]; s != nil; s = s.next){
+ sn := s.name;
+ if(len sn == n && sn == name)
+ return s;
+ }
+
+
+ s = ref Sym;
+ s.token = -1;
+ s.name = name;
+ s.hash = h;
+ s.next = strings[h];
+ strings[h] = s;
+ return s;
+}
+
+stringcat(s, t: ref Sym): ref Sym
+{
+ return enterstring(s.name+t.name);
+}
+
+enter(name: string, token: int): ref Sym
+{
+ h := 0;
+ n := len name;
+ for(i := 0; i < n; i++){
+ c := d := name[i];
+ c ^= c << 6;
+ h += (c << 11) ^ (c >> 1);
+ h ^= (d << 14) + (d << 7) + (d << 4) + d;
+ }
+
+ h &= HashSize-1;
+ for(s := symbols[h]; s != nil; s = s.next){
+ sn := s.name;
+ if(len sn == n && sn == name)
+ return s;
+ }
+
+ if(token == 0)
+ token = Lid;
+ s = ref Sym;
+ s.token = token;
+ s.name = name;
+ s.hash = h;
+ s.next = symbols[h];
+ symbols[h] = s;
+ return s;
+}
+
+stringpr(sym: ref Sym): string
+{
+ s := sym.name;
+ n := len s;
+ if(n > 10)
+ n = 10;
+ sb := "\"";
+ for(i := 0; i < n; i++){
+ case c := s[i]{
+ '\\' or
+ '"' or
+ '\n' or
+ '\r' or
+ '\t' or
+ '\b' or
+ '\a' or
+ '\v' or
+ '\u0000' =>
+ sb[len sb] = '\\';
+ sb[len sb] = unescmap[c];
+ * =>
+ sb[len sb] = c;
+ }
+ }
+ if(n != len s)
+ sb += "...";
+ sb[len sb] = '"';
+ return sb;
+}
+
+warn(line: Line, msg: string)
+{
+ if(errors || !dowarn)
+ return;
+ fprint(stderr, "%s: warning: %s\n", lineconv(line), msg);
+}
+
+nwarn(n: ref Node, msg: string)
+{
+ if(errors || !dowarn)
+ return;
+ fprint(stderr, "%s: warning: %s\n", lineconv(n.src.start), msg);
+}
+
+error(line: Line, msg: string)
+{
+ errors++;
+ if(errors > maxerr)
+ return;
+ fprint(stderr, "%s: %s\n", lineconv(line), msg);
+ if(errors == maxerr)
+ fprint(stderr, "too many errors, stopping\n");
+}
+
+nerror(n: ref Node, msg: string)
+{
+ errors++;
+ if(errors > maxerr)
+ return;
+ fprint(stderr, "%s: %s\n", lineconv(n.src.start), msg);
+ if(errors == maxerr)
+ fprint(stderr, "too many errors, stopping\n");
+}
+
+YYLEX.error(nil: self ref YYLEX, msg: string)
+{
+ errors++;
+ if(errors > maxerr)
+ return;
+ if(lasttok != 0)
+ fprint(stderr, "%s: near ` %s ` : %s\n", lineconv(lineno<<PosBits), toksp(lasttok), msg);
+ else
+ fprint(stderr, "%s: %s\n", lineconv(lineno<<PosBits), msg);
+ if(errors == maxerr)
+ fprint(stderr, "too many errors, stopping\n");
+}
+
+yyerror(msg: string)
+{
+ yyctxt.error(msg);
+}
+
+fatal(msg: string)
+{
+ if(errors == 0 || fabort)
+ fprint(stderr, "fatal limbo compiler error: %s\n", msg);
+ if(bout != nil)
+ sys->remove(outfile);
+ if(fabort){
+ n: ref Node;
+ if(n.ty == nil); # abort
+ }
+ raise "fail:error";
+}
+
+hex(v, n: int): string
+{
+ return sprint("%.*ux", n, v);
+}
+
+bhex(v: big, n: int): string
+{
+ return sprint("%.*bux", n, v);
+}
diff --git a/appl/cmd/limbo/limbo.b b/appl/cmd/limbo/limbo.b
new file mode 100644
index 00000000..c8f51779
--- /dev/null
+++ b/appl/cmd/limbo/limbo.b
@@ -0,0 +1,3099 @@
+implement Limbo;
+
+#line 2 "limbo.y"
+include "limbo.m";
+include "draw.m";
+
+Limbo: module {
+
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+
+ YYSTYPE: adt{
+ tok: Tok;
+ ids: ref Decl;
+ node: ref Node;
+ ty: ref Type;
+ types: ref Typelist;
+ };
+
+ YYLEX: adt {
+ lval: YYSTYPE;
+ lex: fn(nil: self ref YYLEX): int;
+ error: fn(nil: self ref YYLEX, err: string);
+ };
+Landeq: con 57346;
+Loreq: con 57347;
+Lxoreq: con 57348;
+Llsheq: con 57349;
+Lrsheq: con 57350;
+Laddeq: con 57351;
+Lsubeq: con 57352;
+Lmuleq: con 57353;
+Ldiveq: con 57354;
+Lmodeq: con 57355;
+Lexpeq: con 57356;
+Ldeclas: con 57357;
+Lload: con 57358;
+Loror: con 57359;
+Landand: con 57360;
+Lcons: con 57361;
+Leq: con 57362;
+Lneq: con 57363;
+Lleq: con 57364;
+Lgeq: con 57365;
+Llsh: con 57366;
+Lrsh: con 57367;
+Lexp: con 57368;
+Lcomm: con 57369;
+Linc: con 57370;
+Ldec: con 57371;
+Lof: con 57372;
+Lref: con 57373;
+Lif: con 57374;
+Lelse: con 57375;
+Lfn: con 57376;
+Lexcept: con 57377;
+Lraises: con 57378;
+Lmdot: con 57379;
+Lto: con 57380;
+Lor: con 57381;
+Lrconst: con 57382;
+Lconst: con 57383;
+Lid: con 57384;
+Ltid: con 57385;
+Lsconst: con 57386;
+Llabs: con 57387;
+Lnil: con 57388;
+Llen: con 57389;
+Lhd: con 57390;
+Ltl: con 57391;
+Ltagof: con 57392;
+Limplement: con 57393;
+Limport: con 57394;
+Linclude: con 57395;
+Lcon: con 57396;
+Ltype: con 57397;
+Lmodule: con 57398;
+Lcyclic: con 57399;
+Ladt: con 57400;
+Larray: con 57401;
+Llist: con 57402;
+Lchan: con 57403;
+Lself: con 57404;
+Ldo: con 57405;
+Lwhile: con 57406;
+Lfor: con 57407;
+Lbreak: con 57408;
+Lalt: con 57409;
+Lcase: con 57410;
+Lpick: con 57411;
+Lcont: con 57412;
+Lreturn: con 57413;
+Lexit: con 57414;
+Lspawn: con 57415;
+Lraise: con 57416;
+Lfix: con 57417;
+
+};
+
+#line 27 "limbo.y"
+ #
+ # lex.b
+ #
+ signdump: string; # name of function for sig debugging
+ superwarn: int;
+ debug: array of int;
+ noline: Line;
+ nosrc: Src;
+ arrayz: int;
+ emitcode: string; # emit stub routines for system module functions
+ emitdyn: int; # emit as above but for dynamic modules
+ emitsbl: string; # emit symbol file for sysm modules
+ emitstub: int; # emit type and call frames for system modules
+ emittab: string; # emit table of runtime functions for this module
+ errors: int;
+ mustcompile: int;
+ dontcompile: int;
+ asmsym: int; # generate symbols in assembly language?
+ bout: ref Bufio->Iobuf; # output file
+ bsym: ref Bufio->Iobuf; # symbol output file; nil => no sym out
+ gendis: int; # generate dis or asm?
+ fixss: int;
+ newfnptr: int; # ISELF and -ve indices
+ optims: int;
+
+ #
+ # decls.b
+ #
+ scope: int;
+ # impmod: ref Sym; # name of implementation module
+ impmods: ref Decl; # name of implementation module(s)
+ nildecl: ref Decl; # declaration for limbo's nil
+ selfdecl: ref Decl; # declaration for limbo's self
+
+ #
+ # types.b
+ #
+ tany: ref Type;
+ tbig: ref Type;
+ tbyte: ref Type;
+ terror: ref Type;
+ tint: ref Type;
+ tnone: ref Type;
+ treal: ref Type;
+ tstring: ref Type;
+ texception: ref Type;
+ tunknown: ref Type;
+ tfnptr: ref Type;
+ rtexception: ref Type;
+ descriptors: ref Desc; # list of all possible descriptors
+ tattr: array of Tattr;
+
+ #
+ # nodes.b
+ #
+ opcommute: array of int;
+ oprelinvert: array of int;
+ isused: array of int;
+ casttab: array of array of int; # instruction to cast from [1] to [2]
+
+ nfns: int; # functions defined
+ nfnexp: int;
+ fns: array of ref Decl; # decls for fns defined
+ tree: ref Node; # root of parse tree
+
+ parset: int; # time to parse
+ checkt: int; # time to typecheck
+ gent: int; # time to generate code
+ writet: int; # time to write out code
+ symt: int; # time to write out symbols
+YYEOFCODE: con 1;
+YYERRCODE: con 2;
+YYMAXDEPTH: con 200;
+
+#line 1630 "limbo.y"
+
+
+include "keyring.m";
+
+sys: Sys;
+ print, fprint, sprint: import sys;
+
+bufio: Bufio;
+ Iobuf: import bufio;
+
+str: String;
+
+keyring:Keyring;
+ md5: import keyring;
+
+math: Math;
+ import_real, export_real, isnan: import math;
+
+yyctxt: ref YYLEX;
+
+canonnan: real;
+
+debug = array[256] of {* => 0};
+
+noline = -1;
+nosrc = Src(-1, -1);
+
+infile: string;
+
+# front end
+include "arg.m";
+include "lex.b";
+include "types.b";
+include "nodes.b";
+include "decls.b";
+
+include "typecheck.b";
+
+# back end
+include "gen.b";
+include "ecom.b";
+include "asm.b";
+include "dis.b";
+include "sbl.b";
+include "stubs.b";
+include "com.b";
+include "optim.b";
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ s: string;
+
+ sys = load Sys Sys->PATH;
+ keyring = load Keyring Keyring->PATH;
+ math = load Math Math->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil){
+ sys->print("can't load %s: %r\n", Bufio->PATH);
+ raise("fail:bad module");
+ }
+ str = load String String->PATH;
+ if(str == nil){
+ sys->print("can't load %s: %r\n", String->PATH);
+ raise("fail:bad module");
+ }
+
+ stderr = sys->fildes(2);
+ yyctxt = ref YYLEX;
+
+ math->FPcontrol(0, Math->INVAL|Math->ZDIV|Math->OVFL|Math->UNFL|Math->INEX);
+ na := array[1] of {0.};
+ import_real(array[8] of {byte 16r7f, * => byte 16rff}, na);
+ canonnan = na[0];
+ if(!isnan(canonnan))
+ fatal("bad canonical NaN");
+
+ lexinit();
+ typeinit();
+ optabinit();
+
+ gendis = 1;
+ asmsym = 0;
+ maxerr = 20;
+ ofile := "";
+ ext := "";
+
+ arg := Arg.init(argv);
+ while(c := arg.opt()){
+ case c{
+ 'Y' =>
+ emitsbl = arg.arg();
+ if(emitsbl == nil)
+ usage();
+ 'C' =>
+ dontcompile = 1;
+ 'D' =>
+ #
+ # debug flags:
+ #
+ # a alt compilation
+ # A array constructor compilation
+ # b boolean and branch compilation
+ # c case compilation
+ # d function declaration
+ # D descriptor generation
+ # e expression compilation
+ # E addressable expression compilation
+ # f print arguments for compiled functions
+ # F constant folding
+ # g print out globals
+ # m module declaration and type checking
+ # n nil references
+ # s print sizes of output file sections
+ # S type signing
+ # t type checking function bodies
+ # T timing
+ # v global var and constant compilation
+ # x adt verification
+ # Y tuple compilation
+ # z Z bug fixes
+ #
+ s = arg.arg();
+ for(i := 0; i < len s; i++){
+ c = s[i];
+ if(c < len debug)
+ debug[c] = 1;
+ }
+ 'I' =>
+ s = arg.arg();
+ if(s == "")
+ usage();
+ addinclude(s);
+ 'G' =>
+ asmsym = 1;
+ 'S' =>
+ gendis = 0;
+ 'a' =>
+ emitstub = 1;
+ 'A' =>
+ emitstub = emitdyn = 1;
+ 'c' =>
+ mustcompile = 1;
+ 'e' =>
+ maxerr = 1000;
+ 'f' =>
+ fabort = 1;
+ 'F' =>
+ newfnptr = 1;
+ 'g' =>
+ dosym = 1;
+ 'i' =>
+ dontinline = 1;
+ 'o' =>
+ ofile = arg.arg();
+ 'O' =>
+ optims = 1;
+ 's' =>
+ s = arg.arg();
+ if(s != nil)
+ fixss = int s;
+ 't' =>
+ emittab = arg.arg();
+ if(emittab == nil)
+ usage();
+ 'T' =>
+ emitcode = arg.arg();
+ if(emitcode == nil)
+ usage();
+ 'd' =>
+ emitcode = arg.arg();
+ if(emitcode == nil)
+ usage();
+ emitdyn = 1;
+ 'w' =>
+ superwarn = dowarn;
+ dowarn = 1;
+ 'x' =>
+ ext = arg.arg();
+ 'X' =>
+ signdump = arg.arg();
+ 'z' =>
+ arrayz = 1;
+ * =>
+ usage();
+ }
+ }
+
+ addinclude("/module");
+
+ argv = arg.argv;
+ arg = nil;
+
+ if(argv == nil){
+ usage();
+ }else if(ofile != nil){
+ if(len argv != 1)
+ usage();
+ translate(hd argv, ofile, mkfileext(ofile, ".dis", ".sbl"));
+ }else{
+ pr := len argv != 1;
+ if(ext == ""){
+ ext = ".s";
+ if(gendis)
+ ext = ".dis";
+ }
+ for(; argv != nil; argv = tl argv){
+ file := hd argv;
+ (nil, s) = str->splitr(file, "/");
+ if(pr)
+ print("%s:\n", s);
+ out := mkfileext(s, ".b", ext);
+ translate(file, out, mkfileext(out, ext, ".sbl"));
+ }
+ }
+ if (toterrors > 0)
+ raise("fail:errors");
+}
+
+usage()
+{
+ fprint(stderr, "usage: limbo [-GSagwe] [-I incdir] [-o outfile] [-{T|t|d} module] [-D debug] file ...\n");
+ raise("fail:usage");
+}
+
+mkfileext(file, oldext, ext: string): string
+{
+ n := len file;
+ n2 := len oldext;
+ if(n >= n2 && file[n-n2:] == oldext)
+ file = file[:n-n2];
+ return file + ext;
+}
+
+translate(in, out, dbg: string)
+{
+ infile = in;
+ outfile = out;
+ errors = 0;
+ bins[0] = bufio->open(in, Bufio->OREAD);
+ if(bins[0] == nil){
+ fprint(stderr, "can't open %s: %r\n", in);
+ toterrors++;
+ return;
+ }
+ doemit := emitcode != "" || emitstub || emittab != "" || emitsbl != "";
+ if(!doemit){
+ bout = bufio->create(out, Bufio->OWRITE, 8r666);
+ if(bout == nil){
+ fprint(stderr, "can't open %s: %r\n", out);
+ toterrors++;
+ bins[0].close();
+ return;
+ }
+ if(dosym){
+ bsym = bufio->create(dbg, Bufio->OWRITE, 8r666);
+ if(bsym == nil)
+ fprint(stderr, "can't open %s: %r\n", dbg);
+ }
+ }
+
+ lexstart(in);
+
+ popscopes();
+ typestart();
+ declstart();
+ nfnexp = 0;
+
+ parset = sys->millisec();
+ yyparse(yyctxt);
+ parset = sys->millisec() - parset;
+
+ checkt = sys->millisec();
+ entry := typecheck(!doemit);
+ checkt = sys->millisec() - checkt;
+
+ modcom(entry);
+
+ fns = nil;
+ nfns = 0;
+ descriptors = nil;
+
+ if(debug['T'])
+ print("times: parse=%d type=%d: gen=%d write=%d symbols=%d\n",
+ parset, checkt, gent, writet, symt);
+
+ if(bout != nil)
+ bout.close();
+ if(bsym != nil)
+ bsym.close();
+ toterrors += errors;
+ if(errors && bout != nil)
+ sys->remove(out);
+ if(errors && bsym != nil)
+ sys->remove(dbg);
+}
+
+pwd(): string
+{
+ workdir := load Workdir Workdir->PATH;
+ if(workdir == nil)
+ cd := "/";
+ else
+ cd = workdir->init();
+ # sys->print("pwd: %s\n", cd);
+ return cd;
+}
+
+cleanname(s: string): string
+{
+ ls, path: list of string;
+
+ if(s == nil)
+ return nil;
+ if(s[0] != '/' && s[0] != '\\')
+ (nil, ls) = sys->tokenize(pwd(), "/\\");
+ for( ; ls != nil; ls = tl ls)
+ path = hd ls :: path;
+ (nil, ls) = sys->tokenize(s, "/\\");
+ for( ; ls != nil; ls = tl ls){
+ n := hd ls;
+ if(n == ".")
+ ;
+ else if (n == ".."){
+ if(path != nil)
+ path = tl path;
+ }
+ else
+ path = n :: path;
+ }
+ p := "";
+ for( ; path != nil; path = tl path)
+ p = "/" + hd path + p;
+ if(p == nil)
+ p = "/";
+ # sys->print("cleanname: %s\n", p);
+ return p;
+}
+
+srcpath(): string
+{
+ srcp := cleanname(infile);
+ # sys->print("srcpath: %s\n", srcp);
+ return srcp;
+}
+yyexca := array[] of {-1, 1,
+ 1, -1,
+ -2, 0,
+-1, 3,
+ 1, 3,
+ -2, 0,
+-1, 17,
+ 39, 88,
+ 50, 62,
+ 54, 88,
+ 98, 62,
+ -2, 252,
+-1, 211,
+ 59, 29,
+ 71, 29,
+ -2, 0,
+-1, 230,
+ 1, 2,
+ -2, 0,
+-1, 273,
+ 50, 176,
+ -2, 257,
+-1, 308,
+ 59, 41,
+ 71, 41,
+ 91, 41,
+ -2, 0,
+-1, 310,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 380,
+ 50, 62,
+ 98, 62,
+ -2, 252,
+-1, 381,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 387,
+ 53, 71,
+ 54, 71,
+ -2, 110,
+-1, 389,
+ 53, 72,
+ 54, 72,
+ -2, 112,
+-1, 421,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 428,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 443,
+ 53, 71,
+ 54, 71,
+ -2, 111,
+-1, 444,
+ 53, 72,
+ 54, 72,
+ -2, 113,
+-1, 452,
+ 71, 279,
+ 98, 279,
+ -2, 163,
+-1, 469,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 486,
+ 50, 126,
+ 98, 126,
+ -2, 239,
+-1, 491,
+ 71, 276,
+ -2, 0,
+-1, 503,
+ 59, 47,
+ 71, 47,
+ -2, 0,
+-1, 508,
+ 59, 41,
+ 71, 41,
+ 91, 41,
+ -2, 0,
+-1, 514,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 548,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 554,
+ 71, 154,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 562,
+ 56, 59,
+ 62, 59,
+ -2, 62,
+-1, 568,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 573,
+ 71, 157,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 577,
+ 72, 176,
+ -2, 163,
+-1, 596,
+ 71, 160,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 602,
+ 71, 168,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 606,
+ 72, 175,
+ 85, 150,
+ 86, 150,
+ 87, 150,
+ 89, 150,
+ 90, 150,
+ 91, 150,
+ -2, 0,
+-1, 609,
+ 50, 62,
+ 56, 171,
+ 62, 171,
+ 98, 62,
+ -2, 252,
+};
+YYNPROD: con 284;
+YYPRIVATE: con 57344;
+yytoknames: array of string;
+yystates: array of string;
+yydebug: con 0;
+YYLAST: con 2727;
+yyact := array[] of {
+ 379, 591, 453, 364, 505, 384, 412, 310, 369, 314,
+ 359, 451, 449, 185, 84, 83, 432, 298, 270, 15,
+ 8, 49, 213, 102, 320, 12, 42, 110, 48, 78,
+ 79, 80, 4, 35, 198, 51, 23, 459, 363, 6,
+ 458, 3, 6, 544, 486, 491, 365, 14, 382, 21,
+ 14, 353, 400, 293, 350, 423, 225, 285, 118, 330,
+ 286, 226, 223, 46, 31, 112, 465, 11, 105, 517,
+ 566, 599, 308, 186, 164, 165, 166, 167, 168, 169,
+ 170, 171, 172, 173, 174, 175, 176, 43, 117, 309,
+ 182, 183, 184, 349, 71, 10, 349, 205, 10, 208,
+ 93, 286, 286, 422, 32, 37, 119, 114, 40, 294,
+ 349, 294, 32, 585, 44, 286, 119, 428, 427, 426,
+ 547, 430, 429, 431, 231, 232, 233, 234, 235, 236,
+ 237, 238, 239, 240, 241, 242, 485, 244, 245, 246,
+ 247, 248, 249, 250, 251, 252, 253, 254, 255, 256,
+ 257, 258, 259, 260, 261, 262, 263, 264, 265, 186,
+ 6, 483, 273, 230, 482, 22, 481, 438, 14, 22,
+ 271, 424, 267, 210, 5, 409, 407, 565, 279, 187,
+ 513, 410, 284, 87, 420, 419, 418, 228, 94, 288,
+ 85, 312, 311, 90, 289, 99, 269, 415, 217, 202,
+ 5, 415, 47, 92, 82, 22, 209, 26, 303, 25,
+ 212, 19, 24, 218, 229, 508, 10, 354, 96, 601,
+ 98, 95, 100, 595, 101, 88, 89, 86, 572, 194,
+ 195, 17, 87, 557, 553, 18, 297, 19, 536, 85,
+ 525, 77, 90, 313, 326, 305, 490, 13, 512, 112,
+ 323, 318, 92, 82, 468, 207, 399, 17, 87, 383,
+ 498, 18, 215, 23, 479, 85, 316, 467, 90, 6,
+ 398, 2, 500, 13, 88, 89, 86, 14, 92, 82,
+ 194, 195, 361, 186, 43, 282, 219, 340, 194, 195,
+ 77, 114, 193, 211, 487, 499, 338, 182, 500, 559,
+ 88, 89, 86, 336, 194, 195, 488, 535, 87, 324,
+ 341, 44, 87, 325, 580, 85, 77, 579, 90, 85,
+ 381, 348, 90, 206, 19, 10, 358, 357, 92, 82,
+ 214, 393, 92, 82, 604, 33, 389, 387, 391, 448,
+ 614, 194, 195, 402, 45, 539, 194, 195, 18, 392,
+ 88, 89, 86, 356, 88, 89, 86, 321, 194, 195,
+ 192, 194, 195, 403, 404, 530, 77, 281, 317, 108,
+ 77, 416, 493, 19, 19, 421, 436, 495, 612, 186,
+ 301, 385, 604, 435, 564, 437, 507, 216, 603, 493,
+ 434, 441, 439, 115, 115, 600, 562, 116, 116, 452,
+ 543, 340, 183, 444, 443, 504, 414, 45, 316, 493,
+ 22, 18, 493, 480, 493, 597, 336, 493, 588, 70,
+ 574, 493, 63, 555, 540, 73, 473, 494, 469, 433,
+ 478, 442, 476, 76, 75, 69, 68, 74, 291, 18,
+ 54, 55, 62, 60, 61, 64, 87, 290, 268, 452,
+ 157, 91, 120, 85, 91, 104, 90, 65, 66, 67,
+ 159, 489, 507, 39, 497, 103, 92, 82, 194, 195,
+ 594, 510, 186, 77, 568, 477, 168, 487, 36, 518,
+ 523, 466, 522, 515, 516, 511, 406, 417, 88, 89,
+ 86, 87, 452, 527, 523, 529, 528, 408, 85, 329,
+ 533, 90, 593, 526, 77, 91, 224, 91, 532, 537,
+ 106, 92, 82, 34, 545, 91, 401, 177, 546, 541,
+ 523, 331, 552, 397, 335, 556, 91, 592, 299, 554,
+ 332, 300, 201, 88, 89, 86, 158, 200, 161, 197,
+ 162, 163, 560, 563, 441, 316, 179, 446, 445, 77,
+ 160, 159, 570, 328, 227, 577, 569, 575, 571, 573,
+ 81, 477, 181, 97, 177, 346, 180, 523, 178, 583,
+ 345, 41, 584, 203, 577, 606, 587, 138, 139, 140,
+ 137, 135, 586, 72, 561, 548, 386, 327, 414, 222,
+ 596, 221, 549, 73, 598, 477, 475, 577, 602, 605,
+ 91, 76, 75, 45, 607, 74, 611, 18, 474, 471,
+ 613, 425, 137, 135, 196, 477, 199, 91, 39, 188,
+ 91, 91, 19, 91, 204, 524, 243, 360, 538, 307,
+ 91, 183, 168, 287, 29, 220, 141, 142, 138, 139,
+ 140, 137, 135, 368, 91, 91, 30, 121, 1, 464,
+ 272, 274, 315, 477, 123, 124, 125, 126, 127, 128,
+ 129, 130, 131, 132, 133, 134, 136, 542, 156, 155,
+ 154, 153, 152, 151, 149, 150, 145, 146, 147, 148,
+ 144, 143, 141, 142, 138, 139, 140, 137, 135, 582,
+ 343, 581, 413, 503, 502, 590, 27, 589, 91, 144,
+ 143, 141, 142, 138, 139, 140, 137, 135, 28, 283,
+ 16, 411, 306, 355, 91, 9, 551, 550, 521, 520,
+ 91, 7, 450, 337, 266, 506, 292, 371, 109, 295,
+ 296, 107, 113, 111, 20, 87, 38, 0, 0, 199,
+ 0, 91, 85, 0, 0, 90, 0, 99, 342, 0,
+ 0, 91, 91, 319, 322, 92, 82, 0, 0, 0,
+ 0, 87, 0, 0, 0, 91, 91, 0, 85, 91,
+ 96, 90, 98, 95, 0, 0, 0, 88, 89, 86,
+ 0, 92, 82, 0, 0, 0, 0, 0, 0, 0,
+ 0, 87, 282, 77, 0, 0, 0, 0, 85, 0,
+ 0, 90, 0, 88, 89, 86, 0, 333, 91, 0,
+ 455, 92, 82, 0, 0, 0, 0, 91, 0, 77,
+ 0, 91, 0, 347, 0, 50, 91, 0, 91, 351,
+ 0, 0, 0, 88, 89, 86, 0, 91, 0, 0,
+ 52, 53, 454, 91, 0, 0, 59, 72, 0, 77,
+ 390, 57, 58, 0, 63, 0, 0, 73, 0, 0,
+ 395, 396, 0, 0, 0, 76, 75, 69, 68, 74,
+ 0, 18, 54, 55, 62, 60, 61, 64, 405, 0,
+ 0, 0, 91, 0, 0, 0, 91, 0, 0, 65,
+ 66, 67, 145, 146, 147, 148, 144, 143, 141, 142,
+ 138, 139, 140, 137, 135, 77, 0, 91, 0, 0,
+ 0, 0, 0, 366, 0, 0, 0, 196, 0, 0,
+ 91, 0, 0, 0, 0, 0, 447, 0, 50, 0,
+ 456, 0, 0, 0, 0, 460, 0, 461, 0, 0,
+ 0, 0, 0, 52, 53, 56, 97, 0, 0, 59,
+ 378, 0, 472, 0, 57, 58, 0, 63, 370, 0,
+ 73, 0, 0, 0, 0, 0, 0, 0, 76, 75,
+ 380, 68, 74, 0, 18, 54, 55, 62, 60, 61,
+ 64, 367, 509, 366, 0, 0, 13, 0, 0, 0,
+ 0, 496, 65, 66, 67, 501, 0, 0, 50, 372,
+ 0, 0, 0, 373, 374, 377, 375, 376, 77, 0,
+ 0, 0, 0, 52, 53, 56, 501, 0, 0, 59,
+ 378, 0, 0, 0, 57, 58, 0, 63, 370, 534,
+ 73, 0, 0, 0, 0, 0, 0, 0, 76, 75,
+ 380, 68, 74, 0, 18, 54, 55, 62, 60, 61,
+ 64, 367, 470, 366, 0, 0, 13, 0, 0, 0,
+ 0, 0, 65, 66, 67, 0, 0, 0, 50, 372,
+ 0, 0, 0, 373, 374, 377, 375, 376, 77, 0,
+ 0, 0, 0, 52, 53, 56, 0, 0, 0, 59,
+ 378, 0, 0, 0, 57, 58, 0, 63, 370, 0,
+ 73, 0, 0, 0, 0, 0, 0, 0, 76, 75,
+ 380, 68, 74, 0, 18, 54, 55, 62, 60, 61,
+ 64, 367, 440, 366, 0, 0, 13, 0, 0, 0,
+ 0, 0, 65, 66, 67, 0, 0, 0, 50, 372,
+ 0, 0, 0, 373, 374, 377, 375, 376, 77, 0,
+ 0, 0, 0, 52, 53, 56, 0, 0, 0, 59,
+ 378, 0, 0, 0, 57, 58, 0, 63, 370, 0,
+ 73, 0, 0, 0, 0, 0, 0, 0, 76, 75,
+ 380, 68, 74, 0, 18, 54, 55, 62, 60, 61,
+ 64, 367, 362, 608, 0, 0, 13, 0, 0, 0,
+ 0, 0, 65, 66, 67, 0, 0, 0, 50, 372,
+ 0, 0, 0, 373, 374, 377, 375, 376, 77, 0,
+ 0, 0, 0, 52, 53, 610, 0, 0, 0, 59,
+ 378, 0, 0, 0, 57, 58, 0, 63, 370, 0,
+ 73, 0, 0, 0, 0, 0, 0, 0, 76, 75,
+ 609, 68, 74, 0, 18, 54, 55, 62, 60, 61,
+ 64, 367, 576, 0, 0, 0, 13, 0, 0, 0,
+ 0, 0, 65, 66, 67, 0, 0, 50, 0, 372,
+ 0, 0, 0, 373, 374, 377, 375, 376, 77, 0,
+ 0, 0, 52, 53, 454, 0, 0, 0, 59, 378,
+ 0, 0, 0, 57, 58, 0, 63, 370, 0, 73,
+ 0, 0, 0, 0, 0, 0, 0, 76, 75, 380,
+ 68, 74, 0, 18, 54, 55, 62, 60, 61, 64,
+ 367, 366, 0, 0, 0, 13, 0, 0, 0, 0,
+ 0, 65, 66, 67, 0, 0, 50, 0, 372, 0,
+ 0, 0, 373, 374, 377, 375, 376, 77, 0, 0,
+ 0, 52, 53, 56, 0, 0, 0, 59, 378, 0,
+ 0, 0, 57, 58, 0, 63, 370, 0, 73, 0,
+ 0, 0, 0, 0, 0, 0, 76, 75, 380, 68,
+ 74, 0, 18, 54, 55, 62, 60, 61, 64, 367,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 65, 66, 67, 50, 0, 0, 0, 372, 0, 0,
+ 0, 373, 374, 377, 375, 376, 77, 0, 52, 53,
+ 56, 0, 0, 0, 59, 72, 0, 0, 0, 57,
+ 58, 0, 63, 0, 0, 73, 0, 0, 0, 0,
+ 0, 0, 0, 76, 75, 69, 275, 74, 0, 18,
+ 54, 55, 62, 60, 61, 64, 0, 0, 0, 50,
+ 0, 0, 0, 0, 0, 278, 0, 276, 277, 67,
+ 0, 0, 0, 0, 52, 53, 56, 0, 0, 0,
+ 59, 72, 0, 77, 280, 57, 58, 0, 63, 0,
+ 0, 73, 0, 0, 0, 0, 0, 0, 0, 76,
+ 75, 69, 68, 74, 0, 18, 54, 55, 62, 60,
+ 61, 64, 0, 0, 50, 0, 0, 0, 0, 0,
+ 0, 0, 0, 65, 66, 67, 0, 0, 0, 52,
+ 53, 56, 0, 0, 0, 59, 72, 0, 0, 77,
+ 57, 58, 0, 63, 0, 0, 73, 0, 0, 0,
+ 0, 0, 0, 0, 76, 75, 69, 68, 74, 0,
+ 18, 54, 55, 62, 60, 61, 64, 0, 0, 0,
+ 52, 53, 56, 0, 0, 0, 59, 72, 65, 66,
+ 67, 57, 58, 0, 63, 0, 0, 73, 0, 0,
+ 0, 0, 0, 0, 77, 76, 75, 69, 68, 74,
+ 0, 18, 54, 55, 62, 60, 61, 64, 0, 0,
+ 0, 0, 0, 0, 0, 0, 87, 0, 0, 65,
+ 66, 67, 0, 85, 0, 0, 90, 0, 99, 0,
+ 0, 0, 0, 0, 0, 77, 92, 82, 149, 150,
+ 145, 146, 147, 148, 144, 143, 141, 142, 138, 139,
+ 140, 137, 135, 463, 462, 0, 0, 101, 88, 89,
+ 86, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 136, 77, 156, 155, 154, 153, 152,
+ 151, 149, 150, 145, 146, 147, 148, 144, 143, 141,
+ 142, 138, 139, 140, 137, 135, 155, 154, 153, 152,
+ 151, 149, 150, 145, 146, 147, 148, 144, 143, 141,
+ 142, 138, 139, 140, 137, 135, 123, 124, 125, 126,
+ 127, 128, 129, 130, 131, 132, 133, 134, 136, 567,
+ 156, 155, 154, 153, 152, 151, 149, 150, 145, 146,
+ 147, 148, 144, 143, 141, 142, 138, 139, 140, 137,
+ 135, 154, 153, 152, 151, 149, 150, 145, 146, 147,
+ 148, 144, 143, 141, 142, 138, 139, 140, 137, 135,
+ 0, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 136, 558, 156, 155, 154, 153, 152,
+ 151, 149, 150, 145, 146, 147, 148, 144, 143, 141,
+ 142, 138, 139, 140, 137, 135, 152, 151, 149, 150,
+ 145, 146, 147, 148, 144, 143, 141, 142, 138, 139,
+ 140, 137, 135, 0, 0, 0, 123, 124, 125, 126,
+ 127, 128, 129, 130, 131, 132, 133, 134, 136, 531,
+ 156, 155, 154, 153, 152, 151, 149, 150, 145, 146,
+ 147, 148, 144, 143, 141, 142, 138, 139, 140, 137,
+ 135, 151, 149, 150, 145, 146, 147, 148, 144, 143,
+ 141, 142, 138, 139, 140, 137, 135, 0, 0, 0,
+ 0, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 136, 484, 156, 155, 154, 153, 152,
+ 151, 149, 150, 145, 146, 147, 148, 144, 143, 141,
+ 142, 138, 139, 140, 137, 135, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 123, 124, 125, 126,
+ 127, 128, 129, 130, 131, 132, 133, 134, 136, 352,
+ 156, 155, 154, 153, 152, 151, 149, 150, 145, 146,
+ 147, 148, 144, 143, 141, 142, 138, 139, 140, 137,
+ 135, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 136, 344, 156, 155, 154, 153, 152,
+ 151, 149, 150, 145, 146, 147, 148, 144, 143, 141,
+ 142, 138, 139, 140, 137, 135, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 123, 124, 125, 126,
+ 127, 128, 129, 130, 131, 132, 133, 134, 136, 304,
+ 156, 155, 154, 153, 152, 151, 149, 150, 145, 146,
+ 147, 148, 144, 143, 141, 142, 138, 139, 140, 137,
+ 135, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 136, 302, 156, 155, 154, 153, 152,
+ 151, 149, 150, 145, 146, 147, 148, 144, 143, 141,
+ 142, 138, 139, 140, 137, 135, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 123, 124, 125, 126,
+ 127, 128, 129, 130, 131, 132, 133, 134, 136, 191,
+ 156, 155, 154, 153, 152, 151, 149, 150, 145, 146,
+ 147, 148, 144, 143, 141, 142, 138, 139, 140, 137,
+ 135, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 136, 190, 156, 155, 154, 153, 152,
+ 151, 149, 150, 145, 146, 147, 148, 144, 143, 141,
+ 142, 138, 139, 140, 137, 135, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 123, 124, 125, 126,
+ 127, 128, 129, 130, 131, 132, 133, 134, 136, 189,
+ 156, 155, 154, 153, 152, 151, 149, 150, 145, 146,
+ 147, 148, 144, 143, 141, 142, 138, 139, 140, 137,
+ 135, 0, 87, 0, 0, 0, 87, 0, 0, 85,
+ 0, 0, 90, 388, 0, 0, 90, 0, 0, 0,
+ 0, 0, 92, 394, 0, 0, 92, 82, 0, 0,
+ 0, 0, 0, 0, 122, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 88, 89, 86, 0, 88, 89,
+ 86, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 77, 0, 0, 0, 77, 123, 124, 125, 126, 127,
+ 128, 129, 130, 131, 132, 133, 134, 136, 0, 156,
+ 155, 154, 153, 152, 151, 149, 150, 145, 146, 147,
+ 148, 144, 143, 141, 142, 138, 139, 140, 137, 135,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 123, 124,
+ 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+ 136, 578, 156, 155, 154, 153, 152, 151, 149, 150,
+ 145, 146, 147, 148, 144, 143, 141, 142, 138, 139,
+ 140, 137, 135, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 136, 519, 156, 155, 154, 153, 152,
+ 151, 149, 150, 145, 146, 147, 148, 144, 143, 141,
+ 142, 138, 139, 140, 137, 135, 0, 0, 0, 123,
+ 124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
+ 134, 136, 492, 156, 155, 154, 153, 152, 151, 149,
+ 150, 145, 146, 147, 148, 144, 143, 141, 142, 138,
+ 139, 140, 137, 135, 0, 0, 0, 339, 123, 124,
+ 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+ 136, 0, 156, 155, 154, 153, 152, 151, 149, 150,
+ 145, 146, 147, 148, 144, 143, 141, 142, 138, 139,
+ 140, 137, 135, 0, 0, 0, 334, 123, 124, 125,
+ 126, 127, 128, 129, 130, 131, 132, 133, 134, 136,
+ 0, 156, 155, 154, 153, 152, 151, 149, 150, 145,
+ 146, 147, 148, 144, 143, 141, 142, 138, 139, 140,
+ 137, 135, 0, 514, 123, 124, 125, 126, 127, 128,
+ 129, 130, 131, 132, 133, 134, 136, 0, 156, 155,
+ 154, 153, 152, 151, 149, 150, 145, 146, 147, 148,
+ 144, 143, 141, 142, 138, 139, 140, 137, 135, 0,
+ 457, 123, 124, 125, 126, 127, 128, 129, 130, 131,
+ 132, 133, 134, 136, 0, 156, 155, 154, 153, 152,
+ 151, 149, 150, 145, 146, 147, 148, 144, 143, 141,
+ 142, 138, 139, 140, 137, 135, 156, 155, 154, 153,
+ 152, 151, 149, 150, 145, 146, 147, 148, 144, 143,
+ 141, 142, 138, 139, 140, 137, 135,
+};
+yypact := array[] of {
+ 198,-1000, 351, 172,-1000, 140,-1000,-1000, 137, 135,
+ 692, 630, 14, 274, 463,-1000, 424, 530,-1000, 285,
+ -35, 130,-1000,-1000,-1000,-1000,-1000,1507,1507,1507,
+1507, 752, 583, 116, 144, 413, 396, -19, 460, 335,
+-1000, 351, 18,-1000,-1000,-1000, 393,-1000,2272,-1000,
+ 391, 497,1548,1548,1548,1548,1548,1548,1548,1548,
+1548,1548,1548,1548,1548, 523, 501, 521,1548, 376,
+1548,-1000,1507, 579,-1000,-1000,-1000, 580,2217,2162,
+2107, 288,-1000,-1000,-1000, 752, 494, 752, 492, 487,
+ 530,-1000, 532,-1000,-1000, 752,1507, 251,1507, 134,
+ 223, 530, 260, 348, 530, 216, 752, 551, 549, -36,
+-1000, 456, 6, -37,-1000,-1000,-1000, 512,-1000, 285,
+-1000, 172,-1000,1507,1507,1507,1507,1507,1507,1507,
+1507,1507,1507,1507,1507, 622,1507,1507,1507,1507,
+1507,1507,1507,1507,1507,1507,1507,1507,1507,1507,
+1507,1507,1507,1507,1507,1507,1507,1507,1507, 389,
+ 544,1396,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000,-1000,-1000,-1000,-1000,-1000,1452, 307, 215,
+ 752,1507,-1000,-1000,-1000, 17,2667,-1000,1507,-1000,
+-1000,-1000,-1000,1507, 388, 379, 415, 752, 13, 415,
+ 752, 752, 579, 452, 308,2052,-1000,1507,1997,-1000,
+ 752, 627, 2,-1000,-1000, 121, 285,-1000,-1000, 351,
+ 415,-1000,-1000, 334, 273, 273, 250,-1000,-1000,-1000,
+ 172,2667,2667,2667,2667,2667,2667,2667,2667,2667,
+2667,2667,2667,1507,2667, 575, 575, 575, 575, 543,
+ 543, 604, 604, 669, 669, 669, 669, 866, 866,1624,
+1848,1794,1741,1741,1687,2688, 547, -38,-1000, 406,
+ 511, 449, -39,2667,-1000,1548, 476, 485, 752,2554,
+ 479,1548,1507, 415,2515,-1000,1507, 260, 650,1942,
+ 529, 524, 415,-1000, 752, 415, 415, 413, 12, 415,
+ 752,-1000,-1000,1887,-1000, 11, 146,-1000, 625, 212,
+1121,-1000,-1000, 8, 188,-1000, 319, 546,-1000, 415,
+-1000,2277, 415,-1000,-1000,-1000,2667,-1000,-1000,1507,
+1396,2273, 722, 415, 478, 200,-1000, 185, -46, 471,
+2667,-1000,1507,-1000,-1000, 452, 452, 415,-1000, 407,
+-1000, 415,-1000, 104,-1000,-1000, 447, 103,-1000, 110,
+-1000, 351,-1000,-1000,-1000, 437, 114,-1000, 5, 99,
+ 572, 32, 370, 370,1507,1507,1507, 95,1507,2667,
+ 376,1051,-1000,-1000, 351,-1000,-1000,-1000, 752,-1000,
+ 415, 506, 505,2667,1548, 415, 415, 269, 808,-1000,
+1507, 752,2630, -2, -5, 415, 752,-1000,1587,-1000,
+ -21,-1000,-1000,-1000, 431, 197, 183, 696,-1000,-1000,
+-1000, 981, 570, 752,-1000,1507, 569, 557,1329,1507,
+ 194, 354, 94,-1000, 92, 89,1832, 64,-1000, 4,
+-1000,-1000, 244,-1000,-1000,-1000,-1000, 415, 808, 175,
+ -53,-1000,2477, 365,1548,-1000, 415,-1000,-1000,-1000,
+ 415, 305, 752,1507,-1000, 190, 219, 403, 145, 911,
+ 420,1507, 176,2593,1507,1507, -17, 429,2424, 808,
+ 609,-1000,-1000,-1000,-1000,-1000,-1000, 193,-1000, 169,
+-1000, 808,1507, 808,1507,-1000, 293,1777, 351,1507,
+ 752, 235, 167, 626,-1000, 283, 368,-1000, 625,-1000,
+ 341, 3,-1000,1507,1329, 48, 545, 553,-1000, 808,
+ 163,-1000, 361,2477,1507,-1000,-1000,2667,-1000,2667,
+-1000,-1000, 162,1722, 227,-1000,-1000, 337, 327,-1000,
+ 325, 106, 0,-1000,-1000,1667, 426,1507,1329,1507,
+ 157,-1000, 358,-1000,1260,-1000,2371,-1000,-1000,-1000,
+ 255, 427,-1000, 252,-1000,-1000, 808,-1000,1329, 41,
+-1000, 542,-1000,1260,-1000, 356, 114,2477, 468,-1000,
+-1000, 152,-1000, 353,-1000,1507, -1, 333,-1000, 148,
+-1000, 326,-1000,-1000,-1000,-1000,1260,-1000, 535,-1000,
+-1000,-1000,1191,-1000, 468, 316,1329, 278, 114, 376,
+1548,-1000,-1000,-1000,-1000,
+};
+yypgo := array[] of {
+ 0, 528, 736, 105, 33, 24, 419, 15, 14, 46,
+ 734, 733, 732, 34, 731, 728, 27, 727, 16, 4,
+ 725, 108, 8, 0, 21, 35, 13, 724, 723, 94,
+ 25, 67, 26, 12, 722, 11, 2, 38, 41, 32,
+ 721, 22, 3, 7, 719, 718, 717, 716, 715, 20,
+ 713, 712, 711, 10, 710, 697, 695, 1, 694, 693,
+ 692, 6, 5, 691, 689, 667, 19, 23, 652, 9,
+ 651, 18, 650, 649, 17, 648, 647, 643, 633,
+};
+yyr1 := array[] of {
+ 0, 76, 75, 75, 38, 38, 39, 39, 39, 39,
+ 39, 39, 39, 39, 39, 39, 39, 30, 30, 37,
+ 37, 37, 37, 37, 37, 37, 66, 66, 48, 51,
+ 51, 51, 50, 50, 50, 50, 50, 49, 49, 73,
+ 73, 53, 53, 53, 52, 52, 52, 62, 62, 61,
+ 61, 60, 58, 58, 58, 59, 59, 59, 19, 20,
+ 20, 9, 10, 10, 6, 6, 74, 74, 74, 74,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 7, 7, 8, 8, 13, 13, 21, 21,
+ 2, 2, 2, 3, 3, 4, 4, 14, 14, 15,
+ 15, 16, 16, 16, 16, 11, 12, 12, 12, 12,
+ 5, 5, 5, 5, 40, 67, 67, 67, 41, 41,
+ 41, 54, 54, 43, 43, 43, 77, 77, 42, 42,
+ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
+ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
+ 17, 17, 18, 18, 44, 45, 45, 46, 47, 47,
+ 63, 64, 64, 36, 36, 36, 36, 36, 55, 56,
+ 56, 57, 57, 57, 57, 22, 22, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 25, 25, 25,
+ 78, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 29, 29, 31, 72, 72, 71, 71, 70,
+ 70, 70, 70, 65, 65, 32, 32, 32, 32, 27,
+ 27, 28, 28, 26, 26, 33, 33, 34, 34, 35,
+ 35, 69, 68, 68,
+};
+yyr2 := array[] of {
+ 0, 0, 5, 1, 1, 2, 2, 1, 1, 2,
+ 2, 4, 4, 4, 4, 4, 6, 1, 3, 3,
+ 5, 5, 4, 6, 5, 1, 4, 7, 6, 0,
+ 2, 1, 4, 2, 5, 5, 1, 8, 11, 0,
+ 4, 0, 2, 1, 1, 1, 5, 0, 2, 5,
+ 4, 4, 2, 2, 1, 2, 4, 4, 1, 1,
+ 3, 1, 1, 3, 6, 4, 1, 2, 3, 4,
+ 1, 1, 1, 3, 6, 2, 3, 3, 3, 3,
+ 4, 1, 1, 4, 3, 6, 1, 3, 0, 3,
+ 3, 3, 5, 1, 3, 1, 5, 0, 1, 1,
+ 3, 3, 3, 3, 3, 1, 1, 1, 3, 3,
+ 2, 3, 2, 3, 4, 4, 2, 0, 3, 2,
+ 4, 2, 4, 0, 2, 2, 3, 5, 2, 2,
+ 4, 3, 4, 6, 2, 5, 7, 10, 6, 8,
+ 3, 3, 3, 3, 3, 6, 5, 8, 2, 8,
+ 0, 2, 0, 1, 2, 2, 4, 2, 2, 4,
+ 2, 2, 4, 1, 3, 1, 3, 1, 2, 2,
+ 4, 1, 1, 3, 1, 0, 1, 1, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 1, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 6, 8, 7,
+ 5, 3, 6, 4, 2, 2, 2, 1, 4, 3,
+ 0, 4, 3, 3, 4, 6, 2, 2, 1, 1,
+ 1, 6, 1, 1, 3, 1, 3, 1, 1, 1,
+ 3, 3, 2, 1, 0, 1, 1, 3, 3, 0,
+ 1, 1, 2, 1, 3, 1, 2, 1, 3, 1,
+ 3, 2, 2, 4,
+};
+yychk := array[] of {
+-1000, -75, 73, -38, -39, 2, -37, -40, -49, -48,
+ -29, -31, -30, 75, -9, -66, -54, 59, 63, 39,
+ -10, -9, 59, -39, 72, 72, 72, 4, 16, 4,
+ 16, 50, 98, 61, 50, -4, 54, -3, -2, 39,
+ -21, 41, -32, -31, -29, 59, 98, 72, -23, -24,
+ 17, -25, 32, 33, 64, 65, 34, 43, 44, 38,
+ 67, 68, 66, 46, 69, 81, 82, 83, 60, 59,
+ -6, -29, 39, 49, 61, 58, 57, 97, -23, -23,
+ -23, -1, 60, -7, -8, 46, 83, 39, 81, 82,
+ 49, -6, 59, -31, 72, 77, 74, -1, 76, 51,
+ 78, 80, -67, 52, 59, 87, 50, -14, 34, -15,
+ -16, -11, -30, -12, -31, 59, 63, -9, 40, 98,
+ 59, -76, 72, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 38, 16, 37, 34, 35,
+ 36, 32, 33, 31, 30, 26, 27, 28, 29, 24,
+ 25, 23, 22, 21, 20, 19, 18, 59, 39, 54,
+ 53, 41, 43, 44, -24, -24, -24, -24, -24, -24,
+ -24, -24, -24, -24, -24, -24, -24, 41, 45, 45,
+ 45, 41, -24, -24, -24, -26, -23, -3, 39, 72,
+ 72, 72, 72, 4, 53, 54, -1, 45, -13, -1,
+ 45, 45, -21, 41, -1, -23, 72, 4, -23, 72,
+ 39, 70, -21, -41, 70, 2, 39, -29, -21, 70,
+ -1, 40, 40, 98, 50, 50, 98, 42, -31, -29,
+ -38, -23, -23, -23, -23, -23, -23, -23, -23, -23,
+ -23, -23, -23, 4, -23, -23, -23, -23, -23, -23,
+ -23, -23, -23, -23, -23, -23, -23, -23, -23, -23,
+ -23, -23, -23, -23, -23, -23, -27, -26, 59, -25,
+ -71, -22, -72, -23, -70, 60, 81, 82, 79, -23,
+ 42, 60, 70, -1, -23, 40, 98, -78, -23, -23,
+ 59, 59, -1, 40, 98, -1, -1, -4, -74, -1,
+ 79, 72, 72, -23, 72, -13, -51, 2, 70, 87,
+ -43, 71, 70, -32, -69, -68, -9, 34, -16, -1,
+ -5, 84, -1, -5, 59, 63, -23, 40, 42, 50,
+ 98, 45, 45, -1, 42, 45, -24, -28, -26, 42,
+ -23, -41, 98, 40, 72, 41, 41, -1, -67, 98,
+ 42, -1, 72, 40, 71, -50, -9, -49, -66, -53,
+ 2, 70, 71, -37, -42, -9, 2, 70, -77, -22,
+ 47, -17, 88, 92, 93, 95, 96, 94, 39, -23,
+ 59, -43, 40, 71, -62, 62, 40, -7, 46, -8,
+ -1, -22, -71, -23, 60, -1, -1, 45, 70, 71,
+ 98, 45, -23, -74, -74, -1, 79, 72, 50, 72,
+ 71, -52, -61, -60, -9, 91, -69, 50, 72, 71,
+ 70, -43, 98, 50, 72, 39, 87, 86, 85, 90,
+ 89, 91, -18, 59, -18, -22, -23, -22, 72, -26,
+ 71, -61, -9, -7, -8, 42, 42, -1, 70, -33,
+ -34, -35, -23, -36, 34, 2, -1, 40, 42, 42,
+ -1, -1, 77, 76, -73, 87, 50, 70, 71, -43,
+ 71, 39, -1, -23, 39, 39, -42, -9, -23, 70,
+ 59, 72, 72, 72, 72, 72, 40, 50, 62, -33,
+ 71, 98, 55, 56, 62, 72, -1, -23, 70, 76,
+ 79, -1, -58, -59, 2, -19, -20, 59, 70, 71,
+ 51, -26, 72, 4, 40, -22, -22, 86, 50, 70,
+ -44, -45, -36, -23, 16, 71, -35, -23, -36, -23,
+ 72, 72, -69, -23, -1, 72, 71, -62, 2, 62,
+ 56, -53, -65, 59, 40, -23, -42, 72, 40, 39,
+ -46, -47, -36, 71, -43, 62, -23, 71, 72, 72,
+ -19, -9, 59, -19, 59, 71, 70, 72, 48, -22,
+ -42, -22, 71, -43, 62, -36, 2, -23, 70, 62,
+ 62, -63, -64, -36, -42, 72, 40, -36, 62, -55,
+ -56, -57, 59, 34, 2, 71, -43, 62, -22, 72,
+ 62, 71, -43, 62, 56, -36, 40, -57, 2, 59,
+ 34, -57, 62, -42, 62,
+};
+yydef := array[] of {
+ 0, -2, 0, -2, 4, 0, 7, 8, 0, 0,
+ 0, 17, 0, 0, 0, 25, 0, -2, 253, 0,
+ 61, 0, 62, 5, 6, 9, 10, 0, 0, 0,
+ 0, 0, 0, 0, 0, 117, 0, 95, 93, 97,
+ 121, 0, 0, 265, 266, 252, 0, 1, 0, 177,
+ 0, 213, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 252,
+ 0, 237, 0, 0, 248, 249, 250, 0, 0, 0,
+ 0, 0, 70, 71, 72, 0, 0, 0, 0, 0,
+ 88, 81, 82, 18, 19, 0, 0, 0, 0, 0,
+ 0, 88, 0, 0, 88, 0, 0, 0, 0, 98,
+ 99, 0, 0, 105, 17, 106, 107, 0, 254, 0,
+ 63, 0, 11, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 269, 0,
+ 0, 175, 246, 247, 214, 215, 216, 217, 218, 219,
+ 220, 221, 222, 223, 224, 225, 226, 0, 0, 0,
+ 0, 0, 234, 235, 236, 0, 273, 240, 0, 13,
+ 12, 14, 15, 0, 0, 0, 75, 0, 0, 86,
+ 0, 0, 0, 0, 0, 0, 22, 0, 0, 26,
+ 0, -2, 0, 114, 123, 0, 0, 116, 122, 0,
+ 94, 90, 91, 0, 0, 0, 0, 89, 267, 268,
+ -2, 178, 179, 180, 181, 182, 183, 184, 185, 186,
+ 187, 188, 189, 0, 191, 193, 194, 195, 196, 197,
+ 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
+ 208, 209, 210, 211, 212, 192, 0, 270, 242, 243,
+ 255, 0, 0, -2, 258, 259, 0, 0, 0, 0,
+ 0, 0, 0, 231, 0, 239, 0, 0, 0, 0,
+ 73, 84, 76, 77, 0, 78, 79, 117, 0, 66,
+ 0, 20, 21, 0, 24, 0, 0, 31, -2, 0,
+ -2, 119, 123, 0, 0, 47, 0, 0, 100, 101,
+ 102, 0, 103, 104, 108, 109, 190, 238, 244, 175,
+ 0, 0, 0, 262, 0, 0, 233, 0, 271, 0,
+ 274, 241, 0, 65, 16, 0, 0, 87, 80, 0,
+ 83, 67, 23, 0, 28, 30, 0, 0, 36, 0,
+ 43, 0, 118, 124, 125, 0, 0, 123, 0, 0,
+ 0, 0, 152, 152, 175, 0, 175, 0, 0, 176,
+ -2, -2, 115, 96, 281, 282, 92, -2, 0, -2,
+ 0, 0, 256, 257, 70, 260, 261, 0, 0, 230,
+ 272, 0, 0, 0, 0, 68, 0, 27, 0, 33,
+ 39, 42, 44, 45, 0, 0, 0, 151, 128, 129,
+ 123, -2, 0, 0, 134, 0, 0, 0, -2, 0,
+ 0, 0, 0, 153, 0, 0, 0, 0, 148, 0,
+ 120, 48, 0, -2, -2, 245, 251, 227, 0, 0,
+ 275, 277, -2, 0, 165, 167, 232, 64, 74, 85,
+ 69, 0, 0, 0, 37, 0, 0, 0, 0, -2,
+ 131, 0, 0, 0, 175, 175, 0, 0, 0, 0,
+ 0, 140, 141, 142, 143, 144, -2, 0, 283, 0,
+ 229, -2, 0, 0, 0, 32, 0, 0, 0, 0,
+ 0, 0, 0, -2, 54, 0, 58, 59, -2, 130,
+ 264, 0, 132, 0, -2, 0, 0, 0, 151, 0,
+ 0, 123, 0, 163, 0, 228, 278, 164, 166, 280,
+ 34, 35, 0, 0, 0, 50, 51, 52, 53, 55,
+ 0, 0, 0, 263, 127, 0, 135, 175, -2, 175,
+ 0, 123, 0, 146, -2, 155, 0, 40, 46, 49,
+ 0, 0, -2, 0, 60, 38, 0, 133, -2, 0,
+ 138, 0, 145, -2, 158, 0, 167, -2, 0, 56,
+ 57, 0, 123, 0, 136, 175, 0, 0, 156, 0,
+ 123, 0, 171, 172, 174, 149, -2, 161, 0, 139,
+ 159, 147, -2, 169, 0, 0, -2, 0, 174, -2,
+ 172, 173, 162, 137, 170,
+};
+yytok1 := array[] of {
+ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 64, 3, 3, 3, 36, 23, 3,
+ 39, 40, 34, 32, 98, 33, 54, 35, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 50, 72,
+ 26, 4, 27, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 41, 3, 42, 22, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 70, 21, 71, 65,
+};
+yytok2 := array[] of {
+ 2, 3, 5, 6, 7, 8, 9, 10, 11, 12,
+ 13, 14, 15, 16, 17, 18, 19, 20, 24, 25,
+ 28, 29, 30, 31, 37, 38, 43, 44, 45, 46,
+ 47, 48, 49, 51, 52, 53, 55, 56, 57, 58,
+ 59, 60, 61, 62, 63, 66, 67, 68, 69, 73,
+ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
+ 84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
+ 94, 95, 96, 97,
+};
+yytok3 := array[] of {
+ 0
+};
+
+YYSys: module
+{
+ FD: adt
+ {
+ fd: int;
+ };
+ fildes: fn(fd: int): ref FD;
+ fprint: fn(fd: ref FD, s: string, *): int;
+};
+
+yysys: YYSys;
+yystderr: ref YYSys->FD;
+
+YYFLAG: con -1000;
+
+# parser for yacc output
+
+yytokname(yyc: int): string
+{
+ if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil)
+ return yytoknames[yyc-1];
+ return "<"+string yyc+">";
+}
+
+yystatname(yys: int): string
+{
+ if(yys >= 0 && yys < len yystates && yystates[yys] != nil)
+ return yystates[yys];
+ return "<"+string yys+">\n";
+}
+
+yylex1(yylex: ref YYLEX): int
+{
+ c : int;
+ yychar := yylex.lex();
+ if(yychar <= 0)
+ c = yytok1[0];
+ else if(yychar < len yytok1)
+ c = yytok1[yychar];
+ else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2)
+ c = yytok2[yychar-YYPRIVATE];
+ else{
+ n := len yytok3;
+ c = 0;
+ for(i := 0; i < n; i+=2) {
+ if(yytok3[i+0] == yychar) {
+ c = yytok3[i+1];
+ break;
+ }
+ }
+ if(c == 0)
+ c = yytok2[1]; # unknown char
+ }
+ if(yydebug >= 3)
+ yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c));
+ return c;
+}
+
+YYS: adt
+{
+ yyv: YYSTYPE;
+ yys: int;
+};
+
+yyparse(yylex: ref YYLEX): int
+{
+ if(yydebug >= 1 && yysys == nil) {
+ yysys = load YYSys "$Sys";
+ yystderr = yysys->fildes(2);
+ }
+
+ yys := array[YYMAXDEPTH] of YYS;
+
+ yyval: YYSTYPE;
+ yystate := 0;
+ yychar := -1;
+ yynerrs := 0; # number of errors
+ yyerrflag := 0; # error recovery flag
+ yyp := -1;
+ yyn := 0;
+
+yystack:
+ for(;;){
+ # put a state and value onto the stack
+ if(yydebug >= 4)
+ yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+ yyp++;
+ if(yyp >= len yys)
+ yys = (array[len yys * 2] of YYS)[0:] = yys;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = yyval;
+
+ for(;;){
+ yyn = yypact[yystate];
+ if(yyn > YYFLAG) { # simple state
+ if(yychar < 0)
+ yychar = yylex1(yylex);
+ yyn += yychar;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yyn = yyact[yyn];
+ if(yychk[yyn] == yychar) { # valid shift
+ yychar = -1;
+ yyp++;
+ if(yyp >= len yys)
+ yys = (array[len yys * 2] of YYS)[0:] = yys;
+ yystate = yyn;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = yylex.lval;
+ if(yyerrflag > 0)
+ yyerrflag--;
+ if(yydebug >= 4)
+ yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+ continue;
+ }
+ }
+ }
+
+ # default state action
+ yyn = yydef[yystate];
+ if(yyn == -2) {
+ if(yychar < 0)
+ yychar = yylex1(yylex);
+
+ # look through exception table
+ for(yyxi:=0;; yyxi+=2)
+ if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate)
+ break;
+ for(yyxi += 2;; yyxi += 2) {
+ yyn = yyexca[yyxi];
+ if(yyn < 0 || yyn == yychar)
+ break;
+ }
+ yyn = yyexca[yyxi+1];
+ if(yyn < 0){
+ yyn = 0;
+ break yystack;
+ }
+ }
+
+ if(yyn != 0)
+ break;
+
+ # error ... attempt to resume parsing
+ if(yyerrflag == 0) { # brand new error
+ yylex.error("syntax error");
+ yynerrs++;
+ if(yydebug >= 1) {
+ yysys->fprint(yystderr, "%s", yystatname(yystate));
+ yysys->fprint(yystderr, "saw %s\n", yytokname(yychar));
+ }
+ }
+
+ if(yyerrflag != 3) { # incompletely recovered error ... try again
+ yyerrflag = 3;
+
+ # find a state where "error" is a legal shift action
+ while(yyp >= 0) {
+ yyn = yypact[yys[yyp].yys] + YYERRCODE;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yystate = yyact[yyn]; # simulate a shift of "error"
+ if(yychk[yystate] == YYERRCODE)
+ continue yystack;
+ }
+
+ # the current yyp has no shift onn "error", pop stack
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n",
+ yys[yyp].yys, yys[yyp-1].yys );
+ yyp--;
+ }
+ # there is no state on the stack with an error shift ... abort
+ yyn = 1;
+ break yystack;
+ }
+
+ # no shift yet; clobber input char
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar));
+ if(yychar == YYEOFCODE) {
+ yyn = 1;
+ break yystack;
+ }
+ yychar = -1;
+ # try again in the same state
+ }
+
+ # reduction by production yyn
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+ yypt := yyp;
+ yyp -= yyr2[yyn];
+# yyval = yys[yyp+1].yyv;
+ yym := yyn;
+
+ # consult goto table to find next state
+ yyn = yyr1[yyn];
+ yyg := yypgo[yyn];
+ yyj := yyg + yys[yyp].yys + 1;
+
+ if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+ yystate = yyact[yyg];
+ case yym {
+
+1=>
+#line 151 "limbo.y"
+{
+ impmods = yys[yypt-1].yyv.ids;
+ }
+2=>
+#line 154 "limbo.y"
+{
+ tree = rotater(yys[yypt-0].yyv.node);
+ }
+3=>
+#line 158 "limbo.y"
+{
+ impmods = nil;
+ tree = rotater(yys[yypt-0].yyv.node);
+ }
+4=>
+yyval.node = yys[yyp+1].yyv.node;
+5=>
+#line 166 "limbo.y"
+{
+ if(yys[yypt-1].yyv.node == nil)
+ yyval.node = yys[yypt-0].yyv.node;
+ else if(yys[yypt-0].yyv.node == nil)
+ yyval.node = yys[yypt-1].yyv.node;
+ else
+ yyval.node = mkbin(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node);
+ }
+6=>
+#line 177 "limbo.y"
+{
+ yyval.node = nil;
+ }
+7=>
+yyval.node = yys[yyp+1].yyv.node;
+8=>
+yyval.node = yys[yyp+1].yyv.node;
+9=>
+yyval.node = yys[yyp+1].yyv.node;
+10=>
+yyval.node = yys[yyp+1].yyv.node;
+11=>
+#line 185 "limbo.y"
+{
+ yyval.node = mkbin(Oas, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node);
+ }
+12=>
+#line 189 "limbo.y"
+{
+ yyval.node = mkbin(Oas, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node);
+ }
+13=>
+#line 193 "limbo.y"
+{
+ yyval.node = mkbin(Odas, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node);
+ }
+14=>
+#line 197 "limbo.y"
+{
+ yyval.node = mkbin(Odas, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node);
+ }
+15=>
+#line 201 "limbo.y"
+{
+ yyerror("illegal declaration");
+ yyval.node = nil;
+ }
+16=>
+#line 206 "limbo.y"
+{
+ yyerror("illegal declaration");
+ yyval.node = nil;
+ }
+17=>
+yyval.node = yys[yyp+1].yyv.node;
+18=>
+#line 214 "limbo.y"
+{
+ yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+19=>
+#line 220 "limbo.y"
+{
+ includef(yys[yypt-1].yyv.tok.v.idval);
+ yyval.node = nil;
+ }
+20=>
+#line 225 "limbo.y"
+{
+ yyval.node = typedecl(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.ty);
+ }
+21=>
+#line 229 "limbo.y"
+{
+ yyval.node = importdecl(yys[yypt-1].yyv.node, yys[yypt-4].yyv.ids);
+ yyval.node.src.start = yys[yypt-4].yyv.ids.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+22=>
+#line 235 "limbo.y"
+{
+ yyval.node = vardecl(yys[yypt-3].yyv.ids, yys[yypt-1].yyv.ty);
+ }
+23=>
+#line 239 "limbo.y"
+{
+ yyval.node = mkbin(Ovardecli, vardecl(yys[yypt-5].yyv.ids, yys[yypt-3].yyv.ty), varinit(yys[yypt-5].yyv.ids, yys[yypt-1].yyv.node));
+ }
+24=>
+#line 243 "limbo.y"
+{
+ yyval.node = condecl(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.node);
+ }
+25=>
+yyval.node = yys[yyp+1].yyv.node;
+26=>
+#line 250 "limbo.y"
+{
+ yyval.node = exdecl(yys[yypt-3].yyv.ids, nil);
+ }
+27=>
+#line 254 "limbo.y"
+{
+ yyval.node = exdecl(yys[yypt-6].yyv.ids, revids(yys[yypt-2].yyv.ids));
+ }
+28=>
+#line 260 "limbo.y"
+{
+ yys[yypt-5].yyv.ids.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ yyval.node = moddecl(yys[yypt-5].yyv.ids, rotater(yys[yypt-1].yyv.node));
+ }
+29=>
+#line 267 "limbo.y"
+{
+ yyval.node = nil;
+ }
+30=>
+#line 271 "limbo.y"
+{
+ if(yys[yypt-1].yyv.node == nil)
+ yyval.node = yys[yypt-0].yyv.node;
+ else if(yys[yypt-0].yyv.node == nil)
+ yyval.node = yys[yypt-1].yyv.node;
+ else
+ yyval.node = mkn(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node);
+ }
+31=>
+#line 280 "limbo.y"
+{
+ yyval.node = nil;
+ }
+32=>
+#line 286 "limbo.y"
+{
+ yyval.node = fielddecl(Dglobal, typeids(yys[yypt-3].yyv.ids, yys[yypt-1].yyv.ty));
+ }
+33=>
+yyval.node = yys[yyp+1].yyv.node;
+34=>
+#line 291 "limbo.y"
+{
+ yyval.node = typedecl(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.ty);
+ }
+35=>
+#line 295 "limbo.y"
+{
+ yyval.node = condecl(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.node);
+ }
+36=>
+yyval.node = yys[yyp+1].yyv.node;
+37=>
+#line 302 "limbo.y"
+{
+ yys[yypt-7].yyv.ids.src.stop = yys[yypt-1].yyv.tok.src.stop;
+ yyval.node = adtdecl(yys[yypt-7].yyv.ids, rotater(yys[yypt-2].yyv.node));
+ yyval.node.ty.polys = yys[yypt-4].yyv.ids;
+ yyval.node.ty.val = rotater(yys[yypt-0].yyv.node);
+ }
+38=>
+#line 309 "limbo.y"
+{
+ yys[yypt-10].yyv.ids.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ yyval.node = adtdecl(yys[yypt-10].yyv.ids, rotater(yys[yypt-1].yyv.node));
+ yyval.node.ty.polys = yys[yypt-7].yyv.ids;
+ yyval.node.ty.val = rotater(yys[yypt-4].yyv.node);
+ }
+39=>
+#line 318 "limbo.y"
+{
+ yyval.node = nil;
+ }
+40=>
+#line 322 "limbo.y"
+{
+ yyval.node = yys[yypt-1].yyv.node;
+ }
+41=>
+#line 328 "limbo.y"
+{
+ yyval.node = nil;
+ }
+42=>
+#line 332 "limbo.y"
+{
+ if(yys[yypt-1].yyv.node == nil)
+ yyval.node = yys[yypt-0].yyv.node;
+ else if(yys[yypt-0].yyv.node == nil)
+ yyval.node = yys[yypt-1].yyv.node;
+ else
+ yyval.node = mkn(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node);
+ }
+43=>
+#line 341 "limbo.y"
+{
+ yyval.node = nil;
+ }
+44=>
+yyval.node = yys[yyp+1].yyv.node;
+45=>
+yyval.node = yys[yyp+1].yyv.node;
+46=>
+#line 349 "limbo.y"
+{
+ yyval.node = condecl(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.node);
+ }
+47=>
+#line 355 "limbo.y"
+{
+ yyval.node = nil;
+ }
+48=>
+#line 359 "limbo.y"
+{
+ if(yys[yypt-1].yyv.node == nil)
+ yyval.node = yys[yypt-0].yyv.node;
+ else if(yys[yypt-0].yyv.node == nil)
+ yyval.node = yys[yypt-1].yyv.node;
+ else
+ yyval.node = mkn(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node);
+ }
+49=>
+#line 370 "limbo.y"
+{
+ for(d := yys[yypt-4].yyv.ids; d != nil; d = d.next)
+ d.cyc = byte 1;
+ yyval.node = fielddecl(Dfield, typeids(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.ty));
+ }
+50=>
+#line 376 "limbo.y"
+{
+ yyval.node = fielddecl(Dfield, typeids(yys[yypt-3].yyv.ids, yys[yypt-1].yyv.ty));
+ }
+51=>
+#line 382 "limbo.y"
+{
+ yyval.node = yys[yypt-1].yyv.node;
+ }
+52=>
+#line 388 "limbo.y"
+{
+ yys[yypt-1].yyv.node.right.right = yys[yypt-0].yyv.node;
+ yyval.node = yys[yypt-1].yyv.node;
+ }
+53=>
+#line 393 "limbo.y"
+{
+ yyval.node = nil;
+ }
+54=>
+#line 397 "limbo.y"
+{
+ yyval.node = nil;
+ }
+55=>
+#line 403 "limbo.y"
+{
+ yyval.node = mkn(Opickdecl, nil, mkn(Oseq, fielddecl(Dtag, yys[yypt-1].yyv.ids), nil));
+ typeids(yys[yypt-1].yyv.ids, mktype(yys[yypt-1].yyv.ids.src.start, yys[yypt-1].yyv.ids.src.stop, Tadtpick, nil, nil));
+ }
+56=>
+#line 408 "limbo.y"
+{
+ yys[yypt-3].yyv.node.right.right = yys[yypt-2].yyv.node;
+ yyval.node = mkn(Opickdecl, yys[yypt-3].yyv.node, mkn(Oseq, fielddecl(Dtag, yys[yypt-1].yyv.ids), nil));
+ typeids(yys[yypt-1].yyv.ids, mktype(yys[yypt-1].yyv.ids.src.start, yys[yypt-1].yyv.ids.src.stop, Tadtpick, nil, nil));
+ }
+57=>
+#line 414 "limbo.y"
+{
+ yyval.node = mkn(Opickdecl, nil, mkn(Oseq, fielddecl(Dtag, yys[yypt-1].yyv.ids), nil));
+ typeids(yys[yypt-1].yyv.ids, mktype(yys[yypt-1].yyv.ids.src.start, yys[yypt-1].yyv.ids.src.stop, Tadtpick, nil, nil));
+ }
+58=>
+#line 421 "limbo.y"
+{
+ yyval.ids = revids(yys[yypt-0].yyv.ids);
+ }
+59=>
+#line 427 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, nil);
+ }
+60=>
+#line 431 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, yys[yypt-2].yyv.ids);
+ }
+61=>
+#line 437 "limbo.y"
+{
+ yyval.ids = revids(yys[yypt-0].yyv.ids);
+ }
+62=>
+#line 443 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, nil);
+ }
+63=>
+#line 447 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, yys[yypt-2].yyv.ids);
+ }
+64=>
+#line 453 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-5].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tfix, nil, nil);
+ yyval.ty.val = mkbin(Oseq, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node);
+ }
+65=>
+#line 458 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-3].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tfix, nil, nil);
+ yyval.ty.val = yys[yypt-1].yyv.node;
+ }
+66=>
+#line 465 "limbo.y"
+{
+ yyval.types = addtype(yys[yypt-0].yyv.ty, nil);
+ }
+67=>
+#line 469 "limbo.y"
+{
+ yyval.types = addtype(yys[yypt-0].yyv.ty, nil);
+ yys[yypt-0].yyv.ty.flags |= CYCLIC;
+ }
+68=>
+#line 474 "limbo.y"
+{
+ yyval.types = addtype(yys[yypt-0].yyv.ty, yys[yypt-2].yyv.types);
+ }
+69=>
+#line 478 "limbo.y"
+{
+ yyval.types = addtype(yys[yypt-0].yyv.ty, yys[yypt-3].yyv.types);
+ yys[yypt-0].yyv.ty.flags |= CYCLIC;
+ }
+70=>
+#line 485 "limbo.y"
+{
+ yyval.ty = mkidtype(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval);
+ }
+71=>
+#line 489 "limbo.y"
+{
+ yyval.ty = yys[yypt-0].yyv.ty;
+ }
+72=>
+#line 493 "limbo.y"
+{
+ yyval.ty = yys[yypt-0].yyv.ty;
+ }
+73=>
+#line 497 "limbo.y"
+{
+ yyval.ty = mkarrowtype(yys[yypt-2].yyv.ty.src.start, yys[yypt-0].yyv.tok.src.stop, yys[yypt-2].yyv.ty, yys[yypt-0].yyv.tok.v.idval);
+ }
+74=>
+#line 501 "limbo.y"
+{
+ yyval.ty = mkarrowtype(yys[yypt-5].yyv.ty.src.start, yys[yypt-3].yyv.tok.src.stop, yys[yypt-5].yyv.ty, yys[yypt-3].yyv.tok.v.idval);
+ yyval.ty = mkinsttype(yys[yypt-5].yyv.ty.src, yyval.ty, yys[yypt-1].yyv.types);
+ }
+75=>
+#line 506 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-1].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tref, yys[yypt-0].yyv.ty, nil);
+ }
+76=>
+#line 510 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tchan, yys[yypt-0].yyv.ty, nil);
+ }
+77=>
+#line 514 "limbo.y"
+{
+ if(yys[yypt-1].yyv.ids.next == nil)
+ yyval.ty = yys[yypt-1].yyv.ids.ty;
+ else
+ yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Ttuple, nil, revids(yys[yypt-1].yyv.ids));
+ }
+78=>
+#line 521 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tarray, yys[yypt-0].yyv.ty, nil);
+ }
+79=>
+#line 525 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tlist, yys[yypt-0].yyv.ty, nil);
+ }
+80=>
+#line 529 "limbo.y"
+{
+ yys[yypt-1].yyv.ty.src.start = yys[yypt-3].yyv.tok.src.start;
+ yys[yypt-1].yyv.ty.polys = yys[yypt-2].yyv.ids;
+ yys[yypt-1].yyv.ty.eraises = yys[yypt-0].yyv.node;
+ yyval.ty = yys[yypt-1].yyv.ty;
+ }
+81=>
+yyval.ty = yys[yyp+1].yyv.ty;
+82=>
+#line 549 "limbo.y"
+{
+ yyval.ty = mkidtype(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval);
+ }
+83=>
+#line 553 "limbo.y"
+{
+ yyval.ty = mkinsttype(yys[yypt-3].yyv.tok.src, mkidtype(yys[yypt-3].yyv.tok.src, yys[yypt-3].yyv.tok.v.idval), yys[yypt-1].yyv.types);
+ }
+84=>
+#line 559 "limbo.y"
+{
+ yyval.ty = mkdottype(yys[yypt-2].yyv.ty.src.start, yys[yypt-0].yyv.tok.src.stop, yys[yypt-2].yyv.ty, yys[yypt-0].yyv.tok.v.idval);
+ }
+85=>
+#line 563 "limbo.y"
+{
+ yyval.ty = mkdottype(yys[yypt-5].yyv.ty.src.start, yys[yypt-3].yyv.tok.src.stop, yys[yypt-5].yyv.ty, yys[yypt-3].yyv.tok.v.idval);
+ yyval.ty = mkinsttype(yys[yypt-5].yyv.ty.src, yyval.ty, yys[yypt-1].yyv.types);
+ }
+86=>
+#line 570 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-0].yyv.ty.src, nil, yys[yypt-0].yyv.ty, nil);
+ }
+87=>
+#line 574 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-2].yyv.ids.src, nil, yys[yypt-0].yyv.ty, yys[yypt-2].yyv.ids);
+ }
+88=>
+#line 580 "limbo.y"
+{
+ yyval.ids = nil;
+ }
+89=>
+#line 584 "limbo.y"
+{
+ yyval.ids = polydecl(yys[yypt-1].yyv.ids);
+ }
+90=>
+#line 590 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tfn, tnone, yys[yypt-1].yyv.ids);
+ }
+91=>
+#line 594 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tfn, tnone, nil);
+ yyval.ty.varargs = byte 1;
+ }
+92=>
+#line 599 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-4].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tfn, tnone, yys[yypt-3].yyv.ids);
+ yyval.ty.varargs = byte 1;
+ }
+93=>
+#line 606 "limbo.y"
+{
+ yyval.ty = yys[yypt-0].yyv.ty;
+ }
+94=>
+#line 610 "limbo.y"
+{
+ yys[yypt-2].yyv.ty.tof = yys[yypt-0].yyv.ty;
+ yys[yypt-2].yyv.ty.src.stop = yys[yypt-0].yyv.ty.src.stop;
+ yyval.ty = yys[yypt-2].yyv.ty;
+ }
+95=>
+#line 618 "limbo.y"
+{
+ yyval.ty = yys[yypt-0].yyv.ty;
+ }
+96=>
+#line 622 "limbo.y"
+{
+ yyval.ty = yys[yypt-4].yyv.ty;
+ yyval.ty.val = rotater(yys[yypt-1].yyv.node);
+ }
+97=>
+#line 629 "limbo.y"
+{
+ yyval.ids = nil;
+ }
+98=>
+yyval.ids = yys[yyp+1].yyv.ids;
+99=>
+yyval.ids = yys[yyp+1].yyv.ids;
+100=>
+#line 637 "limbo.y"
+{
+ yyval.ids = appdecls(yys[yypt-2].yyv.ids, yys[yypt-0].yyv.ids);
+ }
+101=>
+#line 643 "limbo.y"
+{
+ yyval.ids = typeids(yys[yypt-2].yyv.ids, yys[yypt-0].yyv.ty);
+ }
+102=>
+#line 647 "limbo.y"
+{
+ yyval.ids = typeids(yys[yypt-2].yyv.ids, yys[yypt-0].yyv.ty);
+ for(d := yyval.ids; d != nil; d = d.next)
+ d.implicit = byte 1;
+ }
+103=>
+#line 653 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-2].yyv.node.src, enter("junk", 0), yys[yypt-0].yyv.ty, nil);
+ yyval.ids.store = Darg;
+ yyerror("illegal argument declaraion");
+ }
+104=>
+#line 659 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-2].yyv.node.src, enter("junk", 0), yys[yypt-0].yyv.ty, nil);
+ yyval.ids.store = Darg;
+ yyerror("illegal argument declaraion");
+ }
+105=>
+#line 667 "limbo.y"
+{
+ yyval.ids = revids(yys[yypt-0].yyv.ids);
+ }
+106=>
+#line 673 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, nil);
+ yyval.ids.store = Darg;
+ }
+107=>
+#line 678 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-0].yyv.tok.src, nil, nil, nil);
+ yyval.ids.store = Darg;
+ }
+108=>
+#line 683 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, yys[yypt-2].yyv.ids);
+ yyval.ids.store = Darg;
+ }
+109=>
+#line 688 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-0].yyv.tok.src, nil, nil, yys[yypt-2].yyv.ids);
+ yyval.ids.store = Darg;
+ }
+110=>
+#line 695 "limbo.y"
+{
+ yyval.ty = yys[yypt-0].yyv.ty;
+ }
+111=>
+#line 699 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-1].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tref, yys[yypt-0].yyv.ty, nil);
+ }
+112=>
+#line 703 "limbo.y"
+{
+ yyval.ty = yys[yypt-0].yyv.ty;
+ }
+113=>
+#line 707 "limbo.y"
+{
+ yyval.ty = mktype(yys[yypt-1].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tref, yys[yypt-0].yyv.ty, nil);
+ }
+114=>
+#line 713 "limbo.y"
+{
+ yyval.node = fndecl(yys[yypt-3].yyv.node, yys[yypt-2].yyv.ty, yys[yypt-0].yyv.node);
+ nfns++;
+ # patch up polydecs
+ if(yys[yypt-3].yyv.node.op == Odot){
+ if(yys[yypt-3].yyv.node.right.left != nil){
+ yys[yypt-2].yyv.ty.polys = yys[yypt-3].yyv.node.right.left.decl;
+ yys[yypt-3].yyv.node.right.left = nil;
+ }
+ if(yys[yypt-3].yyv.node.left.op == Oname && yys[yypt-3].yyv.node.left.left != nil){
+ yyval.node.decl = yys[yypt-3].yyv.node.left.left.decl;
+ yys[yypt-3].yyv.node.left.left = nil;
+ }
+ }
+ else{
+ if(yys[yypt-3].yyv.node.left != nil){
+ yys[yypt-2].yyv.ty.polys = yys[yypt-3].yyv.node.left.decl;
+ yys[yypt-3].yyv.node.left = nil;
+ }
+ }
+ yys[yypt-2].yyv.ty.eraises = yys[yypt-1].yyv.node;
+ yyval.node.src = yys[yypt-3].yyv.node.src;
+ }
+115=>
+#line 739 "limbo.y"
+{
+ yyval.node = mkn(Otuple, rotater(yys[yypt-1].yyv.node), nil);
+ yyval.node.src.start = yys[yypt-3].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+116=>
+#line 745 "limbo.y"
+{
+ yyval.node = mkn(Otuple, mkunary(Oseq, yys[yypt-0].yyv.node), nil);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.node.src.stop;
+ }
+117=>
+#line 751 "limbo.y"
+{
+ yyval.node = nil;
+ }
+118=>
+#line 757 "limbo.y"
+{
+ if(yys[yypt-1].yyv.node == nil){
+ yys[yypt-1].yyv.node = mkn(Onothing, nil, nil);
+ yys[yypt-1].yyv.node.src.start = curline();
+ yys[yypt-1].yyv.node.src.stop = yys[yypt-1].yyv.node.src.start;
+ }
+ yyval.node = rotater(yys[yypt-1].yyv.node);
+ yyval.node.src.start = yys[yypt-2].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+119=>
+#line 768 "limbo.y"
+{
+ yyval.node = mkn(Onothing, nil, nil);
+ }
+120=>
+#line 772 "limbo.y"
+{
+ yyval.node = mkn(Onothing, nil, nil);
+ }
+121=>
+#line 778 "limbo.y"
+{
+ yyval.node = mkname(yys[yypt-1].yyv.tok.src, yys[yypt-1].yyv.tok.v.idval);
+ if(yys[yypt-0].yyv.ids != nil){
+ yyval.node.left = mkn(Onothing, nil ,nil);
+ yyval.node.left.decl = yys[yypt-0].yyv.ids;
+ }
+ }
+122=>
+#line 786 "limbo.y"
+{
+ yyval.node = mkbin(Odot, yys[yypt-3].yyv.node, mkname(yys[yypt-1].yyv.tok.src, yys[yypt-1].yyv.tok.v.idval));
+ if(yys[yypt-0].yyv.ids != nil){
+ yyval.node.right.left = mkn(Onothing, nil ,nil);
+ yyval.node.right.left.decl = yys[yypt-0].yyv.ids;
+ }
+ }
+123=>
+#line 796 "limbo.y"
+{
+ yyval.node = nil;
+ }
+124=>
+#line 800 "limbo.y"
+{
+ if(yys[yypt-1].yyv.node == nil)
+ yyval.node = yys[yypt-0].yyv.node;
+ else if(yys[yypt-0].yyv.node == nil)
+ yyval.node = yys[yypt-1].yyv.node;
+ else
+ yyval.node = mkbin(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node);
+ }
+125=>
+#line 809 "limbo.y"
+{
+ if(yys[yypt-1].yyv.node == nil)
+ yyval.node = yys[yypt-0].yyv.node;
+ else
+ yyval.node = mkbin(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node);
+ }
+128=>
+#line 822 "limbo.y"
+{
+ yyval.node = mkn(Onothing, nil, nil);
+ yyval.node.src.start = curline();
+ yyval.node.src.stop = yyval.node.src.start;
+ }
+129=>
+#line 828 "limbo.y"
+{
+ yyval.node = mkn(Onothing, nil, nil);
+ yyval.node.src.start = curline();
+ yyval.node.src.stop = yyval.node.src.start;
+ }
+130=>
+#line 834 "limbo.y"
+{
+ yyval.node = mkn(Onothing, nil, nil);
+ yyval.node.src.start = curline();
+ yyval.node.src.stop = yyval.node.src.start;
+ }
+131=>
+#line 840 "limbo.y"
+{
+ if(yys[yypt-1].yyv.node == nil){
+ yys[yypt-1].yyv.node = mkn(Onothing, nil, nil);
+ yys[yypt-1].yyv.node.src.start = curline();
+ yys[yypt-1].yyv.node.src.stop = yys[yypt-1].yyv.node.src.start;
+ }
+ yyval.node = mkscope(rotater(yys[yypt-1].yyv.node));
+ }
+132=>
+#line 849 "limbo.y"
+{
+ yyerror("illegal declaration");
+ yyval.node = mkn(Onothing, nil, nil);
+ yyval.node.src.start = curline();
+ yyval.node.src.stop = yyval.node.src.start;
+ }
+133=>
+#line 856 "limbo.y"
+{
+ yyerror("illegal declaration");
+ yyval.node = mkn(Onothing, nil, nil);
+ yyval.node.src.start = curline();
+ yyval.node.src.stop = yyval.node.src.start;
+ }
+134=>
+#line 863 "limbo.y"
+{
+ yyval.node = yys[yypt-1].yyv.node;
+ }
+135=>
+#line 867 "limbo.y"
+{
+ yyval.node = mkn(Oif, yys[yypt-2].yyv.node, mkunary(Oseq, yys[yypt-0].yyv.node));
+ yyval.node.src.start = yys[yypt-4].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.node.src.stop;
+ }
+136=>
+#line 873 "limbo.y"
+{
+ yyval.node = mkn(Oif, yys[yypt-4].yyv.node, mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node));
+ yyval.node.src.start = yys[yypt-6].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.node.src.stop;
+ }
+137=>
+#line 879 "limbo.y"
+{
+ yyval.node = mkunary(Oseq, yys[yypt-0].yyv.node);
+ if(yys[yypt-2].yyv.node.op != Onothing)
+ yyval.node.right = yys[yypt-2].yyv.node;
+ yyval.node = mkbin(Ofor, yys[yypt-4].yyv.node, yyval.node);
+ yyval.node.decl = yys[yypt-9].yyv.ids;
+ if(yys[yypt-6].yyv.node.op != Onothing)
+ yyval.node = mkbin(Oseq, yys[yypt-6].yyv.node, yyval.node);
+ }
+138=>
+#line 889 "limbo.y"
+{
+ yyval.node = mkn(Ofor, yys[yypt-2].yyv.node, mkunary(Oseq, yys[yypt-0].yyv.node));
+ yyval.node.src.start = yys[yypt-4].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.node.src.stop;
+ yyval.node.decl = yys[yypt-5].yyv.ids;
+ }
+139=>
+#line 896 "limbo.y"
+{
+ yyval.node = mkn(Odo, yys[yypt-2].yyv.node, yys[yypt-5].yyv.node);
+ yyval.node.src.start = yys[yypt-6].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-1].yyv.tok.src.stop;
+ yyval.node.decl = yys[yypt-7].yyv.ids;
+ }
+140=>
+#line 903 "limbo.y"
+{
+ yyval.node = mkn(Obreak, nil, nil);
+ yyval.node.decl = yys[yypt-1].yyv.ids;
+ yyval.node.src = yys[yypt-2].yyv.tok.src;
+ }
+141=>
+#line 909 "limbo.y"
+{
+ yyval.node = mkn(Ocont, nil, nil);
+ yyval.node.decl = yys[yypt-1].yyv.ids;
+ yyval.node.src = yys[yypt-2].yyv.tok.src;
+ }
+142=>
+#line 915 "limbo.y"
+{
+ yyval.node = mkn(Oret, yys[yypt-1].yyv.node, nil);
+ yyval.node.src = yys[yypt-2].yyv.tok.src;
+ if(yys[yypt-1].yyv.node.op == Onothing)
+ yyval.node.left = nil;
+ else
+ yyval.node.src.stop = yys[yypt-1].yyv.node.src.stop;
+ }
+143=>
+#line 924 "limbo.y"
+{
+ yyval.node = mkn(Ospawn, yys[yypt-1].yyv.node, nil);
+ yyval.node.src.start = yys[yypt-2].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-1].yyv.node.src.stop;
+ }
+144=>
+#line 930 "limbo.y"
+{
+ yyval.node = mkn(Oraise, yys[yypt-1].yyv.node, nil);
+ yyval.node.src.start = yys[yypt-2].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-1].yyv.node.src.stop;
+ }
+145=>
+#line 936 "limbo.y"
+{
+ yyval.node = mkn(Ocase, yys[yypt-3].yyv.node, caselist(yys[yypt-1].yyv.node, nil));
+ yyval.node.src = yys[yypt-3].yyv.node.src;
+ yyval.node.decl = yys[yypt-5].yyv.ids;
+ }
+146=>
+#line 942 "limbo.y"
+{
+ yyval.node = mkn(Oalt, caselist(yys[yypt-1].yyv.node, nil), nil);
+ yyval.node.src = yys[yypt-3].yyv.tok.src;
+ yyval.node.decl = yys[yypt-4].yyv.ids;
+ }
+147=>
+#line 948 "limbo.y"
+{
+ yyval.node = mkn(Opick, mkbin(Odas, mkname(yys[yypt-5].yyv.tok.src, yys[yypt-5].yyv.tok.v.idval), yys[yypt-3].yyv.node), caselist(yys[yypt-1].yyv.node, nil));
+ yyval.node.src.start = yys[yypt-5].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-3].yyv.node.src.stop;
+ yyval.node.decl = yys[yypt-7].yyv.ids;
+ }
+148=>
+#line 955 "limbo.y"
+{
+ yyval.node = mkn(Oexit, nil, nil);
+ yyval.node.src = yys[yypt-1].yyv.tok.src;
+ }
+149=>
+#line 960 "limbo.y"
+{
+ if(yys[yypt-6].yyv.node == nil){
+ yys[yypt-6].yyv.node = mkn(Onothing, nil, nil);
+ yys[yypt-6].yyv.node.src.start = yys[yypt-6].yyv.node.src.stop = curline();
+ }
+ yys[yypt-6].yyv.node = mkscope(rotater(yys[yypt-6].yyv.node));
+ yyval.node = mkbin(Oexstmt, yys[yypt-6].yyv.node, mkn(Oexcept, yys[yypt-3].yyv.node, caselist(yys[yypt-1].yyv.node, nil)));
+ }
+150=>
+#line 975 "limbo.y"
+{
+ yyval.ids = nil;
+ }
+151=>
+#line 979 "limbo.y"
+{
+ if(yys[yypt-1].yyv.ids.next != nil)
+ yyerror("only one identifier allowed in a label");
+ yyval.ids = yys[yypt-1].yyv.ids;
+ }
+152=>
+#line 987 "limbo.y"
+{
+ yyval.ids = nil;
+ }
+153=>
+#line 991 "limbo.y"
+{
+ yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, nil);
+ }
+154=>
+#line 997 "limbo.y"
+{
+ yys[yypt-1].yyv.node.left.right.right = yys[yypt-0].yyv.node;
+ yyval.node = yys[yypt-1].yyv.node;
+ }
+155=>
+#line 1004 "limbo.y"
+{
+ yyval.node = mkunary(Oseq, mkscope(mkunary(Olabel, rotater(yys[yypt-1].yyv.node))));
+ }
+156=>
+#line 1008 "limbo.y"
+{
+ yys[yypt-3].yyv.node.left.right.right = yys[yypt-2].yyv.node;
+ yyval.node = mkbin(Oseq, mkscope(mkunary(Olabel, rotater(yys[yypt-1].yyv.node))), yys[yypt-3].yyv.node);
+ }
+157=>
+#line 1015 "limbo.y"
+{
+ yys[yypt-1].yyv.node.left.right = mkscope(yys[yypt-0].yyv.node);
+ yyval.node = yys[yypt-1].yyv.node;
+ }
+158=>
+#line 1022 "limbo.y"
+{
+ yyval.node = mkunary(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node)));
+ }
+159=>
+#line 1026 "limbo.y"
+{
+ yys[yypt-3].yyv.node.left.right = mkscope(yys[yypt-2].yyv.node);
+ yyval.node = mkbin(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node)), yys[yypt-3].yyv.node);
+ }
+160=>
+#line 1033 "limbo.y"
+{
+ yys[yypt-1].yyv.node.left.right = mkscope(yys[yypt-0].yyv.node);
+ yyval.node = yys[yypt-1].yyv.node;
+ }
+161=>
+#line 1040 "limbo.y"
+{
+ yyval.node = mkunary(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node)));
+ }
+162=>
+#line 1044 "limbo.y"
+{
+ yys[yypt-3].yyv.node.left.right = mkscope(yys[yypt-2].yyv.node);
+ yyval.node = mkbin(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node)), yys[yypt-3].yyv.node);
+ }
+163=>
+yyval.node = yys[yyp+1].yyv.node;
+164=>
+#line 1052 "limbo.y"
+{
+ yyval.node = mkbin(Orange, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+165=>
+#line 1056 "limbo.y"
+{
+ yyval.node = mkn(Owild, nil, nil);
+ yyval.node.src = yys[yypt-0].yyv.tok.src;
+ }
+166=>
+#line 1061 "limbo.y"
+{
+ yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+167=>
+#line 1065 "limbo.y"
+{
+ yyval.node = mkn(Onothing, nil, nil);
+ yyval.node.src.start = curline();
+ yyval.node.src.stop = yyval.node.src.start;
+ }
+168=>
+#line 1073 "limbo.y"
+{
+ yys[yypt-1].yyv.node.left.right = mkscope(yys[yypt-0].yyv.node);
+ yyval.node = yys[yypt-1].yyv.node;
+ }
+169=>
+#line 1080 "limbo.y"
+{
+ yyval.node = mkunary(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node)));
+ }
+170=>
+#line 1084 "limbo.y"
+{
+ yys[yypt-3].yyv.node.left.right = mkscope(yys[yypt-2].yyv.node);
+ yyval.node = mkbin(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node)), yys[yypt-3].yyv.node);
+ }
+171=>
+#line 1091 "limbo.y"
+{
+ yyval.node = mkname(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval);
+ }
+172=>
+#line 1095 "limbo.y"
+{
+ yyval.node = mkn(Owild, nil, nil);
+ yyval.node.src = yys[yypt-0].yyv.tok.src;
+ }
+173=>
+#line 1100 "limbo.y"
+{
+ yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+174=>
+#line 1104 "limbo.y"
+{
+ yyval.node = mkn(Onothing, nil, nil);
+ yyval.node.src.start = curline();
+ yyval.node.src.stop = yyval.node.src.start;
+ }
+175=>
+#line 1112 "limbo.y"
+{
+ yyval.node = mkn(Onothing, nil, nil);
+ yyval.node.src.start = curline();
+ yyval.node.src.stop = yyval.node.src.start;
+ }
+176=>
+yyval.node = yys[yyp+1].yyv.node;
+177=>
+yyval.node = yys[yyp+1].yyv.node;
+178=>
+#line 1122 "limbo.y"
+{
+ yyval.node = mkbin(Oas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+179=>
+#line 1126 "limbo.y"
+{
+ yyval.node = mkbin(Oandas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+180=>
+#line 1130 "limbo.y"
+{
+ yyval.node = mkbin(Ooras, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+181=>
+#line 1134 "limbo.y"
+{
+ yyval.node = mkbin(Oxoras, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+182=>
+#line 1138 "limbo.y"
+{
+ yyval.node = mkbin(Olshas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+183=>
+#line 1142 "limbo.y"
+{
+ yyval.node = mkbin(Orshas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+184=>
+#line 1146 "limbo.y"
+{
+ yyval.node = mkbin(Oaddas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+185=>
+#line 1150 "limbo.y"
+{
+ yyval.node = mkbin(Osubas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+186=>
+#line 1154 "limbo.y"
+{
+ yyval.node = mkbin(Omulas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+187=>
+#line 1158 "limbo.y"
+{
+ yyval.node = mkbin(Odivas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+188=>
+#line 1162 "limbo.y"
+{
+ yyval.node = mkbin(Omodas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+189=>
+#line 1166 "limbo.y"
+{
+ yyval.node = mkbin(Oexpas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+190=>
+#line 1170 "limbo.y"
+{
+ yyval.node = mkbin(Osnd, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node);
+ }
+191=>
+#line 1174 "limbo.y"
+{
+ yyval.node = mkbin(Odas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+192=>
+#line 1178 "limbo.y"
+{
+ yyval.node = mkn(Oload, yys[yypt-0].yyv.node, nil);
+ yyval.node.src.start = yys[yypt-2].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.node.src.stop;
+ yyval.node.ty = mkidtype(yys[yypt-1].yyv.tok.src, yys[yypt-1].yyv.tok.v.idval);
+ }
+193=>
+#line 1185 "limbo.y"
+{
+ yyval.node = yyval.node = mkbin(Oexp, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+194=>
+#line 1189 "limbo.y"
+{
+ yyval.node = mkbin(Omul, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+195=>
+#line 1193 "limbo.y"
+{
+ yyval.node = mkbin(Odiv, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+196=>
+#line 1197 "limbo.y"
+{
+ yyval.node = mkbin(Omod, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+197=>
+#line 1201 "limbo.y"
+{
+ yyval.node = mkbin(Oadd, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+198=>
+#line 1205 "limbo.y"
+{
+ yyval.node = mkbin(Osub, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+199=>
+#line 1209 "limbo.y"
+{
+ yyval.node = mkbin(Orsh, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+200=>
+#line 1213 "limbo.y"
+{
+ yyval.node = mkbin(Olsh, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+201=>
+#line 1217 "limbo.y"
+{
+ yyval.node = mkbin(Olt, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+202=>
+#line 1221 "limbo.y"
+{
+ yyval.node = mkbin(Ogt, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+203=>
+#line 1225 "limbo.y"
+{
+ yyval.node = mkbin(Oleq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+204=>
+#line 1229 "limbo.y"
+{
+ yyval.node = mkbin(Ogeq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+205=>
+#line 1233 "limbo.y"
+{
+ yyval.node = mkbin(Oeq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+206=>
+#line 1237 "limbo.y"
+{
+ yyval.node = mkbin(Oneq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+207=>
+#line 1241 "limbo.y"
+{
+ yyval.node = mkbin(Oand, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+208=>
+#line 1245 "limbo.y"
+{
+ yyval.node = mkbin(Oxor, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+209=>
+#line 1249 "limbo.y"
+{
+ yyval.node = mkbin(Oor, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+210=>
+#line 1253 "limbo.y"
+{
+ yyval.node = mkbin(Ocons, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+211=>
+#line 1257 "limbo.y"
+{
+ yyval.node = mkbin(Oandand, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+212=>
+#line 1261 "limbo.y"
+{
+ yyval.node = mkbin(Ooror, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+213=>
+yyval.node = yys[yyp+1].yyv.node;
+214=>
+#line 1268 "limbo.y"
+{
+ yys[yypt-0].yyv.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ yyval.node = yys[yypt-0].yyv.node;
+ }
+215=>
+#line 1273 "limbo.y"
+{
+ yyval.node = mkunary(Oneg, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+216=>
+#line 1278 "limbo.y"
+{
+ yyval.node = mkunary(Onot, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+217=>
+#line 1283 "limbo.y"
+{
+ yyval.node = mkunary(Ocomp, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+218=>
+#line 1288 "limbo.y"
+{
+ yyval.node = mkunary(Oind, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+219=>
+#line 1293 "limbo.y"
+{
+ yyval.node = mkunary(Opreinc, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+220=>
+#line 1298 "limbo.y"
+{
+ yyval.node = mkunary(Opredec, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+221=>
+#line 1303 "limbo.y"
+{
+ yyval.node = mkunary(Orcv, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+222=>
+#line 1308 "limbo.y"
+{
+ yyval.node = mkunary(Ohd, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+223=>
+#line 1313 "limbo.y"
+{
+ yyval.node = mkunary(Otl, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+224=>
+#line 1318 "limbo.y"
+{
+ yyval.node = mkunary(Olen, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+225=>
+#line 1323 "limbo.y"
+{
+ yyval.node = mkunary(Oref, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+226=>
+#line 1328 "limbo.y"
+{
+ yyval.node = mkunary(Otagof, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ }
+227=>
+#line 1333 "limbo.y"
+{
+ yyval.node = mkn(Oarray, yys[yypt-3].yyv.node, nil);
+ yyval.node.ty = mktype(yys[yypt-5].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tarray, yys[yypt-0].yyv.ty, nil);
+ yyval.node.src = yyval.node.ty.src;
+ }
+228=>
+#line 1339 "limbo.y"
+{
+ yyval.node = mkn(Oarray, yys[yypt-5].yyv.node, yys[yypt-1].yyv.node);
+ yyval.node.src.start = yys[yypt-7].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+229=>
+#line 1345 "limbo.y"
+{
+ yyval.node = mkn(Onothing, nil, nil);
+ yyval.node.src.start = yys[yypt-5].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-4].yyv.tok.src.stop;
+ yyval.node = mkn(Oarray, yyval.node, yys[yypt-1].yyv.node);
+ yyval.node.src.start = yys[yypt-6].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+230=>
+#line 1354 "limbo.y"
+{
+ yyval.node = etolist(yys[yypt-1].yyv.node);
+ yyval.node.src.start = yys[yypt-4].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+231=>
+#line 1360 "limbo.y"
+{
+ yyval.node = mkn(Ochan, nil, nil);
+ yyval.node.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tchan, yys[yypt-0].yyv.ty, nil);
+ yyval.node.src = yyval.node.ty.src;
+ }
+232=>
+#line 1366 "limbo.y"
+{
+ yyval.node = mkn(Ochan, yys[yypt-3].yyv.node, nil);
+ yyval.node.ty = mktype(yys[yypt-5].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tchan, yys[yypt-0].yyv.ty, nil);
+ yyval.node.src = yyval.node.ty.src;
+ }
+233=>
+#line 1372 "limbo.y"
+{
+ yyval.node = mkunary(Ocast, yys[yypt-0].yyv.node);
+ yyval.node.ty = mktype(yys[yypt-3].yyv.tok.src.start, yys[yypt-0].yyv.node.src.stop, Tarray, mkidtype(yys[yypt-1].yyv.tok.src, yys[yypt-1].yyv.tok.v.idval), nil);
+ yyval.node.src = yyval.node.ty.src;
+ }
+234=>
+#line 1378 "limbo.y"
+{
+ yyval.node = mkunary(Ocast, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ yyval.node.ty = mkidtype(yyval.node.src, yys[yypt-1].yyv.tok.v.idval);
+ }
+235=>
+#line 1384 "limbo.y"
+{
+ yyval.node = mkunary(Ocast, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ yyval.node.ty = mkidtype(yyval.node.src, yys[yypt-1].yyv.tok.v.idval);
+ }
+236=>
+#line 1390 "limbo.y"
+{
+ yyval.node = mkunary(Ocast, yys[yypt-0].yyv.node);
+ yyval.node.src.start = yys[yypt-1].yyv.tok.src.start;
+ yyval.node.ty = yys[yypt-1].yyv.ty;
+ }
+237=>
+yyval.node = yys[yyp+1].yyv.node;
+238=>
+#line 1399 "limbo.y"
+{
+ yyval.node = mkn(Ocall, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node);
+ yyval.node.src.start = yys[yypt-3].yyv.node.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+239=>
+#line 1405 "limbo.y"
+{
+ yyval.node = yys[yypt-1].yyv.node;
+ if(yys[yypt-1].yyv.node.op == Oseq)
+ yyval.node = mkn(Otuple, rotater(yys[yypt-1].yyv.node), nil);
+ else
+ yyval.node.flags |= byte PARENS;
+ yyval.node.src.start = yys[yypt-2].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+240=>
+#line 1415 "limbo.y"
+{
+# n := mkdeclname($1, mkids($1, enter(".fn"+string nfnexp++, 0), nil, nil));
+# $<node>$ = fndef(n, $2);
+# nfns++;
+ }
+241=>
+#line 1420 "limbo.y"
+{
+# $$ = fnfinishdef($<node>3, $4);
+# $$ = mkdeclname($1, $$.left.decl);
+ yyerror("urt unk");
+ yyval.node = nil;
+ }
+242=>
+#line 1427 "limbo.y"
+{
+ yyval.node = mkbin(Odot, yys[yypt-2].yyv.node, mkname(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval));
+ }
+243=>
+#line 1431 "limbo.y"
+{
+ yyval.node = mkbin(Omdot, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+244=>
+#line 1435 "limbo.y"
+{
+ yyval.node = mkbin(Oindex, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node);
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+245=>
+#line 1440 "limbo.y"
+{
+ if(yys[yypt-3].yyv.node.op == Onothing)
+ yys[yypt-3].yyv.node.src = yys[yypt-2].yyv.tok.src;
+ if(yys[yypt-1].yyv.node.op == Onothing)
+ yys[yypt-1].yyv.node.src = yys[yypt-2].yyv.tok.src;
+ yyval.node = mkbin(Oslice, yys[yypt-5].yyv.node, mkbin(Oseq, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node));
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+246=>
+#line 1449 "limbo.y"
+{
+ yyval.node = mkunary(Oinc, yys[yypt-1].yyv.node);
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+247=>
+#line 1454 "limbo.y"
+{
+ yyval.node = mkunary(Odec, yys[yypt-1].yyv.node);
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+248=>
+#line 1459 "limbo.y"
+{
+ yyval.node = mksconst(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval);
+ }
+249=>
+#line 1463 "limbo.y"
+{
+ yyval.node = mkconst(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.ival);
+ if(yys[yypt-0].yyv.tok.v.ival > big 16r7fffffff || yys[yypt-0].yyv.tok.v.ival < big -16r7fffffff)
+ yyval.node.ty = tbig;
+ }
+250=>
+#line 1469 "limbo.y"
+{
+ yyval.node = mkrconst(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.rval);
+ }
+251=>
+#line 1473 "limbo.y"
+{
+ yyval.node = mkbin(Oindex, yys[yypt-5].yyv.node, rotater(mkbin(Oseq, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node)));
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+252=>
+#line 1480 "limbo.y"
+{
+ yyval.node = mkname(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval);
+ }
+253=>
+#line 1484 "limbo.y"
+{
+ yyval.node = mknil(yys[yypt-0].yyv.tok.src);
+ }
+254=>
+#line 1490 "limbo.y"
+{
+ yyval.node = mkn(Otuple, rotater(yys[yypt-1].yyv.node), nil);
+ yyval.node.src.start = yys[yypt-2].yyv.tok.src.start;
+ yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop;
+ }
+255=>
+yyval.node = yys[yyp+1].yyv.node;
+256=>
+#line 1499 "limbo.y"
+{
+ yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+257=>
+yyval.node = yys[yyp+1].yyv.node;
+258=>
+yyval.node = yys[yyp+1].yyv.node;
+259=>
+#line 1509 "limbo.y"
+{
+ yyval.node = mkn(Otype, nil, nil);
+ yyval.node.ty = mkidtype(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval);
+ yyval.node.src = yyval.node.ty.src;
+ }
+260=>
+#line 1515 "limbo.y"
+{
+ yyval.node = mkn(Otype, nil, nil);
+ yyval.node.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tarray, yys[yypt-0].yyv.ty, nil);
+ yyval.node.src = yyval.node.ty.src;
+ }
+261=>
+#line 1521 "limbo.y"
+{
+ yyval.node = mkn(Otype, nil, nil);
+ yyval.node.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tlist, yys[yypt-0].yyv.ty, nil);
+ yyval.node.src = yyval.node.ty.src;
+ }
+262=>
+#line 1527 "limbo.y"
+{
+ yyval.node = mkn(Otype, nil ,nil);
+ yyval.node.ty = yys[yypt-0].yyv.ty;
+ yyval.node.ty.flags |= CYCLIC;
+ yyval.node.src = yyval.node.ty.src;
+ }
+263=>
+#line 1536 "limbo.y"
+{
+ yyval.node = mkname(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval);
+ }
+264=>
+#line 1540 "limbo.y"
+{
+ yyval.node = nil;
+ }
+265=>
+yyval.node = yys[yyp+1].yyv.node;
+266=>
+yyval.node = yys[yyp+1].yyv.node;
+267=>
+#line 1548 "limbo.y"
+{
+ yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+268=>
+#line 1552 "limbo.y"
+{
+ yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+269=>
+#line 1558 "limbo.y"
+{
+ yyval.node = nil;
+ }
+270=>
+#line 1562 "limbo.y"
+{
+ yyval.node = rotater(yys[yypt-0].yyv.node);
+ }
+271=>
+yyval.node = yys[yyp+1].yyv.node;
+272=>
+yyval.node = yys[yyp+1].yyv.node;
+273=>
+yyval.node = yys[yyp+1].yyv.node;
+274=>
+#line 1573 "limbo.y"
+{
+ yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+275=>
+#line 1579 "limbo.y"
+{
+ yyval.node = rotater(yys[yypt-0].yyv.node);
+ }
+276=>
+#line 1583 "limbo.y"
+{
+ yyval.node = rotater(yys[yypt-1].yyv.node);
+ }
+277=>
+yyval.node = yys[yyp+1].yyv.node;
+278=>
+#line 1590 "limbo.y"
+{
+ yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node);
+ }
+279=>
+#line 1596 "limbo.y"
+{
+ yyval.node = mkn(Oelem, nil, yys[yypt-0].yyv.node);
+ yyval.node.src = yys[yypt-0].yyv.node.src;
+ }
+280=>
+#line 1601 "limbo.y"
+{
+ yyval.node = mkbin(Oelem, rotater(yys[yypt-2].yyv.node), yys[yypt-0].yyv.node);
+ }
+281=>
+#line 1607 "limbo.y"
+{
+ if(yys[yypt-1].yyv.node.op == Oseq)
+ yys[yypt-1].yyv.node.right.left = rotater(yys[yypt-0].yyv.node);
+ else
+ yys[yypt-1].yyv.node.left = rotater(yys[yypt-0].yyv.node);
+ yyval.node = yys[yypt-1].yyv.node;
+ }
+282=>
+#line 1617 "limbo.y"
+{
+ yyval.node = typedecl(yys[yypt-1].yyv.ids, mktype(yys[yypt-1].yyv.ids.src.start, yys[yypt-0].yyv.tok.src.stop, Tpoly, nil, nil));
+ }
+283=>
+#line 1621 "limbo.y"
+{
+ if(yys[yypt-3].yyv.node.op == Oseq)
+ yys[yypt-3].yyv.node.right.left = rotater(yys[yypt-2].yyv.node);
+ else
+ yys[yypt-3].yyv.node.left = rotater(yys[yypt-2].yyv.node);
+ yyval.node = mkbin(Oseq, yys[yypt-3].yyv.node, typedecl(yys[yypt-1].yyv.ids, mktype(yys[yypt-1].yyv.ids.src.start, yys[yypt-0].yyv.tok.src.stop, Tpoly, nil, nil)));
+ }
+ }
+ }
+
+ return yyn;
+}
diff --git a/appl/cmd/limbo/limbo.m b/appl/cmd/limbo/limbo.m
new file mode 100644
index 00000000..8c2efce0
--- /dev/null
+++ b/appl/cmd/limbo/limbo.m
@@ -0,0 +1,527 @@
+include "sys.m";
+include "math.m";
+include "string.m";
+include "bufio.m";
+include "isa.m";
+include "workdir.m";
+
+# internal dis ops
+IEXC: con MAXDIS;
+IEXC0: con (MAXDIS+1);
+INOOP: con (MAXDIS+2);
+
+# temporary
+LDT: con 1;
+
+STemp: con NREG * IBY2WD;
+RTemp: con STemp + IBY2WD;
+DTemp: con RTemp + IBY2WD;
+MaxTemp: con DTemp + IBY2WD;
+MaxReg: con 1 << 16;
+MaxAlign: con IBY2LG;
+StrSize: con 256;
+MaxIncPath: con 32; # max directories in include path
+MaxScope: con 64; # max nested {}
+MaxInclude: con 32; # max nested include ""
+ScopeBuiltin,
+ScopeNils,
+ScopeGlobal: con iota;
+
+Line: type int;
+PosBits: con 10;
+PosMask: con (1 << PosBits) - 1;
+
+Src: adt
+{
+ start: Line;
+ stop: Line;
+};
+
+File: adt
+{
+ name: string;
+ abs: int; # absolute line of start of the part of file
+ off: int; # offset to line in the file
+ in: int; # absolute line where included
+ act: string; # name of real file with #line fake file
+ actoff: int; # offset from fake line to real line
+ sbl: int; # symbol file number
+};
+
+Val: adt
+{
+ idval: ref Sym;
+ ival: big;
+ rval: real;
+};
+
+Tok: adt
+{
+ src: Src;
+ v: Val;
+};
+
+#
+# addressing modes
+#
+ Aimm, # immediate
+ Amp, # global
+ Ampind, # global indirect
+ Afp, # activation frame
+ Afpind, # frame indirect
+ Apc, # branch
+ Adesc, # type descriptor immediate
+ Aoff, # offset in module description table
+ Anoff, # above encoded as a -ve
+ Aerr, # error
+ Anone, # no operand
+ Aldt, # linkage descriptor table immediate
+ Aend: con byte iota;
+
+Addr: adt
+{
+ reg: int;
+ offset: int;
+ decl: cyclic ref Decl;
+};
+
+Inst: adt
+{
+ src: Src;
+ op: int; # could be a byte
+ pc: int;
+ reach: byte; # could a control path reach this instruction?
+ sm: byte; # operand addressing modes
+ mm: byte;
+ dm: byte;
+ s: cyclic Addr; # operands
+ m: cyclic Addr;
+ d: cyclic Addr;
+ branch: cyclic ref Inst; # branch destination
+ next: cyclic ref Inst;
+ block: int; # blocks nested inside
+};
+
+Case: adt
+{
+ nlab: int;
+ nsnd: int;
+ offset: int; # offset in mp
+ labs: cyclic array of Label;
+ wild: cyclic ref Node; # if nothing matches
+ iwild: cyclic ref Inst;
+};
+
+Label: adt
+{
+ node: cyclic ref Node;
+ isptr: int; # true if the labelled alt channel is a pointer
+ start: cyclic ref Node; # value in range [start, stop) => code
+ stop: cyclic ref Node;
+ inst: cyclic ref Inst;
+};
+
+#
+# storage classes
+#
+ Dtype,
+ Dfn,
+ Dglobal,
+ Darg,
+ Dlocal,
+ Dconst,
+ Dfield,
+ Dtag, # pick tags
+ Dimport, # imported identifier
+ Dunbound, # unbound identified
+ Dundef,
+ Dwundef, # undefined, but don't whine
+
+ Dend: con iota;
+
+Decl: adt
+{
+ src: Src; # where declaration
+ sym: cyclic ref Sym; # name
+ store: int; # storage class
+ nid: byte; # block grouping for locals
+ inline: byte; # inline function
+ handler: byte; # fn has exception handler(s)
+ das: byte; # declared with :=
+ dot: cyclic ref Decl; # parent adt or module
+ ty: cyclic ref Type;
+ refs: int; # number of references
+ offset: int;
+ tag: int; # union tag
+
+ scope: int; # in which it was declared
+ next: cyclic ref Decl; # list in same scope, field or argument list, etc.
+ old: cyclic ref Decl; # declaration of the symbol in enclosing scope
+
+ eimport: cyclic ref Node; # expr from which imported
+ importid: cyclic ref Decl; # identifier imported
+ timport: cyclic ref Decl; # stack of identifiers importing a type
+
+ init: cyclic ref Node; # data initialization
+ tref: int; # 1 => is a tmp; >=2 => tmp in use
+ cycle: byte; # can create a cycle
+ cyc: byte; # so labelled in source
+ cycerr: byte; # delivered an error message for cycle?
+ implicit: byte; # implicit first argument in an adt?
+
+ iface: cyclic ref Decl; # used external declarations in a module
+
+ locals: cyclic ref Decl; # locals for a function
+ link: cyclic ref Decl; # pointer to parent function or function argument or local share or parent type dec
+ pc: cyclic ref Inst; # start of function
+ # endpc: cyclic ref Inst; # limit of function - unused
+
+# should be able to move this to Type
+ desc: ref Desc; # heap descriptor
+};
+
+Desc: adt
+{
+ id: int; # dis type identifier
+ used: int; # actually used in output?
+ map: array of byte; # byte map of pointers
+ size: int; # length of the object
+ nmap: int; # length of good bytes in map
+ next: cyclic ref Desc;
+};
+
+Dlist: adt
+{
+ d: ref Decl;
+ next: cyclic ref Dlist;
+};
+
+Except: adt
+{
+ p1: ref Inst; # first pc covered
+ p2: ref Inst; # last pc not covered
+ c: ref Case; # exception case instructions
+ d: ref Decl; # exception definition if any
+ zn: ref Node; # list of nodes to zero in handler
+ desc: ref Desc; # descriptor map for above
+ ne: int; # number of exceptions (ie not strings) in case
+ next: cyclic ref Except;
+};
+
+Sym: adt
+{
+ token: int;
+ name: string;
+ hash: int;
+ next: cyclic ref Sym;
+ decl: cyclic ref Decl;
+ unbound: cyclic ref Decl; # place holder for unbound symbols
+};
+
+#
+# ops for nodes
+#
+ Oadd,
+ Oaddas,
+ Oadr,
+ Oadtdecl,
+ Oalt,
+ Oand,
+ Oandand,
+ Oandas,
+ Oarray,
+ Oas,
+ Obreak,
+ Ocall,
+ Ocase,
+ Ocast,
+ Ochan,
+ Ocomma,
+ Ocomp,
+ Ocondecl,
+ Ocons,
+ Oconst,
+ Ocont,
+ Odas,
+ Odec,
+ Odiv,
+ Odivas,
+ Odo,
+ Odot,
+ Oelem,
+ Oeq,
+ Oexcept,
+ Oexdecl,
+ Oexit,
+ Oexp,
+ Oexpas,
+ Oexstmt,
+ Ofielddecl,
+ Ofnptr,
+ Ofor,
+ Ofunc,
+ Ogeq,
+ Ogt,
+ Ohd,
+ Oif,
+ Oimport,
+ Oinc,
+ Oind,
+ Oindex,
+ Oinds,
+ Oindx,
+ Oinv,
+ Ojmp,
+ Olabel,
+ Olen,
+ Oleq,
+ Oload,
+ Olsh,
+ Olshas,
+ Olt,
+ Omdot,
+ Omod,
+ Omodas,
+ Omoddecl,
+ Omul,
+ Omulas,
+ Oname,
+ Oneg,
+ Oneq,
+ Onot,
+ Onothing,
+ Oor,
+ Ooras,
+ Ooror,
+ Opick,
+ Opickdecl,
+ Opredec,
+ Opreinc,
+ Oraise,
+ Orange,
+ Orcv,
+ Oref,
+ Oret,
+ Orsh,
+ Orshas,
+ Oscope,
+ Oself,
+ Oseq,
+ Oslice,
+ Osnd,
+ Ospawn,
+ Osub,
+ Osubas,
+ Otagof,
+ Otl,
+ Otuple,
+ Otype,
+ Otypedecl,
+ Oused,
+ Ovardecl,
+ Ovardecli,
+ Owild,
+ Oxor,
+ Oxoras,
+
+ Oend: con iota + 1;
+
+#
+# moves
+#
+ Mas,
+ Mcons,
+ Mhd,
+ Mtl,
+
+ Mend: con iota;
+
+#
+# addressability
+#
+ Rreg, # v(fp)
+ Rmreg, # v(mp)
+ Roff, # $v
+ Rnoff, # $v encoded as -ve
+ Rdesc, # $v
+ Rdescp, # $v
+ Rconst, # $v
+ Ralways, # preceeding are always addressable
+ Radr, # v(v(fp))
+ Rmadr, # v(v(mp))
+ Rcant, # following are not quite addressable
+ Rpc, # branch address
+ Rmpc, # cross module branch address
+ Rareg, # $v(fp)
+ Ramreg, # $v(mp)
+ Raadr, # $v(v(fp))
+ Ramadr, # $v(v(mp))
+ Rldt, # $v
+
+ Rend: con byte iota;
+
+
+Const: adt
+{
+ val: big;
+ rval: real;
+};
+
+PARENS: con 1;
+TEMP: con 2;
+FNPTRA: con 4; # argument
+FNPTR2: con 8; # 2nd parameter
+FNPTRN: con 16; # use -ve offset
+FNPTR: con FNPTRA|FNPTR2|FNPTRN;
+
+Node: adt
+{
+ src: Src;
+ op: int;
+ addable: byte;
+ flags: byte;
+ temps: byte;
+ left: cyclic ref Node;
+ right: cyclic ref Node;
+ ty: cyclic ref Type;
+ decl: cyclic ref Decl;
+ c: ref Const; # for Oconst
+};
+
+ #
+ # types visible to limbo
+ #
+ Tnone,
+ Tadt,
+ Tadtpick, # pick case of an adt
+ Tarray,
+ Tbig, # 64 bit int
+ Tbyte, # 8 bit unsigned int
+ Tchan,
+ Treal,
+ Tfn,
+ Tint, # 32 bit int
+ Tlist,
+ Tmodule,
+ Tref,
+ Tstring,
+ Ttuple,
+ Texception,
+ Tfix,
+ Tpoly,
+
+ #
+ # internal use types
+ #
+ Tainit, # array initializers
+ Talt, # alt channels
+ Tany, # type of nil
+ Tarrow, # unresolved ty->ty types
+ Tcase, # case labels
+ Tcasel, # case big labels
+ Tcasec, # case string labels
+ Tdot, # unresolved ty.id types
+ Terror,
+ Tgoto, # goto labels
+ Tid, # id with unknown type
+ Tiface, # module interface
+ Texcept, # exception handler tables
+ Tinst, # instantiated adt
+
+ Tend: con iota;
+
+ #
+ # marks for various phases of verifing types
+ #
+ OKbind, # type decls are bound
+ OKverify, # type looks ok
+ OKsized, # started figuring size
+ OKref, # recorded use of type
+ OKclass, # equivalence class found
+ OKcyc, # checked for cycles
+ OKcycsize, # checked for cycles and size
+ OKmodref: # started checking for a module handle
+
+ con byte 1 << iota;
+ OKmask: con byte 16rff;
+
+ #
+ # recursive marks
+ #
+ TReq,
+ TRcom,
+ TRcyc,
+ TRvis:
+ con byte 1 << iota;
+
+# type flags
+FULLARGS: con byte 1; # all hidden args added
+INST: con byte 2; # instantiated adt
+CYCLIC: con byte 4; # cyclic type
+POLY: con byte 8; # polymorphic types inside
+NOPOLY: con byte 16; # no polymorphic types inside
+
+# must put some picks in here
+Type: adt
+{
+ src: Src;
+ kind: int;
+ ok: byte; # set when type is verified
+ varargs: byte; # if a function, ends with vargs?
+ linkall: byte; # put all iface fns in external linkage?
+ rec: byte; # in the middle of recursive type
+ pr: byte; # in the middle of printing a recursive type
+ cons: byte; # exception constant
+ flags: byte;
+ sbl: int; # slot in .sbl adt table
+ sig: int; # signature for dynamic type check
+ size: int; # storage required, in bytes
+ align: int; # alignment in bytes
+ decl: cyclic ref Decl;
+ tof: cyclic ref Type;
+ ids: cyclic ref Decl;
+ tags: cyclic ref Decl;# tagged fields in an adt
+ polys: cyclic ref Decl;# polymorphic fields in fn or adt
+ cse: cyclic ref Case;# case or goto labels
+ teq: cyclic ref Type;# temporary equiv class for equiv checking
+ tcom: cyclic ref Type;# temporary equiv class for compat checking
+ eq: cyclic ref Teq; # real equiv class
+ eraises: cyclic ref Node; # for Tfn only
+ val: cyclic ref Node; # for Tfix, Tfn, Tadt only
+ tlist: cyclic ref Typelist; # for Tinst only
+ tmap: cyclic ref Tpair; # for Tadt only
+};
+
+#
+# type equivalence classes
+#
+Teq: adt
+{
+ id: int; # for signing
+ ty: cyclic ref Type;# an instance of the class
+ eq: cyclic ref Teq; # used to link eq sets
+};
+
+Tattr: adt
+{
+ isptr: int;
+ refable: int;
+ conable: int;
+ isbig: int;
+ vis: int; # type visible to users
+};
+
+Tpair: adt
+{
+ t1: cyclic ref Type;
+ t2: cyclic ref Type;
+ nxt: cyclic ref Tpair;
+};
+
+Typelist: adt
+{
+ t: cyclic ref Type;
+ nxt: cyclic ref Typelist;
+};
+
+Sother, Sloop, Sscope : con iota;
diff --git a/appl/cmd/limbo/limbo.y b/appl/cmd/limbo/limbo.y
new file mode 100644
index 00000000..0c56bd1b
--- /dev/null
+++ b/appl/cmd/limbo/limbo.y
@@ -0,0 +1,1973 @@
+%{
+include "limbo.m";
+include "draw.m";
+
+%}
+
+%module Limbo
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+
+ YYSTYPE: adt{
+ tok: Tok;
+ ids: ref Decl;
+ node: ref Node;
+ ty: ref Type;
+ types: ref Typelist;
+ };
+
+ YYLEX: adt {
+ lval: YYSTYPE;
+ lex: fn(nil: self ref YYLEX): int;
+ error: fn(nil: self ref YYLEX, err: string);
+ };
+}
+
+%{
+ #
+ # lex.b
+ #
+ signdump: string; # name of function for sig debugging
+ superwarn: int;
+ debug: array of int;
+ noline: Line;
+ nosrc: Src;
+ arrayz: int;
+ emitcode: string; # emit stub routines for system module functions
+ emitdyn: int; # emit as above but for dynamic modules
+ emitsbl: string; # emit symbol file for sysm modules
+ emitstub: int; # emit type and call frames for system modules
+ emittab: string; # emit table of runtime functions for this module
+ errors: int;
+ mustcompile: int;
+ dontcompile: int;
+ asmsym: int; # generate symbols in assembly language?
+ bout: ref Bufio->Iobuf; # output file
+ bsym: ref Bufio->Iobuf; # symbol output file; nil => no sym out
+ gendis: int; # generate dis or asm?
+ fixss: int;
+ newfnptr: int; # ISELF and -ve indices
+ optims: int;
+
+ #
+ # decls.b
+ #
+ scope: int;
+ # impmod: ref Sym; # name of implementation module
+ impmods: ref Decl; # name of implementation module(s)
+ nildecl: ref Decl; # declaration for limbo's nil
+ selfdecl: ref Decl; # declaration for limbo's self
+
+ #
+ # types.b
+ #
+ tany: ref Type;
+ tbig: ref Type;
+ tbyte: ref Type;
+ terror: ref Type;
+ tint: ref Type;
+ tnone: ref Type;
+ treal: ref Type;
+ tstring: ref Type;
+ texception: ref Type;
+ tunknown: ref Type;
+ tfnptr: ref Type;
+ rtexception: ref Type;
+ descriptors: ref Desc; # list of all possible descriptors
+ tattr: array of Tattr;
+
+ #
+ # nodes.b
+ #
+ opcommute: array of int;
+ oprelinvert: array of int;
+ isused: array of int;
+ casttab: array of array of int; # instruction to cast from [1] to [2]
+
+ nfns: int; # functions defined
+ nfnexp: int;
+ fns: array of ref Decl; # decls for fns defined
+ tree: ref Node; # root of parse tree
+
+ parset: int; # time to parse
+ checkt: int; # time to typecheck
+ gent: int; # time to generate code
+ writet: int; # time to write out code
+ symt: int; # time to write out symbols
+%}
+
+%type <ty> type fnarg fnargret fnargretp adtk fixtype iditype dotiditype
+%type <ids> ids rids nids nrids tuplist forms ftypes ftype
+ bclab bctarg ptags rptags polydec
+%type <node> zexp exp monexp term elist zelist celist
+ idatom idterms idterm idlist
+ initlist elemlist elem qual
+ decl topdecls topdecl fndef fbody stmt stmts qstmts qbodies cqstmts cqbodies
+ mdecl adtdecl mfield mfields field fields fnname
+ pstmts pbodies pqual pfields pfbody pdecl dfield dfields
+ eqstmts eqbodies idexc edecl raises tpoly tpolys texp export exportlist forpoly
+%type <types> types
+
+%right <tok.src> '=' Landeq Loreq Lxoreq Llsheq Lrsheq
+ Laddeq Lsubeq Lmuleq Ldiveq Lmodeq Lexpeq Ldeclas
+%left <tok.src> Lload
+%left <tok.src> Loror
+%left <tok.src> Landand
+%right <tok.src> Lcons
+%left <tok.src> '|'
+%left <tok.src> '^'
+%left <tok.src> '&'
+%left <tok.src> Leq Lneq
+%left <tok.src> '<' '>' Lleq Lgeq
+%left <tok.src> Llsh Lrsh
+%left <tok.src> '+' '-'
+%left <tok.src> '*' '/' '%'
+%right <tok.src> Lexp
+%right <tok.src> Lcomm
+
+%left <tok.src> '(' ')' '[' ']' Linc Ldec Lof Lref
+%right <tok.src> Lif Lelse Lfn ':' Lexcept Lraises
+%left <tok.src> Lmdot
+%left <tok.src> '.'
+
+%left <tok.src> Lto
+%left <tok.src> Lor
+
+
+%nonassoc <tok.v.rval> Lrconst
+%nonassoc <tok.v.ival> Lconst
+%nonassoc <tok.v.idval> Lid Ltid Lsconst
+%nonassoc <tok.src> Llabs Lnil
+ '!' '~' Llen Lhd Ltl Ltagof
+ '{' '}' ';'
+ Limplement Limport Linclude
+ Lcon Ltype Lmodule Lcyclic
+ Ladt Larray Llist Lchan Lself
+ Ldo Lwhile Lfor Lbreak
+ Lalt Lcase Lpick Lcont
+ Lreturn Lexit Lspawn Lraise Lfix
+%%
+prog : Limplement ids ';'
+ {
+ impmods = $2;
+ } topdecls
+ {
+ tree = rotater($5);
+ }
+ | topdecls
+ {
+ impmods = nil;
+ tree = rotater($1);
+ }
+ ;
+
+topdecls: topdecl
+ | topdecls topdecl
+ {
+ if($1 == nil)
+ $$ = $2;
+ else if($2 == nil)
+ $$ = $1;
+ else
+ $$ = mkbin(Oseq, $1, $2);
+ }
+ ;
+
+topdecl : error ';'
+ {
+ $$ = nil;
+ }
+ | decl
+ | fndef
+ | adtdecl ';'
+ | mdecl ';'
+ | idatom '=' exp ';'
+ {
+ $$ = mkbin(Oas, $1, $3);
+ }
+ | idterm '=' exp ';'
+ {
+ $$ = mkbin(Oas, $1, $3);
+ }
+ | idatom Ldeclas exp ';'
+ {
+ $$ = mkbin(Odas, $1, $3);
+ }
+ | idterm Ldeclas exp ';'
+ {
+ $$ = mkbin(Odas, $1, $3);
+ }
+ | idterms ':' type ';'
+ {
+ yyerror("illegal declaration");
+ $$ = nil;
+ }
+ | idterms ':' type '=' exp ';'
+ {
+ yyerror("illegal declaration");
+ $$ = nil;
+ }
+ ;
+
+idterms : idterm
+ | idterms ',' idterm
+ {
+ $$ = mkbin(Oseq, $1, $3);
+ }
+ ;
+
+decl : Linclude Lsconst ';'
+ {
+ includef($2);
+ $$ = nil;
+ }
+ | ids ':' Ltype type ';'
+ {
+ $$ = typedecl($1, $4);
+ }
+ | ids ':' Limport exp ';'
+ {
+ $$ = importdecl($4, $1);
+ $$.src.start = $1.src.start;
+ $$.src.stop = $5.stop;
+ }
+ | ids ':' type ';'
+ {
+ $$ = vardecl($1, $3);
+ }
+ | ids ':' type '=' exp ';'
+ {
+ $$ = mkbin(Ovardecli, vardecl($1, $3), varinit($1, $5));
+ }
+ | ids ':' Lcon exp ';'
+ {
+ $$ = condecl($1, $4);
+ }
+ | edecl
+ ;
+
+edecl : ids ':' Lexcept ';'
+ {
+ $$ = exdecl($1, nil);
+ }
+ | ids ':' Lexcept '(' tuplist ')' ';'
+ {
+ $$ = exdecl($1, revids($5));
+ }
+ ;
+
+mdecl : ids ':' Lmodule '{' mfields '}'
+ {
+ $1.src.stop = $6.stop;
+ $$ = moddecl($1, rotater($5));
+ }
+ ;
+
+mfields :
+ {
+ $$ = nil;
+ }
+ | mfields mfield
+ {
+ if($1 == nil)
+ $$ = $2;
+ else if($2 == nil)
+ $$ = $1;
+ else
+ $$ = mkn(Oseq, $1, $2);
+ }
+ | error
+ {
+ $$ = nil;
+ }
+ ;
+
+mfield : ids ':' type ';'
+ {
+ $$ = fielddecl(Dglobal, typeids($1, $3));
+ }
+ | adtdecl ';'
+ | ids ':' Ltype type ';'
+ {
+ $$ = typedecl($1, $4);
+ }
+ | ids ':' Lcon exp ';'
+ {
+ $$ = condecl($1, $4);
+ }
+ | edecl
+ ;
+
+adtdecl : ids ':' Ladt polydec '{' fields '}' forpoly
+ {
+ $1.src.stop = $7.stop;
+ $$ = adtdecl($1, rotater($6));
+ $$.ty.polys = $4;
+ $$.ty.val = rotater($8);
+ }
+ | ids ':' Ladt polydec Lfor '{' tpolys '}' '{' fields '}'
+ {
+ $1.src.stop = $11.stop;
+ $$ = adtdecl($1, rotater($10));
+ $$.ty.polys = $4;
+ $$.ty.val = rotater($7);
+ }
+ ;
+
+forpoly :
+ {
+ $$ = nil;
+ }
+ | Lfor '{' tpolys '}'
+ {
+ $$ = $3;
+ }
+ ;
+
+fields :
+ {
+ $$ = nil;
+ }
+ | fields field
+ {
+ if($1 == nil)
+ $$ = $2;
+ else if($2 == nil)
+ $$ = $1;
+ else
+ $$ = mkn(Oseq, $1, $2);
+ }
+ | error
+ {
+ $$ = nil;
+ }
+ ;
+
+field : dfield
+ | pdecl
+ | ids ':' Lcon exp ';'
+ {
+ $$ = condecl($1, $4);
+ }
+ ;
+
+dfields :
+ {
+ $$ = nil;
+ }
+ | dfields dfield
+ {
+ if($1 == nil)
+ $$ = $2;
+ else if($2 == nil)
+ $$ = $1;
+ else
+ $$ = mkn(Oseq, $1, $2);
+ }
+ ;
+
+dfield : ids ':' Lcyclic type ';'
+ {
+ for(d := $1; d != nil; d = d.next)
+ d.cyc = byte 1;
+ $$ = fielddecl(Dfield, typeids($1, $4));
+ }
+ | ids ':' type ';'
+ {
+ $$ = fielddecl(Dfield, typeids($1, $3));
+ }
+ ;
+
+pdecl : Lpick '{' pfields '}'
+ {
+ $$ = $3;
+ }
+ ;
+
+pfields : pfbody dfields
+ {
+ $1.right.right = $2;
+ $$ = $1;
+ }
+ | pfbody error
+ {
+ $$ = nil;
+ }
+ | error
+ {
+ $$ = nil;
+ }
+ ;
+
+pfbody : ptags Llabs
+ {
+ $$ = mkn(Opickdecl, nil, mkn(Oseq, fielddecl(Dtag, $1), nil));
+ typeids($1, mktype($1.src.start, $1.src.stop, Tadtpick, nil, nil));
+ }
+ | pfbody dfields ptags Llabs
+ {
+ $1.right.right = $2;
+ $$ = mkn(Opickdecl, $1, mkn(Oseq, fielddecl(Dtag, $3), nil));
+ typeids($3, mktype($3.src.start, $3.src.stop, Tadtpick, nil, nil));
+ }
+ | pfbody error ptags Llabs
+ {
+ $$ = mkn(Opickdecl, nil, mkn(Oseq, fielddecl(Dtag, $3), nil));
+ typeids($3, mktype($3.src.start, $3.src.stop, Tadtpick, nil, nil));
+ }
+ ;
+
+ptags : rptags
+ {
+ $$ = revids($1);
+ }
+ ;
+
+rptags : Lid
+ {
+ $$ = mkids($<tok.src>1, $1, nil, nil);
+ }
+ | rptags Lor Lid
+ {
+ $$ = mkids($<tok.src>3, $3, nil, $1);
+ }
+ ;
+
+ids : rids
+ {
+ $$ = revids($1);
+ }
+ ;
+
+rids : Lid
+ {
+ $$ = mkids($<tok.src>1, $1, nil, nil);
+ }
+ | rids ',' Lid
+ {
+ $$ = mkids($<tok.src>3, $3, nil, $1);
+ }
+ ;
+
+fixtype : Lfix '(' exp ',' exp ')'
+ {
+ $$ = mktype($1.start, $6.stop, Tfix, nil, nil);
+ $$.val = mkbin(Oseq, $3, $5);
+ }
+ | Lfix '(' exp ')'
+ {
+ $$ = mktype($1.start, $4.stop, Tfix, nil, nil);
+ $$.val = $3;
+ }
+ ;
+
+types : type
+ {
+ $$ = addtype($1, nil);
+ }
+ | Lcyclic type
+ {
+ $$ = addtype($2, nil);
+ $2.flags |= CYCLIC;
+ }
+ | types ',' type
+ {
+ $$ = addtype($3, $1);
+ }
+ | types ',' Lcyclic type
+ {
+ $$ = addtype($4, $1);
+ $4.flags |= CYCLIC;
+ }
+ ;
+
+type : Ltid
+ {
+ $$ = mkidtype($<tok.src>1, $1);
+ }
+ | iditype
+ {
+ $$ = $1;
+ }
+ | dotiditype
+ {
+ $$ = $1;
+ }
+ | type Lmdot Lid
+ {
+ $$ = mkarrowtype($1.src.start, $<tok.src>3.stop, $1, $3);
+ }
+ | type Lmdot Lid '[' types ']'
+ {
+ $$ = mkarrowtype($1.src.start, $<tok.src>3.stop, $1, $3);
+ $$ = mkinsttype($1.src, $$, $5);
+ }
+ | Lref type
+ {
+ $$ = mktype($1.start, $2.src.stop, Tref, $2, nil);
+ }
+ | Lchan Lof type
+ {
+ $$ = mktype($1.start, $3.src.stop, Tchan, $3, nil);
+ }
+ | '(' tuplist ')'
+ {
+ if($2.next == nil)
+ $$ = $2.ty;
+ else
+ $$ = mktype($1.start, $3.stop, Ttuple, nil, revids($2));
+ }
+ | Larray Lof type
+ {
+ $$ = mktype($1.start, $3.src.stop, Tarray, $3, nil);
+ }
+ | Llist Lof type
+ {
+ $$ = mktype($1.start, $3.src.stop, Tlist, $3, nil);
+ }
+ | Lfn polydec fnargretp raises
+ {
+ $3.src.start = $1.start;
+ $3.polys = $2;
+ $3.eraises = $4;
+ $$ = $3;
+ }
+ | fixtype
+# | Lexcept
+# {
+# $$ = mktype($1.start, $1.stop, Texception, nil, nil);
+# $$.cons = byte 1;
+# }
+# | Lexcept '(' tuplist ')'
+# {
+# $$ = mktype($1.start, $4.stop, Texception, nil, revids($3));
+# $$.cons = byte 1;
+# }
+ ;
+
+iditype : Lid
+ {
+ $$ = mkidtype($<tok.src>1, $1);
+ }
+ | Lid '[' types ']'
+ {
+ $$ = mkinsttype($<tok.src>1, mkidtype($<tok.src>1, $1), $3);
+ }
+ ;
+
+dotiditype : type '.' Lid
+ {
+ $$ = mkdottype($1.src.start, $<tok.src>3.stop, $1, $3);
+ }
+ | type '.' Lid '[' types ']'
+ {
+ $$ = mkdottype($1.src.start, $<tok.src>3.stop, $1, $3);
+ $$ = mkinsttype($1.src, $$, $5);
+ }
+ ;
+
+tuplist : type
+ {
+ $$ = mkids($1.src, nil, $1, nil);
+ }
+ | tuplist ',' type
+ {
+ $$ = mkids($1.src, nil, $3, $1);
+ }
+ ;
+
+polydec :
+ {
+ $$ = nil;
+ }
+ | '[' ids ']'
+ {
+ $$ = polydecl($2);
+ }
+ ;
+
+fnarg : '(' forms ')'
+ {
+ $$ = mktype($1.start, $3.stop, Tfn, tnone, $2);
+ }
+ | '(' '*' ')'
+ {
+ $$ = mktype($1.start, $3.stop, Tfn, tnone, nil);
+ $$.varargs = byte 1;
+ }
+ | '(' ftypes ',' '*' ')'
+ {
+ $$ = mktype($1.start, $5.stop, Tfn, tnone, $2);
+ $$.varargs = byte 1;
+ }
+ ;
+
+fnargret: fnarg %prec ':'
+ {
+ $$ = $1;
+ }
+ | fnarg ':' type
+ {
+ $1.tof = $3;
+ $1.src.stop = $3.src.stop;
+ $$ = $1;
+ }
+ ;
+
+fnargretp: fnargret %prec '='
+ {
+ $$ = $1;
+ }
+ | fnargret Lfor '{' tpolys '}'
+ {
+ $$ = $1;
+ $$.val = rotater($4);
+ }
+ ;
+
+forms :
+ {
+ $$ = nil;
+ }
+ | ftypes
+ ;
+
+ftypes : ftype
+ | ftypes ',' ftype
+ {
+ $$ = appdecls($1, $3);
+ }
+ ;
+
+ftype : nids ':' type
+ {
+ $$ = typeids($1, $3);
+ }
+ | nids ':' adtk
+ {
+ $$ = typeids($1, $3);
+ for(d := $$; d != nil; d = d.next)
+ d.implicit = byte 1;
+ }
+ | idterms ':' type
+ {
+ $$ = mkids($1.src, enter("junk", 0), $3, nil);
+ $$.store = Darg;
+ yyerror("illegal argument declaraion");
+ }
+ | idterms ':' adtk
+ {
+ $$ = mkids($1.src, enter("junk", 0), $3, nil);
+ $$.store = Darg;
+ yyerror("illegal argument declaraion");
+ }
+ ;
+
+nids : nrids
+ {
+ $$ = revids($1);
+ }
+ ;
+
+nrids : Lid
+ {
+ $$ = mkids($<tok.src>1, $1, nil, nil);
+ $$.store = Darg;
+ }
+ | Lnil
+ {
+ $$ = mkids($1, nil, nil, nil);
+ $$.store = Darg;
+ }
+ | nrids ',' Lid
+ {
+ $$ = mkids($<tok.src>3, $3, nil, $1);
+ $$.store = Darg;
+ }
+ | nrids ',' Lnil
+ {
+ $$ = mkids($3, nil, nil, $1);
+ $$.store = Darg;
+ }
+ ;
+
+adtk : Lself iditype
+ {
+ $$ = $2;
+ }
+ | Lself Lref iditype
+ {
+ $$ = mktype($<tok.src>2.start, $<tok.src>3.stop, Tref, $3, nil);
+ }
+ | Lself dotiditype
+ {
+ $$ = $2;
+ }
+ | Lself Lref dotiditype
+ {
+ $$ = mktype($<tok.src>2.start, $<tok.src>3.stop, Tref, $3, nil);
+ }
+ ;
+
+fndef : fnname fnargretp raises fbody
+ {
+ $$ = fndecl($1, $2, $4);
+ nfns++;
+ # patch up polydecs
+ if($1.op == Odot){
+ if($1.right.left != nil){
+ $2.polys = $1.right.left.decl;
+ $1.right.left = nil;
+ }
+ if($1.left.op == Oname && $1.left.left != nil){
+ $$.decl = $1.left.left.decl;
+ $1.left.left = nil;
+ }
+ }
+ else{
+ if($1.left != nil){
+ $2.polys = $1.left.decl;
+ $1.left = nil;
+ }
+ }
+ $2.eraises = $3;
+ $$.src = $1.src;
+ }
+ ;
+
+raises : Lraises '(' idlist ')'
+ {
+ $$ = mkn(Otuple, rotater($3), nil);
+ $$.src.start = $1.start;
+ $$.src.stop = $4.stop;
+ }
+ | Lraises idatom
+ {
+ $$ = mkn(Otuple, mkunary(Oseq, $2), nil);
+ $$.src.start = $1.start;
+ $$.src.stop = $2.src.stop;
+ }
+ | %prec Lraises
+ {
+ $$ = nil;
+ }
+ ;
+
+fbody : '{' stmts '}'
+ {
+ if($2 == nil){
+ $2 = mkn(Onothing, nil, nil);
+ $2.src.start = curline();
+ $2.src.stop = $2.src.start;
+ }
+ $$ = rotater($2);
+ $$.src.start = $1.start;
+ $$.src.stop = $3.stop;
+ }
+ | error '}'
+ {
+ $$ = mkn(Onothing, nil, nil);
+ }
+ | error '{' stmts '}'
+ {
+ $$ = mkn(Onothing, nil, nil);
+ }
+ ;
+
+fnname : Lid polydec
+ {
+ $$ = mkname($<tok.src>1, $1);
+ if($2 != nil){
+ $$.left = mkn(Onothing, nil ,nil);
+ $$.left.decl = $2;
+ }
+ }
+ | fnname '.' Lid polydec
+ {
+ $$ = mkbin(Odot, $1, mkname($<tok.src>3, $3));
+ if($4 != nil){
+ $$.right.left = mkn(Onothing, nil ,nil);
+ $$.right.left.decl = $4;
+ }
+ }
+ ;
+
+stmts :
+ {
+ $$ = nil;
+ }
+ | stmts decl
+ {
+ if($1 == nil)
+ $$ = $2;
+ else if($2 == nil)
+ $$ = $1;
+ else
+ $$ = mkbin(Oseq, $1, $2);
+ }
+ | stmts stmt
+ {
+ if($1 == nil)
+ $$ = $2;
+ else
+ $$ = mkbin(Oseq, $1, $2);
+ }
+ ;
+
+elists : '(' elist ')'
+ | elists ',' '(' elist ')'
+ ;
+
+stmt : error ';'
+ {
+ $$ = mkn(Onothing, nil, nil);
+ $$.src.start = curline();
+ $$.src.stop = $$.src.start;
+ }
+ | error '}'
+ {
+ $$ = mkn(Onothing, nil, nil);
+ $$.src.start = curline();
+ $$.src.stop = $$.src.start;
+ }
+ | error '{' stmts '}'
+ {
+ $$ = mkn(Onothing, nil, nil);
+ $$.src.start = curline();
+ $$.src.stop = $$.src.start;
+ }
+ | '{' stmts '}'
+ {
+ if($2 == nil){
+ $2 = mkn(Onothing, nil, nil);
+ $2.src.start = curline();
+ $2.src.stop = $2.src.start;
+ }
+ $$ = mkscope(rotater($2));
+ }
+ | elists ':' type ';'
+ {
+ yyerror("illegal declaration");
+ $$ = mkn(Onothing, nil, nil);
+ $$.src.start = curline();
+ $$.src.stop = $$.src.start;
+ }
+ | elists ':' type '=' exp';'
+ {
+ yyerror("illegal declaration");
+ $$ = mkn(Onothing, nil, nil);
+ $$.src.start = curline();
+ $$.src.stop = $$.src.start;
+ }
+ | zexp ';'
+ {
+ $$ = $1;
+ }
+ | Lif '(' exp ')' stmt
+ {
+ $$ = mkn(Oif, $3, mkunary(Oseq, $5));
+ $$.src.start = $1.start;
+ $$.src.stop = $5.src.stop;
+ }
+ | Lif '(' exp ')' stmt Lelse stmt
+ {
+ $$ = mkn(Oif, $3, mkbin(Oseq, $5, $7));
+ $$.src.start = $1.start;
+ $$.src.stop = $7.src.stop;
+ }
+ | bclab Lfor '(' zexp ';' zexp ';' zexp ')' stmt
+ {
+ $$ = mkunary(Oseq, $10);
+ if($8.op != Onothing)
+ $$.right = $8;
+ $$ = mkbin(Ofor, $6, $$);
+ $$.decl = $1;
+ if($4.op != Onothing)
+ $$ = mkbin(Oseq, $4, $$);
+ }
+ | bclab Lwhile '(' zexp ')' stmt
+ {
+ $$ = mkn(Ofor, $4, mkunary(Oseq, $6));
+ $$.src.start = $2.start;
+ $$.src.stop = $6.src.stop;
+ $$.decl = $1;
+ }
+ | bclab Ldo stmt Lwhile '(' zexp ')' ';'
+ {
+ $$ = mkn(Odo, $6, $3);
+ $$.src.start = $2.start;
+ $$.src.stop = $7.stop;
+ $$.decl = $1;
+ }
+ | Lbreak bctarg ';'
+ {
+ $$ = mkn(Obreak, nil, nil);
+ $$.decl = $2;
+ $$.src = $1;
+ }
+ | Lcont bctarg ';'
+ {
+ $$ = mkn(Ocont, nil, nil);
+ $$.decl = $2;
+ $$.src = $1;
+ }
+ | Lreturn zexp ';'
+ {
+ $$ = mkn(Oret, $2, nil);
+ $$.src = $1;
+ if($2.op == Onothing)
+ $$.left = nil;
+ else
+ $$.src.stop = $2.src.stop;
+ }
+ | Lspawn exp ';'
+ {
+ $$ = mkn(Ospawn, $2, nil);
+ $$.src.start = $1.start;
+ $$.src.stop = $2.src.stop;
+ }
+ | Lraise zexp ';'
+ {
+ $$ = mkn(Oraise, $2, nil);
+ $$.src.start = $1.start;
+ $$.src.stop = $2.src.stop;
+ }
+ | bclab Lcase exp '{' cqstmts '}'
+ {
+ $$ = mkn(Ocase, $3, caselist($5, nil));
+ $$.src = $3.src;
+ $$.decl = $1;
+ }
+ | bclab Lalt '{' qstmts '}'
+ {
+ $$ = mkn(Oalt, caselist($4, nil), nil);
+ $$.src = $2;
+ $$.decl = $1;
+ }
+ | bclab Lpick Lid Ldeclas exp '{' pstmts '}'
+ {
+ $$ = mkn(Opick, mkbin(Odas, mkname($<tok.src>3, $3), $5), caselist($7, nil));
+ $$.src.start = $<tok.src>3.start;
+ $$.src.stop = $5.src.stop;
+ $$.decl = $1;
+ }
+ | Lexit ';'
+ {
+ $$ = mkn(Oexit, nil, nil);
+ $$.src = $1;
+ }
+ | '{' stmts '}' Lexcept idexc '{' eqstmts '}'
+ {
+ if($2 == nil){
+ $2 = mkn(Onothing, nil, nil);
+ $2.src.start = $2.src.stop = curline();
+ }
+ $2 = mkscope(rotater($2));
+ $$ = mkbin(Oexstmt, $2, mkn(Oexcept, $5, caselist($7, nil)));
+ }
+# | stmt Lexcept idexc '{' eqstmts '}'
+# {
+# $$ = mkbin(Oexstmt, $1, mkn(Oexcept, $3, caselist($5, nil)));
+# }
+ ;
+
+bclab :
+ {
+ $$ = nil;
+ }
+ | ids ':'
+ {
+ if($1.next != nil)
+ yyerror("only one identifier allowed in a label");
+ $$ = $1;
+ }
+ ;
+
+bctarg :
+ {
+ $$ = nil;
+ }
+ | Lid
+ {
+ $$ = mkids($<tok.src>1, $1, nil, nil);
+ }
+ ;
+
+qstmts : qbodies stmts
+ {
+ $1.left.right.right = $2;
+ $$ = $1;
+ }
+ ;
+
+qbodies : qual Llabs
+ {
+ $$ = mkunary(Oseq, mkscope(mkunary(Olabel, rotater($1))));
+ }
+ | qbodies stmts qual Llabs
+ {
+ $1.left.right.right = $2;
+ $$ = mkbin(Oseq, mkscope(mkunary(Olabel, rotater($3))), $1);
+ }
+ ;
+
+cqstmts : cqbodies stmts
+ {
+ $1.left.right = mkscope($2);
+ $$ = $1;
+ }
+ ;
+
+cqbodies : qual Llabs
+ {
+ $$ = mkunary(Oseq, mkunary(Olabel, rotater($1)));
+ }
+ | cqbodies stmts qual Llabs
+ {
+ $1.left.right = mkscope($2);
+ $$ = mkbin(Oseq, mkunary(Olabel, rotater($3)), $1);
+ }
+ ;
+
+eqstmts : eqbodies stmts
+ {
+ $1.left.right = mkscope($2);
+ $$ = $1;
+ }
+ ;
+
+eqbodies : qual Llabs
+ {
+ $$ = mkunary(Oseq, mkunary(Olabel, rotater($1)));
+ }
+ | eqbodies stmts qual Llabs
+ {
+ $1.left.right = mkscope($2);
+ $$ = mkbin(Oseq, mkunary(Olabel, rotater($3)), $1);
+ }
+ ;
+
+qual : exp
+ | exp Lto exp
+ {
+ $$ = mkbin(Orange, $1, $3);
+ }
+ | '*'
+ {
+ $$ = mkn(Owild, nil, nil);
+ $$.src = $1;
+ }
+ | qual Lor qual
+ {
+ $$ = mkbin(Oseq, $1, $3);
+ }
+ | error
+ {
+ $$ = mkn(Onothing, nil, nil);
+ $$.src.start = curline();
+ $$.src.stop = $$.src.start;
+ }
+ ;
+
+pstmts : pbodies stmts
+ {
+ $1.left.right = mkscope($2);
+ $$ = $1;
+ }
+ ;
+
+pbodies : pqual Llabs
+ {
+ $$ = mkunary(Oseq, mkunary(Olabel, rotater($1)));
+ }
+ | pbodies stmts pqual Llabs
+ {
+ $1.left.right = mkscope($2);
+ $$ = mkbin(Oseq, mkunary(Olabel, rotater($3)), $1);
+ }
+ ;
+
+pqual : Lid
+ {
+ $$ = mkname($<tok>1.src, $1);
+ }
+ | '*'
+ {
+ $$ = mkn(Owild, nil, nil);
+ $$.src = $1;
+ }
+ | pqual Lor pqual
+ {
+ $$ = mkbin(Oseq, $1, $3);
+ }
+ | error
+ {
+ $$ = mkn(Onothing, nil, nil);
+ $$.src.start = curline();
+ $$.src.stop = $$.src.start;
+ }
+ ;
+
+zexp :
+ {
+ $$ = mkn(Onothing, nil, nil);
+ $$.src.start = curline();
+ $$.src.stop = $$.src.start;
+ }
+ | exp
+ ;
+
+exp : monexp
+ | exp '=' exp
+ {
+ $$ = mkbin(Oas, $1, $3);
+ }
+ | exp Landeq exp
+ {
+ $$ = mkbin(Oandas, $1, $3);
+ }
+ | exp Loreq exp
+ {
+ $$ = mkbin(Ooras, $1, $3);
+ }
+ | exp Lxoreq exp
+ {
+ $$ = mkbin(Oxoras, $1, $3);
+ }
+ | exp Llsheq exp
+ {
+ $$ = mkbin(Olshas, $1, $3);
+ }
+ | exp Lrsheq exp
+ {
+ $$ = mkbin(Orshas, $1, $3);
+ }
+ | exp Laddeq exp
+ {
+ $$ = mkbin(Oaddas, $1, $3);
+ }
+ | exp Lsubeq exp
+ {
+ $$ = mkbin(Osubas, $1, $3);
+ }
+ | exp Lmuleq exp
+ {
+ $$ = mkbin(Omulas, $1, $3);
+ }
+ | exp Ldiveq exp
+ {
+ $$ = mkbin(Odivas, $1, $3);
+ }
+ | exp Lmodeq exp
+ {
+ $$ = mkbin(Omodas, $1, $3);
+ }
+ | exp Lexpeq exp
+ {
+ $$ = mkbin(Oexpas, $1, $3);
+ }
+ | exp Lcomm '=' exp
+ {
+ $$ = mkbin(Osnd, $1, $4);
+ }
+ | exp Ldeclas exp
+ {
+ $$ = mkbin(Odas, $1, $3);
+ }
+ | Lload Lid exp %prec Lload
+ {
+ $$ = mkn(Oload, $3, nil);
+ $$.src.start = $<tok.src.start>1;
+ $$.src.stop = $3.src.stop;
+ $$.ty = mkidtype($<tok.src>2, $2);
+ }
+ | exp Lexp exp
+ {
+ $$ = $$ = mkbin(Oexp, $1, $3);
+ }
+ | exp '*' exp
+ {
+ $$ = mkbin(Omul, $1, $3);
+ }
+ | exp '/' exp
+ {
+ $$ = mkbin(Odiv, $1, $3);
+ }
+ | exp '%' exp
+ {
+ $$ = mkbin(Omod, $1, $3);
+ }
+ | exp '+' exp
+ {
+ $$ = mkbin(Oadd, $1, $3);
+ }
+ | exp '-' exp
+ {
+ $$ = mkbin(Osub, $1, $3);
+ }
+ | exp Lrsh exp
+ {
+ $$ = mkbin(Orsh, $1, $3);
+ }
+ | exp Llsh exp
+ {
+ $$ = mkbin(Olsh, $1, $3);
+ }
+ | exp '<' exp
+ {
+ $$ = mkbin(Olt, $1, $3);
+ }
+ | exp '>' exp
+ {
+ $$ = mkbin(Ogt, $1, $3);
+ }
+ | exp Lleq exp
+ {
+ $$ = mkbin(Oleq, $1, $3);
+ }
+ | exp Lgeq exp
+ {
+ $$ = mkbin(Ogeq, $1, $3);
+ }
+ | exp Leq exp
+ {
+ $$ = mkbin(Oeq, $1, $3);
+ }
+ | exp Lneq exp
+ {
+ $$ = mkbin(Oneq, $1, $3);
+ }
+ | exp '&' exp
+ {
+ $$ = mkbin(Oand, $1, $3);
+ }
+ | exp '^' exp
+ {
+ $$ = mkbin(Oxor, $1, $3);
+ }
+ | exp '|' exp
+ {
+ $$ = mkbin(Oor, $1, $3);
+ }
+ | exp Lcons exp
+ {
+ $$ = mkbin(Ocons, $1, $3);
+ }
+ | exp Landand exp
+ {
+ $$ = mkbin(Oandand, $1, $3);
+ }
+ | exp Loror exp
+ {
+ $$ = mkbin(Ooror, $1, $3);
+ }
+ ;
+
+monexp : term
+ | '+' monexp
+ {
+ $2.src.start = $1.start;
+ $$ = $2;
+ }
+ | '-' monexp
+ {
+ $$ = mkunary(Oneg, $2);
+ $$.src.start = $1.start;
+ }
+ | '!' monexp
+ {
+ $$ = mkunary(Onot, $2);
+ $$.src.start = $1.start;
+ }
+ | '~' monexp
+ {
+ $$ = mkunary(Ocomp, $2);
+ $$.src.start = $1.start;
+ }
+ | '*' monexp
+ {
+ $$ = mkunary(Oind, $2);
+ $$.src.start = $1.start;
+ }
+ | Linc monexp
+ {
+ $$ = mkunary(Opreinc, $2);
+ $$.src.start = $1.start;
+ }
+ | Ldec monexp
+ {
+ $$ = mkunary(Opredec, $2);
+ $$.src.start = $1.start;
+ }
+ | Lcomm monexp
+ {
+ $$ = mkunary(Orcv, $2);
+ $$.src.start = $1.start;
+ }
+ | Lhd monexp
+ {
+ $$ = mkunary(Ohd, $2);
+ $$.src.start = $1.start;
+ }
+ | Ltl monexp
+ {
+ $$ = mkunary(Otl, $2);
+ $$.src.start = $1.start;
+ }
+ | Llen monexp
+ {
+ $$ = mkunary(Olen, $2);
+ $$.src.start = $1.start;
+ }
+ | Lref monexp
+ {
+ $$ = mkunary(Oref, $2);
+ $$.src.start = $1.start;
+ }
+ | Ltagof monexp
+ {
+ $$ = mkunary(Otagof, $2);
+ $$.src.start = $1.start;
+ }
+ | Larray '[' exp ']' Lof type
+ {
+ $$ = mkn(Oarray, $3, nil);
+ $$.ty = mktype($1.start, $6.src.stop, Tarray, $6, nil);
+ $$.src = $$.ty.src;
+ }
+ | Larray '[' exp ']' Lof '{' initlist '}'
+ {
+ $$ = mkn(Oarray, $3, $7);
+ $$.src.start = $1.start;
+ $$.src.stop = $8.stop;
+ }
+ | Larray '[' ']' Lof '{' initlist '}'
+ {
+ $$ = mkn(Onothing, nil, nil);
+ $$.src.start = $2.start;
+ $$.src.stop = $3.stop;
+ $$ = mkn(Oarray, $$, $6);
+ $$.src.start = $1.start;
+ $$.src.stop = $7.stop;
+ }
+ | Llist Lof '{' celist '}'
+ {
+ $$ = etolist($4);
+ $$.src.start = $1.start;
+ $$.src.stop = $5.stop;
+ }
+ | Lchan Lof type
+ {
+ $$ = mkn(Ochan, nil, nil);
+ $$.ty = mktype($1.start, $3.src.stop, Tchan, $3, nil);
+ $$.src = $$.ty.src;
+ }
+ | Lchan '[' exp ']' Lof type
+ {
+ $$ = mkn(Ochan, $3, nil);
+ $$.ty = mktype($1.start, $6.src.stop, Tchan, $6, nil);
+ $$.src = $$.ty.src;
+ }
+ | Larray Lof Ltid monexp
+ {
+ $$ = mkunary(Ocast, $4);
+ $$.ty = mktype($1.start, $4.src.stop, Tarray, mkidtype($<tok.src>3, $3), nil);
+ $$.src = $$.ty.src;
+ }
+ | Ltid monexp
+ {
+ $$ = mkunary(Ocast, $2);
+ $$.src.start = $<tok.src>1.start;
+ $$.ty = mkidtype($$.src, $1);
+ }
+ | Lid monexp
+ {
+ $$ = mkunary(Ocast, $2);
+ $$.src.start = $<tok.src>1.start;
+ $$.ty = mkidtype($$.src, $1);
+ }
+ | fixtype monexp
+ {
+ $$ = mkunary(Ocast, $2);
+ $$.src.start = $<tok.src>1.start;
+ $$.ty = $1;
+ }
+ ;
+
+term : idatom
+ | term '(' zelist ')'
+ {
+ $$ = mkn(Ocall, $1, $3);
+ $$.src.start = $1.src.start;
+ $$.src.stop = $4.stop;
+ }
+ | '(' elist ')'
+ {
+ $$ = $2;
+ if($2.op == Oseq)
+ $$ = mkn(Otuple, rotater($2), nil);
+ else
+ $$.flags |= byte PARENS;
+ $$.src.start = $1.start;
+ $$.src.stop = $3.stop;
+ }
+ | Lfn fnargret
+ {
+# n := mkdeclname($1, mkids($1, enter(".fn"+string nfnexp++, 0), nil, nil));
+# $<node>$ = fndef(n, $2);
+# nfns++;
+ } fbody
+ {
+# $$ = fnfinishdef($<node>3, $4);
+# $$ = mkdeclname($1, $$.left.decl);
+ yyerror("urt unk");
+ $$ = nil;
+ }
+ | term '.' Lid
+ {
+ $$ = mkbin(Odot, $1, mkname($<tok.src>3, $3));
+ }
+ | term Lmdot term
+ {
+ $$ = mkbin(Omdot, $1, $3);
+ }
+ | term '[' export ']'
+ {
+ $$ = mkbin(Oindex, $1, $3);
+ $$.src.stop = $4.stop;
+ }
+ | term '[' zexp ':' zexp ']'
+ {
+ if($3.op == Onothing)
+ $3.src = $4;
+ if($5.op == Onothing)
+ $5.src = $4;
+ $$ = mkbin(Oslice, $1, mkbin(Oseq, $3, $5));
+ $$.src.stop = $6.stop;
+ }
+ | term Linc
+ {
+ $$ = mkunary(Oinc, $1);
+ $$.src.stop = $2.stop;
+ }
+ | term Ldec
+ {
+ $$ = mkunary(Odec, $1);
+ $$.src.stop = $2.stop;
+ }
+ | Lsconst
+ {
+ $$ = mksconst($<tok.src>1, $1);
+ }
+ | Lconst
+ {
+ $$ = mkconst($<tok.src>1, $1);
+ if($1 > big 16r7fffffff || $1 < big -16r7fffffff)
+ $$.ty = tbig;
+ }
+ | Lrconst
+ {
+ $$ = mkrconst($<tok.src>1, $1);
+ }
+ | term '[' exportlist ',' export ']'
+ {
+ $$ = mkbin(Oindex, $1, rotater(mkbin(Oseq, $3, $5)));
+ $$.src.stop = $6.stop;
+ }
+ ;
+
+idatom : Lid
+ {
+ $$ = mkname($<tok.src>1, $1);
+ }
+ | Lnil
+ {
+ $$ = mknil($<tok.src>1);
+ }
+ ;
+
+idterm : '(' idlist ')'
+ {
+ $$ = mkn(Otuple, rotater($2), nil);
+ $$.src.start = $1.start;
+ $$.src.stop = $3.stop;
+ }
+ ;
+
+exportlist : export
+ | exportlist ',' export
+ {
+ $$ = mkbin(Oseq, $1, $3);
+ }
+ ;
+
+export : exp
+ | texp
+ ;
+
+texp : Ltid
+ {
+ $$ = mkn(Otype, nil, nil);
+ $$.ty = mkidtype($<tok.src>1, $1);
+ $$.src = $$.ty.src;
+ }
+ | Larray Lof type
+ {
+ $$ = mkn(Otype, nil, nil);
+ $$.ty = mktype($1.start, $3.src.stop, Tarray, $3, nil);
+ $$.src = $$.ty.src;
+ }
+ | Llist Lof type
+ {
+ $$ = mkn(Otype, nil, nil);
+ $$.ty = mktype($1.start, $3.src.stop, Tlist, $3, nil);
+ $$.src = $$.ty.src;
+ }
+ | Lcyclic type
+ {
+ $$ = mkn(Otype, nil ,nil);
+ $$.ty = $2;
+ $$.ty.flags |= CYCLIC;
+ $$.src = $$.ty.src;
+ }
+ ;
+
+idexc : Lid
+ {
+ $$ = mkname($<tok.src>1, $1);
+ }
+ | # empty
+ {
+ $$ = nil;
+ }
+ ;
+
+idlist : idterm
+ | idatom
+ | idlist ',' idterm
+ {
+ $$ = mkbin(Oseq, $1, $3);
+ }
+ | idlist ',' idatom
+ {
+ $$ = mkbin(Oseq, $1, $3);
+ }
+ ;
+
+zelist :
+ {
+ $$ = nil;
+ }
+ | elist
+ {
+ $$ = rotater($1);
+ }
+ ;
+
+celist : elist
+ | elist ','
+ ;
+
+elist : exp
+ | elist ',' exp
+ {
+ $$ = mkbin(Oseq, $1, $3);
+ }
+ ;
+
+initlist : elemlist
+ {
+ $$ = rotater($1);
+ }
+ | elemlist ','
+ {
+ $$ = rotater($1);
+ }
+ ;
+
+elemlist : elem
+ | elemlist ',' elem
+ {
+ $$ = mkbin(Oseq, $1, $3);
+ }
+ ;
+
+elem : exp
+ {
+ $$ = mkn(Oelem, nil, $1);
+ $$.src = $1.src;
+ }
+ | qual Llabs exp
+ {
+ $$ = mkbin(Oelem, rotater($1), $3);
+ }
+ ;
+
+tpolys : tpoly dfields
+ {
+ if($1.op == Oseq)
+ $1.right.left = rotater($2);
+ else
+ $1.left = rotater($2);
+ $$ = $1;
+ }
+ ;
+
+tpoly : ids Llabs
+ {
+ $$ = typedecl($1, mktype($1.src.start, $2.stop, Tpoly, nil, nil));
+ }
+ | tpoly dfields ids Llabs
+ {
+ if($1.op == Oseq)
+ $1.right.left = rotater($2);
+ else
+ $1.left = rotater($2);
+ $$ = mkbin(Oseq, $1, typedecl($3, mktype($3.src.start, $4.stop, Tpoly, nil, nil)));
+ }
+ ;
+
+%%
+
+include "keyring.m";
+
+sys: Sys;
+ print, fprint, sprint: import sys;
+
+bufio: Bufio;
+ Iobuf: import bufio;
+
+str: String;
+
+keyring:Keyring;
+ md5: import keyring;
+
+math: Math;
+ import_real, export_real, isnan: import math;
+
+yyctxt: ref YYLEX;
+
+canonnan: real;
+
+debug = array[256] of {* => 0};
+
+noline = -1;
+nosrc = Src(-1, -1);
+
+infile: string;
+
+# front end
+include "arg.m";
+include "lex.b";
+include "types.b";
+include "nodes.b";
+include "decls.b";
+
+include "typecheck.b";
+
+# back end
+include "gen.b";
+include "ecom.b";
+include "asm.b";
+include "dis.b";
+include "sbl.b";
+include "stubs.b";
+include "com.b";
+include "optim.b";
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ s: string;
+
+ sys = load Sys Sys->PATH;
+ keyring = load Keyring Keyring->PATH;
+ math = load Math Math->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil){
+ sys->print("can't load %s: %r\n", Bufio->PATH);
+ raise("fail:bad module");
+ }
+ str = load String String->PATH;
+ if(str == nil){
+ sys->print("can't load %s: %r\n", String->PATH);
+ raise("fail:bad module");
+ }
+
+ stderr = sys->fildes(2);
+ yyctxt = ref YYLEX;
+
+ math->FPcontrol(0, Math->INVAL|Math->ZDIV|Math->OVFL|Math->UNFL|Math->INEX);
+ na := array[1] of {0.};
+ import_real(array[8] of {byte 16r7f, * => byte 16rff}, na);
+ canonnan = na[0];
+ if(!isnan(canonnan))
+ fatal("bad canonical NaN");
+
+ lexinit();
+ typeinit();
+ optabinit();
+
+ gendis = 1;
+ asmsym = 0;
+ maxerr = 20;
+ ofile := "";
+ ext := "";
+
+ arg := Arg.init(argv);
+ while(c := arg.opt()){
+ case c{
+ 'Y' =>
+ emitsbl = arg.arg();
+ if(emitsbl == nil)
+ usage();
+ 'C' =>
+ dontcompile = 1;
+ 'D' =>
+ #
+ # debug flags:
+ #
+ # a alt compilation
+ # A array constructor compilation
+ # b boolean and branch compilation
+ # c case compilation
+ # d function declaration
+ # D descriptor generation
+ # e expression compilation
+ # E addressable expression compilation
+ # f print arguments for compiled functions
+ # F constant folding
+ # g print out globals
+ # m module declaration and type checking
+ # n nil references
+ # s print sizes of output file sections
+ # S type signing
+ # t type checking function bodies
+ # T timing
+ # v global var and constant compilation
+ # x adt verification
+ # Y tuple compilation
+ # z Z bug fixes
+ #
+ s = arg.arg();
+ for(i := 0; i < len s; i++){
+ c = s[i];
+ if(c < len debug)
+ debug[c] = 1;
+ }
+ 'I' =>
+ s = arg.arg();
+ if(s == "")
+ usage();
+ addinclude(s);
+ 'G' =>
+ asmsym = 1;
+ 'S' =>
+ gendis = 0;
+ 'a' =>
+ emitstub = 1;
+ 'A' =>
+ emitstub = emitdyn = 1;
+ 'c' =>
+ mustcompile = 1;
+ 'e' =>
+ maxerr = 1000;
+ 'f' =>
+ fabort = 1;
+ 'F' =>
+ newfnptr = 1;
+ 'g' =>
+ dosym = 1;
+ 'i' =>
+ dontinline = 1;
+ 'o' =>
+ ofile = arg.arg();
+ 'O' =>
+ optims = 1;
+ 's' =>
+ s = arg.arg();
+ if(s != nil)
+ fixss = int s;
+ 't' =>
+ emittab = arg.arg();
+ if(emittab == nil)
+ usage();
+ 'T' =>
+ emitcode = arg.arg();
+ if(emitcode == nil)
+ usage();
+ 'd' =>
+ emitcode = arg.arg();
+ if(emitcode == nil)
+ usage();
+ emitdyn = 1;
+ 'w' =>
+ superwarn = dowarn;
+ dowarn = 1;
+ 'x' =>
+ ext = arg.arg();
+ 'X' =>
+ signdump = arg.arg();
+ 'z' =>
+ arrayz = 1;
+ * =>
+ usage();
+ }
+ }
+
+ addinclude("/module");
+
+ argv = arg.argv;
+ arg = nil;
+
+ if(argv == nil){
+ usage();
+ }else if(ofile != nil){
+ if(len argv != 1)
+ usage();
+ translate(hd argv, ofile, mkfileext(ofile, ".dis", ".sbl"));
+ }else{
+ pr := len argv != 1;
+ if(ext == ""){
+ ext = ".s";
+ if(gendis)
+ ext = ".dis";
+ }
+ for(; argv != nil; argv = tl argv){
+ file := hd argv;
+ (nil, s) = str->splitr(file, "/");
+ if(pr)
+ print("%s:\n", s);
+ out := mkfileext(s, ".b", ext);
+ translate(file, out, mkfileext(out, ext, ".sbl"));
+ }
+ }
+ if (toterrors > 0)
+ raise("fail:errors");
+}
+
+usage()
+{
+ fprint(stderr, "usage: limbo [-GSagwe] [-I incdir] [-o outfile] [-{T|t|d} module] [-D debug] file ...\n");
+ raise("fail:usage");
+}
+
+mkfileext(file, oldext, ext: string): string
+{
+ n := len file;
+ n2 := len oldext;
+ if(n >= n2 && file[n-n2:] == oldext)
+ file = file[:n-n2];
+ return file + ext;
+}
+
+translate(in, out, dbg: string)
+{
+ infile = in;
+ outfile = out;
+ errors = 0;
+ bins[0] = bufio->open(in, Bufio->OREAD);
+ if(bins[0] == nil){
+ fprint(stderr, "can't open %s: %r\n", in);
+ toterrors++;
+ return;
+ }
+ doemit := emitcode != "" || emitstub || emittab != "" || emitsbl != "";
+ if(!doemit){
+ bout = bufio->create(out, Bufio->OWRITE, 8r666);
+ if(bout == nil){
+ fprint(stderr, "can't open %s: %r\n", out);
+ toterrors++;
+ bins[0].close();
+ return;
+ }
+ if(dosym){
+ bsym = bufio->create(dbg, Bufio->OWRITE, 8r666);
+ if(bsym == nil)
+ fprint(stderr, "can't open %s: %r\n", dbg);
+ }
+ }
+
+ lexstart(in);
+
+ popscopes();
+ typestart();
+ declstart();
+ nfnexp = 0;
+
+ parset = sys->millisec();
+ yyparse(yyctxt);
+ parset = sys->millisec() - parset;
+
+ checkt = sys->millisec();
+ entry := typecheck(!doemit);
+ checkt = sys->millisec() - checkt;
+
+ modcom(entry);
+
+ fns = nil;
+ nfns = 0;
+ descriptors = nil;
+
+ if(debug['T'])
+ print("times: parse=%d type=%d: gen=%d write=%d symbols=%d\n",
+ parset, checkt, gent, writet, symt);
+
+ if(bout != nil)
+ bout.close();
+ if(bsym != nil)
+ bsym.close();
+ toterrors += errors;
+ if(errors && bout != nil)
+ sys->remove(out);
+ if(errors && bsym != nil)
+ sys->remove(dbg);
+}
+
+pwd(): string
+{
+ workdir := load Workdir Workdir->PATH;
+ if(workdir == nil)
+ cd := "/";
+ else
+ cd = workdir->init();
+ # sys->print("pwd: %s\n", cd);
+ return cd;
+}
+
+cleanname(s: string): string
+{
+ ls, path: list of string;
+
+ if(s == nil)
+ return nil;
+ if(s[0] != '/' && s[0] != '\\')
+ (nil, ls) = sys->tokenize(pwd(), "/\\");
+ for( ; ls != nil; ls = tl ls)
+ path = hd ls :: path;
+ (nil, ls) = sys->tokenize(s, "/\\");
+ for( ; ls != nil; ls = tl ls){
+ n := hd ls;
+ if(n == ".")
+ ;
+ else if (n == ".."){
+ if(path != nil)
+ path = tl path;
+ }
+ else
+ path = n :: path;
+ }
+ p := "";
+ for( ; path != nil; path = tl path)
+ p = "/" + hd path + p;
+ if(p == nil)
+ p = "/";
+ # sys->print("cleanname: %s\n", p);
+ return p;
+}
+
+srcpath(): string
+{
+ srcp := cleanname(infile);
+ # sys->print("srcpath: %s\n", srcp);
+ return srcp;
+}
diff --git a/appl/cmd/limbo/mkfile b/appl/cmd/limbo/mkfile
new file mode 100644
index 00000000..2a555510
--- /dev/null
+++ b/appl/cmd/limbo/mkfile
@@ -0,0 +1,35 @@
+<../../../mkconfig
+
+TARG= limbo.dis\
+
+MODULES=\
+ arg.m\
+ disoptab.m\
+ isa.m\
+ limbo.m\
+ opname.m\
+ asm.b\
+ com.b\
+ decls.b\
+ dis.b\
+ ecom.b\
+ gen.b\
+ lex.b\
+ nodes.b\
+ optim.b\
+ sbl.b\
+ stubs.b\
+ typecheck.b\
+ types.b\
+
+SYSMODULES= \
+ bufio.m\
+ draw.m\
+ keyring.m\
+ math.m\
+ string.m\
+ sys.m\
+
+DISBIN=$ROOT/dis
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/limbo/nodes.b b/appl/cmd/limbo/nodes.b
new file mode 100644
index 00000000..61e97dc0
--- /dev/null
+++ b/appl/cmd/limbo/nodes.b
@@ -0,0 +1,1402 @@
+include "opname.m";
+
+znode: Node;
+
+isused = array[Oend] of
+{
+ Oas => 1,
+ Odas => 1,
+ Oaddas => 1,
+ Osubas => 1,
+ Omulas => 1,
+ Odivas => 1,
+ Omodas => 1,
+ Oexpas => 1,
+ Oandas => 1,
+ Ooras => 1,
+ Oxoras => 1,
+ Olshas => 1,
+ Onothing => 1,
+ Orshas => 1,
+ Oinc => 1,
+ Odec => 1,
+ Opreinc => 1,
+ Opredec => 1,
+ Ocall => 1,
+ Oraise => 1,
+ Ospawn => 1,
+ Osnd => 1,
+ Orcv => 1,
+
+ * => 0
+};
+
+sideeffect := array[Oend] of
+{
+ Oas => 1,
+ Odas => 1,
+ Oaddas => 1,
+ Osubas => 1,
+ Omulas => 1,
+ Odivas => 1,
+ Omodas => 1,
+ Oexpas => 1,
+ Oandas => 1,
+ Ooras => 1,
+ Oxoras => 1,
+ Olshas => 1,
+ Orshas => 1,
+ Oinc => 1,
+ Odec => 1,
+ Opreinc => 1,
+ Opredec => 1,
+ Ocall => 1,
+ Oraise => 1,
+ Ospawn => 1,
+ Osnd => 1,
+ Orcv => 1,
+
+ Oadr => 1,
+ Oarray => 1,
+ Ocast => 1,
+ Ochan => 1,
+ Ocons => 1,
+ Odiv => 1,
+ Odot => 1,
+ Oind => 1,
+ Oindex => 1,
+ Oinds => 1,
+ Oindx => 1,
+ Olen => 1,
+ Oload => 1,
+ Omod => 1,
+ Oref => 1,
+
+ * => 0
+};
+
+opcommute = array[Oend] of
+{
+ Oeq => Oeq,
+ Oneq => Oneq,
+ Olt => Ogt,
+ Ogt => Olt,
+ Ogeq => Oleq,
+ Oleq => Ogeq,
+ Oadd => Oadd,
+ Omul => Omul,
+ Oxor => Oxor,
+ Oor => Oor,
+ Oand => Oand,
+
+ * => 0
+};
+
+oprelinvert = array[Oend] of
+{
+
+ Oeq => Oneq,
+ Oneq => Oeq,
+ Olt => Ogeq,
+ Ogt => Oleq,
+ Ogeq => Olt,
+ Oleq => Ogt,
+
+ * => 0
+};
+
+isrelop := array[Oend] of
+{
+
+ Oeq => 1,
+ Oneq => 1,
+ Olt => 1,
+ Oleq => 1,
+ Ogt => 1,
+ Ogeq => 1,
+ Oandand => 1,
+ Ooror => 1,
+ Onot => 1,
+
+ * => 0
+};
+
+ipow(x: big, n: int): big
+{
+ inv: int;
+ r: big;
+
+ inv = 0;
+ if(n < 0){
+ n = -n;
+ inv = 1;
+ }
+ r = big 1;
+ for(;;){
+ if(n&1)
+ r *= x;
+ if((n >>= 1) == 0)
+ break;
+ x *= x;
+ }
+ if(inv)
+ r = big 1/r;
+ return r;
+}
+
+rpow(x: real, n: int): real
+{
+ inv: int;
+ r: real;
+
+ inv = 0;
+ if(n < 0){
+ n = -n;
+ inv = 1;
+ }
+ r = 1.0;
+ for(;;){
+ if(n&1)
+ r *= x;
+ if((n >>= 1) == 0)
+ break;
+ x *= x;
+ }
+ if(inv)
+ r = 1.0/r;
+ return r;
+}
+
+real2fix(v: real, t: ref Type): big
+{
+ return big(v/scale(t));
+}
+
+fix2fix(v: big, f: ref Type, t: ref Type): big
+{
+ return big(real v * (scale(f)/scale(t)));
+}
+
+fix2real(v: big, f: ref Type): real
+{
+ return real v * scale(f);
+}
+
+istuple(n: ref Node): int
+{
+ d: ref Decl;
+
+ case(n.op){
+ Otuple =>
+ return 1;
+ Oname =>
+ d = n.decl;
+ if(d.importid != nil)
+ d = d.importid;
+ return d.store == Dconst && (n.ty.kind == Ttuple || n.ty.kind == Tadt);
+ Odot =>
+ return 0; # istuple(n.left);
+ }
+ return 0;
+}
+
+tuplemem(n: ref Node, d: ref Decl): ref Node
+{
+ ty: ref Type;
+ ids: ref Decl;
+
+ ty = n.ty;
+ n = n.left;
+ for(ids = ty.ids; ids != nil; ids = ids.next){
+ if(ids.sym == d.sym)
+ break;
+ else
+ n = n.right;
+ }
+ if(n == nil)
+ fatal("tuplemem cannot cope !\n");
+ return n.left;
+}
+
+varcom(v: ref Decl): int
+{
+ n := v.init;
+ n = fold(n);
+ v.init = n;
+ if(debug['v'])
+ print("variable '%s' val %s\n", v.sym.name, expconv(n));
+ if(n == nil)
+ return 1;
+
+ tn := ref znode;
+ tn.op = Oname;
+ tn.decl = v;
+ tn.src = v.src;
+ tn.ty = v.ty;
+ return initable(tn, n, 0);
+}
+
+initable(v, n: ref Node, allocdep: int): int
+{
+ case n.ty.kind{
+ Tiface or
+ Tgoto or
+ Tcase or
+ Tcasel or
+ Tcasec or
+ Talt or
+ Texcept =>
+ return 1;
+ Tint or
+ Tbig or
+ Tbyte or
+ Treal or
+ Tstring or
+ Tfix =>
+ if(n.op != Oconst)
+ break;
+ return 1;
+ Tadt or
+ Tadtpick or
+ Ttuple =>
+ if(n.op == Otuple)
+ n = n.left;
+ else if(n.op == Ocall)
+ n = n.right;
+ else
+ break;
+ for(; n != nil; n = n.right)
+ if(!initable(v, n.left, allocdep))
+ return 0;
+ return 1;
+ Tarray =>
+ if(n.op != Oarray)
+ break;
+ if(allocdep >= DADEPTH){
+ nerror(v, expconv(v)+"s initializer has arrays nested more than "+string allocdep+" deep");
+ return 0;
+ }
+ allocdep++;
+ usedesc(mktdesc(n.ty.tof));
+ if(n.left.op != Oconst){
+ nerror(v, expconv(v)+"s size is not a constant");
+ return 0;
+ }
+ for(e := n.right; e != nil; e = e.right)
+ if(!initable(v, e.left.right, allocdep))
+ return 0;
+ return 1;
+ Tany =>
+ return 1;
+ Tref or
+ Tlist or
+ Tpoly or
+ * =>
+ nerror(v, "can't initialize "+etconv(v));
+ return 0;
+ }
+ nerror(v, expconv(v)+"s initializer, "+expconv(n)+", is not a constant expression");
+ return 0;
+}
+
+#
+# merge together two sorted lists, yielding a sorted list
+#
+elemmerge(e, f: ref Node): ref Node
+{
+ r := rock := ref Node;
+ while(e != nil && f != nil){
+ if(e.left.left.c.val <= f.left.left.c.val){
+ r.right = e;
+ e = e.right;
+ }else{
+ r.right = f;
+ f = f.right;
+ }
+ r = r.right;
+ }
+ if(e != nil)
+ r.right = e;
+ else
+ r.right = f;
+ return rock.right;
+}
+
+#
+# recursively split lists and remerge them after they are sorted
+#
+recelemsort(e: ref Node, n: int): ref Node
+{
+ if(n <= 1)
+ return e;
+ m := n / 2 - 1;
+ ee := e;
+ for(i := 0; i < m; i++)
+ ee = ee.right;
+ r := ee.right;
+ ee.right = nil;
+ return elemmerge(recelemsort(e, n / 2),
+ recelemsort(r, (n + 1) / 2));
+}
+
+#
+# sort the elems by index; wild card is first
+#
+elemsort(e: ref Node): ref Node
+{
+ n := 0;
+ for(ee := e; ee != nil; ee = ee.right){
+ if(ee.left.left.op == Owild)
+ ee.left.left.c = ref Const(big -1, 0.);
+ n++;
+ }
+ return recelemsort(e, n);
+}
+
+sametree(n1: ref Node, n2: ref Node): int
+{
+ if(n1 == n2)
+ return 1;
+ if(n1 == nil || n2 == nil)
+ return 0;
+ if(n1.op != n2.op || n1.ty != n2.ty)
+ return 0;
+ if(n1.op == Oconst){
+ case(n1.ty.kind){
+ Tbig or
+ Tbyte or
+ Tint =>
+ return n1.c.val == n2.c.val;
+ Treal =>
+ return n1.c.rval == n2.c.rval;
+ Tfix =>
+ return n1.c.val == n2.c.val && tequal(n1.ty, n2.ty);
+ Tstring =>
+ return n1.decl.sym == n2.decl.sym;
+ }
+ return 0;
+ }
+ return n1.decl == n2.decl && sametree(n1.left, n2.left) && sametree(n1.right, n2.right);
+}
+
+occurs(d: ref Decl, n: ref Node): int
+{
+ if(n == nil)
+ return 0;
+ if(n.op == Oname){
+ if(d == n.decl)
+ return 1;
+ return 0;
+ }
+ return occurs(d, n.left) + occurs(d, n.right);
+}
+
+#
+# left and right subtrees the same
+#
+folds(n: ref Node): ref Node
+{
+ if(hasside(n, 1))
+ return n;
+ case(n.op){
+ Oeq or
+ Oleq or
+ Ogeq =>
+ n.c = ref Const(big 1, 0.0);
+ Osub =>
+ n.c = ref Const(big 0, 0.0);
+ Oxor or
+ Oneq or
+ Olt or
+ Ogt =>
+ n.c = ref Const(big 0, 0.0);
+ Oand or
+ Oor or
+ Oandand or
+ Ooror =>
+ return n.left;
+ * =>
+ return n;
+ }
+ n.op = Oconst;
+ n.left = n.right = nil;
+ n.decl = nil;
+ return n;
+}
+
+#
+# constant folding for typechecked expressions
+#
+fold(n: ref Node): ref Node
+{
+ if(n == nil)
+ return nil;
+ if(debug['F'])
+ print("fold %s\n", nodeconv(n));
+ n = efold(n);
+ if(debug['F'])
+ print("folded %s\n", nodeconv(n));
+ return n;
+}
+
+efold(n: ref Node): ref Node
+{
+ d: ref Decl;
+
+ if(n == nil)
+ return nil;
+
+ left := n.left;
+ right := n.right;
+ case n.op{
+ Oname =>
+ d = n.decl;
+ if(d.importid != nil)
+ d = d.importid;
+ if(d.store != Dconst){
+ if(d.store == Dtag){
+ n.op = Oconst;
+ n.ty = tint;
+ n.c = ref Const(big d.tag, 0.);
+ }
+ break;
+ }
+ case n.ty.kind{
+ Tbig =>
+ n.op = Oconst;
+ n.c = ref Const(d.init.c.val, 0.);
+ Tbyte =>
+ n.op = Oconst;
+ n.c = ref Const(big byte d.init.c.val, 0.);
+ Tint or
+ Tfix =>
+ n.op = Oconst;
+ n.c = ref Const(big int d.init.c.val, 0.);
+ Treal =>
+ n.op = Oconst;
+ n.c = ref Const(big 0, d.init.c.rval);
+ Tstring =>
+ n.op = Oconst;
+ n.decl = d.init.decl;
+ Ttuple =>
+ *n = *d.init;
+ Tadt =>
+ *n = *d.init;
+ n = rewrite(n); # was call
+ Texception =>
+ if(n.ty.cons == byte 0)
+ fatal("non-const exception type in efold");
+ n.op = Oconst;
+ * =>
+ fatal("unknown const type "+typeconv(n.ty)+" in efold");
+ }
+ Oadd =>
+ left = efold(left);
+ right = efold(right);
+ n.left = left;
+ n.right = right;
+ if(n.ty == tstring && right.op == Oconst){
+ if(left.op == Oconst)
+ n = mksconst(n.src, stringcat(left.decl.sym, right.decl.sym));
+ else if(left.op == Oadd && left.ty == tstring && left.right.op == Oconst){
+ left.right = mksconst(n.src, stringcat(left.right.decl.sym, right.decl.sym));
+ n = left;
+ }
+ }
+ Olen =>
+ left = efold(left);
+ n.left = left;
+ if(left.ty == tstring && left.op == Oconst)
+ n = mkconst(n.src, big len left.decl.sym.name);
+ Oslice =>
+ if(right.left.op == Onothing)
+ right.left = mkconst(right.left.src, big 0);
+ n.left = efold(left);
+ n.right = efold(right);
+ Oinds =>
+ n.left = left = efold(left);
+ n.right = right = efold(right);
+ if(right.op == Oconst && left.op == Oconst){
+ ;
+ }
+ Ocast =>
+ n.op = Ocast;
+ left = efold(left);
+ n.left = left;
+ if(n.ty == left.ty || n.ty.kind == Tfix && tequal(n.ty, left.ty))
+ return left;
+ if(left.op == Oconst)
+ return foldcast(n, left);
+ Odot or
+ Omdot =>
+ #
+ # what about side effects from left?
+ #
+ d = right.decl;
+ case d.store{
+ Dconst or
+ Dtag or
+ Dtype =>
+ #
+ # set it up as a name and let that case do the hard work
+ #
+ n.op = Oname;
+ n.decl = d;
+ n.left = nil;
+ n.right = nil;
+ return efold(n);
+ }
+ n.left = efold(left);
+ if(n.left.op == Otuple)
+ n = tuplemem(n.left, d);
+ else
+ n.right = efold(right);
+ Otagof =>
+ if(n.decl != nil){
+ n.op = Oconst;
+ n.left = nil;
+ n.right = nil;
+ n.c = ref Const(big n.decl.tag, 0.);
+ return efold(n);
+ }
+ n.left = efold(left);
+ Oif =>
+ n.left = left = efold(left);
+ n.right = right = efold(right);
+ if(left.op == Oconst){
+ if(left.c.val != big 0)
+ return right.left;
+ else
+ return right.right;
+ }
+ * =>
+ n.left = efold(left);
+ n.right = efold(right);
+ }
+
+ left = n.left;
+ right = n.right;
+ if(left == nil)
+ return n;
+
+ if(right == nil){
+ if(left.op == Oconst){
+ if(left.ty == tint || left.ty == tbyte || left.ty == tbig)
+ return foldc(n);
+ if(left.ty == treal)
+ return foldr(n);
+ }
+ return n;
+ }
+
+ if(left.op == Oconst){
+ case n.op{
+ Olsh or
+ Orsh =>
+ if(left.c.val == big 0 && !hasside(right, 1))
+ return left;
+ Ooror =>
+ if(left.ty == tint || left.ty == tbyte || left.ty == tbig){
+ if(left.c.val == big 0){
+ n = mkbin(Oneq, right, mkconst(right.src, big 0));
+ n.ty = right.ty;
+ n.left.ty = right.ty;
+ return efold(n);
+ }
+ left.c.val = big 1;
+ return left;
+ }
+ Oandand =>
+ if(left.ty == tint || left.ty == tbyte || left.ty == tbig){
+ if(left.c.val == big 0)
+ return left;
+ n = mkbin(Oneq, right, mkconst(right.src, big 0));
+ n.ty = right.ty;
+ n.left.ty = right.ty;
+ return efold(n);
+ }
+ }
+ }
+ if(left.op == Oconst && right.op != Oconst
+ && opcommute[n.op]
+ && n.ty != tstring){
+ n.op = opcommute[n.op];
+ n.left = right;
+ n.right = left;
+ left = right;
+ right = n.right;
+ }
+ if(right.op == Oconst && left.op == n.op && left.right.op == Oconst
+ && (n.op == Oadd || n.op == Omul || n.op == Oor || n.op == Oxor || n.op == Oand)
+ && n.ty != tstring){
+ n.left = left.left;
+ left.left = right;
+ right = efold(left);
+ n.right = right;
+ left = n.left;
+ }
+ if(right.op == Oconst){
+ if(n.op == Oexp && left.ty == treal){
+ if(left.op == Oconst)
+ return foldr(n);
+ return n;
+ }
+ if(right.ty == tint || right.ty == tbyte || left.ty == tbig){
+ if(left.op == Oconst)
+ return foldc(n);
+ return foldvc(n);
+ }
+ if(right.ty == treal && left.op == Oconst)
+ return foldr(n);
+ }
+ if(sametree(left, right))
+ return folds(n);
+ return n;
+}
+
+#
+# does evaluating the node have any side effects?
+#
+hasside(n: ref Node, strict: int): int
+{
+ for(; n != nil; n = n.right){
+ if(sideeffect[n.op] && (strict || n.op != Oadr && n.op != Oind))
+ return 1;
+ if(hasside(n.left, strict))
+ return 1;
+ }
+ return 0;
+}
+
+hascall(n: ref Node): int
+{
+ for(; n != nil; n = n.right){
+ if(n.op == Ocall || n.op == Ospawn)
+ return 1;
+ if(hascall(n.left))
+ return 1;
+ }
+ return 0;
+}
+
+hasasgns(n: ref Node): int
+{
+ if(n == nil)
+ return 0;
+ if(n.op != Ocall && isused[n.op] && n.op != Onothing)
+ return 1;
+ return hasasgns(n.left) || hasasgns(n.right);
+}
+
+nodes(n: ref Node): int
+{
+ if(n == nil)
+ return 0;
+ return 1+nodes(n.left)+nodes(n.right);
+}
+
+foldcast(n, left: ref Node): ref Node
+{
+ case left.ty.kind{
+ Tint =>
+ left.c.val = big int left.c.val;
+ return foldcasti(n, left);
+ Tbyte =>
+ left.c.val = big byte left.c.val;
+ return foldcasti(n, left);
+ Tbig =>
+ return foldcasti(n, left);
+ Treal =>
+ case n.ty.kind{
+ Tint or
+ Tbyte or
+ Tbig =>
+ left.c.val = big left.c.rval;
+ Tfix =>
+ left.c.val = real2fix(left.c.rval, n.ty);
+ Tstring =>
+ return mksconst(n.src, enterstring(string left.c.rval));
+ * =>
+ return n;
+ }
+ Tfix =>
+ case n.ty.kind{
+ Tint or
+ Tbyte or
+ Tbig =>
+ left.c.val = big fix2real(left.c.val, left.ty);
+ Treal =>
+ left.c.rval = fix2real(left.c.val, left.ty);
+ Tfix =>
+ if(tequal(left.ty, n.ty))
+ return left;
+ left.c.val = fix2fix(left.c.val, left.ty, n.ty);
+ Tstring =>
+ return mksconst(n.src, enterstring(string fix2real(left.c.val, left.ty)));
+ * =>
+ return n;
+ }
+ break;
+ Tstring =>
+ case n.ty.kind{
+ Tint or
+ Tbyte or
+ Tbig =>
+ left.c = ref Const(big left.decl.sym.name, 0.);
+ Treal =>
+ left.c = ref Const(big 0, real left.decl.sym.name);
+ Tfix =>
+ left.c = ref Const(real2fix(real left.decl.sym.name, n.ty), 0.);
+ * =>
+ return n;
+ }
+ * =>
+ return n;
+ }
+ left.ty = n.ty;
+ left.src = n.src;
+ return left;
+}
+
+#
+# left is some kind of int type
+#
+foldcasti(n, left: ref Node): ref Node
+{
+ case n.ty.kind{
+ Tint =>
+ left.c.val = big int left.c.val;
+ Tbyte =>
+ left.c.val = big byte left.c.val;
+ Tbig =>
+ ;
+ Treal =>
+ left.c.rval = real left.c.val;
+ Tfix =>
+ left.c.val = real2fix(real left.c.val, n.ty);
+ Tstring =>
+ return mksconst(n.src, enterstring(string left.c.val));
+ * =>
+ return n;
+ }
+ left.ty = n.ty;
+ left.src = n.src;
+ return left;
+}
+
+#
+# right is a const int
+#
+foldvc(n: ref Node): ref Node
+{
+ left := n.left;
+ right := n.right;
+ case n.op{
+ Oadd or
+ Osub or
+ Oor or
+ Oxor or
+ Olsh or
+ Orsh or
+ Ooror =>
+ if(right.c.val == big 0)
+ return left;
+ if(n.op == Ooror && !hasside(left, 1))
+ return right;
+ Oand =>
+ if(right.c.val == big 0 && !hasside(left, 1))
+ return right;
+ Omul =>
+ if(right.c.val == big 1)
+ return left;
+ if(right.c.val == big 0 && !hasside(left, 1))
+ return right;
+ Odiv =>
+ if(right.c.val == big 1)
+ return left;
+ Omod =>
+ if(right.c.val == big 1 && !hasside(left, 1)){
+ right.c.val = big 0;
+ return right;
+ }
+ Oexp =>
+ if(right.c.val == big 0){
+ right.c.val = big 1;
+ return right;
+ }
+ if(right.c.val == big 1)
+ return left;
+ Oandand =>
+ if(right.c.val != big 0)
+ return left;
+ if(!hasside(left, 1))
+ return right;
+ Oneq =>
+ if(!isrelop[left.op])
+ return n;
+ if(right.c.val == big 0)
+ return left;
+ n.op = Onot;
+ n.right = nil;
+ Oeq =>
+ if(!isrelop[left.op])
+ return n;
+ if(right.c.val != big 0)
+ return left;
+ n.op = Onot;
+ n.right = nil;
+ }
+ return n;
+}
+
+#
+# left and right are const ints
+#
+foldc(n: ref Node): ref Node
+{
+ v: big;
+ rv, nb: int;
+
+ left := n.left;
+ right := n.right;
+ case n.op{
+ Oadd =>
+ v = left.c.val + right.c.val;
+ Osub =>
+ v = left.c.val - right.c.val;
+ Omul =>
+ v = left.c.val * right.c.val;
+ Odiv =>
+ if(right.c.val == big 0){
+ nerror(n, "divide by 0 in constant expression");
+ return n;
+ }
+ v = left.c.val / right.c.val;
+ Omod =>
+ if(right.c.val == big 0){
+ nerror(n, "mod by 0 in constant expression");
+ return n;
+ }
+ v = left.c.val % right.c.val;
+ Oexp =>
+ if(left.c.val == big 0 && right.c.val < big 0){
+ nerror(n, "0 to negative power in constant expression");
+ return n;
+ }
+ v = ipow(left.c.val, int right.c.val);
+ Oand =>
+ v = left.c.val & right.c.val;
+ Oor =>
+ v = left.c.val | right.c.val;
+ Oxor =>
+ v = left.c.val ^ right.c.val;
+ Olsh =>
+ v = left.c.val;
+ rv = int right.c.val;
+ if(rv < 0 || rv >= n.ty.size * 8){
+ nwarn(n, "shift amount "+string rv+" out of range");
+ rv = 0;
+ }
+ if(rv == 0)
+ break;
+ v <<= rv;
+ Orsh =>
+ v = left.c.val;
+ rv = int right.c.val;
+ nb = n.ty.size * 8;
+ if(rv < 0 || rv >= nb){
+ nwarn(n, "shift amount "+string rv+" out of range");
+ rv = 0;
+ }
+ if(rv == 0)
+ break;
+ v >>= rv;
+ Oneg =>
+ v = -left.c.val;
+ Ocomp =>
+ v = ~left.c.val;
+ Oeq =>
+ v = big(left.c.val == right.c.val);
+ Oneq =>
+ v = big(left.c.val != right.c.val);
+ Ogt =>
+ v = big(left.c.val > right.c.val);
+ Ogeq =>
+ v = big(left.c.val >= right.c.val);
+ Olt =>
+ v = big(left.c.val < right.c.val);
+ Oleq =>
+ v = big(left.c.val <= right.c.val);
+ Oandand =>
+ v = big(int left.c.val && int right.c.val);
+ Ooror =>
+ v = big(int left.c.val || int right.c.val);
+ Onot =>
+ v = big(left.c.val == big 0);
+ * =>
+ return n;
+ }
+ if(n.ty == tint)
+ v = big int v;
+ else if(n.ty == tbyte)
+ v = big byte v;
+ n.left = nil;
+ n.right = nil;
+ n.decl = nil;
+ n.op = Oconst;
+ n.c = ref Const(v, 0.);
+ return n;
+}
+
+#
+# left and right are const reals
+#
+foldr(n: ref Node): ref Node
+{
+ rv := 0.;
+ v := big 0;
+
+ left := n.left;
+ right := n.right;
+ case n.op{
+ Ocast =>
+ return n;
+ Oadd =>
+ rv = left.c.rval + right.c.rval;
+ Osub =>
+ rv = left.c.rval - right.c.rval;
+ Omul =>
+ rv = left.c.rval * right.c.rval;
+ Odiv =>
+ rv = left.c.rval / right.c.rval;
+ Oexp =>
+ rv = rpow(left.c.rval, int right.c.val);
+ Oneg =>
+ rv = -left.c.rval;
+ Oinv =>
+ if(left.c.rval == 0.0){
+ error(n.src.start, "divide by 0 in fixed point type");
+ return n;
+ }
+ rv = 1.0/left.c.rval;
+ Oeq =>
+ v = big(left.c.rval == right.c.rval);
+ Oneq =>
+ v = big(left.c.rval != right.c.rval);
+ Ogt =>
+ v = big(left.c.rval > right.c.rval);
+ Ogeq =>
+ v = big(left.c.rval >= right.c.rval);
+ Olt =>
+ v = big(left.c.rval < right.c.rval);
+ Oleq =>
+ v = big(left.c.rval <= right.c.rval);
+ * =>
+ return n;
+ }
+ n.left = nil;
+ n.right = nil;
+ n.op = Oconst;
+
+ if(isnan(rv))
+ rv = canonnan;
+
+ n.c = ref Const(v, rv);
+ return n;
+}
+
+varinit(d: ref Decl, e: ref Node): ref Node
+{
+ n := mkdeclname(e.src, d);
+ if(d.next == nil)
+ return mkbin(Oas, n, e);
+ return mkbin(Oas, n, varinit(d.next, e));
+}
+
+#
+# given: an Oseq list with left == next or the last child
+# make a list with the right == next
+# ie: Oseq(Oseq(a, b),c) ==> Oseq(a, Oseq(b, Oseq(c, nil))))
+#
+rotater(e: ref Node): ref Node
+{
+ if(e == nil)
+ return e;
+ if(e.op != Oseq)
+ return mkunary(Oseq, e);
+ e.right = mkunary(Oseq, e.right);
+ while(e.left.op == Oseq){
+ left := e.left;
+ e.left = left.right;
+ left.right = e;
+ e = left;
+ }
+ return e;
+}
+
+#
+# reverse the case labels list
+#
+caselist(s, nr: ref Node): ref Node
+{
+ r := s.right;
+ s.right = nr;
+ if(r == nil)
+ return s;
+ return caselist(r, s);
+}
+
+#
+# e is a seq of expressions; make into cons's to build a list
+#
+etolist(e: ref Node): ref Node
+{
+ if(e == nil)
+ return nil;
+ n := mknil(e.src);
+ n.src.start = n.src.stop;
+ if(e.op != Oseq)
+ return mkbin(Ocons, e, n);
+ e.right = mkbin(Ocons, e.right, n);
+ while(e.left.op == Oseq){
+ e.op = Ocons;
+ left := e.left;
+ e.left = left.right;
+ left.right = e;
+ e = left;
+ }
+ e.op = Ocons;
+ return e;
+}
+
+dupn(resrc: int, src: Src, n: ref Node): ref Node
+{
+ nn := ref *n;
+ if(resrc)
+ nn.src = src;
+ if(nn.left != nil)
+ nn.left = dupn(resrc, src, nn.left);
+ if(nn.right != nil)
+ nn.right = dupn(resrc, src, nn.right);
+ return nn;
+}
+
+mkn(op: int, left, right: ref Node): ref Node
+{
+ n := ref Node;
+ n.op = op;
+ n.flags = byte 0;
+ n.left = left;
+ n.right = right;
+ return n;
+}
+
+mkunary(op: int, left: ref Node): ref Node
+{
+ n := ref Node;
+ n.src = left.src;
+ n.op = op;
+ n.flags = byte 0;
+ n.left = left;
+ return n;
+}
+
+mkbin(op: int, left, right: ref Node): ref Node
+{
+ n := ref Node;
+ n.src.start = left.src.start;
+ n.src.stop = right.src.stop;
+ n.op = op;
+ n.flags = byte 0;
+ n.left = left;
+ n.right = right;
+ return n;
+}
+
+mkdeclname(src: Src, d: ref Decl): ref Node
+{
+ n := ref Node;
+ n.src = src;
+ n.op = Oname;
+ n.flags = byte 0;
+ n.decl = d;
+ n.ty = d.ty;
+ d.refs++;
+ return n;
+}
+
+mknil(src: Src): ref Node
+{
+ return mkdeclname(src, nildecl);
+}
+
+mkname(src: Src, s: ref Sym): ref Node
+{
+ n := ref Node;
+ n.src = src;
+ n.op = Oname;
+ n.flags = byte 0;
+ if(s.unbound == nil){
+ s.unbound = mkdecl(src, Dunbound, nil);
+ s.unbound.sym = s;
+ }
+ n.decl = s.unbound;
+ return n;
+}
+
+mkconst(src: Src, v: big): ref Node
+{
+ n := ref Node;
+ n.src = src;
+ n.op = Oconst;
+ n.flags = byte 0;
+ n.ty = tint;
+ n.c = ref Const(v, 0.);
+ return n;
+}
+
+mkrconst(src: Src, v: real): ref Node
+{
+ n := ref Node;
+ n.src = src;
+ n.op = Oconst;
+ n.flags = byte 0;
+ n.ty = treal;
+ n.c = ref Const(big 0, v);
+ return n;
+}
+
+mksconst(src: Src, s: ref Sym): ref Node
+{
+ n := ref Node;
+ n.src = src;
+ n.op = Oconst;
+ n.flags = byte 0;
+ n.ty = tstring;
+ n.decl = mkdecl(src, Dconst, tstring);
+ n.decl.sym = s;
+ return n;
+}
+
+opconv(op: int): string
+{
+ if(op < 0 || op > Oend)
+ return "op "+string op;
+ return opname[op];
+}
+
+etconv(n: ref Node): string
+{
+ s := expconv(n);
+ if(n.ty == tany || n.ty == tnone || n.ty == terror)
+ return s;
+ s += " of type ";
+ s += typeconv(n.ty);
+ return s;
+}
+
+expconv(n: ref Node): string
+{
+ return "'" + subexpconv(n) + "'";
+}
+
+subexpconv(n: ref Node): string
+{
+ if(n == nil)
+ return "";
+ s := "";
+ if(int n.flags & PARENS)
+ s[len s] = '(';
+ case n.op{
+ Obreak or
+ Ocont =>
+ s += opname[n.op];
+ if(n.decl != nil)
+ s += " "+n.decl.sym.name;
+ Oexit or
+ Owild =>
+ s += opname[n.op];
+ Onothing =>
+ ;
+ Oadr or
+ Oused =>
+ s += subexpconv(n.left);
+ Oseq =>
+ s += eprintlist(n, ", ");
+ Oname =>
+ if(n.decl == nil)
+ s += "<nil>";
+ else
+ s += n.decl.sym.name;
+ Oconst =>
+ if(n.ty.kind == Tstring){
+ s += stringpr(n.decl.sym);
+ break;
+ }
+ if(n.decl != nil && n.decl.sym != nil){
+ s += n.decl.sym.name;
+ break;
+ }
+ case n.ty.kind{
+ Tbig or
+ Tint or
+ Tbyte =>
+ s += string n.c.val;
+ Treal =>
+ s += string n.c.rval;
+ Tfix =>
+ s += string n.c.val + "(" + string n.ty.val.c.rval + ")";
+ * =>
+ s += opname[n.op];
+ }
+ Ocast =>
+ s += typeconv(n.ty);
+ s[len s] = ' ';
+ s += subexpconv(n.left);
+ Otuple =>
+ if(n.ty != nil && n.ty.kind == Tadt)
+ s += n.ty.decl.sym.name;
+ s[len s] = '(';
+ s += eprintlist(n.left, ", ");
+ s[len s] = ')';
+ Ochan =>
+ if(n.left != nil){
+ s += "chan [";
+ s += subexpconv(n.left);
+ s += "] of ";
+ s += typeconv(n.ty.tof);
+ }
+ else
+ s += "chan of "+typeconv(n.ty.tof);
+ Oarray =>
+ s += "array [";
+ if(n.left != nil)
+ s += subexpconv(n.left);
+ s += "] of ";
+ if(n.right != nil){
+ s += "{";
+ s += eprintlist(n.right, ", ");
+ s += "}";
+ }else{
+ s += typeconv(n.ty.tof);
+ }
+ Oelem or
+ Olabel =>
+ if(n.left != nil){
+ s += eprintlist(n.left, " or ");
+ s += " =>";
+ }
+ s += subexpconv(n.right);
+ Orange =>
+ s += subexpconv(n.left);
+ s += " to ";
+ s += subexpconv(n.right);
+ Ospawn =>
+ s += "spawn ";
+ s += subexpconv(n.left);
+ Oraise =>
+ s += "raise ";
+ s += subexpconv(n.left);
+ Ocall =>
+ s += subexpconv(n.left);
+ s += "(";
+ s += eprintlist(n.right, ", ");
+ s += ")";
+ Oinc or
+ Odec =>
+ s += subexpconv(n.left);
+ s += opname[n.op];
+ Oindex or
+ Oindx or
+ Oinds =>
+ s += subexpconv(n.left);
+ s += "[";
+ s += subexpconv(n.right);
+ s += "]";
+ Oslice =>
+ s += subexpconv(n.left);
+ s += "[";
+ s += subexpconv(n.right.left);
+ s += ":";
+ s += subexpconv(n.right.right);
+ s += "]";
+ Oload =>
+ s += "load ";
+ s += typeconv(n.ty);
+ s += " ";
+ s += subexpconv(n.left);
+ Oref or
+ Olen or
+ Ohd or
+ Otl or
+ Otagof =>
+ s += opname[n.op];
+ s[len s] = ' ';
+ s += subexpconv(n.left);
+ * =>
+ if(n.right == nil){
+ s += opname[n.op];
+ s += subexpconv(n.left);
+ }else{
+ s += subexpconv(n.left);
+ s += opname[n.op];
+ s += subexpconv(n.right);
+ }
+ }
+ if(int n.flags & PARENS)
+ s[len s] = ')';
+ return s;
+}
+
+eprintlist(elist: ref Node, sep: string): string
+{
+ if(elist == nil)
+ return "";
+ s := "";
+ for(; elist.right != nil; elist = elist.right){
+ if(elist.op == Onothing)
+ continue;
+ if(elist.left.op == Ofnptr)
+ return s;
+ s += subexpconv(elist.left);
+ if(elist.right.left.op != Ofnptr)
+ s += sep;
+ }
+ s += subexpconv(elist.left);
+ return s;
+}
+
+nodeconv(n: ref Node): string
+{
+ return nprint(n, 0);
+}
+
+nprint(n: ref Node, indent: int): string
+{
+ if(n == nil)
+ return "";
+ s := "\n";
+ for(i := 0; i < indent; i++)
+ s[len s] = ' ';
+ case n.op{
+ Oname =>
+ if(n.decl == nil)
+ s += "<nil>";
+ else
+ s += n.decl.sym.name;
+ Oconst =>
+ if(n.decl != nil && n.decl.sym != nil)
+ s += n.decl.sym.name;
+ else
+ s += opconv(n.op);
+ if(n.ty == tint || n.ty == tbyte || n.ty == tbig)
+ s += " (" + string n.c.val + ")";
+ * =>
+ s += opconv(n.op);
+ }
+ s += " " + typeconv(n.ty) + " " + string n.addable + " " + string n.temps;
+ indent += 2;
+ s += nprint(n.left, indent);
+ s += nprint(n.right, indent);
+ return s;
+}
diff --git a/appl/cmd/limbo/opname.m b/appl/cmd/limbo/opname.m
new file mode 100644
index 00000000..50da6ec9
--- /dev/null
+++ b/appl/cmd/limbo/opname.m
@@ -0,0 +1,109 @@
+opname := array[Oend+1] of
+{
+ "unknown",
+
+ Oadd => "+",
+ Oaddas => "+=",
+ Oadr => "adr",
+ Oadtdecl => "adtdecl",
+ Oalt => "alt",
+ Oand => "&",
+ Oandand => "&&",
+ Oandas => "&=",
+ Oarray => "array",
+ Oas => "=",
+ Obreak => "break",
+ Ocall => "call",
+ Ocase => "case",
+ Ocast => "cast",
+ Ochan => "chan",
+ Ocomma => ",",
+ Ocomp => "~",
+ Ocondecl => "condecl",
+ Ocons => "::",
+ Oconst => "const",
+ Ocont => "continue",
+ Odas => ":=",
+ Odec => "--",
+ Odiv => "/",
+ Odivas => "/=",
+ Odo => "do",
+ Odot => ".",
+ Oelem => "elem",
+ Oeq => "==",
+ Oexcept => "except",
+ Oexdecl => "exdecl",
+ Oexit => "exit",
+ Oexp => "**",
+ Oexpas => "**=",
+ Oexstmt => "exstat",
+ Ofielddecl => "fielddecl",
+ Ofnptr => "fnptr",
+ Ofor => "for",
+ Ofunc => "fn(){}",
+ Ogeq => ">=",
+ Ogt => ">",
+ Ohd => "hd",
+ Oif => "if",
+ Oimport => "import",
+ Oinc => "++",
+ Oind => "*",
+ Oindex => "index",
+ Oinds => "inds",
+ Oindx => "indx",
+ Oinv => "inv",
+ Ojmp => "jmp",
+ Olabel => "label",
+ Olen => "len",
+ Oleq => "<=",
+ Oload => "load",
+ Olsh => "<<",
+ Olshas => "<<=",
+ Olt => "<",
+ Omdot => "->",
+ Omod => "%",
+ Omodas => "%=",
+ Omoddecl => "moddecl",
+ Omul => "*",
+ Omulas => "*=",
+ Oname => "name",
+ Oneg => "-",
+ Oneq => "!=",
+ Onot => "!",
+ Onothing => "nothing",
+ Oor => "|",
+ Ooras => "|=",
+ Ooror => "||",
+ Opick => "pick",
+ Opickdecl => "pickdec",
+ Opredec => "--",
+ Opreinc => "++",
+ Oraise => "raise",
+ Orange => "range",
+ Orcv => "<-",
+ Oref => "ref",
+ Oret => "return",
+ Orsh => ">>",
+ Orshas => ">>=",
+ Oscope => "scope",
+ Oself => "self",
+ Oseq => "seq",
+ Oslice => "slice",
+ Osnd => "<-=",
+ Ospawn => "spawn",
+ Osub => "-",
+ Osubas => "-=",
+ Otagof => "tagof",
+ Otl => "tl",
+ Otuple => "tuple",
+ Otype => "type",
+ Otypedecl => "typedecl",
+ Oused => "used",
+ Ovardecl => "vardecl",
+ Ovardecli => "vardecli",
+ Owild => "*",
+ Oxor => "^",
+ Oxoras => "^=",
+
+ Oend => "unknown"
+};
diff --git a/appl/cmd/limbo/optim.b b/appl/cmd/limbo/optim.b
new file mode 100644
index 00000000..ac437fab
--- /dev/null
+++ b/appl/cmd/limbo/optim.b
@@ -0,0 +1,3 @@
+optim(nil: ref Inst, nil: ref Decl)
+{
+}
diff --git a/appl/cmd/limbo/sbl.b b/appl/cmd/limbo/sbl.b
new file mode 100644
index 00000000..0ae69d5f
--- /dev/null
+++ b/appl/cmd/limbo/sbl.b
@@ -0,0 +1,397 @@
+
+sbltname := array[Tend] of
+{
+ Tnone => byte 'n',
+ Tadt => byte 'a',
+ Tadtpick => byte 'a',
+ Tarray => byte 'A',
+ Tbig => byte 'B',
+ Tbyte => byte 'b',
+ Tchan => byte 'C',
+ Treal => byte 'f',
+ Tfn => byte 'F',
+ Tint => byte 'i',
+ Tlist => byte 'L',
+ Tmodule => byte 'm',
+ Tref => byte 'R',
+ Tstring => byte 's',
+ Ttuple => byte 't',
+ Texception => byte 't',
+ Tfix => byte 'i',
+ Tpoly => byte 'P',
+
+ Tainit => byte '?',
+ Talt => byte '?',
+ Tany => byte 'N',
+ Tarrow => byte '?',
+ Tcase => byte '?',
+ Tcasel => byte '?',
+ Tcasec => byte '?',
+ Tdot => byte '?',
+ Terror => byte '?',
+ Tgoto => byte '?',
+ Tid => byte '?',
+ Tiface => byte '?',
+ Texcept => byte '?',
+ Tinst => byte '?',
+};
+sbltadtpick: con byte 'p';
+
+sfiles: ref Sym;
+ftail: ref Sym;
+nsfiles: int;
+blockid: int;
+lastf: int;
+lastline: int;
+
+MAXSBLINT: con 12;
+MAXSBLSRC: con 6*(MAXSBLINT+1);
+
+sblmod(m: ref Decl)
+{
+ bsym.puts("limbo .sbl 2.1\n");
+ bsym.puts(m.sym.name);
+ bsym.putb(byte '\n');
+
+ blockid = 0;
+ nsfiles = 0;
+ sfiles = ftail = nil;
+ lastf = 0;
+ lastline = 0;
+}
+
+sblfile(name: string): int
+{
+ i := 0;
+ for(s := sfiles; s != nil; s = s.next){
+ if(s.name == name)
+ return i;
+ i++;
+ }
+ s = ref Sym;
+ s.name = name;
+ s.next = nil;
+ if(sfiles == nil)
+ sfiles = s;
+ else
+ ftail.next = s;
+ ftail = s;
+ nsfiles = i + 1;
+ return i;
+}
+
+filename(s: string): string
+{
+ (nil, file) := str->splitr(s, "/ \\");
+ return file;
+}
+
+sblfiles()
+{
+ for(i := 0; i < nfiles; i++)
+ files[i].sbl = sblfile(files[i].name);
+ bsym.puts(string nsfiles);
+ bsym.putb(byte '\n');
+ for(s := sfiles; s != nil; s = s.next){
+ bsym.puts(filename(s.name));
+ bsym.putb(byte '\n');
+ }
+}
+
+sblint(buf: array of byte, off, v: int): int
+{
+ if(v == 0){
+ buf[off++] = byte '0';
+ return off;
+ }
+ stop := off + MAXSBLINT;
+ if(v < 0){
+ buf[off++] = byte '-';
+ v = -v;
+ }
+ n := stop;
+ while(v > 0){
+ buf[n -= 1] = byte(v % 10 + '0');
+ v = v / 10;
+ }
+ while(n < stop)
+ buf[off++] = buf[n++];
+ return off;
+}
+
+sblsrcconvb(buf: array of byte, off: int, src: Src): int
+{
+ (startf, startl) := fline(src.start >> PosBits);
+ (stopf, stopl) := fline(src.stop >> PosBits);
+ if(lastf != startf.sbl){
+ off = sblint(buf, off, startf.sbl);
+ buf[off++] = byte ':';
+ }
+ if(lastline != startl){
+ off = sblint(buf, off, startl);
+ buf[off++] = byte '.';
+ }
+ off = sblint(buf, off, (src.start & PosMask));
+ buf[off++] = byte ',';
+ if(startf.sbl != stopf.sbl){
+ off = sblint(buf, off, stopf.sbl);
+ buf[off++] = byte ':';
+ }
+ if(startl != stopl){
+ off = sblint(buf, off, stopl);
+ buf[off++] = byte '.';
+ }
+ off = sblint(buf, off, (src.stop & PosMask));
+ buf[off++] = byte ' ';
+ lastf = stopf.sbl;
+ lastline = stopl;
+ return off;
+}
+
+sblsrcconv(src: Src): string
+{
+ s := "";
+ (startf, startl) := fline(src.start >> PosBits);
+ (stopf, stopl) := fline(src.stop >> PosBits);
+ if(lastf != startf.sbl){
+ s += string startf.sbl;
+ s[len s] = ':';
+ }
+ if(lastline != startl){
+ s += string startl;
+ s[len s] = '.';
+ }
+ s += string (src.start & PosMask);
+ s[len s] = ',';
+ if(startf.sbl != stopf.sbl){
+ s += string stopf.sbl;
+ s[len s] = ':';
+ }
+ if(startl != stopl){
+ s += string stopl;
+ s[len s] = '.';
+ }
+ s += string (src.stop & PosMask);
+ s[len s] = ' ';
+ lastf = stopf.sbl;
+ lastline = stopl;
+ return s;
+}
+
+isnilsrc(s: Src): int
+{
+ return s.start == 0 && s.stop == 0;
+}
+
+isnilstopsrc(s: Src): int
+{
+ return s.stop == 0;
+}
+
+sblinst(in: ref Inst, ninst: int)
+{
+ src: Src;
+
+ MAXSBL: con 8*1024;
+ buf := array[MAXSBL] of byte;
+ n := 0;
+ bsym.puts(string ninst);
+ bsym.putb(byte '\n');
+ sblblocks := array[nblocks] of {* => -1};
+ for(; in != nil; in = in.next){
+ if(in.op == INOOP)
+ continue;
+ if(in.src.start < 0)
+ fatal("no file specified for "+instconv(in));
+ if(n >= (MAXSBL - MAXSBLSRC - MAXSBLINT - 1)){
+ bsym.write(buf, n);
+ n = 0;
+ }
+ if(isnilsrc(in.src))
+ in.src = src;
+ else if(isnilstopsrc(in.src)){ # how does this happen ?
+ in.src.stop = in.src.start;
+ in.src.stop++;
+ }
+ n = sblsrcconvb(buf, n, in.src);
+ src = in.src;
+ b := sblblocks[in.block];
+ if(b < 0)
+ sblblocks[in.block] = b = blockid++;
+ n = sblint(buf, n, b);
+ buf[n++] = byte '\n';
+ }
+ if(n > 0)
+ bsym.write(buf, n);
+}
+
+sblty(tys: array of ref Decl, ntys: int)
+{
+ bsym.puts(string ntys);
+ bsym.putb(byte '\n');
+ for(i := 0; i < ntys; i++){
+ d := tys[i];
+ d.ty.sbl = i;
+ }
+ for(i = 0; i < ntys; i++){
+ d := tys[i];
+ sbltype(d.ty, 1);
+ }
+}
+
+sblfn(fns: array of ref Decl, nfns: int)
+{
+ bsym.puts(string nfns);
+ bsym.putb(byte '\n');
+ for(i := 0; i < nfns; i++){
+ f := fns[i];
+ if(ispoly(f))
+ rmfnptrs(f);
+ bsym.puts(string f.pc.pc);
+ bsym.putb(byte ':');
+ if(f.dot != nil && f.dot.ty.kind == Tadt){
+ bsym.puts(f.dot.sym.name);
+ bsym.putb(byte '.');
+ }
+ bsym.puts(f.sym.name);
+ bsym.putb(byte '\n');
+ sbldecl(f.ty.ids, Darg);
+ sbldecl(f.locals, Dlocal);
+ sbltype(f.ty.tof, 0);
+ }
+}
+
+sblvar(vars: ref Decl)
+{
+ sbldecl(vars, Dglobal);
+}
+
+isvis(id: ref Decl): int
+{
+ if(!tattr[id.ty.kind].vis
+ || id.sym == nil
+ || id.sym.name == ""
+ || id.sym.name[0] == '.')
+ return 0;
+ if(id.ty == tstring && id.init != nil && id.init.op == Oconst)
+ return 0;
+ if(id.src.start < 0 || id.src.stop < 0)
+ return 0;
+ return 1;
+}
+
+sbldecl(ids: ref Decl, store: int)
+{
+ n := 0;
+ for(id := ids; id != nil; id = id.next){
+ if(id.store != store || !isvis(id))
+ continue;
+ n++;
+ }
+ bsym.puts(string n);
+ bsym.putb(byte '\n');
+ for(id = ids; id != nil; id = id.next){
+ if(id.store != store || !isvis(id))
+ continue;
+ bsym.puts(string id.offset);
+ bsym.putb(byte ':');
+ bsym.puts(id.sym.name);
+ bsym.putb(byte ':');
+ bsym.puts(sblsrcconv(id.src));
+ sbltype(id.ty, 0);
+ bsym.putb(byte '\n');
+ }
+}
+
+sbltype(t: ref Type, force: int)
+{
+ if(t.kind == Tadtpick)
+ t = t.decl.dot.ty;
+
+ d := t.decl;
+ if(!force && d != nil && d.ty.sbl >= 0){
+ bsym.putb(byte '@');
+ bsym.puts(string d.ty.sbl);
+ bsym.putb(byte '\n');
+ return;
+ }
+
+ if(t.rec != byte 0)
+ fatal("recursive sbl type: "+typeconv(t));
+
+ t.rec = byte 1;
+ case t.kind{
+ * =>
+ fatal("bad type in sbltype: "+typeconv(t));
+ Tnone or
+ Tany or
+ Tint or
+ Tbig or
+ Tbyte or
+ Treal or
+ Tstring or
+ Tfix or
+ Tpoly =>
+ bsym.putb(sbltname[t.kind]);
+ Tfn =>
+ bsym.putb(sbltname[t.kind]);
+ sbldecl(t.ids, Darg);
+ sbltype(t.tof, 0);
+ Tarray or
+ Tlist or
+ Tchan or
+ Tref =>
+ bsym.putb(sbltname[t.kind]);
+ if(t.kind == Tref && t.tof.kind == Tfn){
+ tattr[Tany].vis = 1;
+ sbltype(tfnptr, 0);
+ tattr[Tany].vis = 0;
+ }
+ else
+ sbltype(t.tof, 0);
+ Ttuple or
+ Texception =>
+ bsym.putb(sbltname[t.kind]);
+ bsym.puts(string t.size);
+ bsym.putb(byte '.');
+ sbldecl(t.ids, Dfield);
+ Tadt =>
+ if(t.tags != nil)
+ bsym.putb(sbltadtpick);
+ else
+ bsym.putb(sbltname[t.kind]);
+ if(d.dot != nil && !isimpmod(d.dot.sym))
+ bsym.puts(d.dot.sym.name + "->");
+ bsym.puts(d.sym.name);
+ bsym.putb(byte ' ');
+ bsym.puts(sblsrcconv(d.src));
+ bsym.puts(string d.ty.size);
+ bsym.putb(byte '\n');
+ sbldecl(t.ids, Dfield);
+ if(t.tags != nil){
+ bsym.puts(string t.decl.tag);
+ bsym.putb(byte '\n');
+ lastt : ref Type = nil;
+ for(tg := t.tags; tg != nil; tg = tg.next){
+ bsym.puts(tg.sym.name);
+ bsym.putb(byte ':');
+ bsym.puts(sblsrcconv(tg.src));
+ if(lastt == tg.ty){
+ bsym.putb(byte '\n');
+ }else{
+ bsym.puts(string tg.ty.size);
+ bsym.putb(byte '\n');
+ sbldecl(tg.ty.ids, Dfield);
+ }
+ lastt = tg.ty;
+ }
+ }
+ Tmodule =>
+ bsym.putb(sbltname[t.kind]);
+ bsym.puts(d.sym.name);
+ bsym.putb(byte '\n');
+ bsym.puts(sblsrcconv(d.src));
+ sbldecl(t.ids, Dglobal);
+ }
+ t.rec = byte 0;
+}
diff --git a/appl/cmd/limbo/stubs.b b/appl/cmd/limbo/stubs.b
new file mode 100644
index 00000000..acb24b81
--- /dev/null
+++ b/appl/cmd/limbo/stubs.b
@@ -0,0 +1,575 @@
+#
+# write out some stub C code for limbo modules
+#
+emit(globals: ref Decl)
+{
+ for(m := globals; m != nil; m = m.next){
+ if(m.store != Dtype || m.ty.kind != Tmodule)
+ continue;
+ m.ty = usetype(m.ty);
+ for(d := m.ty.ids; d != nil; d = d.next){
+ d.ty = usetype(d.ty);
+ if(d.store == Dglobal || d.store == Dfn)
+ modrefable(d.ty);
+ if(d.store == Dtype && d.ty.kind == Tadt){
+ for(id := d.ty.ids; id != nil; id = id.next){
+ id.ty = usetype(id.ty);
+ modrefable(d.ty);
+ }
+ }
+ }
+ }
+ if(emitstub){
+ print("#pragma hjdicks x4\n");
+ print("#pragma pack x4\n");
+ adtstub(globals);
+ modstub(globals);
+ print("#pragma pack off\n");
+ print("#pragma hjdicks off\n");
+ }
+ if(emittab != nil)
+ modtab(globals);
+ if(emitcode != nil)
+ modcode(globals);
+ if(emitsbl != nil)
+ modsbl(globals);
+}
+
+modsbl(globals: ref Decl)
+{
+ for(d := globals; d != nil; d = d.next)
+ if(d.store == Dtype && d.ty.kind == Tmodule && d.sym.name == emitsbl)
+ break;
+
+ if(d == nil)
+ return;
+ bsym = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+
+ sblmod(d);
+ sblfiles();
+ n := 0;
+ genstart();
+ for(id := d.ty.tof.ids; id != nil; id = id.next){
+ if(id.sym.name == ".mp")
+ continue;
+ pushblock();
+ id.pc = genrawop(id.src, INOP, nil, nil, nil);
+ id.pc.pc = n++;
+ popblock();
+ }
+ firstinst = firstinst.next;
+ sblinst(firstinst, n);
+# (adts, nadts) := findadts(globals);
+ sblty(adts, nadts);
+ fs := array[n] of ref Decl;
+ n = 0;
+ for(id = d.ty.tof.ids; id != nil; id = id.next){
+ if(id.sym.name == ".mp")
+ continue;
+ fs[n] = id;
+ n++;
+ }
+ sblfn(fs, n);
+ sblvar(nil);
+}
+
+lowercase(f: string): string
+{
+ for(i := 0; i < len f; i++)
+ if(f[i] >= 'A' && f[i] <= 'Z')
+ f[i] += 'a' - 'A';
+ return f;
+}
+
+modcode(globals: ref Decl)
+{
+ buf: string;
+
+ if(emitdyn){
+ buf = lowercase(emitcode);
+ print("#include \"%s.h\"\n", buf);
+ }
+ else{
+ print("#include <lib9.h>\n");
+ print("#include <isa.h>\n");
+ print("#include <interp.h>\n");
+ print("#include \"%smod.h\"\n", emitcode);
+ }
+ print("\n");
+
+ for(d := globals; d != nil; d = d.next)
+ if(d.store == Dtype && d.ty.kind == Tmodule && d.sym.name == emitcode)
+ break;
+
+ if(d == nil)
+ return;
+
+ #
+ # stub types
+ #
+ for(id := d.ty.ids; id != nil; id = id.next){
+ if(id.store == Dtype && id.ty.kind == Tadt){
+ id.ty = usetype(id.ty);
+ print("Type*\tT_%s;\n", id.sym.name);
+ }
+ }
+
+ #
+ # type maps
+ #
+ if(emitdyn){
+ for(id = d.ty.ids; id != nil; id = id.next)
+ if(id.store == Dtype && id.ty.kind == Tadt)
+ print("uchar %s_map[] = %s_%s_map;\n",
+ id.sym.name, emitcode, id.sym.name);
+ }
+
+ #
+ # heap allocation and garbage collection for a type
+ #
+ if(emitdyn){
+ for(id = d.ty.ids; id != nil; id = id.next)
+ if(id.store == Dtype && id.ty.kind == Tadt){
+ print("\n%s_%s*\n%salloc%s(void)\n{\n\tHeap *h;\n\n\th = heap(T_%s);\n\treturn H2D(%s_%s*, h);\n}\n", emitcode, id.sym.name, emitcode, id.sym.name, id.sym.name, emitcode, id.sym.name);
+ print("\nvoid\n%sfree%s(Heap *h, int swept)\n{\n\t%s_%s *d;\n\n\td = H2D(%s_%s*, h);\n\tfreeheap(h, swept);\n}\n", emitcode, id.sym.name, emitcode, id.sym.name, emitcode, id.sym.name);
+ }
+ }
+
+ #
+ # initialization function
+ #
+ if(emitdyn)
+ print("\nvoid\n%sinit(void)\n{\n", emitcode);
+ else{
+ print("\nvoid\n%smodinit(void)\n{\n", emitcode);
+ print("\tbuiltinmod(\"$%s\", %smodtab);\n", emitcode, emitcode);
+ }
+ for(id = d.ty.ids; id != nil; id = id.next)
+ if(id.store == Dtype && id.ty.kind == Tadt){
+ if(emitdyn)
+ print("\tT_%s = dtype(%sfree%s, %s_%s_size, %s_map, sizeof(%s_map));\n",
+ id.sym.name, emitcode, id.sym.name, emitcode, id.sym.name, id.sym.name, id.sym.name);
+ else
+ print("\tT_%s = dtype(freeheap, sizeof(%s), %smap, sizeof(%smap));\n",
+ id.sym.name, id.sym.name, id.sym.name, id.sym.name);
+ }
+ print("}\n");
+
+ #
+ # end function
+ #
+ if(emitdyn){
+ print("\nvoid\n%send(void)\n{\n", emitcode);
+ for(id = d.ty.ids; id != nil; id = id.next)
+ if(id.store == Dtype && id.ty.kind == Tadt)
+ print("\tfreetype(T_%s);\n", id.sym.name);
+ print("}\n");
+ }
+
+ #
+ # stub functions
+ #
+ for(id = d.ty.tof.ids; id != nil; id = id.next){
+ print("\nvoid\n%s_%s(void *fp)\n{\n\tF_%s_%s *f = fp;\n",
+ id.dot.sym.name, id.sym.name,
+ id.dot.sym.name, id.sym.name);
+ if(id.ty.tof != tnone && tattr[id.ty.tof.kind].isptr)
+ print("\n\tdestroy(*f->ret);\n\t*f->ret = H;\n");
+ print("}\n");
+ }
+
+ if(emitdyn)
+ print("\n#include \"%smod.h\"\n", buf);
+}
+
+modtab(globals: ref Decl)
+{
+ print("typedef struct{char *name; long sig; void (*fn)(void*); int size; int np; uchar map[16];} Runtab;\n");
+ for(d := globals; d != nil; d = d.next){
+ if(d.store == Dtype && d.ty.kind == Tmodule && d.sym.name == emittab){
+ n := 0;
+ print("Runtab %smodtab[]={\n", d.sym.name);
+ for(id := d.ty.tof.ids; id != nil; id = id.next){
+ n++;
+ print("\t\"");
+ if(id.dot != d)
+ print("%s.", id.dot.sym.name);
+ print("%s\",0x%ux,%s_%s,", id.sym.name, sign(id),
+ id.dot.sym.name, id.sym.name);
+ if(id.ty.varargs != byte 0)
+ print("0,0,{0},");
+ else{
+ md := mkdesc(idoffsets(id.ty.ids, MaxTemp, MaxAlign), id.ty.ids);
+ print("%d,%d,%s,", md.size, md.nmap, mapconv(md));
+ }
+ print("\n");
+ }
+ print("\t0\n};\n");
+ print("#define %smodlen %d\n", d.sym.name, n);
+ }
+ }
+}
+
+#
+# produce activation records for all the functions in modules
+#
+modstub(globals: ref Decl)
+{
+ for(d := globals; d != nil; d = d.next){
+ if(d.store != Dtype || d.ty.kind != Tmodule)
+ continue;
+ arg := 0;
+ for(id := d.ty.tof.ids; id != nil; id = id.next){
+ s := id.dot.sym.name + "_" + id.sym.name;
+ if(emitdyn && id.dot.dot != nil)
+ s = id.dot.dot.sym.name + "_" + s;
+ print("void %s(void*);\ntypedef struct F_%s F_%s;\nstruct F_%s\n{\n",
+ s, s, s, s);
+ print(" WORD regs[NREG-1];\n");
+ if(id.ty.tof != tnone)
+ print(" %s* ret;\n", ctypeconv(id.ty.tof));
+ else
+ print(" WORD noret;\n");
+ print(" uchar temps[%d];\n", MaxTemp-NREG*IBY2WD);
+ offset := MaxTemp;
+ for(m := id.ty.ids; m != nil; m = m.next){
+ p := "";
+ if(m.sym != nil)
+ p = m.sym.name;
+ else
+ p = "arg"+string arg;
+
+ #
+ # explicit pads for structure alignment
+ #
+ t := m.ty;
+ (offset, nil) = stubalign(offset, t.align, nil);
+ if(offset != m.offset)
+ yyerror("module stub must not contain data objects");
+ # fatal("modstub bad offset");
+ print(" %s %s;\n", ctypeconv(t), p);
+ arg++;
+ offset += t.size;
+#ZZZ need to align?
+ }
+ if(id.ty.varargs != byte 0)
+ print(" WORD vargs;\n");
+ print("};\n");
+ }
+ for(id = d.ty.ids; id != nil; id = id.next)
+ if(id.store == Dconst)
+ constub(id);
+ }
+}
+
+chanstub(in: string, id: ref Decl)
+{
+ print("typedef %s %s_%s;\n", ctypeconv(id.ty.tof), in, id.sym.name);
+ desc := mktdesc(id.ty.tof);
+ print("#define %s_%s_size %d\n", in, id.sym.name, desc.size);
+ print("#define %s_%s_map %s\n", in, id.sym.name, mapconv(desc));
+}
+
+#
+# produce c structs for all adts
+#
+adtstub(globals: ref Decl)
+{
+ t, tt: ref Type;
+ m, d, id: ref Decl;
+
+ for(m = globals; m != nil; m = m.next){
+ if(m.store != Dtype || m.ty.kind != Tmodule)
+ continue;
+ for(d = m.ty.ids; d != nil; d = d.next){
+ if(d.store != Dtype)
+ continue;
+ t = usetype(d.ty);
+ d.ty = t;
+ s := dotprint(d.ty.decl, '_');
+ case d.ty.kind{
+ Tadt =>
+ print("typedef struct %s %s;\n", s, s);
+ Tint or
+ Tbyte or
+ Treal or
+ Tbig or
+ Tfix =>
+ print("typedef %s %s;\n", ctypeconv(t), s);
+ }
+ }
+ }
+ for(m = globals; m != nil; m = m.next){
+ if(m.store != Dtype || m.ty.kind != Tmodule)
+ continue;
+ for(d = m.ty.ids; d != nil; d = d.next){
+ if(d.store != Dtype)
+ continue;
+ t = d.ty;
+ if(t.kind == Tadt || t.kind == Ttuple && t.decl.sym != anontupsym){
+ if(t.tags != nil){
+ pickadtstub(t);
+ continue;
+ }
+ s := dotprint(t.decl, '_');
+ print("struct %s\n{\n", s);
+
+ offset := 0;
+ for(id = t.ids; id != nil; id = id.next){
+ if(id.store == Dfield){
+ tt = id.ty;
+ (offset, nil) = stubalign(offset, tt.align, nil);
+ if(offset != id.offset)
+ fatal("adtstub bad offset");
+ print(" %s %s;\n", ctypeconv(tt), id.sym.name);
+ offset += tt.size;
+ }
+ }
+ if(t.ids == nil){
+ print(" char dummy[1];\n");
+ offset = 1;
+ }
+ (offset, nil)= stubalign(offset, t.align, nil);
+#ZZZ
+(offset, nil) = stubalign(offset, IBY2WD, nil);
+ if(offset != t.size && t.ids != nil)
+ fatal("adtstub: bad size");
+ print("};\n");
+
+ for(id = t.ids; id != nil; id = id.next)
+ if(id.store == Dconst)
+ constub(id);
+
+ for(id = t.ids; id != nil; id = id.next)
+ if(id.ty.kind == Tchan)
+ chanstub(s, id);
+
+ desc := mktdesc(t);
+ if(offset != desc.size && t.ids != nil)
+ fatal("adtstub: bad desc size");
+ print("#define %s_size %d\n", s, offset);
+ print("#define %s_map %s\n", s, mapconv(desc));
+#ZZZ
+if(0)
+ print("struct %s_check {int s[2*(sizeof(%s)==%s_size)-1];};\n", s, s, s);
+ }else if(t.kind == Tchan)
+ chanstub(m.sym.name, d);
+ }
+ }
+}
+
+#
+# emit an expicit pad field for aligning emitted c structs
+# according to limbo's definition
+#
+stubalign(offset: int, a: int, s: string): (int, string)
+{
+ x := offset & (a-1);
+ if(x == 0)
+ return (offset, s);
+ x = a - x;
+ if(s != nil)
+ s += sprint("uchar\t_pad%d[%d]; ", offset, x);
+ else
+ print("\tuchar\t_pad%d[%d];\n", offset, x);
+ offset += x;
+ if((offset & (a-1)) || x >= a)
+ fatal("compiler stub misalign");
+ return (offset, s);
+}
+
+constub(id: ref Decl)
+{
+ s := id.dot.sym.name + "_" + id.sym.name;
+ case id.ty.kind{
+ Tbyte =>
+ print("#define %s %d\n", s, int id.init.c.val & 16rff);
+ Tint or
+ Tfix =>
+ print("#define %s %d\n", s, int id.init.c.val);
+ Tbig =>
+ print("#define %s %bd\n", s, id.init.c.val);
+ Treal =>
+ print("#define %s %g\n", s, id.init.c.rval);
+ Tstring =>
+ print("#define %s \"%s\"\n", s, id.init.decl.sym.name);
+ }
+}
+
+mapconv(d: ref Desc): string
+{
+ s := "{";
+ for(i := 0; i < d.nmap; i++)
+ s += "0x" + hex(int d.map[i], 0) + ",";
+ if(i == 0)
+ s += "0";
+ s += "}";
+ return s;
+}
+
+dotprint(d: ref Decl, dot: int): string
+{
+ s : string;
+ if(d.dot != nil){
+ s = dotprint(d.dot, dot);
+ s[len s] = dot;
+ }
+ if(d.sym == nil)
+ return s;
+ return s + d.sym.name;
+}
+
+ckindname := array[Tend] of
+{
+ Tnone => "void",
+ Tadt => "struct",
+ Tadtpick => "?adtpick?",
+ Tarray => "Array*",
+ Tbig => "LONG",
+ Tbyte => "BYTE",
+ Tchan => "Channel*",
+ Treal => "REAL",
+ Tfn => "?fn?",
+ Tint => "WORD",
+ Tlist => "List*",
+ Tmodule => "Modlink*",
+ Tref => "?ref?",
+ Tstring => "String*",
+ Ttuple => "?tuple?",
+ Texception => "?exception",
+ Tfix => "WORD",
+ Tpoly => "void*",
+
+ Tainit => "?ainit?",
+ Talt => "?alt?",
+ Tany => "void*",
+ Tarrow => "?arrow?",
+ Tcase => "?case?",
+ Tcasel => "?casel?",
+ Tcasec => "?casec?",
+ Tdot => "?dot?",
+ Terror => "?error?",
+ Tgoto => "?goto?",
+ Tid => "?id?",
+ Tiface => "?iface?",
+ Texcept => "?except?",
+ Tinst => "?inst?",
+};
+
+ctypeconv(t: ref Type): string
+{
+ if(t == nil)
+ return "void";
+ s := "";
+ case t.kind{
+ Terror =>
+ return "type error";
+ Tref =>
+ s = ctypeconv(t.tof);
+ s += "*";
+ Tarray or
+ Tlist or
+ Tint or
+ Tbig or
+ Tstring or
+ Treal or
+ Tbyte or
+ Tnone or
+ Tany or
+ Tchan or
+ Tmodule or
+ Tfix or
+ Tpoly =>
+ return ckindname[t.kind];
+ Tadt or
+ Ttuple =>
+ if(t.decl.sym != anontupsym)
+ return dotprint(t.decl, '_');
+ s += "struct{ ";
+ offset := 0;
+ for(id := t.ids; id != nil; id = id.next){
+ tt := id.ty;
+ (offset, s) = stubalign(offset, tt.align, s);
+ if(offset != id.offset)
+ fatal("ctypeconv tuple bad offset");
+ s += ctypeconv(tt);
+ s += " ";
+ s += id.sym.name;
+ s += "; ";
+ offset += tt.size;
+ }
+ (offset, s) = stubalign(offset, t.align, s);
+ if(offset != t.size)
+ fatal(sprint("ctypeconv tuple bad t=%s size=%d offset=%d", typeconv(t), t.size, offset));
+ s += "}";
+ * =>
+ fatal("no C equivalent for type " + string t.kind);
+ }
+ return s;
+}
+
+pickadtstub(t: ref Type)
+{
+ tt: ref Type;
+ desc: ref Desc;
+ id, tg: ref Decl;
+ ok: byte;
+ offset, tgoffset: int;
+
+ buf := dotprint(t.decl, '_');
+ offset = 0;
+ for(tg = t.tags; tg != nil; tg = tg.next)
+ print("#define %s_%s %d\n", buf, tg.sym.name, offset++);
+ print("struct %s\n{\n", buf);
+ print(" int pick;\n");
+ offset = IBY2WD;
+ for(id = t.ids; id != nil; id = id.next){
+ if(id.store == Dfield){
+ tt = id.ty;
+ (offset, nil) = stubalign(offset, tt.align, nil);
+ if(offset != id.offset)
+ fatal("pickadtstub bad offset");
+ print(" %s %s;\n", ctypeconv(tt), id.sym.name);
+ offset += tt.size;
+ }
+ }
+ print(" union{\n");
+ for(tg = t.tags; tg != nil; tg = tg.next){
+ tgoffset = offset;
+ print(" struct{\n");
+ for(id = tg.ty.ids; id != nil; id = id.next){
+ if(id.store == Dfield){
+ tt = id.ty;
+ (tgoffset, nil) = stubalign(tgoffset, tt.align, nil);
+ if(tgoffset != id.offset)
+ fatal("pickadtstub bad offset");
+ print(" %s %s;\n", ctypeconv(tt), id.sym.name);
+ tgoffset += tt.size;
+ }
+ }
+ if(tg.ty.ids == nil)
+ print(" char dummy[1];\n");
+ print(" } %s;\n", tg.sym.name);
+ }
+ print(" } u;\n");
+ print("};\n");
+
+ for(id = t.ids; id != nil; id = id.next)
+ if(id.store == Dconst)
+ constub(id);
+
+ for(id = t.ids; id != nil; id = id.next)
+ if(id.ty.kind == Tchan)
+ chanstub(buf, id);
+
+ for(tg = t.tags; tg != nil; tg = tg.next){
+ ok = tg.ty.tof.ok;
+ tg.ty.tof.ok = OKverify;
+ sizetype(tg.ty.tof);
+ tg.ty.tof.ok = OKmask;
+ desc = mktdesc(tg.ty.tof);
+ tg.ty.tof.ok = ok;
+ print("#define %s_%s_size %d\n", buf, tg.sym.name, tg.ty.size);
+ print("#define %s_%s_map %s\n", buf, tg.sym.name, mapconv(desc));
+ }
+}
diff --git a/appl/cmd/limbo/typecheck.b b/appl/cmd/limbo/typecheck.b
new file mode 100644
index 00000000..fc0d43e4
--- /dev/null
+++ b/appl/cmd/limbo/typecheck.b
@@ -0,0 +1,3223 @@
+fndecls: ref Decl;
+labstack: array of ref Node;
+maxlabdep: int;
+inexcept: ref Node;
+nexc: int;
+fndec: ref Decl;
+
+increfs(id: ref Decl)
+{
+ for( ; id != nil; id = id.link)
+ id.refs++;
+}
+
+fninline(d: ref Decl): int
+{
+ left, right: ref Node;
+
+ n := d.init;
+ if(dontinline || d.inline < byte 0 || d.locals != nil || ispoly(d) || n.ty.tof.kind == Tnone || nodes(n) >= 100)
+ return 0;
+ n = n.right;
+ if(n.op == Oseq && n.right == nil)
+ n = n.left;
+ #
+ # inline
+ # (a) return e;
+ # (b) if(c) return e1; else return e2;
+ # (c) if(c) return e1; return e2;
+ #
+ case(n.op){
+ Oret =>
+ break;
+ Oif =>
+ right = n.right;
+ if(right.right == nil || right.left.op != Oret || right.right.op != Oret || !tequal(right.left.left.ty, right.right.left.ty))
+ return 0;
+ break;
+ Oseq =>
+ left = n.left;
+ right = n.right;
+ if(left.op != Oif || left.right.right != nil || left.right.left.op != Oret || right.op != Oseq || right.right != nil || right.left.op != Oret || !tequal(left.right.left.left.ty, right.left.left.ty))
+ return 0;
+ break;
+ * =>
+ return 0;
+ }
+ if(occurs(d, n) || hasasgns(n))
+ return 0;
+ if(n.op == Oseq){
+ left.right.right = right.left;
+ n = left;
+ right = n.right;
+ d.init.right.right = nil;
+ }
+ if(n.op == Oif){
+ n.ty = right.ty = right.left.left.ty;
+ right.left = right.left.left;
+ right.right = right.right.left;
+ d.init.right.left = mkunary(Oret, n);
+ }
+ return 1;
+}
+
+rewind(n: ref Node)
+{
+ r, nn: ref Node;
+
+ r = n;
+ nn = n.left;
+ for(n = n.right; n != nil; n = n.right){
+ if(n.right == nil){
+ r.left = nn;
+ r.right = n.left;
+ }
+ else
+ nn = mkbin(Oindex, nn, n.left);
+ }
+}
+
+ckmod(n: ref Node, id: ref Decl)
+{
+ t: ref Type;
+ d, idc: ref Decl;
+ mod: ref Node;
+
+ if(id == nil)
+ fatal("can't find function: " + nodeconv(n));
+ idc = nil;
+ mod = nil;
+ if(n.op == Oname){
+ idc = id;
+ mod = id.eimport;
+ }
+ else if(n.op == Omdot)
+ mod = n.left;
+ else if(n.op == Odot){
+ idc = id.dot;
+ t = n.left.ty;
+ if(t.kind == Tref)
+ t = t.tof;
+ if(t.kind == Tadtpick)
+ t = t.decl.dot.ty;
+ d = t.decl;
+ while(d != nil && d.link != nil)
+ d = d.link;
+ if(d != nil && d.timport != nil)
+ mod = d.timport.eimport;
+ n.right.left = mod;
+ }
+ if(mod != nil && mod.ty.kind != Tmodule){
+ nerror(n, "cannot use " + expconv(n) + " as a function reference");
+ return;
+ }
+ if(mod != nil){
+ if(valistype(mod)){
+ nerror(n, "cannot use " + expconv(n) + " as a function reference because " + expconv(mod) + " is a module interface");
+ return;
+ }
+ }else if(idc != nil && idc.dot != nil && !isimpmod(idc.dot.sym)){
+ nerror(n, "cannot use " + expconv(n) + " without importing " + idc.sym.name + " from a variable");
+ return;
+ }
+ if(mod != nil)
+ modrefable(n.ty);
+}
+
+addref(n: ref Node)
+{
+ nn: ref Node;
+
+ nn = mkn(0, nil, nil);
+ *nn = *n;
+ n.op = Oref;
+ n.left = nn;
+ n.right = nil;
+ n.decl = nil;
+ n.ty = usetype(mktype(n.src.start, n.src.stop, Tref, nn.ty, nil));
+}
+
+fnref(n: ref Node, id: ref Decl)
+{
+ id.inline = byte -1;
+ ckmod(n, id);
+ addref(n);
+ while(id.link != nil)
+ id = id.link;
+ if(ispoly(id) && encpolys(id) != nil)
+ nerror(n, "cannot have a polymorphic adt function reference " + id.sym.name);
+}
+
+typecheck(checkimp: int): ref Decl
+{
+ entry, d, m: ref Decl;
+
+ if(errors)
+ return nil;
+
+ #
+ # generate the set of all functions
+ # compile one function at a time
+ #
+ gdecl(tree);
+ gbind(tree);
+ fns = array[nfns] of ref Decl;
+ i := gcheck(tree, fns, 0);
+ if(i != nfns)
+ fatal("wrong number of functions found in gcheck");
+
+ maxlabdep = 0;
+ for(i = 0; i < nfns; i++){
+ d = fns[i];
+ if(d != nil)
+ fndec = d;
+ if(d != nil)
+ fncheck(d);
+ fndec = nil;
+ }
+
+ if(errors)
+ return nil;
+
+ entry = nil;
+ if(checkimp){
+ im: ref Decl;
+ dm: ref Dlist;
+
+ if(impmods == nil){
+ yyerror("no implementation module");
+ return nil;
+ }
+ for(im = impmods; im != nil; im = im.next){
+ for(dm = impdecls; dm != nil; dm = dm.next)
+ if(dm.d.sym == im.sym)
+ break;
+ if(dm == nil || dm.d.ty == nil){
+ yyerror("no definition for implementation module "+im.sym.name);
+ return nil;
+ }
+ }
+
+ #
+ # can't check the module spec until all types and imports are determined,
+ # which happens in scheck
+ #
+ for(dm = impdecls; dm != nil; dm = dm.next){
+ im = dm.d;
+ im.refs++;
+ im.ty = usetype(im.ty);
+ if(im.store != Dtype || im.ty.kind != Tmodule){
+ error(im.src.start, "cannot implement "+declconv(im));
+ return nil;
+ }
+ }
+
+ # now check any multiple implementations
+ impdecl = modimp(impdecls, impmods);
+
+ s := enter("init", 0);
+ for(dm = impdecls; dm != nil; dm = dm.next){
+ im = dm.d;
+ for(m = im.ty.ids; m != nil; m = m.next){
+ m.ty = usetype(m.ty);
+ m.refs++;
+
+ if(m.sym == s && m.ty.kind == Tfn && entry == nil)
+ entry = m;
+
+ if(m.store == Dglobal || m.store == Dfn)
+ modrefable(m.ty);
+
+ if(m.store == Dtype && m.ty.kind == Tadt){
+ for(d = m.ty.ids; d != nil; d = d.next){
+ d.ty = usetype(d.ty);
+ modrefable(d.ty);
+ d.refs++;
+ }
+ }
+ }
+ checkrefs(im.ty.ids);
+ }
+ }
+ if(errors)
+ return nil;
+ gsort(tree);
+ tree = nil;
+ return entry;
+}
+#
+# introduce all global declarations
+# also adds all fields to adts and modules
+# note the complications due to nested Odas expressions
+#
+gdecl(n: ref Node)
+{
+ for(;;){
+ if(n == nil)
+ return;
+ if(n.op != Oseq)
+ break;
+ gdecl(n.left);
+ n = n.right;
+ }
+ case n.op{
+ Oimport =>
+ importdecled(n);
+ gdasdecl(n.right);
+ Oadtdecl =>
+ adtdecled(n);
+ Ocondecl =>
+ condecled(n);
+ gdasdecl(n.right);
+ Oexdecl =>
+ exdecled(n);
+ Omoddecl =>
+ moddecled(n);
+ Otypedecl =>
+ typedecled(n);
+ Ovardecl =>
+ vardecled(n);
+ Ovardecli =>
+ vardecled(n.left);
+ gdasdecl(n.right);
+ Ofunc =>
+ fndecled(n);
+ Oas or
+ Odas or
+ Onothing =>
+ gdasdecl(n);
+ * =>
+ fatal("can't deal with "+opconv(n.op)+" in gdecl");
+ }
+}
+
+#
+# bind all global type ids,
+# including those nested inside modules
+# this needs to be done, since we may use such
+# a type later in a nested scope, so if we bound
+# the type ids then, the type could get bound
+# to a nested declaration
+#
+gbind(n: ref Node)
+{
+ ids: ref Decl;
+
+ for(;;){
+ if(n == nil)
+ return;
+ if(n.op != Oseq)
+ break;
+ gbind(n.left);
+ n = n.right;
+ }
+ case n.op{
+ Oas or
+ Ocondecl or
+ Odas or
+ Oexdecl or
+ Ofunc or
+ Oimport or
+ Onothing or
+ Ovardecl or
+ Ovardecli =>
+ break;
+ Ofielddecl =>
+ bindtypes(n.decl.ty);
+ Otypedecl =>
+ bindtypes(n.decl.ty);
+ if(n.left != nil)
+ gbind(n.left);
+ Opickdecl =>
+ gbind(n.left);
+ d := n.right.left.decl;
+ bindtypes(d.ty);
+ repushids(d.ty.ids);
+ gbind(n.right.right);
+ # get new ids for undefined types; propagate outwards
+ ids = popids(d.ty.ids);
+ if(ids != nil)
+ installids(Dundef, ids);
+ Oadtdecl or
+ Omoddecl =>
+ bindtypes(n.ty);
+ if(n.ty.polys != nil)
+ repushids(n.ty.polys);
+ repushids(n.ty.ids);
+ gbind(n.left);
+ # get new ids for undefined types; propagate outwards
+ ids = popids(n.ty.ids);
+ if(ids != nil)
+ installids(Dundef, ids);
+ if(n.ty.polys != nil)
+ popids(n.ty.polys);
+ * =>
+ fatal("can't deal with "+opconv(n.op)+" in gbind");
+ }
+}
+
+#
+# check all of the global declarations
+# bind all type ids referred to within types at the global level
+# record decls for defined functions
+#
+gcheck(n: ref Node, fns: array of ref Decl, nfns: int): int
+{
+ ok, allok: int;
+
+ for(;;){
+ if(n == nil)
+ return nfns;
+ if(n.op != Oseq)
+ break;
+ nfns = gcheck(n.left, fns, nfns);
+ n = n.right;
+ }
+
+ case n.op{
+ Ofielddecl =>
+ if(n.decl.ty.eraises != nil)
+ raisescheck(n.decl.ty);
+ Onothing or
+ Opickdecl =>
+ break;
+ Otypedecl =>
+ tcycle(n.ty);
+ Oadtdecl or
+ Omoddecl =>
+ if(n.ty.polys != nil)
+ repushids(n.ty.polys);
+ repushids(n.ty.ids);
+ if(gcheck(n.left, nil, 0))
+ fatal("gcheck fn decls nested in modules or adts");
+ if(popids(n.ty.ids) != nil)
+ fatal("gcheck installs new ids in a module or adt");
+ if(n.ty.polys != nil)
+ popids(n.ty.polys);
+ Ovardecl =>
+ varcheck(n, 1);
+ Ocondecl =>
+ concheck(n, 1);
+ Oexdecl =>
+ excheck(n, 1);
+ Oimport =>
+ importcheck(n, 1);
+ Ovardecli =>
+ varcheck(n.left, 1);
+ (ok, allok) = echeck(n.right, 0, 1, nil);
+ if(ok){
+ if(allok)
+ n.right = fold(n.right);
+ globalas(n.right.left, n.right.right, allok);
+ }
+ Oas or
+ Odas =>
+ (ok, allok) = echeck(n, 0, 1, nil);
+ if(ok){
+ if(allok)
+ n = fold(n);
+ globalas(n.left, n.right, allok);
+ }
+ Ofunc =>
+ (ok, allok) = echeck(n.left, 0, 1, n);
+ if(ok && n.ty.eraises != nil)
+ raisescheck(n.ty);
+ d : ref Decl = nil;
+ if(ok)
+ d = fnchk(n);
+ fns[nfns++] = d;
+ * =>
+ fatal("can't deal with "+opconv(n.op)+" in gcheck");
+ }
+ return nfns;
+}
+
+#
+# check for unused expression results
+# make sure the any calculated expression has
+# a destination
+#
+checkused(n: ref Node): ref Node
+{
+ #
+ # only nil; and nil = nil; should have type tany
+ #
+ if(n.ty == tany){
+ if(n.op == Oname)
+ return n;
+ if(n.op == Oas)
+ return checkused(n.right);
+ fatal("line "+lineconv(n.src.start)+" checkused "+nodeconv(n));
+ }
+
+ if(n.op == Ocall && n.left.ty.kind == Tfn && n.left.ty.tof != tnone){
+ n = mkunary(Oused, n);
+ n.ty = n.left.ty;
+ return n;
+ }
+ if(n.op == Ocall && isfnrefty(n.left.ty)){
+ if(n.left.ty.tof.tof != tnone){
+ n = mkunary(Oused, n);
+ n.ty = n.left.ty;
+ }
+ return n;
+ }
+ if(isused[n.op] && (n.op != Ocall || n.left.ty.kind == Tfn))
+ return n;
+ t := n.ty;
+ if(t.kind == Tfn)
+ nerror(n, "function "+expconv(n)+" not called");
+ else if(t.kind == Tadt && t.tags != nil || t.kind == Tadtpick)
+ nerror(n, "expressions cannot have type "+typeconv(t));
+ else if(n.op == Otuple){
+ for(nn := n.left; nn != nil; nn = nn.right)
+ checkused(nn.left);
+ }
+ else
+ nwarn(n, "result of expression "+expconv(n)+" not used");
+ n = mkunary(Oused, n);
+ n.ty = n.left.ty;
+ return n;
+}
+
+fncheck(d: ref Decl)
+{
+ n := d.init;
+ if(debug['t'])
+ print("typecheck tree: %s\n", nodeconv(n));
+
+ fndecls = nil;
+ adtp := outerpolys(n.left);
+ if(n.left.op == Odot)
+ repushids(adtp);
+ if(d.ty.polys != nil)
+ repushids(d.ty.polys);
+ repushids(d.ty.ids);
+
+ labdep = 0;
+ labstack = array[maxlabdep] of ref Node;
+ n.right = scheck(n.right, d.ty.tof, Sother);
+ if(labdep != 0)
+ fatal("unbalanced label stack in fncheck");
+ labstack = nil;
+
+ d.locals = appdecls(popids(d.ty.ids), fndecls);
+ if(d.ty.polys != nil)
+ popids(d.ty.polys);
+ if(n.left.op == Odot)
+ popids(adtp);
+ fndecls = nil;
+
+ checkrefs(d.ty.ids);
+ checkrefs(d.ty.polys);
+ checkrefs(d.locals);
+
+ checkraises(n);
+
+ d.inline = byte fninline(d);
+}
+
+scheck(n: ref Node, ret: ref Type, kind : int): ref Node
+{
+ s: ref Sym;
+ rok: int;
+
+ top := n;
+ last: ref Node = nil;
+ for(; n != nil; n = n.right){
+ left := n.left;
+ right := n.right;
+ case n.op{
+ Ovardecl =>
+ vardecled(n);
+ varcheck(n, 0);
+ if (nested() && tmustzero(n.decl.ty))
+ decltozero(n);
+# else if (inloop() && tmustzero(n.decl.ty))
+# decltozero(n);
+ return top;
+ Ovardecli =>
+ vardecled(left);
+ varcheck(left, 0);
+ echeck(right, 0, 0, nil);
+ if (nested() && tmustzero(left.decl.ty))
+ decltozero(left);
+ return top;
+ Otypedecl =>
+ typedecled(n);
+ bindtypes(n.ty);
+ tcycle(n.ty);
+ return top;
+ Ocondecl =>
+ condecled(n);
+ concheck(n, 0);
+ return top;
+ Oexdecl =>
+ exdecled(n);
+ excheck(n, 0);
+ return top;
+ Oimport =>
+ importdecled(n);
+ importcheck(n, 0);
+ return top;
+ Ofunc =>
+ fatal("scheck func");
+ Oscope =>
+ if (kind == Sother)
+ kind = Sscope;
+ pushscope(n, kind);
+ if (left != nil)
+ fatal("Oscope has left field");
+ echeck(left, 0, 0, nil);
+ n.right = scheck(right, ret, Sother);
+ d := popscope();
+ fndecls = appdecls(fndecls, d);
+ return top;
+ Olabel =>
+ echeck(left, 0, 0, nil);
+ n.right = scheck(right, ret, Sother);
+ return top;
+ Oseq =>
+ n.left = scheck(left, ret, Sother);
+ # next time will check n.right
+ Oif =>
+ (rok, nil) = echeck(left, 0, 0, nil);
+ if(rok && left.op != Onothing && left.ty != tint)
+ nerror(n, "if conditional must be an int, not "+etconv(left));
+ right.left = scheck(right.left, ret, Sother);
+ # next time will check n.right.right
+ n = right;
+ Ofor =>
+ (rok, nil) = echeck(left, 0, 0, nil);
+ if(rok && left.op != Onothing && left.ty != tint)
+ nerror(n, "for conditional must be an int, not "+etconv(left));
+ #
+ # do the continue clause before the body
+ # this reflects the ordering of declarations
+ #
+ pushlabel(n);
+ right.right = scheck(right.right, ret, Sother);
+ right.left = scheck(right.left, ret, Sloop);
+ labdep--;
+ if(n.decl != nil && !n.decl.refs)
+ nwarn(n, "label "+n.decl.sym.name+" never referenced");
+ return top;
+ Odo =>
+ (rok, nil) = echeck(left, 0, 0, nil);
+ if(rok && left.op != Onothing && left.ty != tint)
+ nerror(n, "do conditional must be an int, not "+etconv(left));
+ pushlabel(n);
+ n.right = scheck(n.right, ret, Sloop);
+ labdep--;
+ if(n.decl != nil && !n.decl.refs)
+ nwarn(n, "label "+n.decl.sym.name+" never referenced");
+ return top;
+ Oalt or
+ Ocase or
+ Opick or
+ Oexcept =>
+ pushlabel(n);
+ case n.op{
+ Oalt =>
+ altcheck(n, ret);
+ Ocase =>
+ casecheck(n, ret);
+ Opick =>
+ pickcheck(n, ret);
+ Oexcept =>
+ exccheck(n, ret);
+ }
+ labdep--;
+ if(n.decl != nil && !n.decl.refs)
+ nwarn(n, "label "+n.decl.sym.name+" never referenced");
+ return top;
+ Oret =>
+ (rok, nil) = echeck(left, 0, 0, nil);
+ if(!rok)
+ return top;
+ if(left == nil){
+ if(ret != tnone)
+ nerror(n, "return of nothing from a fn of "+typeconv(ret));
+ }else if(ret == tnone){
+ if(left.ty != tnone)
+ nerror(n, "return "+etconv(left)+" from a fn with no return type");
+ }else if(!tcompat(ret, left.ty, 0))
+ nerror(n, "return "+etconv(left)+" from a fn of "+typeconv(ret));
+ return top;
+ Obreak or
+ Ocont =>
+ s = nil;
+ if(n.decl != nil)
+ s = n.decl.sym;
+ for(i := 0; i < labdep; i++){
+ if(s == nil || labstack[i].decl != nil && labstack[i].decl.sym == s){
+ if(n.op == Ocont
+ && labstack[i].op != Ofor && labstack[i].op != Odo)
+ continue;
+ if(s != nil)
+ labstack[i].decl.refs++;
+ return top;
+ }
+ }
+ nerror(n, "no appropriate target for "+expconv(n));
+ return top;
+ Oexit or
+ Onothing =>
+ return top;
+ Oexstmt =>
+ fndec.handler = byte 1;
+ n.left = scheck(left, ret, Sother);
+ n.right = scheck(right, ret, Sother);
+ return top;
+ * =>
+ (nil, rok) = echeck(n, 0, 0, nil);
+ if(rok)
+ n = checkused(n);
+ if(last == nil)
+ return n;
+ last.right = n;
+ return top;
+ }
+ last = n;
+ }
+ return top;
+}
+
+pushlabel(n: ref Node)
+{
+ s: ref Sym;
+
+ if(labdep >= maxlabdep){
+ maxlabdep += MaxScope;
+ labs := array[maxlabdep] of ref Node;
+ labs[:] = labstack;
+ labstack = labs;
+ }
+ if(n.decl != nil){
+ s = n.decl.sym;
+ n.decl.refs = 0;
+ for(i := 0; i < labdep; i++)
+ if(labstack[i].decl != nil && labstack[i].decl.sym == s)
+ nerror(n, "label " + s.name + " duplicated on line " + lineconv(labstack[i].decl.src.start));
+ }
+ labstack[labdep++] = n;
+}
+
+varcheck(n: ref Node, isglobal: int)
+{
+ t := validtype(n.ty, nil);
+ t = topvartype(t, n.decl, isglobal, 0);
+ last := n.left.decl;
+ for(ids := n.decl; ids != last.next; ids = ids.next){
+ ids.ty = t;
+ shareloc(ids);
+ }
+ if(t.eraises != nil)
+ raisescheck(t);
+}
+
+concheck(n: ref Node, isglobal: int)
+{
+ t: ref Type;
+ init: ref Node;
+
+ pushscope(nil, Sother);
+ installids(Dconst, iota);
+ (ok, allok) := echeck(n.right, 0, isglobal, nil);
+ popscope();
+
+ init = n.right;
+ if(!ok){
+ t = terror;
+ }else{
+ t = init.ty;
+ if(!tattr[t.kind].conable){
+ nerror(init, "cannot have a "+typeconv(t)+" constant");
+ allok = 0;
+ }
+ }
+
+ last := n.left.decl;
+ for(ids := n.decl; ids != last.next; ids = ids.next)
+ ids.ty = t;
+
+ if(!allok)
+ return;
+
+ i := 0;
+ for(ids = n.decl; ids != last.next; ids = ids.next){
+ if(ok){
+ iota.init.c.val = big i;
+ ids.init = dupn(0, nosrc, init);
+ if(!varcom(ids))
+ ok = 0;
+ }
+ i++;
+ }
+}
+
+exname(d: ref Decl): string
+{
+ s := "";
+ m := impmods.sym;
+ if(d.dot != nil)
+ m = d.dot.sym;
+ if(m != nil)
+ s += m.name+".";
+ if(fndec != nil)
+ s += fndec.sym.name+".";
+ s += string (scope-ScopeGlobal)+"."+d.sym.name;
+ return s;
+}
+
+excheck(n: ref Node, isglobal: int)
+{
+ t: ref Type;
+ ids, last: ref Decl;
+
+ t = validtype(n.ty, nil);
+ t = topvartype(t, n.decl, isglobal, 0);
+ last = n.left.decl;
+ for(ids = n.decl; ids != last.next; ids = ids.next){
+ ids.ty = t;
+ ids.init = mksconst(n.src, enterstring(exname(ids)));
+ # ids.init = mksconst(n.src, enterstring(ids.sym.name));
+ }
+}
+
+importcheck(n: ref Node, isglobal: int)
+{
+ (ok, nil) := echeck(n.right, 1, isglobal, nil);
+ if(!ok)
+ return;
+
+ m := n.right;
+ if(m.ty.kind != Tmodule || m.op != Oname){
+ nerror(n, "cannot import from "+etconv(m));
+ return;
+ }
+
+ last := n.left.decl;
+ for(id := n.decl; id != last.next; id = id.next){
+ v := namedot(m.ty.ids, id.sym);
+ if(v == nil){
+ error(id.src.start, id.sym.name+" is not a member of "+expconv(m));
+ id.store = Dwundef;
+ continue;
+ }
+ id.store = v.store;
+ v.ty = validtype(v.ty, nil);
+ id.ty = t := v.ty;
+ if(id.store == Dtype && t.decl != nil){
+ id.timport = t.decl.timport;
+ t.decl.timport = id;
+ }
+ id.init = v.init;
+ id.importid = v;
+ id.eimport = m;
+ }
+}
+
+rewcall(n: ref Node, d: ref Decl): ref Decl
+{
+ # put original function back now we're type checked
+ while(d.link != nil)
+ d = d.link;
+ if(n.op == Odot)
+ n.right.decl = d;
+ else if(n.op == Omdot){
+ n.right.right.decl = d;
+ n.right.right.ty = d.ty;
+ }
+ else
+ fatal("bad op in Ocall rewcall");
+ n.ty = n.right.ty = d.ty;
+ d.refs++;
+ usetype(d.ty);
+ return d;
+}
+
+isfnrefty(t: ref Type): int
+{
+ return t.kind == Tref && t.tof.kind == Tfn;
+}
+
+isfnref(d: ref Decl): int
+{
+ case(d.store){
+ Dglobal or
+ Darg or
+ Dlocal or
+ Dfield or
+ Dimport =>
+ return isfnrefty(d.ty);
+ }
+ return 0;
+}
+
+tagopt: int;
+
+#
+# annotate the expression with types
+#
+echeck(n: ref Node, typeok, isglobal: int, par: ref Node): (int, int)
+{
+ tg, id, callee: ref Decl;
+ t, tt: ref Type;
+ ok, allok, max, nocheck, kidsok: int;
+
+ ok = allok = 1;
+ if(n == nil)
+ return (1, 1);
+
+ if(n.op == Oseq){
+ for( ; n != nil && n.op == Oseq; n = n.right){
+ (okl, allokl) := echeck(n.left, typeok == 2, isglobal, n);
+ ok &= okl;
+ allok &= allokl;
+ n.ty = tnone;
+ }
+ if(n == nil)
+ return (ok, allok);
+ }
+
+ left := n.left;
+ right := n.right;
+
+ nocheck = 0;
+ if(n.op == Odot || n.op == Omdot || n.op == Ocall || n.op == Oref || n.op == Otagof || n.op == Oindex)
+ nocheck = 1;
+ if(n.op != Odas # special case
+ && n.op != Oload) # can have better error recovery
+ (ok, allok) = echeck(left, nocheck, isglobal, n);
+ if(n.op != Odas # special case
+ && n.op != Odot # special check
+ && n.op != Omdot # special check
+ && n.op != Ocall # can have better error recovery
+ && n.op != Oindex){
+ (okr, allokr) := echeck(right, 0, isglobal, n);
+ ok &= okr;
+ allok &= allokr;
+ }
+ if(!ok){
+ n.ty = terror;
+ return (0, 0);
+ }
+
+ case n.op{
+ Odas =>
+ (ok, allok) = echeck(right, 0, isglobal, n);
+ if(!ok)
+ right.ty = terror;
+ if(!isglobal && !dasdecl(left)){
+ ok = 0;
+ }else if(!specific(right.ty) || !declasinfer(left, right.ty)){
+ nerror(n, "cannot declare "+expconv(left)+" from "+etconv(right));
+ declaserr(left);
+ ok = 0;
+ }
+ if(right.ty.kind == Texception)
+ left.ty = n.ty = mkextuptype(right.ty);
+ else{
+ left.ty = n.ty = right.ty;
+ usedty(n.ty);
+ }
+ if (nested() && tmustzero(left.ty))
+ decltozero(left);
+ return (ok, allok & ok);
+ Oseq or
+ Onothing =>
+ n.ty = tnone;
+ Owild =>
+ n.ty = tint;
+ Ocast =>
+ t = usetype(n.ty);
+ n.ty = t;
+ tt = left.ty;
+ if(tcompat(t, tt, 0)){
+ left.ty = t;
+ break;
+ }
+ if(tt.kind == Tarray){
+ if(tt.tof == tbyte && t == tstring)
+ break;
+ }else if(t.kind == Tarray){
+ if(t.tof == tbyte && tt == tstring)
+ break;
+ }else if(casttab[tt.kind][t.kind]){
+ break;
+ }
+ nerror(n, "cannot make a "+typeconv(n.ty)+" from "+etconv(left));
+ return (0, 0);
+ Ochan =>
+ n.ty = usetype(n.ty);
+ if(left != nil && left.ty.kind != Tint){
+ nerror(n, "channel size "+etconv(left)+" is not an int");
+ return (0, 0);
+ }
+ Oload =>
+ n.ty = usetype(n.ty);
+ (nil, kidsok) = echeck(left, 0, isglobal, n);
+ if(n.ty.kind != Tmodule){
+ nerror(n, "cannot load a "+typeconv(n.ty));
+ return (0, 0);
+ }
+ if(!kidsok){
+ allok = 0;
+ break;
+ }
+ if(left.ty != tstring){
+ nerror(n, "cannot load a module from "+etconv(left));
+ allok = 0;
+ break;
+ }
+if(n.ty.tof.decl.refs != 0)
+n.ty.tof.decl.refs++;
+n.ty.decl.refs++;
+ usetype(n.ty.tof);
+ Oref =>
+ t = left.ty;
+ if(t.kind != Tadt && t.kind != Tadtpick && t.kind != Tfn && t.kind != Ttuple){
+ nerror(n, "cannot make a ref from "+etconv(left));
+ return (0, 0);
+ }
+ if(!tagopt && t.kind == Tadt && t.tags != nil && valistype(left)){
+ nerror(n, "instances of ref "+expconv(left)+" must be qualified with a pick tag");
+ return (0, 0);
+ }
+ if(t.kind == Tadtpick)
+ t.tof = usetype(t.tof);
+ n.ty = usetype(mktype(n.src.start, n.src.stop, Tref, t, nil));
+ Oarray =>
+ max = 0;
+ if(right != nil){
+ max = assignindices(n);
+ if(max < 0)
+ return (0, 0);
+ if(!specific(right.left.ty)){
+ nerror(n, "type for array not specific");
+ return (0, 0);
+ }
+ n.ty = mktype(n.src.start, n.src.stop, Tarray, right.left.ty, nil);
+ }
+ n.ty = usetype(n.ty);
+
+ if(left.op == Onothing)
+ n.left = left = mkconst(n.left.src, big max);
+
+ if(left.ty.kind != Tint){
+ nerror(n, "array size "+etconv(left)+" is not an int");
+ return (0, 0);
+ }
+ Oelem =>
+ n.ty = right.ty;
+ Orange =>
+ if(left.ty != right.ty
+ || left.ty != tint && left.ty != tstring){
+ nerror(left, "range "+etconv(left)+" to "+etconv(right)+" is not an int or string range");
+ return (0, 0);
+ }
+ n.ty = left.ty;
+ Oname =>
+ id = n.decl;
+ if(id == nil){
+ nerror(n, "name with no declaration");
+ return (0, 0);
+ }
+ if(id.store == Dunbound){
+ s := id.sym;
+ id = s.decl;
+ if(id == nil)
+ id = undefed(n.src, s);
+ # save a little space
+ s.unbound = nil;
+ n.decl = id;
+ id.refs++;
+ }
+ n.ty = id.ty = usetype(id.ty);
+ case id.store{
+ Dfn or
+ Dglobal or
+ Darg or
+ Dlocal or
+ Dimport or
+ Dfield or
+ Dtag =>
+ break;
+ Dunbound =>
+ fatal("unbound symbol found in echeck");
+ Dundef =>
+ nerror(n, id.sym.name+" is not declared");
+ id.store = Dwundef;
+ return (0, 0);
+ Dwundef =>
+ return (0, 0);
+ Dconst =>
+ if(id.init == nil){
+ nerror(n, id.sym.name+"'s value cannot be determined");
+ id.store = Dwundef;
+ return (0, 0);
+ }
+ Dtype =>
+ if(typeok)
+ break;
+ nerror(n, declconv(id)+" is not a variable");
+ return (0, 0);
+ * =>
+ fatal("echeck: unknown symbol storage");
+ }
+
+ if(n.ty == nil){
+ nerror(n, declconv(id)+"'s type is not fully defined");
+ id.store = Dwundef;
+ return (0, 0);
+ }
+ if(id.importid != nil && valistype(id.eimport)
+ && id.store != Dconst && id.store != Dtype && id.store != Dfn){
+ nerror(n, "cannot use "+expconv(n)+" because "+expconv(id.eimport)+" is a module interface");
+ return (0, 0);
+ }
+ if(n.ty.kind == Texception && !int n.ty.cons && par != nil && par.op != Oraise && par.op != Odot){
+ nn := mkn(0, nil, nil);
+ *nn = *n;
+ n.op = Ocast;
+ n.left = nn;
+ n.decl = nil;
+ n.ty = usetype(mkextuptype(n.ty));
+ }
+ # function name as function reference
+ if(id.store == Dfn && (par == nil || (par.op != Odot && par.op != Omdot && par.op != Ocall && par.op != Ofunc)))
+ fnref(n, id);
+ Oconst =>
+ if(n.ty == nil){
+ nerror(n, "no type in "+expconv(n));
+ return (0, 0);
+ }
+ Oas =>
+ t = right.ty;
+ if(t.kind == Texception)
+ t = mkextuptype(t);
+ if(!tcompat(left.ty, t, 1)){
+ nerror(n, "type clash in "+etconv(left)+" = "+etconv(right));
+ return (0, 0);
+ }
+ if(t == tany)
+ t = left.ty;
+ n.ty = t;
+ left.ty = t;
+ if(t.kind == Tadt && t.tags != nil || t.kind == Tadtpick)
+ if(left.ty.kind != Tadtpick || right.ty.kind != Tadtpick)
+ nerror(n, "expressions cannot have type "+typeconv(t));
+ if(left.ty.kind == Texception){
+ nerror(n, "cannot assign to an exception");
+ return (0, 0);
+ }
+ if(islval(left))
+ break;
+ return (0, 0);
+ Osnd =>
+ if(left.ty.kind != Tchan){
+ nerror(n, "cannot send on "+etconv(left));
+ return (0, 0);
+ }
+ if(!tcompat(left.ty.tof, right.ty, 0)){
+ nerror(n, "type clash in "+etconv(left)+" <-= "+etconv(right));
+ return (0, 0);
+ }
+ t = right.ty;
+ if(t == tany)
+ t = left.ty.tof;
+ n.ty = t;
+ Orcv =>
+ t = left.ty;
+ if(t.kind == Tarray)
+ t = t.tof;
+ if(t.kind != Tchan){
+ nerror(n, "cannot receive on "+etconv(left));
+ return (0, 0);
+ }
+ if(left.ty.kind == Tarray)
+ n.ty = usetype(mktype(n.src.start, n.src.stop, Ttuple, nil,
+ mkids(n.src, nil, tint, mkids(n.src, nil, t.tof, nil))));
+ else
+ n.ty = t.tof;
+ Ocons =>
+ if(right.ty.kind != Tlist && right.ty != tany){
+ nerror(n, "cannot :: to "+etconv(right));
+ return (0, 0);
+ }
+ n.ty = right.ty;
+ if(right.ty == tany)
+ n.ty = usetype(mktype(n.src.start, n.src.stop, Tlist, left.ty, nil));
+ else if(!tcompat(right.ty.tof, left.ty, 0)){
+ t = tparent(right.ty.tof, left.ty);
+ if(!tcompat(t, left.ty, 0)){
+ nerror(n, "type clash in "+etconv(left)+" :: "+etconv(right));
+ return (0, 0);
+ }
+ else
+ n.ty = usetype(mktype(n.src.start, n.src.stop, Tlist, t, nil));
+ }
+ Ohd or
+ Otl =>
+ if(left.ty.kind != Tlist || left.ty.tof == nil){
+ nerror(n, "cannot "+opconv(n.op)+" "+etconv(left));
+ return (0, 0);
+ }
+ if(n.op == Ohd)
+ n.ty = left.ty.tof;
+ else
+ n.ty = left.ty;
+ Otuple =>
+ n.ty = usetype(mktype(n.src.start, n.src.stop, Ttuple, nil, tuplefields(left)));
+ Ospawn =>
+ if(left.op != Ocall || left.left.ty.kind != Tfn && !isfnrefty(left.left.ty)){
+ nerror(left, "cannot spawn "+expconv(left));
+ return (0, 0);
+ }
+ if(left.ty != tnone){
+ nerror(left, "cannot spawn functions which return values, such as "+etconv(left));
+ return (0, 0);
+ }
+ Oraise =>
+ if(left.op == Onothing){
+ if(inexcept == nil){
+ nerror(n, expconv(n)+": empty raise not in exception handler");
+ return (0, 0);
+ }
+ n.left = dupn(1, n.src, inexcept);
+ break;
+ }
+ if(left.ty != tstring && left.ty.kind != Texception){
+ nerror(n, expconv(n)+": raise argument "+etconv(left)+" is not a string or exception");
+ return (0, 0);
+ }
+ if((left.op != Ocall || left.left.ty.kind == Tfn) && left.ty.ids != nil && int left.ty.cons){
+ nerror(n, "too few exception arguments");
+ return (0, 0);
+ }
+ Ocall =>
+ (nil, kidsok) = echeck(right, 0, isglobal, nil);
+ t = left.ty;
+ usedty(t);
+ pure := 1;
+ if(t.kind == Tref){
+ pure = 0;
+ t = t.tof;
+ }
+ if(t.kind != Tfn)
+ return callcast(n, kidsok, allok);
+ n.ty = t.tof;
+ if(!kidsok){
+ allok = 0;
+ break;
+ }
+
+ #
+ # get the name to call and any associated module
+ #
+ mod: ref Node = nil;
+ callee = nil;
+ id = nil;
+ tt = nil;
+ if(left.op == Odot){
+ callee = left.right.decl;
+ id = callee.dot;
+ right = passimplicit(left, right);
+ n.right = right;
+ tt = left.left.ty;
+ if(tt.kind == Tref)
+ tt = tt.tof;
+ ttt := tt;
+ if(tt.kind == Tadtpick)
+ ttt = tt.decl.dot.ty;
+ dd := ttt.decl;
+ while(dd != nil && dd.link != nil)
+ dd = dd.link;
+ if(dd != nil && dd.timport != nil)
+ mod = dd.timport.eimport;
+
+ #
+ # stash the import module under a rock,
+ # because we won't be able to get it later
+ # after scopes are popped
+ #
+ left.right.left = mod;
+ }else if(left.op == Omdot){
+ if(left.right.op == Odot){
+ callee = left.right.right.decl;
+ right = passimplicit(left.right, right);
+ n.right = right;
+ tt = left.right.left.ty;
+ if(tt.kind == Tref)
+ tt = tt.tof;
+ }else
+ callee = left.right.decl;
+ mod = left.left;
+ }else if(left.op == Oname){
+ callee = left.decl;
+ id = callee;
+ mod = id.eimport;
+ }else if(pure){
+ nerror(left, expconv(left)+" is not a function name");
+ allok = 0;
+ break;
+ }
+ if(pure && callee == nil)
+ fatal("can't find called function: "+nodeconv(left));
+ if(callee != nil && callee.store != Dfn && !isfnref(callee)){
+ nerror(left, expconv(left)+" is not a function");
+ allok = 0;
+ break;
+ }
+ if(mod != nil && mod.ty.kind != Tmodule){
+ nerror(left, "cannot call "+expconv(left));
+ allok = 0;
+ break;
+ }
+ if(mod != nil){
+ if(valistype(mod)){
+ nerror(left, "cannot call "+expconv(left)+" because "+expconv(mod)+" is a module interface");
+ allok = 0;
+ break;
+ }
+ }else if(id != nil && id.dot != nil && !isimpmod(id.dot.sym)){
+ nerror(left, "cannot call "+expconv(left)+" without importing "+id.sym.name+" from a variable");
+ allok = 0;
+ break;
+ }
+ if(mod != nil)
+ modrefable(left.ty);
+ if(callee != nil && callee.store != Dfn)
+ callee = nil;
+ if(t.varargs != byte 0){
+ t = mkvarargs(left, right);
+ if(left.ty.kind == Tref)
+ left.ty = usetype(mktype(t.src.start, t.src.stop, Tref, t, nil));
+ else
+ left.ty = t;
+ }
+ else if(ispoly(callee) || isfnrefty(left.ty) && left.ty.tof.polys != nil){
+ unifysrc = n.src;
+ if(!argncompat(n, t.ids, right)){
+ allok = 0;
+ break;
+ }
+ (okp, tp) := tunify(left.ty, calltype(left.ty, right, n.ty));
+ if(!okp){
+ nerror(n, "function call type mismatch (" + typeconv(left.ty)+" vs "+typeconv(calltype(left.ty, right, n.ty))+")");
+ allok = 0;
+ }
+ else{
+ (n.ty, tp) = expandtype(n.ty, nil, nil, tp);
+ n.ty = usetype(n.ty);
+ if(ispoly(callee) && tt != nil && (tt.kind == Tadt || tt.kind == Tadtpick) && int (tt.flags&INST))
+ callee = rewcall(left, callee);
+ n.right = passfns(n.src, callee, left, right, tt, tp);
+ }
+ }
+ else if(!argcompat(n, t.ids, right))
+ allok = 0;
+ Odot =>
+ t = left.ty;
+ if(t.kind == Tref)
+ t = t.tof;
+ case t.kind{
+ Tadt or
+ Tadtpick or
+ Ttuple or
+ Texception or
+ Tpoly =>
+ id = namedot(t.ids, right.decl.sym);
+ if(id == nil){
+ id = namedot(t.tags, right.decl.sym);
+ if(id != nil && !valistype(left)){
+ nerror(n, expconv(left)+" is not a type");
+ return (0, 0);
+ }
+ }
+ if(id == nil){
+ id = namedot(t.polys, right.decl.sym);
+ if(id != nil && !valistype(left)){
+ nerror(n, expconv(left)+" is not a type");
+ return (0, 0);
+ }
+ }
+ if(id == nil && t.kind == Tadtpick)
+ id = namedot(t.decl.dot.ty.ids, right.decl.sym);
+ if(id == nil){
+ for(tg = t.tags; tg != nil; tg = tg.next){
+ id = namedot(tg.ty.ids, right.decl.sym);
+ if(id != nil)
+ break;
+ }
+ if(id != nil){
+ nerror(n, "cannot yet index field "+right.decl.sym.name+" of "+etconv(left));
+ return (0, 0);
+ }
+ }
+ if(id == nil)
+ break;
+ if(id.store == Dfield && valistype(left)){
+ nerror(n, expconv(left)+" is not a value");
+ return (0, 0);
+ }
+ id.ty = validtype(id.ty, t.decl);
+ id.ty = usetype(id.ty);
+ break;
+ * =>
+ nerror(left, etconv(left)+" cannot be qualified with .");
+ return (0, 0);
+ }
+ if(id == nil){
+ nerror(n, expconv(right)+" is not a member of "+etconv(left));
+ return (0, 0);
+ }
+ if(id.ty == tunknown){
+ nerror(n, "illegal forward reference to "+expconv(n));
+ return (0, 0);
+ }
+
+ increfs(id);
+ right.decl = id;
+ n.ty = id.ty;
+ if((id.store == Dconst || id.store == Dtag) && hasside(left, 1))
+ nwarn(left, "result of expression "+etconv(left)+" ignored");
+ # function name as function reference
+ if(id.store == Dfn && (par == nil || (par.op != Omdot && par.op != Ocall && par.op != Ofunc)))
+ fnref(n, id);
+ Omdot =>
+ t = left.ty;
+ if(t.kind != Tmodule){
+ nerror(left, etconv(left)+" cannot be qualified with ->");
+ return (0, 0);
+ }
+ id = nil;
+ if(right.op == Oname){
+ id = namedot(t.ids, right.decl.sym);
+ }else if(right.op == Odot){
+ (ok, kidsok) = echeck(right, 0, isglobal, n);
+ allok &= kidsok;
+ if(!ok)
+ return (0, 0);
+ tt = right.left.ty;
+ if(tt.kind == Tref)
+ tt = tt.tof;
+ if(right.ty.kind == Tfn
+ && tt.kind == Tadt
+ && tt.decl.dot == t.decl)
+ id = right.right.decl;
+ }
+ if(id == nil){
+ nerror(n, expconv(right)+" is not a member of "+etconv(left));
+ return (0, 0);
+ }
+ if(id.store != Dconst && id.store != Dtype && id.store != Dtag){
+ if(valistype(left)){
+ nerror(n, expconv(left)+" is not a value");
+ return (0, 0);
+ }
+ }else if(hasside(left, 1))
+ nwarn(left, "result of expression "+etconv(left)+" ignored");
+ if(!typeok && id.store == Dtype){
+ nerror(n, expconv(n)+" is a type, not a value");
+ return (0, 0);
+ }
+ if(id.ty == tunknown){
+ nerror(n, "illegal forward reference to "+expconv(n));
+ return (0, 0);
+ }
+ id.refs++;
+ right.decl = id;
+ n.ty = id.ty = usetype(id.ty);
+ if(id.store == Dglobal)
+ modrefable(id.ty);
+ # function name as function reference
+ if(id.store == Dfn && (par == nil || (par.op != Ocall && par.op != Ofunc)))
+ fnref(n, id);
+ Otagof =>
+ n.ty = tint;
+ t = left.ty;
+ if(t.kind == Tref)
+ t = t.tof;
+ id = nil;
+ case left.op{
+ Oname =>
+ id = left.decl;
+ Odot =>
+ id = left.right.decl;
+ Omdot =>
+ if(left.right.op == Odot)
+ id = left.right.right.decl;
+ }
+ if(id != nil && id.store == Dtag
+ || id != nil && id.store == Dtype && t.kind == Tadt && t.tags != nil)
+ n.decl = id;
+ else if(t.kind == Tadt && t.tags != nil || t.kind == Tadtpick)
+ n.decl = nil;
+ else{
+ nerror(n, "cannot get the tag value for "+etconv(left));
+ return (1, 0);
+ }
+ Oind =>
+ t = left.ty;
+ if(t.kind != Tref || (t.tof.kind != Tadt && t.tof.kind != Tadtpick && t.tof.kind != Ttuple)){
+ nerror(n, "cannot * "+etconv(left));
+ return (0, 0);
+ }
+ n.ty = t.tof;
+ for(tg = t.tof.tags; tg != nil; tg = tg.next)
+ tg.ty.tof = usetype(tg.ty.tof);
+ Oindex =>
+ if(valistype(left)){
+ tagopt = 1;
+ (nil, kidsok) = echeck(right, 2, isglobal, n);
+ tagopt = 0;
+ if(!kidsok)
+ return (0, 0);
+ if((t = exptotype(n)) == nil){
+ nerror(n, expconv(right) + " is not a type list");
+ return (0, 0);
+ }
+ if(!typeok){
+ nerror(n, expconv(left) + " is not a variable");
+ return (0, 0);
+ }
+ *n = *(n.left);
+ n.ty = usetype(t);
+ break;
+ }
+ if(0 && right.op == Oseq){ # a[e1, e2, ...]
+ # array creation to do before we allow this
+ rewind(n);
+ return echeck(n, typeok, isglobal, par);
+ }
+ t = left.ty;
+ (nil, kidsok) = echeck(right, 0, isglobal, n);
+ if(t.kind != Tarray && t != tstring){
+ nerror(n, "cannot index "+etconv(left));
+ return (0, 0);
+ }
+ if(t == tstring){
+ n.op = Oinds;
+ n.ty = tint;
+ }else{
+ n.ty = t.tof;
+ }
+ if(!kidsok){
+ allok = 0;
+ break;
+ }
+ if(right.ty != tint){
+ nerror(n, "cannot index "+etconv(left)+" with "+etconv(right));
+ allok = 0;
+ break;
+ }
+ Oslice =>
+ t = n.ty = left.ty;
+ if(t.kind != Tarray && t != tstring){
+ nerror(n, "cannot slice "+etconv(left)+" with '"+subexpconv(right.left)+":"+subexpconv(right.right)+"'");
+ return (0, 0);
+ }
+ if(right.left.ty != tint && right.left.op != Onothing
+ || right.right.ty != tint && right.right.op != Onothing){
+ nerror(n, "cannot slice "+etconv(left)+" with '"+subexpconv(right.left)+":"+subexpconv(right.right)+"'");
+ return (1, 0);
+ }
+ Olen =>
+ t = left.ty;
+ n.ty = tint;
+ if(t.kind != Tarray && t.kind != Tlist && t != tstring){
+ nerror(n, "len requires an array, string or list in "+etconv(left));
+ return (1, 0);
+ }
+ Ocomp or
+ Onot or
+ Oneg =>
+ n.ty = left.ty;
+usedty(n.ty);
+ case left.ty.kind{
+ Tint =>
+ return (1, allok);
+ Treal or
+ Tfix =>
+ if(n.op == Oneg)
+ return (1, allok);
+ Tbig or
+ Tbyte =>
+ if(n.op == Oneg || n.op == Ocomp)
+ return (1, allok);
+ }
+ nerror(n, "cannot apply "+opconv(n.op)+" to "+etconv(left));
+ return (0, 0);
+ Oinc or
+ Odec or
+ Opreinc or
+ Opredec =>
+ n.ty = left.ty;
+ case left.ty.kind{
+ Tint or
+ Tbig or
+ Tbyte or
+ Treal =>
+ break;
+ * =>
+ nerror(n, "cannot apply "+opconv(n.op)+" to "+etconv(left));
+ return (0, 0);
+ }
+ if(islval(left))
+ break;
+ return(0, 0);
+ Oadd or
+ Odiv or
+ Omul or
+ Osub =>
+ if(mathchk(n, 1))
+ break;
+ return (0, 0);
+ Oexp or
+ Oexpas =>
+ n.ty = left.ty;
+ if(n.ty != tint && n.ty != tbig && n.ty != treal){
+ nerror(n, "exponend " + etconv(left) + " is not int or real");
+ return (0, 0);
+ }
+ if(right.ty != tint){
+ nerror(n, "exponent " + etconv(right) + " is not int");
+ return (0, 0);
+ }
+ if(n.op == Oexpas && !islval(left))
+ return (0, 0);
+ break;
+ # if(mathchk(n, 0)){
+ # if(n.ty != tint){
+ # nerror(n, "exponentiation operands not int");
+ # return (0, 0);
+ # }
+ # break;
+ # }
+ # return (0, 0);
+ Olsh or
+ Orsh =>
+ if(shiftchk(n))
+ break;
+ return (0, 0);
+ Oandand or
+ Ooror =>
+ if(left.ty != tint){
+ nerror(n, opconv(n.op)+"'s left operand is not an int: "+etconv(left));
+ allok = 0;
+ }
+ if(right.ty != tint){
+ nerror(n, opconv(n.op)+"'s right operand is not an int: "+etconv(right));
+ allok = 0;
+ }
+ n.ty = tint;
+ Oand or
+ Omod or
+ Oor or
+ Oxor =>
+ if(mathchk(n, 0))
+ break;
+ return (0, 0);
+ Oaddas or
+ Odivas or
+ Omulas or
+ Osubas =>
+ if(mathchk(n, 1) && islval(left))
+ break;
+ return (0, 0);
+ Olshas or
+ Orshas =>
+ if(shiftchk(n) && islval(left))
+ break;
+ return (0, 0);
+ Oandas or
+ Omodas or
+ Oxoras or
+ Ooras =>
+ if(mathchk(n, 0) && islval(left))
+ break;
+ return (0, 0);
+ Olt or
+ Oleq or
+ Ogt or
+ Ogeq =>
+ if(!mathchk(n, 1))
+ return (0, 0);
+ n.ty = tint;
+ Oeq or
+ Oneq =>
+ case left.ty.kind{
+ Tint or
+ Tbig or
+ Tbyte or
+ Treal or
+ Tstring or
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan or
+ Tany or
+ Tmodule or
+ Tfix or
+ Tpoly =>
+ if(!tcompat(left.ty, right.ty, 0) && !tcompat(right.ty, left.ty, 0))
+ break;
+ t = left.ty;
+ if(t == tany)
+ t = right.ty;
+ if(t == tany)
+ t = tint;
+ if(left.ty == tany)
+ left.ty = t;
+ if(right.ty == tany)
+ right.ty = t;
+ n.ty = tint;
+usedty(n.ty);
+ return (1, allok);
+ }
+ nerror(n, "cannot compare "+etconv(left)+" to "+etconv(right));
+ return (0, 0);
+ Otype =>
+ if(!typeok){
+ nerror(n, expconv(n) + " is not a variable");
+ return (0, 0);
+ }
+ n.ty = usetype(n.ty);
+ * =>
+ fatal("unknown op in typecheck: "+opconv(n.op));
+ }
+usedty(n.ty);
+ return (1, allok);
+}
+
+#
+# n is syntactically a call, but n.left is not a fn
+# check if it's the contructor for an adt
+#
+callcast(n: ref Node, kidsok, allok: int): (int, int)
+{
+ id: ref Decl;
+
+ left := n.left;
+ right := n.right;
+ id = nil;
+ case left.op{
+ Oname =>
+ id = left.decl;
+ Omdot =>
+ if(left.right.op == Odot)
+ id = left.right.right.decl;
+ else
+ id = left.right.decl;
+ Odot =>
+ id = left.right.decl;
+ }
+ if(id == nil || (id.store != Dtype && id.store != Dtag && id.ty.kind != Texception)){
+ nerror(left, expconv(left)+" is not a function or type name");
+ return (0, 0);
+ }
+ if(id.store == Dtag)
+ return tagcast(n, left, right, id, kidsok, allok);
+ t := left.ty;
+ n.ty = t;
+ if(!kidsok)
+ return (1, 0);
+
+ if(t.kind == Tref)
+ t = t.tof;
+ tt := mktype(n.src.start, n.src.stop, Ttuple, nil, tuplefields(right));
+ if(t.kind == Tadt && tcompat(t, tt, 1)){
+ if(right == nil)
+ *n = *n.left;
+ return (1, allok);
+ }
+
+ # try an exception with args
+ tt = mktype(n.src.start, n.src.stop, Texception, nil, tuplefields(right));
+ tt.cons = byte 1;
+ if(t.kind == Texception && t.cons == byte 1 && tcompat(t, tt, 1)){
+ if(right == nil)
+ *n = *n.left;
+ return (1, allok);
+ }
+
+ # try a cast
+ if(t.kind != Texception && right != nil && right.right == nil){ # Oseq but single expression
+ right = right.left;
+ n.op = Ocast;
+ n.left = right;
+ n.right = nil;
+ n.ty = mkidtype(n.src, id.sym);
+ return echeck(n, 0, 0, nil);
+ }
+
+ nerror(left, "cannot make a "+expconv(left)+" from '("+subexpconv(right)+")'");
+ return (0, 0);
+}
+
+tagcast(n, left, right: ref Node, id: ref Decl, kidsok, allok: int): (int, int)
+{
+ left.ty = id.ty;
+ if(left.op == Omdot)
+ left.right.ty = id.ty;
+ n.ty = id.ty;
+ if(!kidsok)
+ return (1, 0);
+ id.ty.tof = usetype(id.ty.tof);
+ if(right != nil)
+ right.ty = id.ty.tof;
+ tt := mktype(n.src.start, n.src.stop, Ttuple, nil, mkids(nosrc, nil, tint, tuplefields(right)));
+ tt.ids.store = Dfield;
+ if(tcompat(id.ty.tof, tt, 1))
+ return (1, allok);
+
+ nerror(left, "cannot make a "+expconv(left)+" from '("+subexpconv(right)+")'");
+ return (0, 0);
+}
+
+valistype(n: ref Node): int
+{
+ case n.op{
+ Oname =>
+ if(n.decl.store == Dtype)
+ return 1;
+ Omdot =>
+ return valistype(n.right);
+ }
+ return 0;
+}
+
+islval(n: ref Node): int
+{
+ s := marklval(n);
+ if(s == 1)
+ return 1;
+ if(s == 0)
+ nerror(n, "cannot assign to "+expconv(n));
+ else
+ circlval(n, n);
+ return 0;
+}
+
+#
+# check to see if n is an lval
+#
+marklval(n: ref Node): int
+{
+ if(n == nil)
+ return 0;
+ case n.op{
+ Oname =>
+ return storespace[n.decl.store] && n.ty.kind != Texception; #ZZZZ && n.decl.tagged == nil;
+ Odot =>
+ if(n.right.decl.store != Dfield)
+ return 0;
+ if(n.right.decl.cycle != byte 0 && n.right.decl.cyc == byte 0)
+ return -1;
+ if(n.left.ty.kind != Tref && marklval(n.left) == 0)
+ nwarn(n, "assignment to "+etconv(n)+" ignored");
+ return 1;
+ Omdot =>
+ if(n.right.decl.store == Dglobal)
+ return 1;
+ return 0;
+ Oind =>
+ for(id := n.ty.ids; id != nil; id = id.next)
+ if(id.cycle != byte 0 && id.cyc == byte 0)
+ return -1;
+ return 1;
+ Oslice =>
+ if(n.right.right.op != Onothing || n.ty == tstring)
+ return 0;
+ return 1;
+ Oinds =>
+ #
+ # make sure we don't change a string constant
+ #
+ case n.left.op{
+ Oconst =>
+ return 0;
+ Oname =>
+ return storespace[n.left.decl.store];
+ Odot or
+ Omdot =>
+ if(n.left.right.decl != nil)
+ return storespace[n.left.right.decl.store];
+ }
+ return 1;
+ Oindex or
+ Oindx =>
+ return 1;
+ Otuple =>
+ for(nn := n.left; nn != nil; nn = nn.right){
+ s := marklval(nn.left);
+ if(s != 1)
+ return s;
+ }
+ return 1;
+ * =>
+ return 0;
+ }
+ return 0;
+}
+
+#
+# n has a circular field assignment.
+# find it and print an error message.
+#
+circlval(n, lval: ref Node): int
+{
+ if(n == nil)
+ return 0;
+ case n.op{
+ Oname =>
+ break;
+ Odot =>
+ if(n.right.decl.cycle != byte 0 && n.right.decl.cyc == byte 0){
+ nerror(lval, "cannot assign to "+expconv(lval)+" because field '"+n.right.decl.sym.name
+ +"' of "+expconv(n.left)+" could complete a cycle to "+expconv(n.left));
+ return -1;
+ }
+ return 1;
+ Oind =>
+ for(id := n.ty.ids; id != nil; id = id.next){
+ if(id.cycle != byte 0 && id.cyc == byte 0){
+ nerror(lval, "cannot assign to "+expconv(lval)+" because field '"+id.sym.name
+ +"' of "+expconv(n)+" could complete a cycle to "+expconv(n));
+ return -1;
+ }
+ }
+ return 1;
+ Oslice =>
+ if(n.right.right.op != Onothing || n.ty == tstring)
+ return 0;
+ return 1;
+ Oindex or
+ Oinds or
+ Oindx =>
+ return 1;
+ Otuple =>
+ for(nn := n.left; nn != nil; nn = nn.right){
+ s := circlval(nn.left, lval);
+ if(s != 1)
+ return s;
+ }
+ return 1;
+ * =>
+ return 0;
+ }
+ return 0;
+}
+
+mathchk(n: ref Node, realok: int): int
+{
+ lt := n.left.ty;
+ rt := n.right.ty;
+ if(rt != lt && !tequal(lt, rt)){
+ nerror(n, "type clash in "+etconv(n.left)+" "+opconv(n.op)+" "+etconv(n.right));
+ return 0;
+ }
+ n.ty = rt;
+ case rt.kind{
+ Tint or
+ Tbig or
+ Tbyte =>
+ return 1;
+ Tstring =>
+ case n.op{
+ Oadd or
+ Oaddas or
+ Ogt or
+ Ogeq or
+ Olt or
+ Oleq =>
+ return 1;
+ }
+ Treal or
+ Tfix =>
+ if(realok)
+ return 1;
+ }
+ nerror(n, "cannot "+opconv(n.op)+" "+etconv(n.left)+" and "+etconv(n.right));
+ return 0;
+}
+
+shiftchk(n: ref Node): int
+{
+ right := n.right;
+ left := n.left;
+ n.ty = left.ty;
+ case n.ty.kind{
+ Tint or
+ Tbyte or
+ Tbig =>
+ if(right.ty.kind != Tint){
+ nerror(n, "shift "+etconv(right)+" is not an int");
+ return 0;
+ }
+ return 1;
+ }
+ nerror(n, "cannot "+opconv(n.op)+" "+etconv(left)+" by "+etconv(right));
+ return 0;
+}
+
+#
+# check for any tany's in t
+#
+specific(t: ref Type): int
+{
+ if(t == nil)
+ return 0;
+ case t.kind{
+ Terror or
+ Tnone or
+ Tint or
+ Tbig or
+ Tstring or
+ Tbyte or
+ Treal or
+ Tfn or
+ Tadt or
+ Tadtpick or
+ Tmodule or
+ Tfix =>
+ return 1;
+ Tany =>
+ return 0;
+ Tpoly =>
+ return 1;
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan =>
+ return specific(t.tof);
+ Ttuple or
+ Texception =>
+ for(d := t.ids; d != nil; d = d.next)
+ if(!specific(d.ty))
+ return 0;
+ return 1;
+ }
+ fatal("unknown type in specific: "+typeconv(t));
+ return 0;
+}
+
+#
+# infer the type of all variable in n from t
+# n is the left-hand exp of a := exp
+#
+declasinfer(n: ref Node, t: ref Type): int
+{
+ if(t.kind == Texception){
+ if(int t.cons)
+ return 0;
+ t = mkextuptype(t);
+ }
+ case n.op{
+ Otuple =>
+ if(t.kind != Ttuple && t.kind != Tadt && t.kind != Tadtpick)
+ return 0;
+ ok := 1;
+ n.ty = t;
+ n = n.left;
+ ids := t.ids;
+ if(t.kind == Tadtpick)
+ ids = t.tof.ids.next;
+ for(; n != nil && ids != nil; ids = ids.next){
+ if(ids.store != Dfield)
+ continue;
+ ok &= declasinfer(n.left, ids.ty);
+ n = n.right;
+ }
+ for(; ids != nil; ids = ids.next)
+ if(ids.store == Dfield)
+ break;
+ if(n != nil || ids != nil)
+ return 0;
+ return ok;
+ Oname =>
+ topvartype(t, n.decl, 0, 0);
+ if(n.decl == nildecl)
+ return 1;
+ n.decl.ty = t;
+ n.ty = t;
+ shareloc(n.decl);
+ return 1;
+ }
+ fatal("unknown op in declasinfer: "+nodeconv(n));
+ return 0;
+}
+
+#
+# an error occured in declaring n;
+# set all decl identifiers to Dwundef
+# so further errors are squashed.
+#
+declaserr(n: ref Node)
+{
+ case n.op{
+ Otuple =>
+ for(n = n.left; n != nil; n = n.right)
+ declaserr(n.left);
+ return;
+ Oname =>
+ if(n.decl != nildecl)
+ n.decl.store = Dwundef;
+ return;
+ }
+ fatal("unknown op in declaserr: "+nodeconv(n));
+}
+
+argcompat(n: ref Node, f: ref Decl, a: ref Node): int
+{
+ for(; a != nil; a = a.right){
+ if(f == nil){
+ nerror(n, expconv(n.left)+": too many function arguments");
+ return 0;
+ }
+ if(!tcompat(f.ty, a.left.ty, 0)){
+ nerror(n, expconv(n.left)+": argument type mismatch: expected "+typeconv(f.ty)+" saw "+etconv(a.left));
+ return 0;
+ }
+ if(a.left.ty == tany)
+ a.left.ty = f.ty;
+ f = f.next;
+ }
+ if(f != nil){
+ nerror(n, expconv(n.left)+": too few function arguments");
+ return 0;
+ }
+ return 1;
+}
+
+argncompat(n: ref Node, f: ref Decl, a: ref Node): int
+{
+ for(; a != nil; a = a.right){
+ if(f == nil){
+ nerror(n, expconv(n.left)+": too many function arguments");
+ return 0;
+ }
+ f = f.next;
+ }
+ if(f != nil){
+ nerror(n, expconv(n.left)+": too few function arguments");
+ return 0;
+ }
+ return 1;
+}
+
+#
+# fn is Odot(adt, methid)
+# pass adt implicitly if needed
+# if not, any side effect of adt will be ingored
+#
+passimplicit(fname, args: ref Node): ref Node
+{
+ t := fname.ty;
+ if(t.ids == nil || t.ids.implicit == byte 0){
+ if(hasside(fname.left, 1))
+ nwarn(fname, "result of expression "+expconv(fname.left)+" ignored");
+ return args;
+ }
+ n := fname.left;
+ if(n.op == Oname && n.decl.store == Dtype){
+ nerror(n, expconv(n)+" is a type and cannot be a self argument");
+ n = mkn(Onothing, nil, nil);
+ n.src = fname.src;
+ n.ty = t.ids.ty;
+ }
+ args = mkn(Oseq, n, args);
+ args.src = n.src;
+ return args;
+}
+
+mem(t: ref Type, d: ref Decl): int
+{
+ for( ; d != nil; d = d.next)
+ if(d.ty == t) # was if(d.ty == t || tequal(d.ty, t))
+ return 1;
+ return 0;
+}
+
+memp(t: ref Type, f: ref Decl): int
+{
+ return mem(t, f.ty.polys) || mem(t, encpolys(f));
+}
+
+passfns0(src: Src, fun: ref Decl, args0: ref Node, args: ref Node, a: ref Node, tp: ref Tpair, polys: ref Decl): (ref Node, ref Node)
+{
+ id, idt, idf: ref Decl;
+ sym: ref Sym;
+ tt: ref Type;
+ na, mod: ref Node;
+
+ for(idt = polys; idt != nil; idt = idt.next){
+ tt = valtmap(idt.ty, tp);
+ if(tt.kind == Tpoly && fndec != nil && !memp(tt, fndec))
+ error(src.start, "cannot determine the instantiated type of " + typeconv(tt));
+ for(idf = idt.ty.ids; idf != nil; idf = idf.next){
+ sym = idf.sym;
+ (id, mod) = fnlookup(sym, tt);
+ while(id != nil && id.link != nil)
+ id = id.link;
+ if(id == nil) # error flagged already
+ continue;
+ id.refs++;
+ id.inline = byte -1;
+ if(tt.kind == Tmodule){ # mod an actual parameter
+ for(;;){
+ if(args0 != nil && tequal(tt, args0.left.ty)){
+ mod = args0.left;
+ break;
+ }
+ if(args0 != nil)
+ args0 = args0.right;
+ }
+ }
+ if(mod == nil && (dot := lmodule(id)) != nil && !isimpmod(dot.sym))
+ error(src.start, "cannot use " + id.sym.name + " without importing " + id.dot.sym.name + " from a variable");
+
+ n := mkn(Ofnptr, mod, mkdeclname(src, id));
+ n.src = src;
+ n.decl = fun;
+ if(tt.kind == Tpoly)
+ n.flags = byte FNPTRA;
+ else
+ n.flags = byte 0;
+ na = mkn(Oseq, n, nil);
+ if(a == nil)
+ args = na;
+ else
+ a.right = na;
+
+ n = mkn(Ofnptr, mod, mkdeclname(src, id));
+ n.src = src;
+ n.decl = fun;
+ if(tt.kind == Tpoly)
+ n.flags = byte (FNPTRA|FNPTR2);
+ else
+ n.flags = byte FNPTR2;
+ a = na.right = mkn(Oseq, n, nil);
+ }
+ if(args0 != nil)
+ args0 = args0.right;
+ }
+ return (args, a);
+}
+
+passfns(src: Src, fun: ref Decl, left: ref Node, args: ref Node, adtt: ref Type, tp: ref Tpair): ref Node
+{
+ a, args0: ref Node;
+
+ a = nil;
+ args0 = args;
+ if(args != nil)
+ for(a = args; a.right != nil; a = a.right)
+ ;
+ if(ispoly(fun))
+ polys := fun.ty.polys;
+ else
+ polys = left.ty.tof.polys;
+ (args, a) = passfns0(src, fun, args0, args, a, tp, polys);
+ if(adtt != nil){
+ if(ispoly(fun))
+ polys = encpolys(fun);
+ else
+ polys = nil;
+ (args, a) = passfns0(src, fun, args0, args, a, adtt.tmap, polys);
+ }
+ return args;
+}
+
+#
+# check the types for a function with a variable number of arguments
+# last typed argument must be a constant string, and must use the
+# print format for describing arguments.
+#
+mkvarargs(n, args: ref Node): ref Type
+{
+ last: ref Decl;
+
+ nt := copytypeids(n.ty);
+ n.ty = nt;
+ f := n.ty.ids;
+ last = nil;
+ if(f == nil){
+ nerror(n, expconv(n)+"'s type is illegal");
+ return nt;
+ }
+ s := args;
+ for(a := args; a != nil; a = a.right){
+ if(f == nil)
+ break;
+ if(!tcompat(f.ty, a.left.ty, 0)){
+ nerror(n, expconv(n)+": argument type mismatch: expected "+typeconv(f.ty)+" saw "+etconv(a.left));
+ return nt;
+ }
+ if(a.left.ty == tany)
+ a.left.ty = f.ty;
+ last = f;
+ f = f.next;
+ s = a;
+ }
+ if(f != nil){
+ nerror(n, expconv(n)+": too few function arguments");
+ return nt;
+ }
+ s.left = fold(s.left);
+ s = s.left;
+ if(s.ty != tstring || s.op != Oconst){
+ nerror(args, expconv(n)+": format argument "+etconv(s)+" is not a string constant");
+ return nt;
+ }
+ fmtcheck(n, s, a);
+ va := tuplefields(a);
+ if(last == nil)
+ nt.ids = va;
+ else
+ last.next = va;
+ return nt;
+}
+
+#
+# check that a print style format string matches it's arguments
+#
+fmtcheck(f, fmtarg, va: ref Node)
+{
+ fmt := fmtarg.decl.sym;
+ s := fmt.name;
+ ns := 0;
+ while(ns < len s){
+ c := s[ns++];
+ if(c != '%')
+ continue;
+
+ verb := -1;
+ n1 := 0;
+ n2 := 0;
+ dot := 0;
+ flag := 0;
+ flags := "";
+ fmtstart := ns - 1;
+ while(ns < len s && verb < 0){
+ c = s[ns++];
+ case c{
+ * =>
+ nerror(f, expconv(f)+": invalid character "+s[ns-1:ns]+" in format '"+s[fmtstart:ns]+"'");
+ return;
+ '.' =>
+ if(dot){
+ nerror(f, expconv(f)+": invalid format '"+s[fmtstart:ns]+"'");
+ return;
+ }
+ n1 = 1;
+ dot = 1;
+ continue;
+ '*' =>
+ if(!n1)
+ n1 = 1;
+ else if(!n2 && dot)
+ n2 = 1;
+ else{
+ nerror(f, expconv(f)+": invalid format '"+s[fmtstart:ns]+"'");
+ return;
+ }
+ if(va == nil){
+ nerror(f, expconv(f)+": too few arguments for format '"+s[fmtstart:ns]+"'");
+ return;
+ }
+ if(va.left.ty.kind != Tint){
+ nerror(f, expconv(f)+": format '"+s[fmtstart:ns]+"' incompatible with argument "+etconv(va.left));
+ return;
+ }
+ va = va.right;
+ '0' to '9' =>
+ while(ns < len s && s[ns] >= '0' && s[ns] <= '9')
+ ns++;
+ if(!n1)
+ n1 = 1;
+ else if(!n2 && dot)
+ n2 = 1;
+ else{
+ nerror(f, expconv(f)+": invalid format '"+s[fmtstart:ns]+"'");
+ return;
+ }
+ '+' or
+ '-' or
+ '#' or
+ ',' or
+ 'b' or
+ 'u' =>
+ for(i := 0; i < flag; i++){
+ if(flags[i] == c){
+ nerror(f, expconv(f)+": duplicate flag "+s[ns-1:ns]+" in format '"+s[fmtstart:ns]+"'");
+ return;
+ }
+ }
+ flags[flag++] = c;
+ '%' or
+ 'r' =>
+ verb = Tnone;
+ 'H' =>
+ verb = Tany;
+ 'c' =>
+ verb = Tint;
+ 'd' or
+ 'o' or
+ 'x' or
+ 'X' =>
+ verb = Tint;
+ for(i := 0; i < flag; i++){
+ if(flags[i] == 'b'){
+ verb = Tbig;
+ break;
+ }
+ }
+ 'e' or
+ 'f' or
+ 'g' or
+ 'E' or
+ 'G' =>
+ verb = Treal;
+ 's' or
+ 'q' =>
+ verb = Tstring;
+ }
+ }
+ if(verb != Tnone){
+ if(verb < 0){
+ nerror(f, expconv(f)+": incomplete format '"+s[fmtstart:ns]+"'");
+ return;
+ }
+ if(va == nil){
+ nerror(f, expconv(f)+": too few arguments for format '"+s[fmtstart:ns]+"'");
+ return;
+ }
+ ty := va.left.ty;
+ if(ty.kind == Texception)
+ ty = mkextuptype(ty);
+ case verb{
+ Tint =>
+ case ty.kind{
+ Tstring or
+ Tarray or
+ Tref or
+ Tchan or
+ Tlist or
+ Tmodule =>
+ if(c == 'x' || c == 'X')
+ verb = ty.kind;
+ }
+ Tany =>
+ if(tattr[ty.kind].isptr)
+ verb = ty.kind;
+ }
+ if(verb != ty.kind){
+ nerror(f, expconv(f)+": format '"+s[fmtstart:ns]+"' incompatible with argument "+etconv(va.left));
+ return;
+ }
+ va = va.right;
+ }
+ }
+ if(va != nil)
+ nerror(f, expconv(f)+": more arguments than formats");
+}
+
+tuplefields(n: ref Node): ref Decl
+{
+ h, last: ref Decl;
+
+ for(; n != nil; n = n.right){
+ d := mkdecl(n.left.src, Dfield, n.left.ty);
+ if(h == nil)
+ h = d;
+ else
+ last.next = d;
+ last = d;
+ }
+ return h;
+}
+
+#
+# make explicit indices for every element in an array initializer
+# return the maximum index
+# sort the indices and check for duplicates
+#
+assignindices(ar: ref Node): int
+{
+ wild, off, q: ref Node;
+
+ amax := 16r7fffffff;
+ size := dupn(0, nosrc, ar.left);
+ if(size.ty == tint){
+ size = fold(size);
+ if(size.op == Oconst)
+ amax = int size.c.val;
+ }
+
+ inits := ar.right;
+ max := -1;
+ last := -1;
+ t := inits.left.ty;
+ wild = nil;
+ nlab := 0;
+ ok := 1;
+ for(n := inits; n != nil; n = n.right){
+ if(!tcompat(t, n.left.ty, 0)){
+ t = tparent(t, n.left.ty);
+ if(!tcompat(t, n.left.ty, 0)){
+ nerror(n.left, "inconsistent types "+typeconv(t)+" and "+typeconv(n.left.ty)+" in array initializer");
+ return -1;
+ }
+ else
+ inits.left.ty = t;
+ }
+ if(t == tany)
+ t = n.left.ty;
+
+ #
+ # make up an index if there isn't one
+ #
+ if(n.left.left == nil)
+ n.left.left = mkn(Oseq, mkconst(n.left.right.src, big(last + 1)), nil);
+
+ for(q = n.left.left; q != nil; q = q.right){
+ off = q.left;
+ if(off.ty != tint){
+ nerror(off, "array index "+etconv(off)+" is not an int");
+ ok = 0;
+ continue;
+ }
+ off = fold(off);
+ case off.op{
+ Owild =>
+ if(wild != nil)
+ nerror(off, "array index * duplicated on line "+lineconv(wild.src.start));
+ wild = off;
+ continue;
+ Orange =>
+ if(off.left.op != Oconst || off.right.op != Oconst){
+ nerror(off, "range "+expconv(off)+" is not constant");
+ off = nil;
+ }else if(off.left.c.val < big 0 || off.right.c.val >= big amax){
+ nerror(off, "array index "+expconv(off)+" out of bounds");
+ off = nil;
+ }else
+ last = int off.right.c.val;
+ Oconst =>
+ last = int off.c.val;
+ if(off.c.val < big 0 || off.c.val >= big amax){
+ nerror(off, "array index "+expconv(off)+" out of bounds");
+ off = nil;
+ }
+ Onothing =>
+ # get here from a syntax error
+ off = nil;
+ * =>
+ nerror(off, "array index "+expconv(off)+" is not constant");
+ off = nil;
+ }
+
+ nlab++;
+ if(off == nil){
+ off = mkconst(n.left.right.src, big(last));
+ ok = 0;
+ }
+ if(last > max)
+ max = last;
+ q.left = off;
+ }
+ }
+
+ #
+ # fix up types of nil elements
+ #
+ for(n = inits; n != nil; n = n.right)
+ if(n.left.ty == tany)
+ n.left.ty = t;
+
+ if(!ok)
+ return -1;
+
+ c := checklabels(inits, tint, nlab, "array index");
+ t = mktype(inits.src.start, inits.src.stop, Tainit, nil, nil);
+ inits.ty = t;
+ t.cse = c;
+
+ return max + 1;
+}
+
+#
+# check the labels of a case statment
+#
+casecheck(cn: ref Node, ret: ref Type)
+{
+ wild: ref Node;
+
+ (rok, nil) := echeck(cn.left, 0, 0, nil);
+ cn.right = scheck(cn.right, ret, Sother);
+ if(!rok)
+ return;
+ arg := cn.left;
+
+ t := arg.ty;
+ if(t != tint && t != tbig && t != tstring){
+ nerror(cn, "case argument "+etconv(arg)+" is not an int or big or string");
+ return;
+ }
+
+ wild = nil;
+ nlab := 0;
+ ok := 1;
+ for(n := cn.right; n != nil; n = n.right){
+ q := n.left.left;
+ if(n.left.right.right == nil)
+ nwarn(q, "no body for case qualifier "+expconv(q));
+ for(; q != nil; q = q.right){
+ left := fold(q.left);
+ q.left = left;
+ case left.op{
+ Owild =>
+ if(wild != nil)
+ nerror(left, "case qualifier * duplicated on line "+lineconv(wild.src.start));
+ wild = left;
+ Orange =>
+ if(left.ty != t)
+ nerror(left, "case qualifier "+etconv(left)+" clashes with "+etconv(arg));
+ else if(left.left.op != Oconst || left.right.op != Oconst){
+ nerror(left, "case range "+expconv(left)+" is not constant");
+ ok = 0;
+ }
+ nlab++;
+ * =>
+ if(left.ty != t){
+ nerror(left, "case qualifier "+etconv(left)+" clashes with "+etconv(arg));
+ ok = 0;
+ }else if(left.op != Oconst){
+ nerror(left, "case qualifier "+expconv(left)+" is not constant");
+ ok = 0;
+ }
+ nlab++;
+ }
+ }
+ }
+
+ if(!ok)
+ return;
+
+ c := checklabels(cn.right, t, nlab, "case qualifier");
+ op := Tcase;
+ if(t == tbig)
+ op = Tcasel;
+ else if(t == tstring)
+ op = Tcasec;
+ t = mktype(cn.src.start, cn.src.stop, op, nil, nil);
+ cn.ty = t;
+ t.cse = c;
+}
+
+#
+# check the labels and bodies of a pick statment
+#
+pickcheck(n: ref Node, ret: ref Type)
+{
+ qs, q, w: ref Node;
+
+ arg := n.left.right;
+ (nil, allok) := echeck(arg, 0, 0, nil);
+ if(!allok)
+ return;
+ t := arg.ty;
+ if(t.kind == Tref)
+ t = t.tof;
+ if(arg.ty.kind != Tref || t.kind != Tadt || t.tags == nil){
+ nerror(arg, "pick argument "+etconv(arg)+" is not a ref adt with pick tags");
+ return;
+ }
+ argty := usetype(mktype(arg.ty.src.start, arg.ty.src.stop, Tref, t, nil));
+
+ arg = n.left.left;
+ pushscope(nil, Sother);
+ dasdecl(arg);
+ arg.decl.ty = argty;
+ arg.ty = argty;
+
+ tags := array[t.decl.tag] of ref Node;
+ w = nil;
+ ok := 1;
+ nlab := 0;
+ for(qs = n.right; qs != nil; qs = qs.right){
+ qt : ref Node = nil;
+ for(q = qs.left.left; q != nil; q = q.right){
+ left := q.left;
+ case left.op{
+ Owild =>
+ # left.ty = tnone;
+ left.ty = t;
+ if(w != nil)
+ nerror(left, "pick qualifier * duplicated on line "+lineconv(w.src.start));
+ w = left;
+ Oname =>
+ id := namedot(t.tags, left.decl.sym);
+ if(id == nil){
+ nerror(left, "pick qualifier "+expconv(left)+" is not a member of "+etconv(arg));
+ ok = 0;
+ continue;
+ }
+
+ left.decl = id;
+ left.ty = id.ty;
+
+ if(tags[id.tag] != nil){
+ nerror(left, "pick qualifier "+expconv(left)+" duplicated on line "+lineconv(tags[id.tag].src.start));
+ ok = 0;
+ }
+ tags[id.tag] = left;
+ nlab++;
+ * =>
+ fatal("pickcheck can't handle "+nodeconv(q));
+ }
+
+ if(qt == nil)
+ qt = left;
+ else if(!tequal(qt.ty, left.ty))
+ nerror(left, "type clash in pick qualifiers "+etconv(qt)+" and "+etconv(left));
+ }
+
+ argty.tof = t;
+ if(qt != nil)
+ argty.tof = qt.ty;
+ qs.left.right = scheck(qs.left.right, ret, Sother);
+ if(qs.left.right == nil)
+ nwarn(qs.left.left, "no body for pick qualifier "+expconv(qs.left.left));
+ }
+ argty.tof = t;
+ for(qs = n.right; qs != nil; qs = qs.right)
+ for(q = qs.left.left; q != nil; q = q.right)
+ q.left = fold(q.left);
+
+ d := popscope();
+ d.refs++;
+ if(d.next != nil)
+ fatal("pickcheck: installing more than one id");
+ fndecls = appdecls(fndecls, d);
+
+ if(!ok)
+ return;
+
+ c := checklabels(n.right, tint, nlab, "pick qualifier");
+ t = mktype(n.src.start, n.src.stop, Tcase, nil, nil);
+ n.ty = t;
+ t.cse = c;
+}
+
+exccheck(en: ref Node, ret: ref Type)
+{
+ ed: ref Decl;
+ wild: ref Node;
+ qt: ref Type;
+
+ pushscope(nil, Sother);
+ if(en.left == nil)
+ en.left = mkdeclname(en.src, mkids(en.src, enter(".ex"+string nexc++, 0), texception, nil));
+ oinexcept := inexcept;
+ inexcept = en.left;
+ dasdecl(en.left);
+ en.left.ty = en.left.decl.ty = texception;
+ ed = en.left.decl;
+ # en.right = scheck(en.right, ret, Sother);
+ t := tstring;
+ wild = nil;
+ nlab := 0;
+ ok := 1;
+ for(n := en.right; n != nil; n = n.right){
+ qt = nil;
+ for(q := n.left.left; q != nil; q = q.right){
+ left := q.left;
+ case left.op{
+ Owild =>
+ left.ty = texception;
+ if(wild != nil)
+ nerror(left, "exception qualifier * duplicated on line "+lineconv(wild.src.start));
+ wild = left;
+ Orange =>
+ left.ty = tnone;
+ nerror(left, "exception qualifier "+expconv(left)+" is illegal");
+ ok = 0;
+ * =>
+ (rok, nil) := echeck(left, 0, 0, nil);
+ if(!rok){
+ ok = 0;
+ break;
+ }
+ left = q.left = fold(left);
+ if(left.ty != t && left.ty.kind != Texception){
+ nerror(left, "exception qualifier "+etconv(left)+" is not a string or exception");
+ ok = 0;
+ }else if(left.op != Oconst){
+ nerror(left, "exception qualifier "+expconv(left)+" is not constant");
+ ok = 0;
+ }
+ else if(left.ty != t)
+ left.ty = mkextype(left.ty);
+ nlab++;
+ }
+
+ if(qt == nil)
+ qt = left.ty;
+ else if(!tequal(qt, left.ty))
+ qt = texception;
+ }
+
+ if(qt != nil)
+ ed.ty = qt;
+ n.left.right = scheck(n.left.right, ret, Sother);
+ if(n.left.right.right == nil)
+ nwarn(n.left.left, "no body for exception qualifier " + expconv(n.left.left));
+ }
+ ed.ty = texception;
+ inexcept = oinexcept;
+ if(!ok)
+ return;
+ c := checklabels(en.right, texception, nlab, "exception qualifier");
+ t = mktype(en.src.start, en.src.stop, Texcept, nil, nil);
+ en.ty = t;
+ t.cse = c;
+ ed = popscope();
+ fndecls = appdecls(fndecls, ed);
+}
+
+#
+# check array and case labels for validity
+#
+checklabels(inits: ref Node, ctype: ref Type, nlab: int, title: string): ref Case
+{
+ n, q, wild: ref Node;
+
+ labs := array[nlab] of Label;
+ i := 0;
+ wild = nil;
+ for(n = inits; n != nil; n = n.right){
+ for(q = n.left.left; q != nil; q = q.right){
+ case q.left.op{
+ Oconst =>
+ labs[i].start = q.left;
+ labs[i].stop = q.left;
+ labs[i++].node = n.left;
+ Orange =>
+ labs[i].start = q.left.left;
+ labs[i].stop = q.left.right;
+ labs[i++].node = n.left;
+ Owild =>
+ wild = n.left;
+ * =>
+ fatal("bogus index in checklabels");
+ }
+ }
+ }
+
+ if(i != nlab)
+ fatal("bad label count: "+string nlab+" then "+string i);
+
+ casesort(ctype, array[nlab] of Label, labs, 0, nlab);
+ for(i = 0; i < nlab; i++){
+ p := labs[i].stop;
+ if(casecmp(ctype, labs[i].start, p) > 0)
+ nerror(labs[i].start, "unmatchable "+title+" "+expconv(labs[i].node));
+ for(e := i + 1; e < nlab; e++){
+ if(casecmp(ctype, labs[e].start, p) <= 0)
+ nerror(labs[e].start, title+" '"+eprintlist(labs[e].node.left, " or ")
+ +"' overlaps with '"+eprintlist(labs[e-1].node.left, " or ")+"' on line "
+ +lineconv(p.src.start));
+
+ #
+ # check for merging case labels
+ #
+ if(ctype != tint
+ || labs[e].start.c.val != p.c.val+big 1
+ || labs[e].node != labs[i].node)
+ break;
+ p = labs[e].stop;
+ }
+ if(e != i + 1){
+ labs[i].stop = p;
+ labs[i+1:] = labs[e:nlab];
+ nlab -= e - (i + 1);
+ }
+ }
+
+ c := ref Case;
+ c.nlab = nlab;
+ c.nsnd = 0;
+ c.labs = labs;
+ c.wild = wild;
+
+ return c;
+}
+
+symcmp(a: ref Sym, b: ref Sym): int
+{
+ if(a.name < b.name)
+ return -1;
+ if(a.name > b.name)
+ return 1;
+ return 0;
+}
+
+matchcmp(na: ref Node, nb: ref Node): int
+{
+ a := na.decl.sym;
+ b := nb.decl.sym;
+ la := len a.name;
+ lb := len b.name;
+ sa := la > 0 && a.name[la-1] == '*';
+ sb := lb > 0 && b.name[lb-1] == '*';
+ if(sa){
+ if(sb){
+ if(la == lb)
+ return symcmp(a, b);
+ return lb-la;
+ }
+ else
+ return 1;
+ }
+ else{
+ if(sb)
+ return -1;
+ else{
+ if(na.ty == tstring){
+ if(nb.ty == tstring)
+ return symcmp(a, b);
+ else
+ return 1;
+ }
+ else{
+ if(nb.ty == tstring)
+ return -1;
+ else
+ return symcmp(a, b);
+ }
+ }
+ }
+}
+
+casecmp(ty: ref Type, a, b: ref Node): int
+{
+ if(ty == tint || ty == tbig){
+ if(a.c.val < b.c.val)
+ return -1;
+ if(a.c.val > b.c.val)
+ return 1;
+ return 0;
+ }
+ if(ty == texception)
+ return matchcmp(a, b);
+ return symcmp(a.decl.sym, b.decl.sym);
+}
+
+casesort(t: ref Type, aux, labs: array of Label, start, stop: int)
+{
+ n := stop - start;
+ if(n <= 1)
+ return;
+ top := mid := start + n / 2;
+
+ casesort(t, aux, labs, start, top);
+ casesort(t, aux, labs, mid, stop);
+
+ #
+ # merge together two sorted label arrays, yielding a sorted array
+ #
+ n = 0;
+ base := start;
+ while(base < top && mid < stop){
+ if(casecmp(t, labs[base].start, labs[mid].start) <= 0)
+ aux[n++] = labs[base++];
+ else
+ aux[n++] = labs[mid++];
+ }
+ if(base < top)
+ aux[n:] = labs[base:top];
+ else if(mid < stop)
+ aux[n:] = labs[mid:stop];
+ labs[start:] = aux[:stop-start];
+}
+
+#
+# binary search for the label corresponding to a given value
+#
+findlab(ty: ref Type, v: ref Node, labs: array of Label, nlab: int): int
+{
+ if(nlab <= 1)
+ return 0;
+ m : int;
+ l := 1;
+ r := nlab - 1;
+ while(l <= r){
+ m = (r + l) / 2;
+ if(casecmp(ty, labs[m].start, v) <= 0)
+ l = m + 1;
+ else
+ r = m - 1;
+ }
+ m = l - 1;
+ if(casecmp(ty, labs[m].start, v) > 0
+ || casecmp(ty, labs[m].stop, v) < 0)
+ fatal("findlab out of range");
+ return m;
+}
+
+altcheck(an: ref Node, ret: ref Type)
+{
+ n, q, left, op, wild: ref Node;
+
+ an.left = scheck(an.left, ret, Sother);
+
+ ok := 1;
+ nsnd := 0;
+ nrcv := 0;
+ wild = nil;
+ for(n = an.left; n != nil; n = n.right){
+ q = n.left.right.left;
+ if(n.left.right.right == nil)
+ nwarn(q, "no body for alt guard "+expconv(q));
+ for(; q != nil; q = q.right){
+ left = q.left;
+ case left.op{
+ Owild =>
+ if(wild != nil)
+ nerror(left, "alt guard * duplicated on line "+lineconv(wild.src.start));
+ wild = left;
+ Orange =>
+ nerror(left, "alt guard "+expconv(left)+" is illegal");
+ ok = 0;
+ * =>
+ op = hascomm(left);
+ if(op == nil){
+ nerror(left, "alt guard "+expconv(left)+" has no communication");
+ ok = 0;
+ break;
+ }
+ if(op.op == Osnd)
+ nsnd++;
+ else
+ nrcv++;
+ }
+ }
+ }
+
+ if(!ok)
+ return;
+
+ c := ref Case;
+ c.nlab = nsnd + nrcv;
+ c.nsnd = nsnd;
+ c.wild = wild;
+
+ an.ty = mktalt(c);
+}
+
+hascomm(n: ref Node): ref Node
+{
+ if(n == nil)
+ return nil;
+ if(n.op == Osnd || n.op == Orcv)
+ return n;
+ r := hascomm(n.left);
+ if(r != nil)
+ return r;
+ return hascomm(n.right);
+}
+
+raisescheck(t: ref Type)
+{
+ if(t.kind != Tfn)
+ return;
+ n := t.eraises;
+ for(nn := n.left; nn != nil; nn = nn.right){
+ (ok, nil) := echeck(nn.left, 0, 0, nil);
+ if(ok && nn.left.ty.kind != Texception)
+ nerror(n, expconv(nn.left) + ": illegal raises expression");
+ }
+}
+
+Elist: adt{
+ d: ref Decl;
+ nxt: cyclic ref Elist;
+};
+
+emerge(el1: ref Elist, el2: ref Elist): ref Elist
+{
+ f: int;
+ el, nxt: ref Elist;
+
+ for( ; el1 != nil; el1 = nxt){
+ f = 0;
+ for(el = el2; el != nil; el = el.nxt){
+ if(el1.d == el.d){
+ f = 1;
+ break;
+ }
+ }
+ nxt = el1.nxt;
+ if(!f){
+ el1.nxt = el2;
+ el2 = el1;
+ }
+ }
+ return el2;
+}
+
+equals(n: ref Node): ref Elist
+{
+ q, nn: ref Node;
+ e, el: ref Elist;
+
+ el = nil;
+ for(q = n.left.left; q != nil; q = q.right){
+ nn = q.left;
+ if(nn.op == Owild)
+ return nil;
+ if(nn.ty.kind != Texception)
+ continue;
+ e = ref Elist(nn.decl, el);
+ el = e;
+ }
+ return el;
+}
+
+caught(d: ref Decl, n: ref Node): int
+{
+ q, nn: ref Node;
+
+ for(n = n.right; n != nil; n = n.right){
+ for(q = n.left.left; q != nil; q = q.right){
+ nn = q.left;
+ if(nn.op == Owild)
+ return 1;
+ if(nn.ty.kind != Texception)
+ continue;
+ if(d == nn.decl)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+raisecheck(n: ref Node, ql: ref Elist): ref Elist
+{
+ exc: int;
+ e: ref Node;
+ el, nel, nxt: ref Elist;
+
+ if(n == nil)
+ return nil;
+ el = nil;
+ for(; n != nil; n = n.right){
+ case(n.op){
+ Oscope =>
+ return raisecheck(n.right, ql);
+ Olabel or
+ Odo =>
+ return raisecheck(n.right, ql);
+ Oif or
+ Ofor =>
+ return emerge(raisecheck(n.right.left, ql),
+ raisecheck(n.right.right, ql));
+ Oalt or
+ Ocase or
+ Opick or
+ Oexcept =>
+ exc = n.op == Oexcept;
+ for(n = n.right; n != nil; n = n.right){
+ ql = nil;
+ if(exc)
+ ql = equals(n);
+ el = emerge(raisecheck(n.left.right, ql), el);
+ }
+ return el;
+ Oseq =>
+ el = emerge(raisecheck(n.left, ql), el);
+ break;
+ Oexstmt =>
+ el = raisecheck(n.left, ql);
+ nel = nil;
+ for( ; el != nil; el = nxt){
+ nxt = el.nxt;
+ if(!caught(el.d, n.right)){
+ el.nxt = nel;
+ nel = el;
+ }
+ }
+ return emerge(nel, raisecheck(n.right, ql));
+ Oraise =>
+ e = n.left;
+ if(e.ty != nil && e.ty.kind == Texception){
+ if(e.ty.cons == byte 0)
+ return ql;
+ if(e.op == Ocall)
+ e = e.left;
+ if(e.op == Omdot)
+ e = e.right;
+ if(e.op != Oname)
+ fatal("exception " + nodeconv(e) + " not a name");
+ el = ref Elist(e.decl, nil);
+ return el;
+ }
+ return nil;
+ * =>
+ return nil;
+ }
+ }
+ return el;
+}
+
+checkraises(n: ref Node)
+{
+ f: int;
+ d: ref Decl;
+ e, el: ref Elist;
+ es, nn: ref Node;
+
+ el = raisecheck(n.right, nil);
+ es = n.ty.eraises;
+ if(es != nil){
+ for(nn = es.left; nn != nil; nn = nn.right){
+ d = nn.left.decl;
+ f = 0;
+ for(e = el; e != nil; e = e.nxt){
+ if(d == e.d){
+ f = 1;
+ e.d = nil;
+ break;
+ }
+ }
+ if(!f)
+ nwarn(n, "function " + expconv(n.left) + " does not raise " + d.sym.name + " but declared");
+ }
+ }
+ for(e = el; e != nil; e = e.nxt)
+ if(e.d != nil)
+ nwarn(n, "function " + expconv(n.left) + " raises " + e.d.sym.name + " but not declared");
+}
+
+# sort all globals in modules now that we've finished with 'last' pointers
+# and before any code generation
+#
+gsort(n: ref Node)
+{
+ for(;;){
+ if(n == nil)
+ return;
+ if(n.op != Oseq)
+ break;
+ gsort(n.left);
+ n = n.right;
+ }
+ if(n.op == Omoddecl && int (n.ty.ok & OKverify)){
+ n.ty.ids = namesort(n.ty.ids);
+ sizeids(n.ty.ids, 0);
+ }
+}
diff --git a/appl/cmd/limbo/types.b b/appl/cmd/limbo/types.b
new file mode 100644
index 00000000..8be8f16d
--- /dev/null
+++ b/appl/cmd/limbo/types.b
@@ -0,0 +1,4234 @@
+
+kindname := array [Tend] of
+{
+ Tnone => "no type",
+ Tadt => "adt",
+ Tadtpick => "adt",
+ Tarray => "array",
+ Tbig => "big",
+ Tbyte => "byte",
+ Tchan => "chan",
+ Treal => "real",
+ Tfn => "fn",
+ Tint => "int",
+ Tlist => "list",
+ Tmodule => "module",
+ Tref => "ref",
+ Tstring => "string",
+ Ttuple => "tuple",
+ Texception => "exception",
+ Tfix => "fixed point",
+ Tpoly => "polymorphic",
+
+ Tainit => "array initializers",
+ Talt => "alt channels",
+ Tany => "polymorphic type",
+ Tarrow => "->",
+ Tcase => "case int labels",
+ Tcasel => "case big labels",
+ Tcasec => "case string labels",
+ Tdot => ".",
+ Terror => "type error",
+ Tgoto => "goto labels",
+ Tid => "id",
+ Tiface => "module interface",
+ Texcept => "exception handler table",
+ Tinst => "instantiated type",
+};
+
+tattr = array[Tend] of
+{
+ # isptr refable conable big vis
+ Tnone => Tattr(0, 0, 0, 0, 0),
+ Tadt => Tattr(0, 1, 1, 1, 1),
+ Tadtpick => Tattr(0, 1, 0, 1, 1),
+ Tarray => Tattr(1, 0, 0, 0, 1),
+ Tbig => Tattr(0, 0, 1, 1, 1),
+ Tbyte => Tattr(0, 0, 1, 0, 1),
+ Tchan => Tattr(1, 0, 0, 0, 1),
+ Treal => Tattr(0, 0, 1, 1, 1),
+ Tfn => Tattr(0, 1, 0, 0, 1),
+ Tint => Tattr(0, 0, 1, 0, 1),
+ Tlist => Tattr(1, 0, 0, 0, 1),
+ Tmodule => Tattr(1, 0, 0, 0, 1),
+ Tref => Tattr(1, 0, 0, 0, 1),
+ Tstring => Tattr(1, 0, 1, 0, 1),
+ Ttuple => Tattr(0, 1, 1, 1, 1),
+ Texception => Tattr(0, 0, 0, 1, 1),
+ Tfix => Tattr(0, 0, 1, 0, 1),
+ Tpoly => Tattr(1, 0, 0, 0, 1),
+
+ Tainit => Tattr(0, 0, 0, 1, 0),
+ Talt => Tattr(0, 0, 0, 1, 0),
+ Tany => Tattr(1, 0, 0, 0, 0),
+ Tarrow => Tattr(0, 0, 0, 0, 1),
+ Tcase => Tattr(0, 0, 0, 1, 0),
+ Tcasel => Tattr(0, 0, 0, 1, 0),
+ Tcasec => Tattr(0, 0, 0, 1, 0),
+ Tdot => Tattr(0, 0, 0, 0, 1),
+ Terror => Tattr(0, 1, 1, 0, 0),
+ Tgoto => Tattr(0, 0, 0, 1, 0),
+ Tid => Tattr(0, 0, 0, 0, 1),
+ Tiface => Tattr(0, 0, 0, 1, 0),
+ Texcept => Tattr(0, 0, 0, 1, 0),
+ Tinst => Tattr(0, 1, 1, 1, 1),
+};
+
+eqclass: array of ref Teq;
+
+ztype: Type;
+eqrec: int;
+eqset: int;
+adts: array of ref Decl;
+nadts: int;
+anontupsym: ref Sym;
+unifysrc: Src;
+
+addtmap(t1: ref Type, t2: ref Type, tph: ref Tpair): ref Tpair
+{
+ tp: ref Tpair;
+
+ tp = ref Tpair;
+ tp.t1 = t1;
+ tp.t2 = t2;
+ tp.nxt = tph;
+ return tp;
+}
+
+valtmap(t: ref Type, tp: ref Tpair): ref Type
+{
+ for( ; tp != nil; tp = tp.nxt)
+ if(tp.t1 == t)
+ return tp.t2;
+ return t;
+}
+
+addtype(t: ref Type, hdl: ref Typelist): ref Typelist
+{
+ tll := ref Typelist;
+ tll.t = t;
+ tll.nxt = nil;
+ if(hdl == nil)
+ return tll;
+ for(p := hdl; p.nxt != nil; p = p.nxt)
+ ;
+ p.nxt = tll;
+ return hdl;
+}
+
+typeinit()
+{
+ anontupsym = enter(".tuple", 0);
+
+ ztype.sbl = -1;
+ ztype.ok = byte 0;
+ ztype.rec = byte 0;
+
+ tbig = mktype(noline, noline, Tbig, nil, nil);
+ tbig.size = IBY2LG;
+ tbig.align = IBY2LG;
+ tbig.ok = OKmask;
+
+ tbyte = mktype(noline, noline, Tbyte, nil, nil);
+ tbyte.size = 1;
+ tbyte.align = 1;
+ tbyte.ok = OKmask;
+
+ tint = mktype(noline, noline, Tint, nil, nil);
+ tint.size = IBY2WD;
+ tint.align = IBY2WD;
+ tint.ok = OKmask;
+
+ treal = mktype(noline, noline, Treal, nil, nil);
+ treal.size = IBY2FT;
+ treal.align = IBY2FT;
+ treal.ok = OKmask;
+
+ tstring = mktype(noline, noline, Tstring, nil, nil);
+ tstring.size = IBY2WD;
+ tstring.align = IBY2WD;
+ tstring.ok = OKmask;
+
+ texception = mktype(noline, noline, Texception, nil, nil);
+ texception.size = IBY2WD;
+ texception.align = IBY2WD;
+ texception.ok = OKmask;
+
+ tany = mktype(noline, noline, Tany, nil, nil);
+ tany.size = IBY2WD;
+ tany.align = IBY2WD;
+ tany.ok = OKmask;
+
+ tnone = mktype(noline, noline, Tnone, nil, nil);
+ tnone.size = 0;
+ tnone.align = 1;
+ tnone.ok = OKmask;
+
+ terror = mktype(noline, noline, Terror, nil, nil);
+ terror.size = 0;
+ terror.align = 1;
+ terror.ok = OKmask;
+
+ tunknown = mktype(noline, noline, Terror, nil, nil);
+ tunknown.size = 0;
+ tunknown.align = 1;
+ tunknown.ok = OKmask;
+
+ tfnptr = mktype(noline, noline, Ttuple, nil, nil);
+ id := tfnptr.ids = mkids(nosrc, nil, tany, nil);
+ id.store = Dfield;
+ id.offset = 0;
+ id.sym = enter("t0", 0);
+ id.src = Src(0, 0);
+ id = tfnptr.ids.next = mkids(nosrc, nil, tint, nil);
+ id.store = Dfield;
+ id.offset = IBY2WD;
+ id.sym = enter("t1", 0);
+ id.src = Src(0, 0);
+
+ rtexception = mktype(noline, noline, Tref, texception, nil);
+ rtexception.size = IBY2WD;
+ rtexception.align = IBY2WD;
+ rtexception.ok = OKmask;
+}
+
+typestart()
+{
+ descriptors = nil;
+ nfns = 0;
+ adts = nil;
+ nadts = 0;
+ selfdecl = nil;
+ if(tfnptr.decl != nil)
+ tfnptr.decl.desc = nil;
+
+ eqclass = array[Tend] of ref Teq;
+
+ typebuiltin(mkids(nosrc, enter("int", 0), nil, nil), tint);
+ typebuiltin(mkids(nosrc, enter("big", 0), nil, nil), tbig);
+ typebuiltin(mkids(nosrc, enter("byte", 0), nil, nil), tbyte);
+ typebuiltin(mkids(nosrc, enter("string", 0), nil, nil), tstring);
+ typebuiltin(mkids(nosrc, enter("real", 0), nil, nil), treal);
+}
+
+modclass(): ref Teq
+{
+ return eqclass[Tmodule];
+}
+
+mktype(start: Line, stop: Line, kind: int, tof: ref Type, args: ref Decl): ref Type
+{
+ t := ref ztype;
+ t.src.start = start;
+ t.src.stop = stop;
+ t.kind = kind;
+ t.tof = tof;
+ t.ids = args;
+ return t;
+}
+
+nalt: int;
+mktalt(c: ref Case): ref Type
+{
+ t := mktype(noline, noline, Talt, nil, nil);
+ t.decl = mkdecl(nosrc, Dtype, t);
+ t.decl.sym = enter(".a"+string nalt++, 0);
+ t.cse = c;
+ return usetype(t);
+}
+
+#
+# copy t and the top level of ids
+#
+copytypeids(t: ref Type): ref Type
+{
+ last: ref Decl;
+
+ nt := ref *t;
+ for(id := t.ids; id != nil; id = id.next){
+ new := ref *id;
+ if(last == nil)
+ nt.ids = new;
+ else
+ last.next = new;
+ last = new;
+ }
+ return nt;
+}
+
+#
+# make each of the ids have type t
+#
+typeids(ids: ref Decl, t: ref Type): ref Decl
+{
+ if(ids == nil)
+ return nil;
+
+ ids.ty = t;
+ for(id := ids.next; id != nil; id = id.next)
+ id.ty = t;
+ return ids;
+}
+
+typebuiltin(d: ref Decl, t: ref Type)
+{
+ d.ty = t;
+ t.decl = d;
+ installids(Dtype, d);
+}
+
+fielddecl(store: int, ids: ref Decl): ref Node
+{
+ n := mkn(Ofielddecl, nil, nil);
+ n.decl = ids;
+ for(; ids != nil; ids = ids.next)
+ ids.store = store;
+ return n;
+}
+
+typedecl(ids: ref Decl, t: ref Type): ref Node
+{
+ if(t.decl == nil)
+ t.decl = ids;
+ n := mkn(Otypedecl, nil, nil);
+ n.decl = ids;
+ n.ty = t;
+ for(; ids != nil; ids = ids.next)
+ ids.ty = t;
+ return n;
+}
+
+typedecled(n: ref Node)
+{
+ installids(Dtype, n.decl);
+}
+
+adtdecl(ids: ref Decl, fields: ref Node): ref Node
+{
+ n := mkn(Oadtdecl, nil, nil);
+ t := mktype(ids.src.start, ids.src.stop, Tadt, nil, nil);
+ n.decl = ids;
+ n.left = fields;
+ n.ty = t;
+ t.decl = ids;
+ for(; ids != nil; ids = ids.next)
+ ids.ty = t;
+ return n;
+}
+
+adtdecled(n: ref Node)
+{
+ d := n.ty.decl;
+ installids(Dtype, d);
+ if(n.ty.polys != nil){
+ pushscope(nil, Sother);
+ installids(Dtype, n.ty.polys);
+ }
+ pushscope(nil, Sother);
+ fielddecled(n.left);
+ n.ty.ids = popscope();
+ if(n.ty.polys != nil)
+ n.ty.polys = popscope();
+ for(ids := n.ty.ids; ids != nil; ids = ids.next)
+ ids.dot = d;
+}
+
+fielddecled(n: ref Node)
+{
+ for(; n != nil; n = n.right){
+ case n.op{
+ Oseq =>
+ fielddecled(n.left);
+ Oadtdecl =>
+ adtdecled(n);
+ return;
+ Otypedecl =>
+ typedecled(n);
+ return;
+ Ofielddecl =>
+ installids(Dfield, n.decl);
+ return;
+ Ocondecl =>
+ condecled(n);
+ gdasdecl(n.right);
+ return;
+ Oexdecl =>
+ exdecled(n);
+ return;
+ Opickdecl =>
+ pickdecled(n);
+ return;
+ * =>
+ fatal("can't deal with "+opname[n.op]+" in fielddecled");
+ }
+ }
+}
+
+pickdecled(n: ref Node): int
+{
+ if(n == nil)
+ return 0;
+ tag := pickdecled(n.left);
+ pushscope(nil, Sother);
+ fielddecled(n.right.right);
+ d := n.right.left.decl;
+ d.ty.ids = popscope();
+ installids(Dtag, d);
+ for(; d != nil; d = d.next)
+ d.tag = tag++;
+ return tag;
+}
+
+#
+# make the tuple type used to initialize adt t
+#
+mkadtcon(t: ref Type): ref Type
+{
+ last: ref Decl;
+
+ nt := ref *t;
+ nt.ids = nil;
+ nt.kind = Ttuple;
+ for(id := t.ids; id != nil; id = id.next){
+ if(id.store != Dfield)
+ continue;
+ new := ref *id;
+ new.cyc = byte 0;
+ if(last == nil)
+ nt.ids = new;
+ else
+ last.next = new;
+ last = new;
+ }
+ last.next = nil;
+ return nt;
+}
+
+#
+# make the tuple type used to initialize t,
+# an adt with pick fields tagged by tg
+#
+mkadtpickcon(t, tgt: ref Type): ref Type
+{
+ last := mkids(tgt.decl.src, nil, tint, nil);
+ last.store = Dfield;
+ nt := mktype(t.src.start, t.src.stop, Ttuple, nil, last);
+ for(id := t.ids; id != nil; id = id.next){
+ if(id.store != Dfield)
+ continue;
+ new := ref *id;
+ new.cyc = byte 0;
+ last.next = new;
+ last = new;
+ }
+ for(id = tgt.ids; id != nil; id = id.next){
+ if(id.store != Dfield)
+ continue;
+ new := ref *id;
+ new.cyc = byte 0;
+ last.next = new;
+ last = new;
+ }
+ last.next = nil;
+ return nt;
+}
+
+#
+# make an identifier type
+#
+mkidtype(src: Src, s: ref Sym): ref Type
+{
+ t := mktype(src.start, src.stop, Tid, nil, nil);
+ if(s.unbound == nil){
+ s.unbound = mkdecl(src, Dunbound, nil);
+ s.unbound.sym = s;
+ }
+ t.decl = s.unbound;
+ return t;
+}
+
+#
+# make a qualified type for t->s
+#
+mkarrowtype(start: Line, stop: Line, t: ref Type, s: ref Sym): ref Type
+{
+ t = mktype(start, stop, Tarrow, t, nil);
+ if(s.unbound == nil){
+ s.unbound = mkdecl(Src(start, stop), Dunbound, nil);
+ s.unbound.sym = s;
+ }
+ t.decl = s.unbound;
+ return t;
+}
+
+#
+# make a qualified type for t.s
+#
+mkdottype(start: Line, stop: Line, t: ref Type, s: ref Sym): ref Type
+{
+ t = mktype(start, stop, Tdot, t, nil);
+ if(s.unbound == nil){
+ s.unbound = mkdecl(Src(start, stop), Dunbound, nil);
+ s.unbound.sym = s;
+ }
+ t.decl = s.unbound;
+ return t;
+}
+
+mkinsttype(src: Src, tt: ref Type, tyl: ref Typelist): ref Type
+{
+ t := mktype(src.start, src.stop, Tinst, tt, nil);
+ t.tlist = tyl;
+ return t;
+}
+
+#
+# look up the name f in the fields of a module, adt, or tuple
+#
+namedot(ids: ref Decl, s: ref Sym): ref Decl
+{
+ for(; ids != nil; ids = ids.next)
+ if(ids.sym == s)
+ return ids;
+ return nil;
+}
+
+#
+# complete the declaration of an adt
+# methods frames get sized in module definition or during function definition
+# place the methods at the end of the field list
+#
+adtdefd(t: ref Type)
+{
+ next, aux, store, auxhd, tagnext: ref Decl;
+
+ if(debug['x'])
+ print("adt %s defd\n", typeconv(t));
+ d := t.decl;
+ tagnext = nil;
+ store = nil;
+ for(id := t.polys; id != nil; id = id.next){
+ id.store = Dtype;
+ id.ty = verifytypes(id.ty, d, nil);
+ }
+ for(id = t.ids; id != nil; id = next){
+ if(id.store == Dtag){
+ if(t.tags != nil)
+ error(id.src.start, "only one set of pick fields allowed");
+ tagnext = pickdefd(t, id);
+ next = tagnext;
+ if(store != nil)
+ store.next = next;
+ else
+ t.ids = next;
+ continue;
+ }else{
+ id.dot = d;
+ next = id.next;
+ store = id;
+ }
+ }
+ aux = nil;
+ store = nil;
+ auxhd = nil;
+ seentags := 0;
+ for(id = t.ids; id != nil; id = next){
+ if(id == tagnext)
+ seentags = 1;
+
+ next = id.next;
+ id.dot = d;
+ id.ty = topvartype(verifytypes(id.ty, d, nil), id, 1, 1);
+ if(id.store == Dfield && id.ty.kind == Tfn)
+ id.store = Dfn;
+ if(id.store == Dfn || id.store == Dconst){
+ if(store != nil)
+ store.next = next;
+ else
+ t.ids = next;
+ if(aux != nil)
+ aux.next = id;
+ else
+ auxhd = id;
+ aux = id;
+ }else{
+ if(seentags)
+ error(id.src.start, "pick fields must be the last data fields in an adt");
+ store = id;
+ }
+ }
+ if(aux != nil)
+ aux.next = nil;
+ if(store != nil)
+ store.next = auxhd;
+ else
+ t.ids = auxhd;
+
+ for(id = t.tags; id != nil; id = id.next){
+ id.ty = verifytypes(id.ty, d, nil);
+ if(id.ty.tof == nil)
+ id.ty.tof = mkadtpickcon(t, id.ty);
+ }
+}
+
+#
+# assemble the data structure for an adt with a pick clause.
+# since the scoping rules for adt pick fields are strange,
+# we have a cutomized check for overlapping defitions.
+#
+pickdefd(t: ref Type, tg: ref Decl): ref Decl
+{
+ lasttg : ref Decl = nil;
+ d := t.decl;
+ t.tags = tg;
+ tag := 0;
+ while(tg != nil){
+ tt := tg.ty;
+ if(tt.kind != Tadtpick || tg.tag != tag)
+ break;
+ tt.decl = tg;
+ lasttg = tg;
+ for(; tg != nil; tg = tg.next){
+ if(tg.ty != tt)
+ break;
+ tag++;
+ lasttg = tg;
+ tg.dot = d;
+ }
+ for(id := tt.ids; id != nil; id = id.next){
+ xid := namedot(t.ids, id.sym);
+ if(xid != nil)
+ error(id.src.start, "redeclaration of "+declconv(id)+
+ " previously declared as "+storeconv(xid)+" on line "+lineconv(xid.src.start));
+ id.dot = d;
+ }
+ }
+ if(lasttg == nil){
+ error(t.src.start, "empty pick field declaration in "+typeconv(t));
+ t.tags = nil;
+ }else
+ lasttg.next = nil;
+ d.tag = tag;
+ return tg;
+}
+
+moddecl(ids: ref Decl, fields: ref Node): ref Node
+{
+ n := mkn(Omoddecl, mkn(Oseq, nil, nil), nil);
+ t := mktype(ids.src.start, ids.src.stop, Tmodule, nil, nil);
+ n.decl = ids;
+ n.left = fields;
+ n.ty = t;
+ return n;
+}
+
+moddecled(n: ref Node)
+{
+ d := n.decl;
+ installids(Dtype, d);
+ isimp := 0;
+ for(ids := d; ids != nil; ids = ids.next){
+ for(im := impmods; im != nil; im = im.next){
+ if(ids.sym == im.sym){
+ isimp = 1;
+ d = ids;
+ dm := ref Dlist;
+ dm.d = ids;
+ dm.next = nil;
+ if(impdecls == nil)
+ impdecls = dm;
+ else{
+ for(dl := impdecls; dl.next != nil; dl = dl.next)
+ ;
+ dl.next = dm;
+ }
+ }
+ }
+ ids.ty = n.ty;
+ }
+ pushscope(nil, Sother);
+ fielddecled(n.left);
+
+ d.ty.ids = popscope();
+
+ #
+ # make the current module the . parent of all contained decls.
+ #
+ for(ids = d.ty.ids; ids != nil; ids = ids.next)
+ ids.dot = d;
+
+ t := d.ty;
+ t.decl = d;
+ if(debug['m'])
+ print("declare module %s\n", d.sym.name);
+
+ #
+ # add the iface declaration in case it's needed later
+ #
+ installids(Dglobal, mkids(d.src, enter(".m."+d.sym.name, 0), tnone, nil));
+
+ if(isimp){
+ for(ids = d.ty.ids; ids != nil; ids = ids.next){
+ s := ids.sym;
+ if(s.decl != nil && s.decl.scope >= scope){
+ dot := s.decl.dot;
+ if(s.decl.store != Dwundef && dot != nil && dot != d && isimpmod(dot.sym) && dequal(ids, s.decl, 0))
+ continue;
+ redecl(ids);
+ ids.old = s.decl.old;
+ }else
+ ids.old = s.decl;
+ s.decl = ids;
+ ids.scope = scope;
+ }
+ }
+}
+
+#
+# for each module in id,
+# link by field ext all of the decls for
+# functions needed in external linkage table
+# collect globals and make a tuple for all of them
+#
+mkiface(m: ref Decl): ref Type
+{
+ iface := last := ref Decl;
+ globals := glast := mkdecl(m.src, Dglobal, mktype(m.src.start, m.src.stop, Tadt, nil, nil));
+ for(id := m.ty.ids; id != nil; id = id.next){
+ case id.store{
+ Dglobal =>
+ glast = glast.next = dupdecl(id);
+ id.iface = globals;
+ glast.iface = id;
+ Dfn =>
+ id.iface = last = last.next = dupdecl(id);
+ last.iface = id;
+ Dtype =>
+ if(id.ty.kind != Tadt)
+ break;
+ for(d := id.ty.ids; d != nil; d = d.next){
+ if(d.store == Dfn){
+ d.iface = last = last.next = dupdecl(d);
+ last.iface = d;
+ }
+ }
+ }
+ }
+ last.next = nil;
+ iface = namesort(iface.next);
+
+ if(globals.next != nil){
+ glast.next = nil;
+ globals.ty.ids = namesort(globals.next);
+ globals.ty.decl = globals;
+ globals.sym = enter(".mp", 0);
+ globals.dot = m;
+ globals.next = iface;
+ iface = globals;
+ }
+
+ #
+ # make the interface type and install an identifier for it
+ # the iface has a ref count if it is loaded
+ #
+ t := mktype(m.src.start, m.src.stop, Tiface, nil, iface);
+ id = enter(".m."+m.sym.name, 0).decl;
+ t.decl = id;
+ id.ty = t;
+
+ #
+ # dummy node so the interface is initialized
+ #
+ id.init = mkn(Onothing, nil, nil);
+ id.init.ty = t;
+ id.init.decl = id;
+ return t;
+}
+
+joiniface(mt, t: ref Type)
+{
+ iface := t.ids;
+ globals := iface;
+ if(iface != nil && iface.store == Dglobal)
+ iface = iface.next;
+ for(id := mt.tof.ids; id != nil; id = id.next){
+ case id.store{
+ Dglobal =>
+ for(d := id.ty.ids; d != nil; d = d.next)
+ d.iface.iface = globals;
+ Dfn =>
+ id.iface.iface = iface;
+ iface = iface.next;
+ * =>
+ fatal("unknown store "+storeconv(id)+" in joiniface");
+ }
+ }
+ if(iface != nil)
+ fatal("join iface not matched");
+ mt.tof = t;
+}
+
+addiface(m: ref Decl, d: ref Decl)
+{
+ t: ref Type;
+ id, last, dd, lastorig: ref Decl;
+
+ if(d == nil || !local(d))
+ return;
+ modrefable(d.ty);
+ if(m == nil){
+ if(impdecls.next != nil)
+ for(dl := impdecls; dl != nil; dl = dl.next)
+ if(dl.d.ty.tof != impdecl.ty.tof) # impdecl last
+ addiface(dl.d, d);
+ addiface(impdecl, d);
+ return;
+ }
+ t = m.ty.tof;
+ last = nil;
+ lastorig = nil;
+ for(id = t.ids; id != nil; id = id.next){
+ if(d == id || d == id.iface)
+ return;
+ last = id;
+ if(id.tag == 0)
+ lastorig = id;
+ }
+ dd = dupdecl(d);
+ if(d.dot == nil)
+ d.dot = dd.dot = m;
+ d.iface = dd;
+ dd.iface = d;
+ if(last == nil)
+ t.ids = dd;
+ else
+ last.next = dd;
+ dd.tag = 1; # mark so not signed
+ if(lastorig == nil)
+ t.ids = namesort(t.ids);
+ else
+ lastorig.next = namesort(lastorig.next);
+}
+
+#
+# eliminate unused declarations from interfaces
+# label offset within interface
+#
+narrowmods()
+{
+ id: ref Decl;
+ for(eq := modclass(); eq != nil; eq = eq.eq){
+ t := eq.ty.tof;
+
+ if(t.linkall == byte 0){
+ last : ref Decl = nil;
+ for(id = t.ids; id != nil; id = id.next){
+ if(id.refs == 0){
+ if(last == nil)
+ t.ids = id.next;
+ else
+ last.next = id.next;
+ }else
+ last = id;
+ }
+
+ #
+ # need to resize smaller interfaces
+ #
+ resizetype(t);
+ }
+
+ offset := 0;
+ for(id = t.ids; id != nil; id = id.next)
+ id.offset = offset++;
+
+ #
+ # rathole to stuff number of entries in interface
+ #
+ t.decl.init.c = ref Const;
+ t.decl.init.c.val = big offset;
+ }
+}
+
+#
+# check to see if any data field of module m if referenced.
+# if so, mark all data in m
+#
+moddataref()
+{
+ for(eq := modclass(); eq != nil; eq = eq.eq){
+ id := eq.ty.tof.ids;
+ if(id != nil && id.store == Dglobal && id.refs)
+ for(id = eq.ty.ids; id != nil; id = id.next)
+ if(id.store == Dglobal)
+ modrefable(id.ty);
+ }
+}
+
+#
+# move the global declarations in interface to the front
+#
+modglobals(mod, globals: ref Decl): ref Decl
+{
+ #
+ # make a copy of all the global declarations
+ # used for making a type descriptor for globals ONLY
+ # note we now have two declarations for the same variables,
+ # which is apt to cause problems if code changes
+ #
+ # here we fix up the offsets for the real declarations
+ #
+ idoffsets(mod.ty.ids, 0, 1);
+
+ last := head := ref Decl;
+ for(id := mod.ty.ids; id != nil; id = id.next)
+ if(id.store == Dglobal)
+ last = last.next = dupdecl(id);
+
+ last.next = globals;
+ return head.next;
+}
+
+#
+# snap all id type names to the actual type
+# check that all types are completely defined
+# verify that the types look ok
+#
+validtype(t: ref Type, inadt: ref Decl): ref Type
+{
+ if(t == nil)
+ return t;
+ bindtypes(t);
+ t = verifytypes(t, inadt, nil);
+ cycsizetype(t);
+ teqclass(t);
+ return t;
+}
+
+usetype(t: ref Type): ref Type
+{
+ if(t == nil)
+ return t;
+ t = validtype(t, nil);
+ reftype(t);
+ return t;
+}
+
+internaltype(t: ref Type): ref Type
+{
+ bindtypes(t);
+ t.ok = OKverify;
+ sizetype(t);
+ t.ok = OKmask;
+ return t;
+}
+
+#
+# checks that t is a valid top-level type
+#
+topvartype(t: ref Type, id: ref Decl, tyok: int, polyok: int): ref Type
+{
+ if(t.kind == Tadt && t.tags != nil || t.kind == Tadtpick)
+ error(id.src.start, "cannot declare "+id.sym.name+" with type "+typeconv(t));
+ if(!tyok && t.kind == Tfn)
+ error(id.src.start, "cannot declare "+id.sym.name+" to be a function");
+ if(!polyok && (t.kind == Tadt || t.kind == Tadtpick) && ispolyadt(t))
+ error(id.src.start, "cannot declare " + id.sym.name + " of a polymorphic type");
+ return t;
+}
+
+toptype(src: Src, t: ref Type): ref Type
+{
+ if(t.kind == Tadt && t.tags != nil || t.kind == Tadtpick)
+ error(src.start, typeconv(t)+", an adt with pick fields, must be used with ref");
+ if(t.kind == Tfn)
+ error(src.start, "data cannot have a fn type like "+typeconv(t));
+ return t;
+}
+
+comtype(src: Src, t: ref Type, adtd: ref Decl): ref Type
+{
+ if(adtd == nil && (t.kind == Tadt || t.kind == Tadtpick) && ispolyadt(t))
+ error(src.start, "polymorphic type " + typeconv(t) + " illegal here");
+ return t;
+}
+
+usedty(t: ref Type)
+{
+ if(t != nil && (t.ok | OKmodref) != OKmask)
+ fatal("used ty " + stypeconv(t) + " " + hex(int t.ok, 2));
+}
+
+bindtypes(t: ref Type)
+{
+ id: ref Decl;
+
+ if(t == nil)
+ return;
+ if((t.ok & OKbind) == OKbind)
+ return;
+ t.ok |= OKbind;
+ case t.kind{
+ Tadt =>
+ if(t.polys != nil){
+ pushscope(nil, Sother);
+ installids(Dtype, t.polys);
+ }
+ if(t.val != nil)
+ mergepolydecs(t);
+ if(t.polys != nil){
+ popscope();
+ for(id = t.polys; id != nil; id = id.next)
+ bindtypes(id.ty);
+ }
+ Tadtpick or
+ Tmodule or
+ Terror or
+ Tint or
+ Tbig or
+ Tstring or
+ Treal or
+ Tbyte or
+ Tnone or
+ Tany or
+ Tiface or
+ Tainit or
+ Talt or
+ Tcase or
+ Tcasel or
+ Tcasec or
+ Tgoto or
+ Texcept or
+ Tfix or
+ Tpoly =>
+ break;
+ Tarray or
+ Tarrow or
+ Tchan or
+ Tdot or
+ Tlist or
+ Tref =>
+ bindtypes(t.tof);
+ Tid =>
+ id = t.decl.sym.decl;
+ if(id == nil)
+ id = undefed(t.src, t.decl.sym);
+ # save a little space
+ id.sym.unbound = nil;
+ t.decl = id;
+ Ttuple or
+ Texception =>
+ for(id = t.ids; id != nil; id = id.next)
+ bindtypes(id.ty);
+ Tfn =>
+ if(t.polys != nil){
+ pushscope(nil, Sother);
+ installids(Dtype, t.polys);
+ }
+ for(id = t.ids; id != nil; id = id.next)
+ bindtypes(id.ty);
+ bindtypes(t.tof);
+ if(t.val != nil)
+ mergepolydecs(t);
+ if(t.polys != nil){
+ popscope();
+ for(id = t.polys; id != nil; id = id.next)
+ bindtypes(id.ty);
+ }
+ Tinst =>
+ bindtypes(t.tof);
+ for(tyl := t.tlist; tyl != nil; tyl = tyl.nxt)
+ bindtypes(tyl.t);
+ * =>
+ fatal("bindtypes: unknown type kind "+string t.kind);
+ }
+}
+
+#
+# walk the type checking for validity
+#
+verifytypes(t: ref Type, adtt: ref Decl, poly: ref Decl): ref Type
+{
+ id: ref Decl;
+
+ if(t == nil)
+ return nil;
+ if((t.ok & OKverify) == OKverify)
+ return t;
+ t.ok |= OKverify;
+if((t.ok & (OKverify|OKbind)) != (OKverify|OKbind))
+fatal("verifytypes bogus ok for " + stypeconv(t));
+ cyc := t.flags&CYCLIC;
+ case t.kind{
+ Terror or
+ Tint or
+ Tbig or
+ Tstring or
+ Treal or
+ Tbyte or
+ Tnone or
+ Tany or
+ Tiface or
+ Tainit or
+ Talt or
+ Tcase or
+ Tcasel or
+ Tcasec or
+ Tgoto or
+ Texcept =>
+ break;
+ Tfix =>
+ n := t.val;
+ ok: int;
+ max := 0.0;
+ if(n.op == Oseq){
+ (ok, nil) = echeck(n.left, 0, 0, n);
+ (ok1, nil) := echeck(n.right, 0, 0, n);
+ if(!ok || !ok1)
+ return terror;
+ if(n.left.ty != treal || n.right.ty != treal){
+ error(t.src.start, "fixed point scale/maximum not real");
+ return terror;
+ }
+ n.right = fold(n.right);
+ if(n.right.op != Oconst){
+ error(t.src.start, "fixed point maximum not constant");
+ return terror;
+ }
+ if((max = n.right.c.rval) <= 0.0){
+ error(t.src.start, "non-positive fixed point maximum");
+ return terror;
+ }
+ n = n.left;
+ }
+ else{
+ (ok, nil) = echeck(n, 0, 0, nil);
+ if(!ok)
+ return terror;
+ if(n.ty != treal){
+ error(t.src.start, "fixed point scale not real");
+ return terror;
+ }
+ }
+ n = t.val = fold(n);
+ if(n.op != Oconst){
+ error(t.src.start, "fixed point scale not constant");
+ return terror;
+ }
+ if(n.c.rval <= 0.0){
+ error(t.src.start, "non-positive fixed point scale");
+ return terror;
+ }
+ ckfix(t, max);
+ Tref =>
+ t.tof = comtype(t.src, verifytypes(t.tof, adtt, nil), adtt);
+ if(t.tof != nil && !tattr[t.tof.kind].refable){
+ error(t.src.start, "cannot have a ref " + typeconv(t.tof));
+ return terror;
+ }
+ if(0 && t.tof.kind == Tfn && t.tof.ids != nil && int t.tof.ids.implicit)
+ error(t.src.start, "function references cannot have a self argument");
+ if(0 && t.tof.kind == Tfn && t.polys != nil)
+ error(t.src.start, "function references cannot be polymorphic");
+ Tchan or
+ Tarray or
+ Tlist =>
+ t.tof = comtype(t.src, toptype(t.src, verifytypes(t.tof, adtt, nil)), adtt);
+ Tid =>
+ t.ok &= ~OKverify;
+ t = verifytypes(idtype(t), adtt, nil);
+ Tarrow =>
+ t.ok &= ~OKverify;
+ t = verifytypes(arrowtype(t, adtt), adtt, nil);
+ Tdot =>
+ #
+ # verify the parent adt & lookup the tag fields
+ #
+ t.ok &= ~OKverify;
+ t = verifytypes(dottype(t, adtt), adtt, nil);
+ Tadt =>
+ #
+ # this is where Tadt may get tag fields added
+ #
+ adtdefd(t);
+ Tadtpick =>
+ for(id = t.ids; id != nil; id = id.next){
+ id.ty = topvartype(verifytypes(id.ty, id.dot, nil), id, 0, 1);
+ if(id.store == Dconst)
+ error(t.src.start, "cannot declare a con like "+id.sym.name+" within a pick");
+ }
+ verifytypes(t.decl.dot.ty, nil, nil);
+ Tmodule =>
+ for(id = t.ids; id != nil; id = id.next){
+ id.ty = verifytypes(id.ty, nil, nil);
+ if(id.store == Dglobal && id.ty.kind == Tfn)
+ id.store = Dfn;
+ if(id.store != Dtype && id.store != Dfn)
+ topvartype(id.ty, id, 0, 0);
+ }
+ Ttuple or
+ Texception =>
+ if(t.decl == nil){
+ t.decl = mkdecl(t.src, Dtype, t);
+ t.decl.sym = anontupsym;
+ }
+ i := 0;
+ for(id = t.ids; id != nil; id = id.next){
+ id.store = Dfield;
+ if(id.sym == nil)
+ id.sym = enter("t"+string i, 0);
+ i++;
+ id.ty = toptype(id.src, verifytypes(id.ty, adtt, nil));
+ }
+ Tfn =>
+ last : ref Decl = nil;
+ for(id = t.ids; id != nil; id = id.next){
+ id.store = Darg;
+ id.ty = topvartype(verifytypes(id.ty, adtt, nil), id, 0, 1);
+ if(id.implicit != byte 0){
+ if(poly != nil)
+ selfd := poly;
+ else
+ selfd = adtt;
+ if(selfd == nil)
+ error(t.src.start, "function is not a member of an adt, so can't use self");
+ else if(id != t.ids)
+ error(id.src.start, "only the first argument can use self");
+ else if(id.ty != selfd.ty && (id.ty.kind != Tref || id.ty.tof != selfd.ty))
+ error(id.src.start, "self argument's type must be "+selfd.sym.name+" or ref "+selfd.sym.name);
+ }
+ last = id;
+ }
+ for(id = t.polys; id != nil; id = id.next){
+ if(adtt != nil){
+ for(id1 := adtt.ty.polys; id1 != nil; id1 = id1.next){
+ if(id1.sym == id.sym)
+ id.ty = id1.ty;
+ }
+ }
+ id.store = Dtype;
+ id.ty = verifytypes(id.ty, adtt, nil);
+ }
+ t.tof = comtype(t.src, toptype(t.src, verifytypes(t.tof, adtt, nil)), adtt);
+ if(t.varargs != byte 0 && (last == nil || last.ty != tstring))
+ error(t.src.start, "variable arguments must be preceded by a string");
+ if(t.varargs != byte 0 && t.polys != nil)
+ error(t.src.start, "polymorphic functions must not have variable arguments");
+ Tpoly =>
+ for(id = t.ids; id != nil; id = id.next){
+ id.store = Dfn;
+ id.ty = verifytypes(id.ty, adtt, t.decl);
+ }
+ Tinst =>
+ t.ok &= ~OKverify;
+ t.tof = verifytypes(t.tof, adtt, nil);
+ for(tyl := t.tlist; tyl != nil; tyl = tyl.nxt)
+ tyl.t = verifytypes(tyl.t, adtt, nil);
+ (t, nil) = insttype(t, adtt, nil);
+ t = verifytypes(t, adtt, nil);
+ * =>
+ fatal("verifytypes: unknown type kind "+string t.kind);
+ }
+ if(int cyc)
+ t.flags |= CYCLIC;
+ return t;
+}
+
+#
+# resolve an id type
+#
+idtype(t: ref Type): ref Type
+{
+ id := t.decl;
+ if(id.store == Dunbound)
+ fatal("idtype: unbound decl");
+ tt := id.ty;
+ if(id.store != Dtype && id.store != Dtag){
+ if(id.store == Dundef){
+ id.store = Dwundef;
+ error(t.src.start, id.sym.name+" is not declared");
+ }else if(id.store == Dimport){
+ id.store = Dwundef;
+ error(t.src.start, id.sym.name+"'s type cannot be determined");
+ }else if(id.store != Dwundef)
+ error(t.src.start, id.sym.name+" is not a type");
+ return terror;
+ }
+ if(tt == nil){
+ error(t.src.start, stypeconv(t)+" not fully defined");
+ return terror;
+ }
+ return tt;
+}
+
+#
+# resolve a -> qualified type
+#
+arrowtype(t: ref Type, adtt: ref Decl): ref Type
+{
+ id := t.decl;
+ if(id.ty != nil){
+ if(id.store == Dunbound)
+ fatal("arrowtype: unbound decl has a type");
+ return id.ty;
+ }
+
+ #
+ # special hack to allow module variables to derive other types
+ #
+ tt := t.tof;
+ if(tt.kind == Tid){
+ id = tt.decl;
+ if(id.store == Dunbound)
+ fatal("arrowtype: Tid's decl unbound");
+ if(id.store == Dimport){
+ id.store = Dwundef;
+ error(t.src.start, id.sym.name+"'s type cannot be determined");
+ return terror;
+ }
+
+ #
+ # forward references to module variables can't be resolved
+ #
+ if(id.store != Dtype && (id.ty.ok & OKbind) != OKbind){
+ error(t.src.start, id.sym.name+"'s type cannot be determined");
+ return terror;
+ }
+
+ if(id.store == Dwundef)
+ return terror;
+ tt = id.ty = verifytypes(id.ty, adtt, nil);
+ if(tt == nil){
+ error(t.tof.src.start, typeconv(t.tof)+" is not a module");
+ return terror;
+ }
+ }else
+ tt = verifytypes(t.tof, adtt, nil);
+ t.tof = tt;
+ if(tt == terror)
+ return terror;
+ if(tt.kind != Tmodule){
+ error(t.src.start, typeconv(tt)+" is not a module");
+ return terror;
+ }
+ id = namedot(tt.ids, t.decl.sym);
+ if(id == nil){
+ error(t.src.start, t.decl.sym.name+" is not a member of "+typeconv(tt));
+ return terror;
+ }
+ if(id.store == Dtype && id.ty != nil){
+ t.decl = id;
+ return id.ty;
+ }
+ error(t.src.start, typeconv(t)+" is not a type");
+ return terror;
+}
+
+#
+# resolve a . qualified type
+#
+dottype(t: ref Type, adtt: ref Decl): ref Type
+{
+ if(t.decl.ty != nil){
+ if(t.decl.store == Dunbound)
+ fatal("dottype: unbound decl has a type");
+ return t.decl.ty;
+ }
+ t.tof = tt := verifytypes(t.tof, adtt, nil);
+ if(tt == terror)
+ return terror;
+ if(tt.kind != Tadt){
+ error(t.src.start, typeconv(tt)+" is not an adt");
+ return terror;
+ }
+ id := namedot(tt.tags, t.decl.sym);
+ if(id != nil && id.ty != nil){
+ t.decl = id;
+ return id.ty;
+ }
+ error(t.src.start, t.decl.sym.name+" is not a pick tag of "+typeconv(tt));
+ return terror;
+}
+
+insttype(t: ref Type, adtt: ref Decl, tp: ref Tpair): (ref Type, ref Tpair)
+{
+ src := t.src;
+ if(t.tof.kind != Tadt && t.tof.kind != Tadtpick){
+ error(src.start, typeconv(t.tof) + " is not an adt");
+ return (terror, nil);
+ }
+ if(t.tof.kind == Tadt)
+ ids := t.tof.polys;
+ else
+ ids = t.tof.decl.dot.ty.polys;
+ if(ids == nil){
+ error(src.start, typeconv(t.tof) + " is not a polymorphic adt");
+ return (terror, nil);
+ }
+ for(tyl := t.tlist; tyl != nil && ids != nil; tyl = tyl.nxt){
+ tt := tyl.t;
+ if(!tattr[tt.kind].isptr){
+ error(src.start, typeconv(tt) + " is not a pointer type");
+ return (terror, nil);
+ }
+ unifysrc = src;
+ (ok, nil) := tunify(ids.ty, tt);
+ if(!ok){
+ error(src.start, "type " + typeconv(tt) + " does not match " + typeconv(ids.ty));
+ return (terror, nil);
+ }
+ # usetype(tt);
+ tt = verifytypes(tt, adtt, nil);
+ tp = addtmap(ids.ty, tt, tp);
+ ids = ids.next;
+ }
+ if(tyl != nil){
+ error(src.start, "too many actual types in instantiation");
+ return (terror, nil);
+ }
+ if(ids != nil){
+ error(src.start, "too few actual types in instantiation");
+ return (terror, nil);
+ }
+ tt := t.tof;
+ (t, nil) = expandtype(tt, t, adtt, tp);
+ if(t == tt && adtt == nil)
+ t = duptype(t);
+ if(t != tt)
+ t.tmap = tp;
+ t.src = src;
+ return (t, tp);
+}
+
+#
+# walk a type, putting all adts, modules, and tuples into equivalence classes
+#
+teqclass(t: ref Type)
+{
+ id: ref Decl;
+
+ if(t == nil || (t.ok & OKclass) == OKclass)
+ return;
+ t.ok |= OKclass;
+ case t.kind{
+ Terror or
+ Tint or
+ Tbig or
+ Tstring or
+ Treal or
+ Tbyte or
+ Tnone or
+ Tany or
+ Tiface or
+ Tainit or
+ Talt or
+ Tcase or
+ Tcasel or
+ Tcasec or
+ Tgoto or
+ Texcept or
+ Tfix or
+ Tpoly =>
+ return;
+ Tref =>
+ teqclass(t.tof);
+ return;
+ Tchan or
+ Tarray or
+ Tlist =>
+ teqclass(t.tof);
+#ZZZ elim return to fix recursive chans, etc
+ if(!debug['Z'])
+ return;
+ Tadt or
+ Tadtpick or
+ Ttuple or
+ Texception =>
+ for(id = t.ids; id != nil; id = id.next)
+ teqclass(id.ty);
+ for(tg := t.tags; tg != nil; tg = tg.next)
+ teqclass(tg.ty);
+ for(id = t.polys; id != nil; id = id.next)
+ teqclass(id.ty);
+ Tmodule =>
+ t.tof = mkiface(t.decl);
+ for(id = t.ids; id != nil; id = id.next)
+ teqclass(id.ty);
+ Tfn =>
+ for(id = t.ids; id != nil; id = id.next)
+ teqclass(id.ty);
+ for(id = t.polys; id != nil; id = id.next)
+ teqclass(id.ty);
+ teqclass(t.tof);
+ return;
+ * =>
+ fatal("teqclass: unknown type kind "+string t.kind);
+ }
+
+ #
+ # find an equivalent type
+ # stupid linear lookup could be made faster
+ #
+ if((t.ok & OKsized) != OKsized)
+ fatal("eqclass type not sized: " + stypeconv(t));
+
+ for(teq := eqclass[t.kind]; teq != nil; teq = teq.eq){
+ if(t.size == teq.ty.size && tequal(t, teq.ty)){
+ t.eq = teq;
+ if(t.kind == Tmodule)
+ joiniface(t, t.eq.ty.tof);
+ return;
+ }
+ }
+
+ #
+ # if no equiv type, make one
+ #
+ eqclass[t.kind] = t.eq = ref Teq(0, t, eqclass[t.kind]);
+}
+
+#
+# record that we've used the type
+# using a type uses all types reachable from that type
+#
+reftype(t: ref Type)
+{
+ id: ref Decl;
+
+ if(t == nil || (t.ok & OKref) == OKref)
+ return;
+ t.ok |= OKref;
+ if(t.decl != nil && t.decl.refs == 0)
+ t.decl.refs++;
+ case t.kind{
+ Terror or
+ Tint or
+ Tbig or
+ Tstring or
+ Treal or
+ Tbyte or
+ Tnone or
+ Tany or
+ Tiface or
+ Tainit or
+ Talt or
+ Tcase or
+ Tcasel or
+ Tcasec or
+ Tgoto or
+ Texcept or
+ Tfix or
+ Tpoly =>
+ break;
+ Tref or
+ Tchan or
+ Tarray or
+ Tlist =>
+ if(t.decl != nil){
+ if(nadts >= len adts){
+ a := array[nadts + 32] of ref Decl;
+ a[0:] = adts;
+ adts = a;
+ }
+ adts[nadts++] = t.decl;
+ }
+ reftype(t.tof);
+ Tadt or
+ Tadtpick or
+ Ttuple or
+ Texception =>
+ if(t.kind == Tadt || t.kind == Ttuple && t.decl.sym != anontupsym){
+ if(nadts >= len adts){
+ a := array[nadts + 32] of ref Decl;
+ a[0:] = adts;
+ adts = a;
+ }
+ adts[nadts++] = t.decl;
+ }
+ for(id = t.ids; id != nil; id = id.next)
+ if(id.store != Dfn)
+ reftype(id.ty);
+ for(tg := t.tags; tg != nil; tg = tg.next)
+ reftype(tg.ty);
+ for(id = t.polys; id != nil; id = id.next)
+ reftype(id.ty);
+ if(t.kind == Tadtpick)
+ reftype(t.decl.dot.ty);
+ Tmodule =>
+ #
+ # a module's elements should get used individually
+ # but do the globals for any sbl file
+ #
+ if(bsym != nil)
+ for(id = t.ids; id != nil; id = id.next)
+ if(id.store == Dglobal)
+ reftype(id.ty);
+ break;
+ Tfn =>
+ for(id = t.ids; id != nil; id = id.next)
+ reftype(id.ty);
+ for(id = t.polys; id != nil; id = id.next)
+ reftype(id.ty);
+ reftype(t.tof);
+ * =>
+ fatal("reftype: unknown type kind "+string t.kind);
+ }
+}
+
+#
+# check all reachable types for cycles and illegal forward references
+# find the size of all the types
+#
+cycsizetype(t: ref Type)
+{
+ id: ref Decl;
+
+ if(t == nil || (t.ok & (OKcycsize|OKcyc|OKsized)) == (OKcycsize|OKcyc|OKsized))
+ return;
+ t.ok |= OKcycsize;
+ case t.kind{
+ Terror or
+ Tint or
+ Tbig or
+ Tstring or
+ Treal or
+ Tbyte or
+ Tnone or
+ Tany or
+ Tiface or
+ Tainit or
+ Talt or
+ Tcase or
+ Tcasel or
+ Tcasec or
+ Tgoto or
+ Texcept or
+ Tfix or
+ Tpoly =>
+ t.ok |= OKcyc;
+ sizetype(t);
+ Tref or
+ Tchan or
+ Tarray or
+ Tlist =>
+ cyctype(t);
+ sizetype(t);
+ cycsizetype(t.tof);
+ Tadt or
+ Ttuple or
+ Texception =>
+ cyctype(t);
+ sizetype(t);
+ for(id = t.ids; id != nil; id = id.next)
+ cycsizetype(id.ty);
+ for(tg := t.tags; tg != nil; tg = tg.next){
+ if((tg.ty.ok & (OKcycsize|OKcyc|OKsized)) == (OKcycsize|OKcyc|OKsized))
+ continue;
+ tg.ty.ok |= (OKcycsize|OKcyc|OKsized);
+ for(id = tg.ty.ids; id != nil; id = id.next)
+ cycsizetype(id.ty);
+ }
+ for(id = t.polys; id != nil; id = id.next)
+ cycsizetype(id.ty);
+ Tadtpick =>
+ t.ok &= ~OKcycsize;
+ cycsizetype(t.decl.dot.ty);
+ Tmodule =>
+ cyctype(t);
+ sizetype(t);
+ for(id = t.ids; id != nil; id = id.next)
+ cycsizetype(id.ty);
+ sizeids(t.ids, 0);
+ Tfn =>
+ cyctype(t);
+ sizetype(t);
+ for(id = t.ids; id != nil; id = id.next)
+ cycsizetype(id.ty);
+ for(id = t.polys; id != nil; id = id.next)
+ cycsizetype(id.ty);
+ cycsizetype(t.tof);
+ sizeids(t.ids, MaxTemp);
+#ZZZ need to align?
+ * =>
+ fatal("cycsizetype: unknown type kind "+string t.kind);
+ }
+}
+
+# check for circularity in type declarations
+# - has to be called before verifytypes
+#
+tcycle(t: ref Type)
+{
+ id: ref Decl;
+ tt: ref Type;
+ tll: ref Typelist;
+
+ if(t == nil)
+ return;
+ case(t.kind){
+ * =>
+ ;
+ Tchan or
+ Tarray or
+ Tref or
+ Tlist or
+ Tdot =>
+ tcycle(t.tof);
+ Tfn or
+ Ttuple =>
+ tcycle(t.tof);
+ for(id = t.ids; id != nil; id = id.next)
+ tcycle(id.ty);
+ Tarrow =>
+ if(int(t.rec&TRvis)){
+ error(t.src.start, "circularity in definition of " + typeconv(t));
+ *t = *terror; # break the cycle
+ return;
+ }
+ tt = t.tof;
+ t.rec |= TRvis;
+ tcycle(tt);
+ if(tt.kind == Tid)
+ tt = tt.decl.ty;
+ id = namedot(tt.ids, t.decl.sym);
+ if(id != nil)
+ tcycle(id.ty);
+ t.rec &= ~TRvis;
+ Tid =>
+ if(int(t.rec&TRvis)){
+ error(t.src.start, "circularity in definition of " + typeconv(t));
+ *t = *terror; # break the cycle
+ return;
+ }
+ t.rec |= TRvis;
+ tcycle(t.decl.ty);
+ t.rec &= ~TRvis;
+ Tinst =>
+ tcycle(t.tof);
+ for(tll = t.tlist; tll != nil; tll = tll.nxt)
+ tcycle(tll.t);
+ }
+}
+
+#
+# marks for checking for arcs
+#
+ ArcValue,
+ ArcList,
+ ArcArray,
+ ArcRef,
+ ArcCyc, # cycle found
+ ArcPolycyc:
+ con 1 << iota;
+
+cyctype(t: ref Type)
+{
+ if((t.ok & OKcyc) == OKcyc)
+ return;
+ t.ok |= OKcyc;
+ t.rec |= TRcyc;
+ case t.kind{
+ Terror or
+ Tint or
+ Tbig or
+ Tstring or
+ Treal or
+ Tbyte or
+ Tnone or
+ Tany or
+ Tfn or
+ Tchan or
+ Tarray or
+ Tref or
+ Tlist or
+ Tfix or
+ Tpoly =>
+ break;
+ Tadt or
+ Tmodule or
+ Ttuple or
+ Texception =>
+ for(id := t.ids; id != nil; id = id.next)
+ cycfield(t, id);
+ for(tg := t.tags; tg != nil; tg = tg.next){
+ if((tg.ty.ok & OKcyc) == OKcyc)
+ continue;
+ tg.ty.ok |= OKcyc;
+ for(id = tg.ty.ids; id != nil; id = id.next)
+ cycfield(t, id);
+ }
+ * =>
+ fatal("cyctype: unknown type kind "+string t.kind);
+ }
+ t.rec &= ~TRcyc;
+}
+
+cycfield(base: ref Type, id: ref Decl)
+{
+ if(!storespace[id.store])
+ return;
+ arc := cycarc(base, id.ty);
+
+ if((arc & (ArcCyc|ArcValue)) == (ArcCyc|ArcValue)){
+ if(id.cycerr == byte 0)
+ error(base.src.start, "illegal type cycle without a reference in field "
+ +id.sym.name+" of "+stypeconv(base));
+ id.cycerr = byte 1;
+ }else if(arc & ArcCyc){
+ if((arc & ArcArray) && id.cyc == byte 0 && !(arc & ArcPolycyc)){
+ if(id.cycerr == byte 0)
+ error(base.src.start, "illegal circular reference to type "+typeconv(id.ty)
+ +" in field "+id.sym.name+" of "+stypeconv(base));
+ id.cycerr = byte 1;
+ }
+ id.cycle = byte 1;
+ }else if(id.cyc != byte 0){
+ if(id.cycerr == byte 0)
+ error(id.src.start, "spurious cyclic qualifier for field "+id.sym.name+" of "+stypeconv(base));
+ id.cycerr = byte 1;
+ }
+}
+
+cycarc(base, t: ref Type): int
+{
+ if(t == nil)
+ return 0;
+ if((t.rec & TRcyc) == TRcyc){
+ if(tequal(t, base)){
+ if(t.kind == Tmodule)
+ return ArcCyc | ArcRef;
+ else
+ return ArcCyc | ArcValue;
+ }
+ return 0;
+ }
+ t.rec |= TRcyc;
+ me := 0;
+ case t.kind{
+ Terror or
+ Tint or
+ Tbig or
+ Tstring or
+ Treal or
+ Tbyte or
+ Tnone or
+ Tany or
+ Tchan or
+ Tfn or
+ Tfix or
+ Tpoly =>
+ break;
+ Tarray =>
+ me = cycarc(base, t.tof) & ~ArcValue | ArcArray;
+ Tref =>
+ me = cycarc(base, t.tof) & ~ArcValue | ArcRef;
+ Tlist =>
+ me = cycarc(base, t.tof) & ~ArcValue | ArcList;
+ Tadt or
+ Tadtpick or
+ Tmodule or
+ Ttuple or
+ Texception =>
+ me = 0;
+ arc: int;
+ for(id := t.ids; id != nil; id = id.next){
+ if(!storespace[id.store])
+ continue;
+ arc = cycarc(base, id.ty);
+ if((arc & ArcCyc) && id.cycerr == byte 0)
+ me |= arc;
+ }
+ for(tg := t.tags; tg != nil; tg = tg.next){
+ arc = cycarc(base, tg.ty);
+ if((arc & ArcCyc) && tg.cycerr == byte 0)
+ me |= arc;
+ }
+
+ if(t.kind == Tmodule)
+ me = me & ArcCyc | ArcRef | ArcPolycyc;
+ else
+ me &= ArcCyc | ArcValue | ArcPolycyc;
+ * =>
+ fatal("cycarc: unknown type kind "+string t.kind);
+ }
+ t.rec &= ~TRcyc;
+ if(int (t.flags&CYCLIC))
+ me |= ArcPolycyc;
+ return me;
+}
+
+#
+# set the sizes and field offsets for t
+# look only as deeply as needed to size this type.
+# cycsize type will clean up the rest.
+#
+sizetype(t: ref Type)
+{
+ id: ref Decl;
+ sz, al, s, a: int;
+
+ if(t == nil)
+ return;
+ if((t.ok & OKsized) == OKsized)
+ return;
+ t.ok |= OKsized;
+if((t.ok & (OKverify|OKsized)) != (OKverify|OKsized))
+fatal("sizetype bogus ok for " + stypeconv(t));
+ case t.kind{
+ * =>
+ fatal("sizetype: unknown type kind "+string t.kind);
+ Terror or
+ Tnone or
+ Tbyte or
+ Tint or
+ Tbig or
+ Tstring or
+ Tany or
+ Treal =>
+ fatal(typeconv(t)+" should have a size");
+ Tref or
+ Tchan or
+ Tarray or
+ Tlist or
+ Tmodule or
+ Tfix or
+ Tpoly =>
+ t.size = t.align = IBY2WD;
+ Tadt or
+ Ttuple or
+ Texception =>
+ if(t.tags == nil){
+#ZZZ
+ if(!debug['z']){
+ (sz, t.align) = sizeids(t.ids, 0);
+ t.size = align(sz, t.align);
+ }else{
+ (sz, nil) = sizeids(t.ids, 0);
+ t.align = IBY2LG;
+ t.size = align(sz, IBY2LG);
+ }
+ return;
+ }
+#ZZZ
+ if(!debug['z']){
+ (sz, al) = sizeids(t.ids, IBY2WD);
+ if(al < IBY2WD)
+ al = IBY2WD;
+ }else{
+ (sz, nil) = sizeids(t.ids, IBY2WD);
+ al = IBY2LG;
+ }
+ for(tg := t.tags; tg != nil; tg = tg.next){
+ if((tg.ty.ok & OKsized) == OKsized)
+ continue;
+ tg.ty.ok |= OKsized;
+#ZZZ
+ if(!debug['z']){
+ (s, a) = sizeids(tg.ty.ids, sz);
+ if(a < al)
+ a = al;
+ tg.ty.size = align(s, a);
+ tg.ty.align = a;
+ }else{
+ (s, nil) = sizeids(tg.ty.ids, sz);
+ tg.ty.size = align(s, IBY2LG);
+ tg.ty.align = IBY2LG;
+ }
+ }
+ Tfn =>
+ t.size = 0;
+ t.align = 1;
+ Tainit =>
+ t.size = 0;
+ t.align = 1;
+ Talt =>
+ t.size = t.cse.nlab * 2*IBY2WD + 2*IBY2WD;
+ t.align = IBY2WD;
+ Tcase or
+ Tcasec =>
+ t.size = t.cse.nlab * 3*IBY2WD + 2*IBY2WD;
+ t.align = IBY2WD;
+ Tcasel =>
+ t.size = t.cse.nlab * 6*IBY2WD + 3*IBY2WD;
+ t.align = IBY2LG;
+ Tgoto =>
+ t.size = t.cse.nlab * IBY2WD + IBY2WD;
+ if(t.cse.iwild != nil)
+ t.size += IBY2WD;
+ t.align = IBY2WD;
+ Tiface =>
+ sz = IBY2WD;
+ for(id = t.ids; id != nil; id = id.next){
+ sz = align(sz, IBY2WD) + IBY2WD;
+ sz += len array of byte id.sym.name + 1;
+ if(id.dot.ty.kind == Tadt)
+ sz += len array of byte id.dot.sym.name + 1;
+ }
+ t.size = sz;
+ t.align = IBY2WD;
+ Texcept =>
+ t.size = 0;
+ t.align = IBY2WD;
+ }
+}
+
+sizeids(id: ref Decl, off: int): (int, int)
+{
+ al := 1;
+ for(; id != nil; id = id.next){
+ if(storespace[id.store]){
+ sizetype(id.ty);
+ #
+ # alignment can be 0 if we have
+ # illegal forward declarations.
+ # just patch a; other code will flag an error
+ #
+ a := id.ty.align;
+ if(a == 0)
+ a = 1;
+
+ if(a > al)
+ al = a;
+
+ off = align(off, a);
+ id.offset = off;
+ off += id.ty.size;
+ }
+ }
+ return (off, al);
+}
+
+align(off, align: int): int
+{
+ if(align == 0)
+ fatal("align 0");
+ while(off % align)
+ off++;
+ return off;
+}
+
+#
+# recalculate a type's size
+#
+resizetype(t: ref Type)
+{
+ if((t.ok & OKsized) == OKsized){
+ t.ok &= ~OKsized;
+ cycsizetype(t);
+ }
+}
+
+#
+# check if a module is accessable from t
+# if so, mark that module interface
+#
+modrefable(t: ref Type)
+{
+ id: ref Decl;
+
+ if(t == nil || (t.ok & OKmodref) == OKmodref)
+ return;
+ if((t.ok & OKverify) != OKverify)
+ fatal("modrefable unused type "+stypeconv(t));
+ t.ok |= OKmodref;
+ case t.kind{
+ Terror or
+ Tint or
+ Tbig or
+ Tstring or
+ Treal or
+ Tbyte or
+ Tnone or
+ Tany or
+ Tfix or
+ Tpoly =>
+ break;
+ Tchan or
+ Tref or
+ Tarray or
+ Tlist =>
+ modrefable(t.tof);
+ Tmodule =>
+ t.tof.linkall = byte 1;
+ t.decl.refs++;
+ for(id = t.ids; id != nil; id = id.next){
+ case id.store{
+ Dglobal or
+ Dfn =>
+ modrefable(id.ty);
+ Dtype =>
+ if(id.ty.kind != Tadt)
+ break;
+ for(m := id.ty.ids; m != nil; m = m.next)
+ if(m.store == Dfn)
+ modrefable(m.ty);
+ }
+ }
+ Tfn or
+ Tadt or
+ Ttuple or
+ Texception =>
+ for(id = t.ids; id != nil; id = id.next)
+ if(id.store != Dfn)
+ modrefable(id.ty);
+ for(tg := t.tags; tg != nil; tg = tg.next){
+ # if((tg.ty.ok & OKmodref) == OKmodref)
+ # continue;
+ tg.ty.ok |= OKmodref;
+ for(id = tg.ty.ids; id != nil; id = id.next)
+ modrefable(id.ty);
+ }
+ for(id = t.polys; id != nil; id = id.next)
+ modrefable(id.ty);
+ modrefable(t.tof);
+ Tadtpick =>
+ modrefable(t.decl.dot.ty);
+ * =>
+ fatal("modrefable: unknown type kind "+string t.kind);
+ }
+}
+
+gendesc(d: ref Decl, size: int, decls: ref Decl): ref Desc
+{
+ if(debug['D'])
+ print("generate desc for %s\n", dotconv(d));
+ if(ispoly(d))
+ addfnptrs(d, 0);
+ desc := usedesc(mkdesc(size, decls));
+ return desc;
+}
+
+mkdesc(size: int, d: ref Decl): ref Desc
+{
+ pmap := array[(size+8*IBY2WD-1) / (8*IBY2WD)] of { * => byte 0 };
+ n := descmap(d, pmap, 0);
+ if(n >= 0)
+ n = n / (8*IBY2WD) + 1;
+ else
+ n = 0;
+ return enterdesc(pmap, size, n);
+}
+
+mktdesc(t: ref Type): ref Desc
+{
+usedty(t);
+ if(debug['D'])
+ print("generate desc for %s\n", typeconv(t));
+ if(t.decl == nil){
+ t.decl = mkdecl(t.src, Dtype, t);
+ t.decl.sym = enter("_mktdesc_", 0);
+ }
+ if(t.decl.desc != nil)
+ return t.decl.desc;
+ pmap := array[(t.size+8*IBY2WD-1) / (8*IBY2WD)] of {* => byte 0};
+ n := tdescmap(t, pmap, 0);
+ if(n >= 0)
+ n = n / (8*IBY2WD) + 1;
+ else
+ n = 0;
+ d := enterdesc(pmap, t.size, n);
+ t.decl.desc = d;
+ return d;
+}
+
+enterdesc(map: array of byte, size, nmap: int): ref Desc
+{
+ last : ref Desc = nil;
+ for(d := descriptors; d != nil; d = d.next){
+ if(d.size > size || d.size == size && d.nmap > nmap)
+ break;
+ if(d.size == size && d.nmap == nmap){
+ c := mapcmp(d.map, map, nmap);
+ if(c == 0)
+ return d;
+ if(c > 0)
+ break;
+ }
+ last = d;
+ }
+
+ d = ref Desc(-1, 0, map, size, nmap, nil);
+ if(last == nil){
+ d.next = descriptors;
+ descriptors = d;
+ }else{
+ d.next = last.next;
+ last.next = d;
+ }
+ return d;
+}
+
+mapcmp(a, b: array of byte, n: int): int
+{
+ for(i := 0; i < n; i++)
+ if(a[i] != b[i])
+ return int a[i] - int b[i];
+ return 0;
+}
+
+usedesc(d: ref Desc): ref Desc
+{
+ d.used = 1;
+ return d;
+}
+
+#
+# create the pointer description byte map for every type in decls
+# each bit corresponds to a word, and is 1 if occupied by a pointer
+# the high bit in the byte maps the first word
+#
+descmap(decls: ref Decl, map: array of byte, start: int): int
+{
+ if(debug['D'])
+ print("descmap offset %d\n", start);
+ last := -1;
+ for(d := decls; d != nil; d = d.next){
+ if(d.store == Dtype && d.ty.kind == Tmodule
+ || d.store == Dfn
+ || d.store == Dconst)
+ continue;
+ if(d.store == Dlocal && d.link != nil)
+ continue;
+ m := tdescmap(d.ty, map, d.offset + start);
+ if(debug['D']){
+ if(d.sym != nil)
+ print("descmap %s type %s offset %d returns %d\n", d.sym.name, typeconv(d.ty), d.offset+start, m);
+ else
+ print("descmap type %s offset %d returns %d\n", typeconv(d.ty), d.offset+start, m);
+ }
+ if(m >= 0)
+ last = m;
+ }
+ return last;
+}
+
+tdescmap(t: ref Type, map: array of byte, offset: int): int
+{
+ i, e, bit: int;
+
+ if(t == nil)
+ return -1;
+
+ m := -1;
+ if(t.kind == Talt){
+ lab := t.cse.labs;
+ e = t.cse.nlab;
+ offset += IBY2WD * 2;
+ for(i = 0; i < e; i++){
+ if(lab[i].isptr){
+ bit = offset / IBY2WD % 8;
+ map[offset / (8*IBY2WD)] |= byte 1 << (7 - bit);
+ m = offset;
+ }
+ offset += 2*IBY2WD;
+ }
+ return m;
+ }
+ if(t.kind == Tcasec){
+ e = t.cse.nlab;
+ offset += IBY2WD;
+ for(i = 0; i < e; i++){
+ bit = offset / IBY2WD % 8;
+ map[offset / (8*IBY2WD)] |= byte 1 << (7 - bit);
+ offset += IBY2WD;
+ bit = offset / IBY2WD % 8;
+ map[offset / (8*IBY2WD)] |= byte 1 << (7 - bit);
+ m = offset;
+ offset += 2*IBY2WD;
+ }
+ return m;
+ }
+
+ if(tattr[t.kind].isptr){
+ bit = offset / IBY2WD % 8;
+ map[offset / (8*IBY2WD)] |= byte 1 << (7 - bit);
+ return offset;
+ }
+ if(t.kind == Tadtpick)
+ t = t.tof;
+ if(t.kind == Ttuple || t.kind == Tadt || t.kind == Texception){
+ if(debug['D'])
+ print("descmap adt offset %d\n", offset);
+ if(t.rec != byte 0)
+ fatal("illegal cyclic type "+stypeconv(t)+" in tdescmap");
+ t.rec = byte 1;
+ offset = descmap(t.ids, map, offset);
+ t.rec = byte 0;
+ return offset;
+ }
+
+ return -1;
+}
+
+tcomset: int;
+
+#
+# can a t2 be assigned to a t1?
+# any means Tany matches all types,
+# not just references
+#
+tcompat(t1, t2: ref Type, any: int): int
+{
+ if(t1 == t2)
+ return 1;
+ if(t1 == nil || t2 == nil)
+ return 0;
+ if(t2.kind == Texception && t1.kind != Texception)
+ t2 = mkextuptype(t2);
+ tcomset = 0;
+ ok := rtcompat(t1, t2, any, 0);
+ v := cleartcomrec(t1) + cleartcomrec(t2);
+ if(v != tcomset)
+ fatal("recid t1 "+stypeconv(t1)+" and t2 "+stypeconv(t2)+" not balanced in tcompat: "+string v+" "+string tcomset);
+ return ok;
+}
+
+rtcompat(t1, t2: ref Type, any: int, inaorc: int): int
+{
+ if(t1 == t2)
+ return 1;
+ if(t1 == nil || t2 == nil)
+ return 0;
+ if(t1.kind == Terror || t2.kind == Terror)
+ return 1;
+ if(t2.kind == Texception && t1.kind != Texception)
+ t2 = mkextuptype(t2);
+
+ t1.rec |= TRcom;
+ t2.rec |= TRcom;
+ case t1.kind{
+ * =>
+ fatal("unknown type "+stypeconv(t1)+" v "+stypeconv(t2)+" in rtcompat");
+ return 0;
+ Tstring =>
+ return t2.kind == Tstring || t2.kind == Tany;
+ Texception =>
+ if(t2.kind == Texception && t1.cons == t2.cons){
+ if(assumetcom(t1, t2))
+ return 1;
+ return idcompat(t1.ids, t2.ids, 0, inaorc);
+ }
+ return 0;
+ Tnone or
+ Tint or
+ Tbig or
+ Tbyte or
+ Treal =>
+ return t1.kind == t2.kind;
+ Tfix =>
+ return t1.kind == t2.kind && sametree(t1.val, t2.val);
+ Tany =>
+ if(tattr[t2.kind].isptr)
+ return 1;
+ return any;
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan =>
+ if(t1.kind != t2.kind){
+ if(t2.kind == Tany)
+ return 1;
+ return 0;
+ }
+ if(t1.kind != Tref && assumetcom(t1, t2))
+ return 1;
+ return rtcompat(t1.tof, t2.tof, 0, t1.kind == Tarray || t1.kind == Tchan || inaorc);
+ Tfn =>
+ break;
+ Ttuple =>
+ if(t2.kind == Tadt && t2.tags == nil
+ || t2.kind == Ttuple){
+ if(assumetcom(t1, t2))
+ return 1;
+ return idcompat(t1.ids, t2.ids, any, inaorc);
+ }
+ if(t2.kind == Tadtpick){
+ t2.tof.rec |= TRcom;
+ if(assumetcom(t1, t2.tof))
+ return 1;
+ return idcompat(t1.ids, t2.tof.ids.next, any, inaorc);
+ }
+ return 0;
+ Tadt =>
+ if(t2.kind == Ttuple && t1.tags == nil){
+ if(assumetcom(t1, t2))
+ return 1;
+ return idcompat(t1.ids, t2.ids, any, inaorc);
+ }
+ if(t1.tags != nil && t2.kind == Tadtpick && !inaorc)
+ t2 = t2.decl.dot.ty;
+ Tadtpick =>
+ #if(t2.kind == Ttuple)
+ # return idcompat(t1.tof.ids.next, t2.ids, any, inaorc);
+ break;
+ Tmodule =>
+ if(t2.kind == Tany)
+ return 1;
+ Tpoly =>
+ if(t2.kind == Tany)
+ return 1;
+ }
+ return tequal(t1, t2);
+}
+
+#
+# add the assumption that t1 and t2 are compatable
+#
+assumetcom(t1, t2: ref Type): int
+{
+ r1, r2: ref Type;
+
+ if(t1.tcom == nil && t2.tcom == nil){
+ tcomset += 2;
+ t1.tcom = t2.tcom = t1;
+ }else{
+ if(t1.tcom == nil){
+ r1 = t1;
+ t1 = t2;
+ t2 = r1;
+ }
+ for(r1 = t1.tcom; r1 != r1.tcom; r1 = r1.tcom)
+ ;
+ for(r2 = t2.tcom; r2 != nil && r2 != r2.tcom; r2 = r2.tcom)
+ ;
+ if(r1 == r2)
+ return 1;
+ if(r2 == nil)
+ tcomset++;
+ t2.tcom = t1;
+ for(; t2 != r1; t2 = r2){
+ r2 = t2.tcom;
+ t2.tcom = r1;
+ }
+ }
+ return 0;
+}
+
+cleartcomrec(t: ref Type): int
+{
+ n := 0;
+ for(; t != nil && (t.rec & TRcom) == TRcom; t = t.tof){
+ t.rec &= ~TRcom;
+ if(t.tcom != nil){
+ t.tcom = nil;
+ n++;
+ }
+ if(t.kind == Tadtpick)
+ n += cleartcomrec(t.tof);
+ if(t.kind == Tmodule)
+ t = t.tof;
+ for(id := t.ids; id != nil; id = id.next)
+ n += cleartcomrec(id.ty);
+ for(id = t.tags; id != nil; id = id.next)
+ n += cleartcomrec(id.ty);
+ for(id = t.polys; id != nil; id = id.next)
+ n += cleartcomrec(id.ty);
+ }
+ return n;
+}
+
+#
+# id1 and id2 are the fields in an adt or tuple
+# simple structural check; ignore names
+#
+idcompat(id1, id2: ref Decl, any: int, inaorc: int): int
+{
+ for(; id1 != nil; id1 = id1.next){
+ if(id1.store != Dfield)
+ continue;
+ while(id2 != nil && id2.store != Dfield)
+ id2 = id2.next;
+ if(id2 == nil
+ || id1.store != id2.store
+ || !rtcompat(id1.ty, id2.ty, any, inaorc))
+ return 0;
+ id2 = id2.next;
+ }
+ while(id2 != nil && id2.store != Dfield)
+ id2 = id2.next;
+ return id2 == nil;
+}
+
+#
+# structural equality on types
+# t->recid is used to detect cycles
+# t->rec is used to clear t->recid
+#
+tequal(t1, t2: ref Type): int
+{
+ eqrec = 0;
+ eqset = 0;
+ ok := rtequal(t1, t2);
+ v := cleareqrec(t1) + cleareqrec(t2);
+ if(0 && v != eqset)
+ fatal("recid t1 "+stypeconv(t1)+" and t2 "+stypeconv(t2)+" not balanced in tequal: "+string v+" "+string eqset);
+ eqset = 0;
+ return ok;
+}
+
+rtequal(t1, t2: ref Type): int
+{
+ #
+ # this is just a shortcut
+ #
+ if(t1 == t2)
+ return 1;
+
+ if(t1 == nil || t2 == nil)
+ return 0;
+ if(t1.kind == Terror || t2.kind == Terror)
+ return 1;
+
+ if(t1.kind != t2.kind)
+ return 0;
+
+ if(t1.eq != nil && t2.eq != nil)
+ return t1.eq == t2.eq;
+
+ t1.rec |= TReq;
+ t2.rec |= TReq;
+ case t1.kind{
+ * =>
+ fatal("bogus type "+stypeconv(t1)+" vs "+stypeconv(t2)+" in rtequal");
+ return 0;
+ Tnone or
+ Tbig or
+ Tbyte or
+ Treal or
+ Tint or
+ Tstring =>
+ #
+ # this should always be caught by t1 == t2 check
+ #
+ fatal("bogus value type "+stypeconv(t1)+" vs "+stypeconv(t2)+" in rtequal");
+ return 1;
+ Tfix =>
+ return sametree(t1.val, t2.val);
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan =>
+ if(t1.kind != Tref && assumeteq(t1, t2))
+ return 1;
+ return rtequal(t1.tof, t2.tof);
+ Tfn =>
+ if(t1.varargs != t2.varargs)
+ return 0;
+ if(!idequal(t1.ids, t2.ids, 0, storespace))
+ return 0;
+ # if(!idequal(t1.polys, t2.polys, 1, nil))
+ if(!pyequal(t1, t2))
+ return 0;
+ return rtequal(t1.tof, t2.tof);
+ Ttuple or
+ Texception =>
+ if(t1.kind != t2.kind || t1.cons != t2.cons)
+ return 0;
+ if(assumeteq(t1, t2))
+ return 1;
+ return idequal(t1.ids, t2.ids, 0, storespace);
+ Tadt or
+ Tadtpick or
+ Tmodule =>
+ if(assumeteq(t1, t2))
+ return 1;
+
+ #
+ # compare interfaces when comparing modules
+ #
+ if(t1.kind == Tmodule)
+ return idequal(t1.tof.ids, t2.tof.ids, 1, nil);
+
+ #
+ # picked adts; check parent,
+ # assuming equiv picked fields,
+ # then check picked fields are equiv
+ #
+ if(t1.kind == Tadtpick && !rtequal(t1.decl.dot.ty, t2.decl.dot.ty))
+ return 0;
+
+ #
+ # adts with pick tags: check picked fields for equality
+ #
+ if(!idequal(t1.tags, t2.tags, 1, nil))
+ return 0;
+
+ # if(!idequal(t1.polys, t2.polys, 1, nil))
+ if(!pyequal(t1, t2))
+ return 0;
+ return idequal(t1.ids, t2.ids, 1, storespace);
+ Tpoly =>
+ if(assumeteq(t1, t2))
+ return 1;
+ if(t1.decl.sym != t2.decl.sym)
+ return 0;
+ return idequal(t1.ids, t2.ids, 1, nil);
+ }
+}
+
+assumeteq(t1, t2: ref Type): int
+{
+ r1, r2: ref Type;
+
+ if(t1.teq == nil && t2.teq == nil){
+ eqrec++;
+ eqset += 2;
+ t1.teq = t2.teq = t1;
+ }else{
+ if(t1.teq == nil){
+ r1 = t1;
+ t1 = t2;
+ t2 = r1;
+ }
+ for(r1 = t1.teq; r1 != r1.teq; r1 = r1.teq)
+ ;
+ for(r2 = t2.teq; r2 != nil && r2 != r2.teq; r2 = r2.teq)
+ ;
+ if(r1 == r2)
+ return 1;
+ if(r2 == nil)
+ eqset++;
+ t2.teq = t1;
+ for(; t2 != r1; t2 = r2){
+ r2 = t2.teq;
+ t2.teq = r1;
+ }
+ }
+ return 0;
+}
+
+#
+# checking structural equality for modules, adts, tuples, and fns
+#
+idequal(id1, id2: ref Decl, usenames: int, storeok: array of int): int
+{
+ #
+ # this is just a shortcut
+ #
+ if(id1 == id2)
+ return 1;
+
+ for(; id1 != nil; id1 = id1.next){
+ if(storeok != nil && !storeok[id1.store])
+ continue;
+ while(id2 != nil && storeok != nil && !storeok[id2.store])
+ id2 = id2.next;
+ if(id2 == nil
+ || usenames && id1.sym != id2.sym
+ || id1.store != id2.store
+ || id1.implicit != id2.implicit
+ || id1.cyc != id2.cyc
+ || (id1.dot == nil) != (id2.dot == nil)
+ || id1.dot != nil && id2.dot != nil && id1.dot.ty.kind != id2.dot.ty.kind
+ || !rtequal(id1.ty, id2.ty))
+ return 0;
+ id2 = id2.next;
+ }
+ while(id2 != nil && storeok != nil && !storeok[id2.store])
+ id2 = id2.next;
+ return id1 == nil && id2 == nil;
+}
+
+
+pyequal(t1: ref Type, t2: ref Type): int
+{
+ pt1, pt2: ref Type;
+ id1, id2: ref Decl;
+
+ if(t1 == t2)
+ return 1;
+ id1 = t1.polys;
+ id2 = t2.polys;
+ for(; id1 != nil; id1 = id1.next){
+ if(id2 == nil)
+ return 0;
+ pt1 = id1.ty;
+ pt2 = id2.ty;
+ if(!rtequal(pt1, pt2)){
+ if(t1.tmap != nil)
+ pt1 = valtmap(pt1, t1.tmap);
+ if(t2.tmap != nil)
+ pt2 = valtmap(pt2, t2.tmap);
+ if(!rtequal(pt1, pt2))
+ return 0;
+ }
+ id2 = id2.next;
+ }
+ return id1 == nil && id2 == nil;
+}
+
+cleareqrec(t: ref Type): int
+{
+ n := 0;
+ for(; t != nil && (t.rec & TReq) == TReq; t = t.tof){
+ t.rec &= ~TReq;
+ if(t.teq != nil){
+ t.teq = nil;
+ n++;
+ }
+ if(t.kind == Tadtpick)
+ n += cleareqrec(t.decl.dot.ty);
+ if(t.kind == Tmodule)
+ t = t.tof;
+ for(id := t.ids; id != nil; id = id.next)
+ n += cleareqrec(id.ty);
+ for(id = t.tags; id != nil; id = id.next)
+ n += cleareqrec(id.ty);
+ for(id = t.polys; id != nil; id = id.next)
+ n += cleareqrec(id.ty);
+ }
+ return n;
+}
+
+raisescompat(n1: ref Node, n2: ref Node): int
+{
+ if(n1 == n2)
+ return 1;
+ if(n2 == nil)
+ return 1; # no need to repeat in definition if given in declaration
+ if(n1 == nil)
+ return 0;
+ for((n1, n2) = (n1.left, n2.left); n1 != nil && n2 != nil; (n1, n2) = (n1.right, n2.right)){
+ if(n1.left.decl != n2.left.decl)
+ return 0;
+ }
+ return n1 == n2;
+}
+
+# t1 a polymorphic type
+fnunify(t1: ref Type, t2: ref Type, tp: ref Tpair, swapped: int): (int, ref Tpair)
+{
+ id, ids: ref Decl;
+ sym: ref Sym;
+ ok: int;
+
+ for(ids = t1.ids; ids != nil; ids = ids.next){
+ sym = ids.sym;
+ (id, nil) = fnlookup(sym, t2);
+ if(id != nil)
+ usetype(id.ty);
+ if(id == nil){
+ if(dowarn)
+ error(unifysrc.start, "type " + typeconv(t2) + " does not have a '" + sym.name + "' function");
+ return (0, tp);
+ }
+ else if(id.ty.kind != Tfn){
+ if(dowarn)
+ error(unifysrc.start, typeconv(id.ty) + " is not a function");
+ return (0, tp);
+ }
+ else{
+ (ok, tp) = rtunify(ids.ty, id.ty, tp, !swapped);
+ if(!ok){
+ if(dowarn)
+ error(unifysrc.start, typeconv(ids.ty) + " and " + typeconv(id.ty) + " are not compatible wrt " + sym.name);
+ return (0, tp);
+ }
+ }
+ }
+ return (1, tp);
+}
+
+fncleareqrec(t1: ref Type, t2: ref Type): int
+{
+ id, ids: ref Decl;
+ n: int;
+
+ n = 0;
+ n += cleareqrec(t1);
+ n += cleareqrec(t2);
+ for(ids = t1.ids; ids != nil; ids = ids.next){
+ (id, nil) = fnlookup(ids.sym, t2);
+ if(id == nil)
+ continue;
+ else{
+ n += cleareqrec(ids.ty);
+ n += cleareqrec(id.ty);
+ }
+ }
+ return n;
+}
+
+tunify(t1: ref Type, t2: ref Type): (int, ref Tpair)
+{
+ v: int;
+ p: ref Tpair;
+
+ eqrec = 0;
+ eqset = 0;
+ (ok, tp) := rtunify(t1, t2, nil, 0);
+ v = cleareqrec(t1) + cleareqrec(t2);
+ for(p = tp; p != nil; p = p.nxt)
+ v += fncleareqrec(p.t1, p.t2);
+ if(0 && v != eqset)
+ fatal("recid t1 " + stypeconv(t1) + " and t2 " + stypeconv(t2) + " not balanced in tunify: " + string v + " " + string eqset);
+ return (ok, tp);
+}
+
+rtunify(t1: ref Type, t2: ref Type, tp: ref Tpair, swapped: int): (int, ref Tpair)
+{
+ ok: int;
+
+ t1 = valtmap(t1, tp);
+ t2 = valtmap(t2, tp);
+ if(t1 == t2)
+ return (1, tp);
+ if(t1 == nil || t2 == nil)
+ return (0, tp);
+ if(t1.kind == Terror || t2.kind == Terror)
+ return (1, tp);
+ if(t1.kind != Tpoly && t2.kind == Tpoly){
+ (t1, t2) = (t2, t1);
+ swapped = !swapped;
+ }
+ if(t1.kind == Tpoly){
+ # if(typein(t1, t2))
+ # return (0, tp);
+ if(!tattr[t2.kind].isptr)
+ return (0, tp);
+ if(t2.kind != Tany)
+ tp = addtmap(t1, t2, tp);
+ return fnunify(t1, t2, tp, swapped);
+ }
+ if(t1.kind != Tany && t2.kind == Tany){
+ (t1, t2) = (t2, t1);
+ swapped = !swapped;
+ }
+ if(t1.kind == Tadt && t1.tags != nil && t2.kind == Tadtpick && !swapped)
+ t2 = t2.decl.dot.ty;
+ if(t2.kind == Tadt && t2.tags != nil && t1.kind == Tadtpick && swapped)
+ t1 = t1.decl.dot.ty;
+ if(t1.kind != Tany && t1.kind != t2.kind)
+ return (0, tp);
+ t1.rec |= TReq;
+ t2.rec |= TReq;
+ case(t1.kind){
+ * =>
+ return (tequal(t1, t2), tp);
+ Tany =>
+ return (tattr[t2.kind].isptr, tp);
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan =>
+ if(t1.kind != Tref && assumeteq(t1, t2))
+ return (1, tp);
+ return rtunify(t1.tof, t2.tof, tp, swapped);
+ Tfn =>
+ (ok, tp) = idunify(t1.ids, t2.ids, tp, swapped);
+ if(!ok)
+ return (0, tp);
+ (ok, tp) = idunify(t1.polys, t2.polys, tp, swapped);
+ if(!ok)
+ return (0, tp);
+ return rtunify(t1.tof, t2.tof, tp, swapped);
+ Ttuple =>
+ if(assumeteq(t1, t2))
+ return (1, tp);
+ return idunify(t1.ids, t2.ids, tp, swapped);
+ Tadt or
+ Tadtpick =>
+ if(assumeteq(t1, t2))
+ return (1, tp);
+ (ok, tp) = idunify(t1.polys, t2.polys, tp, swapped);
+ if(!ok)
+ return (0, tp);
+ (ok, tp) = idunify(t1.tags, t2.tags, tp, swapped);
+ if(!ok)
+ return (0, tp);
+ return idunify(t1.ids, t2.ids, tp, swapped);
+ Tmodule =>
+ if(assumeteq(t1, t2))
+ return (1, tp);
+ return idunify(t1.tof.ids, t2.tof.ids, tp, swapped);
+ Tpoly =>
+ return (t1 == t2, tp);
+ }
+ return (1, tp);
+}
+
+idunify(id1: ref Decl, id2: ref Decl, tp: ref Tpair, swapped: int): (int, ref Tpair)
+{
+ ok: int;
+
+ if(id1 == id2)
+ return (1, tp);
+ for(; id1 != nil; id1 = id1.next){
+ if(id2 == nil)
+ return (0, tp);
+ (ok, tp) = rtunify(id1.ty, id2.ty, tp, swapped);
+ if(!ok)
+ return (0, tp);
+ id2 = id2.next;
+ }
+ return (id1 == nil && id2 == nil, tp);
+}
+
+polyequal(id1: ref Decl, id2: ref Decl): int
+{
+ # allow id2 list to have an optional for clause
+ ck2 := 0;
+ for(d := id2; d != nil; d = d.next)
+ if(d.ty.ids != nil)
+ ck2 = 1;
+ for(; id1 != nil; id1 = id1.next){
+ if(id2 == nil
+ || id1.sym != id2.sym
+ || id1.ty.decl != nil && id2.ty.decl != nil && id1.ty.decl.sym != id2.ty.decl.sym)
+ return 0;
+ if(ck2 && !idequal(id1.ty.ids, id2.ty.ids, 1, nil))
+ return 0;
+ id2 = id2.next;
+ }
+ return id1 == nil && id2 == nil;
+}
+
+calltype(f: ref Type, a: ref Node, rt: ref Type): ref Type
+{
+ t: ref Type;
+ id, first, last: ref Decl;
+
+ first = last = nil;
+ t = mktype(f.src.start, f.src.stop, Tfn, rt, nil);
+ if(f.kind == Tref)
+ t.polys = f.tof.polys;
+ else
+ t.polys = f.polys;
+ for( ; a != nil; a = a.right){
+ id = mkdecl(f.src, Darg, a.left.ty);
+ if(last == nil)
+ first = id;
+ else
+ last.next = id;
+ last = id;
+ }
+ t.ids = first;
+ if(f.kind == Tref)
+ t = mktype(f.src.start, f.src.stop, Tref, t, nil);
+ return t;
+}
+
+duptype(t: ref Type): ref Type
+{
+ nt: ref Type;
+
+ nt = ref Type;
+ *nt = *t;
+ nt.ok &= ~(OKverify|OKref|OKclass|OKsized|OKcycsize|OKcyc);
+ nt.flags |= INST;
+ nt.eq = nil;
+ nt.sbl = -1;
+ if(t.decl != nil && (nt.kind == Tadt || nt.kind == Tadtpick || nt.kind == Ttuple)){
+ nt.decl = dupdecl(t.decl);
+ nt.decl.ty = nt;
+ nt.decl.link = t.decl;
+ if(t.decl.dot != nil){
+ nt.decl.dot = dupdecl(t.decl.dot);
+ nt.decl.dot.link = t.decl.dot;
+ }
+ }
+ else
+ nt.decl = nil;
+ return nt;
+}
+
+dpolys(ids: ref Decl): int
+{
+ p: ref Decl;
+
+ for(p = ids; p != nil; p = p.next)
+ if(tpolys(p.ty))
+ return 1;
+ return 0;
+}
+
+tpolys(t: ref Type): int
+{
+ v: int;
+ tyl: ref Typelist;
+
+ if(t == nil)
+ return 0;
+ if(int(t.flags&(POLY|NOPOLY)))
+ return int(t.flags&POLY);
+ case(t.kind){
+ * =>
+ v = 0;
+ break;
+ Tarrow or
+ Tdot or
+ Tpoly =>
+ v = 1;
+ break;
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan =>
+ v = tpolys(t.tof);
+ break;
+ Tid =>
+ v = tpolys(t.decl.ty);
+ break;
+ Tinst =>
+ for(tyl = t.tlist; tyl != nil; tyl = tyl.nxt)
+ if(tpolys(tyl.t)){
+ v = 1;
+ break;
+ }
+ v = tpolys(t.tof);
+ break;
+ Tfn or
+ Tadt or
+ Tadtpick or
+ Ttuple or
+ Texception =>
+ if(t.polys != nil){
+ v = 1;
+ break;
+ }
+ if(int(t.rec&TRvis))
+ return 0;
+ t.rec |= TRvis;
+ v = tpolys(t.tof) || dpolys(t.polys) || dpolys(t.ids) || dpolys(t.tags);
+ t.rec &= ~TRvis;
+ if(t.kind == Tadtpick && v == 0)
+ v = tpolys(t.decl.dot.ty);
+ break;
+ }
+ if(v)
+ t.flags |= POLY;
+ else
+ t.flags |= NOPOLY;
+ return v;
+}
+
+doccurs(ids: ref Decl, tp: ref Tpair): int
+{
+ p: ref Decl;
+
+ for(p = ids; p != nil; p = p.next){
+ if(toccurs(p.ty, tp))
+ return 1;
+ }
+ return 0;
+}
+
+toccurs(t: ref Type, tp: ref Tpair): int
+{
+ o: int;
+
+ if(t == nil)
+ return 0;
+ if(!int(t.flags&(POLY|NOPOLY)))
+ tpolys(t);
+ if(int(t.flags&NOPOLY))
+ return 0;
+ case(t.kind){
+ * =>
+ fatal("unknown type " + string t.kind + " in toccurs");
+ Tnone or
+ Tbig or
+ Tbyte or
+ Treal or
+ Tint or
+ Tstring or
+ Tfix or
+ Tmodule or
+ Terror =>
+ return 0;
+ Tarrow or
+ Tdot =>
+ return 1;
+ Tpoly =>
+ return valtmap(t, tp) != t;
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan =>
+ return toccurs(t.tof, tp);
+ Tid =>
+ return toccurs(t.decl.ty, tp);
+ Tinst =>
+ for(tyl := t.tlist; tyl != nil; tyl = tyl.nxt)
+ if(toccurs(tyl.t, tp))
+ return 1;
+ return toccurs(t.tof, tp);
+ Tfn or
+ Tadt or
+ Tadtpick or
+ Ttuple or
+ Texception =>
+ if(int(t.rec&TRvis))
+ return 0;
+ t.rec |= TRvis;
+ o = toccurs(t.tof, tp) || doccurs(t.polys, tp) || doccurs(t.ids, tp) || doccurs(t.tags, tp);
+ t.rec &= ~TRvis;
+ if(t.kind == Tadtpick && o == 0)
+ o = toccurs(t.decl.dot.ty, tp);
+ return o;
+ }
+ return 0;
+}
+
+expandids(ids: ref Decl, adtt: ref Decl, tp: ref Tpair, sym: int): (ref Decl, ref Tpair)
+{
+ p, q, nids, last: ref Decl;
+
+ nids = last = nil;
+ for(p = ids; p != nil; p = p.next){
+ q = dupdecl(p);
+ (q.ty, tp) = expandtype(p.ty, nil, adtt, tp);
+ if(sym && q.ty.decl != nil)
+ q.sym = q.ty.decl.sym;
+ if(q.store == Dfn)
+ q.link = p;
+ if(nids == nil)
+ nids = q;
+ else
+ last.next = q;
+ last = q;
+ }
+ return (nids, tp);
+}
+
+expandtype(t: ref Type, instt: ref Type, adtt: ref Decl, tp: ref Tpair): (ref Type, ref Tpair)
+{
+ nt: ref Type;
+
+ if(t == nil)
+ return (nil, tp);
+ if(!toccurs(t, tp))
+ return (t, tp);
+ case(t.kind){
+ * =>
+ fatal("unknown type " + string t.kind + " in expandtype");
+ Tpoly =>
+ return (valtmap(t, tp), tp);
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan =>
+ nt = duptype(t);
+ (nt.tof, tp) = expandtype(t.tof, nil, adtt, tp);
+ return (nt, tp);
+ Tid =>
+ return expandtype(idtype(t), nil, adtt, tp);
+ Tdot =>
+ return expandtype(dottype(t, adtt), nil, adtt, tp);
+ Tarrow =>
+ return expandtype(arrowtype(t, adtt), nil, adtt, tp);
+ Tinst =>
+ if((nt = valtmap(t, tp)) != t)
+ return (nt, tp);
+ (t, tp) = insttype(t, adtt, tp);
+ return expandtype(t, nil, adtt, tp);
+ Tfn or
+ Tadt or
+ Tadtpick or
+ Ttuple or
+ Texception =>
+ if((nt = valtmap(t, tp)) != t)
+ return (nt, tp);
+ if(t.kind == Tadt)
+ adtt = t.decl;
+ nt = duptype(t);
+ tp = addtmap(t, nt, tp);
+ if(instt != nil)
+ tp = addtmap(instt, nt, tp);
+ (nt.tof, tp) = expandtype(t.tof, nil, adtt, tp);
+ (nt.polys, tp) = expandids(t.polys, adtt, tp, 1);
+ (nt.ids, tp) = expandids(t.ids, adtt, tp, 0);
+ (nt.tags, tp) = expandids(t.tags, adtt, tp, 0);
+ if(t.kind == Tadt){
+ for(ids := nt.tags; ids != nil; ids = ids.next)
+ ids.ty.decl.dot = nt.decl;
+ }
+ if(t.kind == Tadtpick){
+ (nt.decl.dot.ty, tp) = expandtype(t.decl.dot.ty, nil, adtt, tp);
+ }
+ if(t.tmap != nil){
+ nt.tmap = nil;
+ for(p := t.tmap; p != nil; p = p.nxt)
+ nt.tmap = addtmap(valtmap(p.t1, tp), valtmap(p.t2, tp), nt.tmap);
+ }
+ return (nt, tp);
+ }
+ return (nil, tp);
+}
+
+#
+# create type signatures
+# sign the same information used
+# for testing type equality
+#
+sign(d: ref Decl): int
+{
+ t := d.ty;
+ if(t.sig != 0)
+ return t.sig;
+
+ if(ispoly(d))
+ rmfnptrs(d);
+
+ sigend := -1;
+ sigalloc := 1024;
+ sig: array of byte;
+ while(sigend < 0 || sigend >= sigalloc){
+ sigalloc *= 2;
+ sig = array[sigalloc] of byte;
+ eqrec = 0;
+ sigend = rtsign(t, sig, 0);
+ v := clearrec(t);
+ if(v != eqrec)
+ fatal("recid not balanced in sign: "+string v+" "+string eqrec);
+ eqrec = 0;
+ }
+
+ if(signdump != "" && dotconv(d) == signdump){
+ print("sign %s len %d\n", dotconv(d), sigend);
+ print("%s\n", string sig[:sigend]);
+ }
+
+ md5sig := array[Keyring->MD5dlen] of {* => byte 0};
+ md5(sig, sigend, md5sig, nil);
+
+ for(i := 0; i < Keyring->MD5dlen; i += 4)
+ t.sig ^= int md5sig[i+0] | (int md5sig[i+1]<<8) | (int md5sig[i+2]<<16) | (int md5sig[i+3]<<24);
+
+ if(debug['S'])
+ print("signed %s type %s len %d sig %#ux\n", dotconv(d), typeconv(t), sigend, t.sig);
+ return t.sig;
+}
+
+SIGSELF: con byte 'S';
+SIGVARARGS: con byte '*';
+SIGCYC: con byte 'y';
+SIGREC: con byte '@';
+
+sigkind := array[Tend] of
+{
+ Tnone => byte 'n',
+ Tadt => byte 'a',
+ Tadtpick => byte 'p',
+ Tarray => byte 'A',
+ Tbig => byte 'B',
+ Tbyte => byte 'b',
+ Tchan => byte 'C',
+ Treal => byte 'r',
+ Tfn => byte 'f',
+ Tint => byte 'i',
+ Tlist => byte 'L',
+ Tmodule => byte 'm',
+ Tref => byte 'R',
+ Tstring => byte 's',
+ Ttuple => byte 't',
+ Texception => byte 'e',
+ Tfix => byte 'x',
+ Tpoly => byte 'P',
+
+ * => byte 0,
+};
+
+rtsign(t: ref Type, sig: array of byte, spos: int): int
+{
+ id: ref Decl;
+
+ if(t == nil)
+ return spos;
+
+ if(spos < 0 || spos + 8 >= len sig)
+ return -1;
+
+ if(t.eq != nil && t.eq.id){
+ if(t.eq.id < 0 || t.eq.id > eqrec)
+ fatal("sign rec "+typeconv(t)+" "+string t.eq.id+" "+string eqrec);
+
+ sig[spos++] = SIGREC;
+ name := array of byte string t.eq.id;
+ if(spos + len name > len sig)
+ return -1;
+ sig[spos:] = name;
+ spos += len name;
+ return spos;
+ }
+ if(t.eq != nil){
+ eqrec++;
+ t.eq.id = eqrec;
+ }
+
+ kind := sigkind[t.kind];
+ sig[spos++] = kind;
+ if(kind == byte 0)
+ fatal("no sigkind for "+typeconv(t));
+
+ t.rec = byte 1;
+ case t.kind{
+ * =>
+ fatal("bogus type "+stypeconv(t)+" in rtsign");
+ return -1;
+ Tnone or
+ Tbig or
+ Tbyte or
+ Treal or
+ Tint or
+ Tstring or
+ Tpoly =>
+ return spos;
+ Tfix =>
+ name := array of byte string t.val.c.rval;
+ if(spos + len name - 1 >= len sig)
+ return -1;
+ sig[spos: ] = name;
+ spos += len name;
+ return spos;
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan =>
+ return rtsign(t.tof, sig, spos);
+ Tfn =>
+ if(t.varargs != byte 0)
+ sig[spos++] = SIGVARARGS;
+ if(t.polys != nil)
+ spos = idsign(t.polys, 0, sig, spos);
+ spos = idsign(t.ids, 0, sig, spos);
+ if(t.eraises != nil)
+ spos = raisessign(t.eraises, sig, spos);
+ return rtsign(t.tof, sig, spos);
+ Ttuple =>
+ return idsign(t.ids, 0, sig, spos);
+ Tadt =>
+ #
+ # this is a little different than in rtequal,
+ # since we flatten the adt we used to represent the globals
+ #
+ if(t.eq == nil){
+ if(t.decl.sym.name != ".mp")
+ fatal("no t.eq field for "+typeconv(t));
+ spos--;
+ for(id = t.ids; id != nil; id = id.next){
+ spos = idsign1(id, 1, sig, spos);
+ if(spos < 0 || spos >= len sig)
+ return -1;
+ sig[spos++] = byte ';';
+ }
+ return spos;
+ }
+ if(t.polys != nil)
+ spos = idsign(t.polys, 0, sig, spos);
+ spos = idsign(t.ids, 1, sig, spos);
+ if(spos < 0 || t.tags == nil)
+ return spos;
+
+ #
+ # convert closing ')' to a ',', then sign any tags
+ #
+ sig[spos-1] = byte ',';
+ for(tg := t.tags; tg != nil; tg = tg.next){
+ name := array of byte (tg.sym.name + "=>");
+ if(spos + len name > len sig)
+ return -1;
+ sig[spos:] = name;
+ spos += len name;
+
+ spos = rtsign(tg.ty, sig, spos);
+ if(spos < 0 || spos >= len sig)
+ return -1;
+
+ if(tg.next != nil)
+ sig[spos++] = byte ',';
+ }
+ if(spos >= len sig)
+ return -1;
+ sig[spos++] = byte ')';
+ return spos;
+ Tadtpick =>
+ spos = idsign(t.ids, 1, sig, spos);
+ if(spos < 0)
+ return spos;
+ return rtsign(t.decl.dot.ty, sig, spos);
+ Tmodule =>
+ if(t.tof.linkall == byte 0)
+ fatal("signing a narrowed module");
+
+ if(spos >= len sig)
+ return -1;
+ sig[spos++] = byte '{';
+ for(id = t.tof.ids; id != nil; id = id.next){
+ if(id.tag)
+ continue;
+ if(id.sym.name == ".mp"){
+ spos = rtsign(id.ty, sig, spos);
+ if(spos < 0)
+ return -1;
+ continue;
+ }
+ spos = idsign1(id, 1, sig, spos);
+ if(spos < 0 || spos >= len sig)
+ return -1;
+ sig[spos++] = byte ';';
+ }
+ if(spos >= len sig)
+ return -1;
+ sig[spos++] = byte '}';
+ return spos;
+ }
+}
+
+idsign(id: ref Decl, usenames: int, sig: array of byte, spos: int): int
+{
+ if(spos >= len sig)
+ return -1;
+ sig[spos++] = byte '(';
+ first := 1;
+ for(; id != nil; id = id.next){
+ if(id.store == Dlocal)
+ fatal("local "+id.sym.name+" in idsign");
+
+ if(!storespace[id.store])
+ continue;
+
+ if(!first){
+ if(spos >= len sig)
+ return -1;
+ sig[spos++] = byte ',';
+ }
+
+ spos = idsign1(id, usenames, sig, spos);
+ if(spos < 0)
+ return -1;
+ first = 0;
+ }
+ if(spos >= len sig)
+ return -1;
+ sig[spos++] = byte ')';
+ return spos;
+}
+
+idsign1(id: ref Decl, usenames: int, sig: array of byte, spos: int): int
+{
+ if(usenames){
+ name := array of byte (id.sym.name+":");
+ if(spos + len name >= len sig)
+ return -1;
+ sig[spos:] = name;
+ spos += len name;
+ }
+
+ if(spos + 2 >= len sig)
+ return -1;
+
+ if(id.implicit != byte 0)
+ sig[spos++] = SIGSELF;
+
+ if(id.cyc != byte 0)
+ sig[spos++] = SIGCYC;
+
+ return rtsign(id.ty, sig, spos);
+}
+
+raisessign(n: ref Node, sig: array of byte, spos: int): int
+{
+ if(spos >= len sig)
+ return -1;
+ sig[spos++] = byte '(';
+ for(nn := n.left; nn != nil; nn = nn.right){
+ s := array of byte nn.left.decl.sym.name;
+ if(spos+len s - 1 >= len sig)
+ return -1;
+ sig[spos: ] = s;
+ spos += len s;
+ if(nn.right != nil){
+ if(spos >= len sig)
+ return -1;
+ sig[spos++] = byte ',';
+ }
+ }
+ if(spos >= len sig)
+ return -1;
+ sig[spos++] = byte ')';
+ return spos;
+}
+
+clearrec(t: ref Type): int
+{
+ id: ref Decl;
+
+ n := 0;
+ for(; t != nil && t.rec != byte 0; t = t.tof){
+ t.rec = byte 0;
+ if(t.eq != nil && t.eq.id != 0){
+ t.eq.id = 0;
+ n++;
+ }
+ if(t.kind == Tmodule){
+ for(id = t.tof.ids; id != nil; id = id.next)
+ n += clearrec(id.ty);
+ return n;
+ }
+ if(t.kind == Tadtpick)
+ n += clearrec(t.decl.dot.ty);
+ for(id = t.ids; id != nil; id = id.next)
+ n += clearrec(id.ty);
+ for(id = t.tags; id != nil; id = id.next)
+ n += clearrec(id.ty);
+ for(id = t.polys; id != nil; id = id.next)
+ n += clearrec(id.ty);
+ }
+ return n;
+}
+
+# must a variable of the given type be zeroed ? (for uninitialized declarations inside loops)
+tmustzero(t : ref Type) : int
+{
+ if(t==nil)
+ return 0;
+ if(tattr[t.kind].isptr)
+ return 1;
+ if(t.kind == Tadtpick)
+ t = t.tof;
+ if(t.kind == Ttuple || t.kind == Tadt)
+ return mustzero(t.ids);
+ return 0;
+}
+
+mustzero(decls : ref Decl) : int
+{
+ d : ref Decl;
+
+ for (d = decls; d != nil; d = d.next)
+ if (tmustzero(d.ty))
+ return 1;
+ return 0;
+}
+
+typeconv(t: ref Type): string
+{
+ if(t == nil)
+ return "nothing";
+ return tprint(t);
+}
+
+stypeconv(t: ref Type): string
+{
+ if(t == nil)
+ return "nothing";
+ return stprint(t);
+}
+
+tprint(t: ref Type): string
+{
+ id: ref Decl;
+
+ if(t == nil)
+ return "";
+ s := "";
+ if(t.kind < 0 || t.kind >= Tend){
+ s += "kind ";
+ s += string t.kind;
+ return s;
+ }
+ if(t.pr != byte 0 && t.decl != nil){
+ if(t.decl.dot != nil && !isimpmod(t.decl.dot.sym)){
+ s += t.decl.dot.sym.name;
+ s += "->";
+ }
+ s += t.decl.sym.name;
+ return s;
+ }
+ t.pr = byte 1;
+ case t.kind{
+ Tarrow =>
+ s += tprint(t.tof);
+ s += "->";
+ s += t.decl.sym.name;
+ Tdot =>
+ s += tprint(t.tof);
+ s += ".";
+ s += t.decl.sym.name;
+ Tid or
+ Tpoly =>
+ s += t.decl.sym.name;
+ Tinst =>
+ s += tprint(t.tof);
+ s += "[";
+ for(tyl := t.tlist; tyl != nil; tyl = tyl.nxt){
+ s += tprint(tyl.t);
+ if(tyl.nxt != nil)
+ s += ", ";
+ }
+ s += "]";
+ Tint or
+ Tbig or
+ Tstring or
+ Treal or
+ Tbyte or
+ Tany or
+ Tnone or
+ Terror or
+ Tainit or
+ Talt or
+ Tcase or
+ Tcasel or
+ Tcasec or
+ Tgoto or
+ Tiface or
+ Texception or
+ Texcept =>
+ s += kindname[t.kind];
+ Tfix =>
+ s += kindname[t.kind] + "(" + expconv(t.val) + ")";
+ Tref =>
+ s += "ref ";
+ s += tprint(t.tof);
+ Tchan or
+ Tarray or
+ Tlist =>
+ s += kindname[t.kind];
+ s += " of ";
+ s += tprint(t.tof);
+ Tadtpick =>
+ s += t.decl.dot.sym.name + "." + t.decl.sym.name;
+ Tadt =>
+ if(t.decl.dot != nil && !isimpmod(t.decl.dot.sym))
+ s += t.decl.dot.sym.name + "->";
+ s += t.decl.sym.name;
+ if(t.polys != nil){
+ s += "[";
+ for(id = t.polys; id != nil; id = id.next){
+ if(t.tmap != nil)
+ s += tprint(valtmap(id.ty, t.tmap));
+ else
+ s += id.sym.name;
+ if(id.next != nil)
+ s += ", ";
+ }
+ s += "]";
+ }
+ Tmodule =>
+ s += t.decl.sym.name;
+ Ttuple =>
+ s += "(";
+ for(id = t.ids; id != nil; id = id.next){
+ s += tprint(id.ty);
+ if(id.next != nil)
+ s += ", ";
+ }
+ s += ")";
+ Tfn =>
+ s += "fn";
+ if(t.polys != nil){
+ s += "[";
+ for(id = t.polys; id != nil; id = id.next){
+ s += id.sym.name;
+ if(id.next != nil)
+ s += ", ";
+ }
+ s += "]";
+ }
+ s += "(";
+ for(id = t.ids; id != nil; id = id.next){
+ if(id.sym == nil)
+ s += "nil: ";
+ else{
+ s += id.sym.name;
+ s += ": ";
+ }
+ if(id.implicit != byte 0)
+ s += "self ";
+ s += tprint(id.ty);
+ if(id.next != nil)
+ s += ", ";
+ }
+ if(t.varargs != byte 0 && t.ids != nil)
+ s += ", *";
+ else if(t.varargs != byte 0)
+ s += "*";
+ if(t.tof != nil && t.tof.kind != Tnone){
+ s += "): ";
+ s += tprint(t.tof);
+ }else
+ s += ")";
+ * =>
+ yyerror("tprint: unknown type kind "+string t.kind);
+ }
+ t.pr = byte 0;
+ return s;
+}
+
+stprint(t: ref Type): string
+{
+ if(t == nil)
+ return "";
+ s := "";
+ case t.kind{
+ Tid =>
+ s += "id ";
+ s += t.decl.sym.name;
+ Tadt or
+ Tadtpick or
+ Tmodule =>
+ return kindname[t.kind] + " " + tprint(t);
+ }
+ return tprint(t);
+}
+
+# generalize ref P.A, ref P.B to ref P
+
+# tparent(t1: ref Type, t2: ref Type): ref Type
+# {
+# if(t1 == nil || t2 == nil || t1.kind != Tref || t2.kind != Tref)
+# return t1;
+# t1 = t1.tof;
+# t2 = t2.tof;
+# if(t1 == nil || t2 == nil || t1.kind != Tadtpick || t2.kind != Tadtpick)
+# return t1;
+# t1 = t1.decl.dot.ty;
+# t2 = t2.decl.dot.ty;
+# if(tequal(t1, t2))
+# return mktype(t1.src.start, t1.src.stop, Tref, t1, nil);
+# return t1;
+# }
+
+tparent0(t1: ref Type, t2: ref Type): int
+{
+ id1, id2: ref Decl;
+
+ if(t1 == t2)
+ return 1;
+ if(t1 == nil || t2 == nil)
+ return 0;
+ if(t1.kind == Tadt && t2.kind == Tadtpick)
+ t2 = t2.decl.dot.ty;
+ if(t1.kind == Tadtpick && t2.kind == Tadt)
+ t1 = t1.decl.dot.ty;
+ if(t1.kind != t2.kind)
+ return 0;
+ case(t1.kind){
+ * =>
+ fatal("unknown type " + string t1.kind + " v " + string t2.kind + " in tparent");
+ break;
+ Terror or
+ Tstring or
+ Tnone or
+ Tint or
+ Tbig or
+ Tbyte or
+ Treal or
+ Tany =>
+ return 1;
+ Texception or
+ Tfix or
+ Tfn or
+ Tadt or
+ Tmodule or
+ Tpoly =>
+ return tcompat(t1, t2, 0);
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan =>
+ return tparent0(t1.tof, t2.tof);
+ Ttuple =>
+ for((id1, id2) = (t1.ids, t2.ids); id1 != nil && id2 != nil; (id1, id2) = (id1.next, id2.next))
+ if(!tparent0(id1.ty, id2.ty))
+ return 0;
+ return id1 == nil && id2 == nil;
+ Tadtpick =>
+ return tequal(t1.decl.dot.ty, t2.decl.dot.ty);
+ }
+ return 0;
+}
+
+tparent1(t1: ref Type, t2: ref Type): ref Type
+{
+ t, nt: ref Type;
+ id, id1, id2, idt: ref Decl;
+
+ if(t1.kind == Tadt && t2.kind == Tadtpick)
+ t2 = t2.decl.dot.ty;
+ if(t1.kind == Tadtpick && t2.kind == Tadt)
+ t1 = t1.decl.dot.ty;
+ case(t1.kind){
+ * =>
+ return t1;
+ Tref or
+ Tlist or
+ Tarray or
+ Tchan =>
+ t = tparent1(t1.tof, t2.tof);
+ if(t == t1.tof)
+ return t1;
+ return mktype(t1.src.start, t1.src.stop, t1.kind, t, nil);
+ Ttuple =>
+ nt = nil;
+ id = nil;
+ for((id1, id2) = (t1.ids, t2.ids); id1 != nil && id2 != nil; (id1, id2) = (id1.next, id2.next)){
+ t = tparent1(id1.ty, id2.ty);
+ if(t != id1.ty){
+ if(nt == nil){
+ nt = mktype(t1.src.start, t1.src.stop, Ttuple, nil, dupdecls(t1.ids));
+ for((id, idt) = (nt.ids, t1.ids); idt != id1; (id, idt) = (id.next, idt.next))
+ ;
+ }
+ id.ty = t;
+ }
+ if(id != nil)
+ id = id.next;
+ }
+ if(nt == nil)
+ return t1;
+ return nt;
+ Tadtpick =>
+ if(tequal(t1, t2))
+ return t1;
+ return t1.decl.dot.ty;
+ }
+ return t1;
+}
+
+tparent(t1: ref Type, t2: ref Type): ref Type
+{
+ if(tparent0(t1, t2))
+ return tparent1(t1, t2);
+ return t1;
+}
+
+#
+# make the tuple type used to initialize an exception type
+#
+mkexbasetype(t: ref Type): ref Type
+{
+ if(t.cons == byte 0)
+ fatal("mkexbasetype on non-constant");
+ last := mkids(t.decl.src, nil, tstring, nil);
+ last.store = Dfield;
+ nt := mktype(t.src.start, t.src.stop, Texception, nil, last);
+ nt.cons = byte 0;
+ new := mkids(t.decl.src, nil, tint, nil);
+ new.store = Dfield;
+ last.next = new;
+ last = new;
+ for(id := t.ids; id != nil; id = id.next){
+ new = ref *id;
+ new.cyc = byte 0;
+ last.next = new;
+ last = new;
+ }
+ last.next = nil;
+ return usetype(nt);
+}
+
+#
+# make an instantiated exception type
+#
+mkextype(t: ref Type): ref Type
+{
+ nt: ref Type;
+
+ if(t.cons == byte 0)
+ fatal("mkextype on non-constant");
+ if(t.tof != nil)
+ return t.tof;
+ nt = copytypeids(t);
+ nt.cons = byte 0;
+ t.tof = usetype(nt);
+ return t.tof;
+}
+
+#
+# convert an instantiated exception type to it's underlying type
+#
+mkextuptype(t: ref Type): ref Type
+{
+ id: ref Decl;
+ nt: ref Type;
+
+ if(int t.cons)
+ return t;
+ if(t.tof != nil)
+ return t.tof;
+ id = t.ids;
+ if(id == nil)
+ nt = t;
+ else if(id.next == nil)
+ nt = id.ty;
+ else{
+ nt = copytypeids(t);
+ nt.cons = byte 0;
+ nt.kind = Ttuple;
+ }
+ t.tof = usetype(nt);
+ return t.tof;
+}
+
+ckfix(t: ref Type, max: real)
+{
+ s := t.val.c.rval;
+ if(max == 0.0)
+ k := (big 1<<32) - big 1;
+ else
+ k = big 2 * big (max/s) + big 1;
+ x := big 1;
+ for(p := 0; k > x; p++)
+ x *= big 2;
+ if(p == 0 || p > 32){
+ error(t.src.start, "cannot fit fixed type into an int");
+ return;
+ }
+ if(p < 32)
+ t.val.c.rval /= real (1<<(32-p));
+}
+
+scale(t: ref Type): real
+{
+ n: ref Node;
+
+ if(t.kind == Tint || t.kind == Treal)
+ return 1.0;
+ if(t.kind != Tfix)
+ fatal("scale() on non fixed point type");
+ n = t.val;
+ if(n.op != Oconst)
+ fatal("non constant scale");
+ if(n.ty != treal)
+ fatal("non real scale");
+ return n.c.rval;
+}
+
+scale2(f: ref Type, t: ref Type): real
+{
+ return scale(f)/scale(t);
+}
+
+# put x in normal form
+nf(x: real): (int, int)
+{
+ p: int;
+ m: real;
+
+ p = 0;
+ m = x;
+ while(m >= 1.0){
+ p++;
+ m /= 2.0;
+ }
+ while(m < 0.5){
+ p--;
+ m *= 2.0;
+ }
+ m *= real (1<<16)*real (1<<15);
+ if(m >= real 16r7fffffff - 0.5)
+ return (p, 16r7fffffff);
+ return (p, int m);
+}
+
+ispow2(x: real): int
+{
+ m: int;
+
+ (nil, m) = nf(x);
+ if(m != 1<<30)
+ return 0;
+ return 1;
+}
+
+round(x: real, n: int): (int, int)
+{
+ if(n != 31)
+ fatal("not 31 in round");
+ return nf(x);
+}
+
+fixmul2(sx: real, sy: real, sr: real): (int, int, int)
+{
+ k, n, a: int;
+ alpha: real;
+
+ alpha = (sx*sy)/sr;
+ n = 31;
+ (k, a) = round(1.0/alpha, n);
+ return (IMULX, 1-k, 0);
+}
+
+fixdiv2(sx: real, sy: real, sr: real): (int, int, int)
+{
+ k, n, b: int;
+ beta: real;
+
+ beta = sx/(sy*sr);
+ n = 31;
+ (k, b) = round(beta, n);
+ return (IDIVX, k-1, 0);
+}
+
+fixmul(sx: real, sy: real, sr: real): (int, int, int)
+{
+ k, m, n, a, v: int;
+ W: big;
+ alpha, eps: real;
+
+ alpha = (sx*sy)/sr;
+ if(ispow2(alpha))
+ return fixmul2(sx, sy, sr);
+ n = 31;
+ (k, a) = round(1.0/alpha, n);
+ m = n-k;
+ if(m < -n-1)
+ return (IMOVW, 0, 0); # result is zero whatever the values
+ v = 0;
+ W = big 0;
+ eps = real(1<<m)/(alpha*real(a)) - 1.0;
+ if(eps < 0.0){
+ v = a-1;
+ eps = -eps;
+ }
+ if(m < 0 && real(1<<n)*eps*real(a) >= real(a)-1.0+real(1<<m))
+ W = (big(1)<<(-m)) - big 1;
+ if(v != 0 || W != big 0)
+ m = m<<2|(v != 0)<<1|(W != big 0);
+ if(v == 0 && W == big 0)
+ return (IMULX0, m, a);
+ else
+ return (IMULX1, m, a);
+}
+
+fixdiv(sx: real, sy: real, sr: real): (int, int, int)
+{
+ k, m, n, b, v: int;
+ W: big;
+ beta, eps: real;
+
+ beta = sx/(sy*sr);
+ if(ispow2(beta))
+ return fixdiv2(sx, sy, sr);
+ n = 31;
+ (k, b) = round(beta, n);
+ m = k-n;
+ if(m <= -2*n)
+ return (IMOVW, 0, 0); #result is zero whatever the values
+ v = 0;
+ W = big 0;
+ eps = (real(1<<m)*real(b))/beta - 1.0;
+ if(eps < 0.0)
+ v = 1;
+ if(m < 0)
+ W = (big(1)<<(-m)) - big 1;
+ if(v != 0 || W != big 0)
+ m = m<<2|(v != 0)<<1|(W != big 0);
+ if(v == 0 && W == big 0)
+ return (IDIVX0, m, b);
+ else
+ return (IDIVX1, m, b);
+}
+
+fixcast(sx: real, sr: real): (int, int, int)
+{
+ (op, p, a) := fixmul(sx, 1.0, sr);
+ return (op-IMULX+ICVTXX, p, a);
+}
+
+fixop(op: int, tx: ref Type, ty: ref Type, tr: ref Type): (int, int, int)
+{
+ sx, sy, sr: real;
+
+ sx = scale(tx);
+ sy = scale(ty);
+ sr = scale(tr);
+ if(op == IMULX)
+ return fixmul(sx, sy, sr);
+ else if(op == IDIVX)
+ return fixdiv(sx, sy, sr);
+ else
+ return fixcast(sx, sr);
+}
+
+ispoly(d: ref Decl): int
+{
+ if(d == nil)
+ return 0;
+ t := d.ty;
+ if(t.kind == Tfn){
+ if(t.polys != nil)
+ return 1;
+ if((d = d.dot) == nil)
+ return 0;
+ t = d.ty;
+ return t.kind == Tadt && t.polys != nil;
+ }
+ return 0;
+}
+
+ispolyadt(t: ref Type): int
+{
+ return (t.kind == Tadt || t.kind == Tadtpick) && t.polys != nil && (t.flags & INST) == byte 0;
+}
+
+polydecl(ids: ref Decl): ref Decl
+{
+ id: ref Decl;
+ t: ref Type;
+
+ for(id = ids; id != nil; id = id.next){
+ t = mktype(id.src.start, id.src.stop, Tpoly, nil, nil);
+ id.ty = t;
+ t.decl = id;
+ }
+ return ids;
+}
+
+# try to convert an expression tree to a type
+exptotype(n: ref Node): ref Type
+{
+ t, tt: ref Type;
+ d: ref Decl;
+ tll: ref Typelist;
+ src: Src;
+
+ if(n == nil)
+ return nil;
+ t = nil;
+ case(n.op){
+ Oname =>
+ if((d = n.decl) != nil && d.store == Dtype)
+ t = d.ty;
+ Otype or Ochan =>
+ t = n.ty;
+ Oref =>
+ t = exptotype(n.left);
+ if(t != nil)
+ t = mktype(n.src.start, n.src.stop, Tref, t, nil);
+ Odot =>
+ t = exptotype(n.left);
+ if(t != nil){
+ d = namedot(t.tags, n.right.decl.sym);
+ if(d == nil)
+ t = nil;
+ else
+ t = d.ty;
+ }
+ if(t == nil)
+ t = exptotype(n.right);
+ Omdot =>
+ t = exptotype(n.right);
+ Oindex =>
+ t = exptotype(n.left);
+ if(t != nil){
+ src = n.src;
+ tll = nil;
+ for(n = n.right; n != nil; n = n.right){
+ if(n.op == Oseq)
+ tt = exptotype(n.left);
+ else
+ tt = exptotype(n);
+ if(tt == nil)
+ return nil;
+ tll = addtype(tt, tll);
+ if(n.op != Oseq)
+ break;
+ }
+ t = mkinsttype(src, t, tll);
+ }
+ }
+ return t;
+}
+
+uname(im: ref Decl): string
+{
+ s := "";
+ for(p := im; p != nil; p = p.next){
+ s += p.sym.name;
+ if(p.next != nil)
+ s += "+";
+ }
+ return s;
+}
+
+# check all implementation modules have consistent declarations
+# and create their union if needed
+#
+modimp(dl: ref Dlist, im: ref Decl): ref Decl
+{
+ u, d, dd, ids, dot, last: ref Decl;
+ s: ref Sym;
+
+ if(dl.next == nil)
+ return dl.d;
+ dl0 := dl;
+ sg0 := 0;
+ un := uname(im);
+ installids(Dglobal, mkids(dl.d.src, enter(".m."+un, 0), tnone, nil));
+ u = dupdecl(dl.d);
+ u.sym = enter(un, 0);
+ u.sym.decl = u;
+ u.ty = mktype(u.src.start, u.src.stop, Tmodule, nil, nil);
+ u.ty.decl = u;
+ for( ; dl != nil; dl = dl.next){
+ d = dl.d;
+ ids = d.ty.tof.ids; # iface
+ if(ids != nil && ids.store == Dglobal) # .mp
+ sg := sign(ids);
+ else
+ sg = 0;
+ if(dl == dl0)
+ sg0 = sg;
+ else if(sg != sg0)
+ error(d.src.start, d.sym.name + "'s module data not consistent with that of " + dl0.d.sym.name + "\n");
+ for(ids = d.ty.ids; ids != nil; ids = ids.next){
+ s = ids.sym;
+ if(s.decl != nil && s.decl.scope >= scope){
+ if(ids == s.decl){
+ dd = dupdecl(ids);
+ if(u.ty.ids == nil)
+ u.ty.ids = dd;
+ else
+ last.next = dd;
+ last = dd;
+ continue;
+ }
+ dot = s.decl.dot;
+ if(s.decl.store != Dwundef && dot != nil && dot != d && isimpmod(dot.sym) && dequal(ids, s.decl, 1))
+ ids.refs = s.decl.refs;
+ else
+ redecl(ids);
+ ids.init = s.decl.init;
+ }
+ }
+ }
+ u.ty = usetype(u.ty);
+ return u;
+}
+
+modres(d: ref Decl)
+{
+ ids, id, n, i: ref Decl;
+ t: ref Type;
+
+ for(ids = d.ty.ids; ids != nil; ids = ids.next){
+ id = ids.sym.decl;
+ if(ids != id){
+ n = ids.next;
+ i = ids.iface;
+ t = ids.ty;
+ *ids = *id;
+ ids.next = n;
+ ids.iface = i;
+ ids.ty = t;
+ }
+ }
+}
+
+# update the fields of duplicate declarations in other implementation modules
+# and their union
+#
+modresolve()
+{
+ dl: ref Dlist;
+
+ dl = impdecls;
+ if(dl.next == nil)
+ return;
+ for( ; dl != nil; dl = dl.next)
+ modres(dl.d);
+ modres(impdecl);
+}
diff --git a/appl/cmd/listen.b b/appl/cmd/listen.b
new file mode 100644
index 00000000..25869223
--- /dev/null
+++ b/appl/cmd/listen.b
@@ -0,0 +1,261 @@
+implement Listen;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+include "keyring.m";
+ keyring: Keyring;
+include "security.m";
+ auth: Auth;
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+
+Listen: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+badmodule(p: string)
+{
+ sys->fprint(stderr(), "listen: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+serverkey: ref Keyring->Authinfo;
+verbose := 0;
+
+init(drawctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ keyring = load Keyring Keyring->PATH;
+ auth = load Auth Auth->PATH;
+ if (auth == nil)
+ badmodule(Auth->PATH);
+ sh = load Sh Sh->PATH;
+ if (sh == nil)
+ badmodule(Sh->PATH);
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+ auth->init();
+ algs: list of string;
+ arg->init(argv);
+ keyfile: string;
+ initscript: string;
+ doauth := 1;
+ synchronous := 0;
+ trusted := 0;
+ arg->setusage("listen [-i {initscript}] [-Ast] [-k keyfile] [-a alg]... addr command [arg...]");
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'a' =>
+ algs = arg->earg() :: algs;
+ 'A' =>
+ doauth = 0;
+ 'f' or
+ 'k' =>
+ keyfile = arg->earg();
+ if (! (keyfile[0] == '/' || (len keyfile > 2 && keyfile[0:2] == "./")))
+ keyfile = "/usr/" + user() + "/keyring/" + keyfile;
+ 'i' =>
+ initscript = arg->earg();
+ 'v' =>
+ verbose = 1;
+ 's' =>
+ synchronous = 1;
+ 't' =>
+ trusted = 1;
+ * =>
+ arg->usage();
+ }
+ }
+ if (doauth && algs == nil)
+ algs = getalgs();
+ if (algs != nil) {
+ if (keyfile == nil)
+ keyfile = "/usr/" + user() + "/keyring/default";
+ serverkey = keyring->readauthinfo(keyfile);
+ if (serverkey == nil) {
+ sys->fprint(stderr(), "listen: cannot read %s: %r\n", keyfile);
+ raise "fail:bad keyfile";
+ }
+ }
+ if(!trusted){
+ sys->unmount(nil, "/mnt/keys"); # should do for now
+ # become none?
+ }
+
+ argv = arg->argv();
+ n := len argv;
+ if (n < 2)
+ arg->usage();
+ arg = nil;
+
+ sync := chan[1] of string;
+ spawn listen(drawctxt, hd argv, tl argv, algs, initscript, sync);
+ e := <-sync;
+ if(e != nil)
+ raise "fail:" + e;
+ if(synchronous){
+ e = <-sync;
+ if(e != nil)
+ raise "fail:" + e;
+ }
+}
+
+listen(drawctxt: ref Draw->Context, addr: string, argv: list of string,
+ algs: list of string, initscript: string, sync: chan of string)
+{
+ {
+ listen1(drawctxt, addr, argv, algs, initscript, sync);
+ } exception e {
+ "fail:*" =>
+ sync <-= e;
+ }
+}
+
+listen1(drawctxt: ref Draw->Context, addr: string, argv: list of string,
+ algs: list of string, initscript: string, sync: chan of string)
+{
+ sys->pctl(Sys->FORKFD, nil);
+
+ ctxt := Context.new(drawctxt);
+ (ok, acon) := sys->announce(addr);
+ if (ok == -1) {
+ sys->fprint(stderr(), "listen: failed to announce on '%s': %r\n", addr);
+ sync <-= "cannot announce";
+ exit;
+ }
+ ctxt.set("user", nil);
+ if (initscript != nil) {
+ ctxt.setlocal("net", ref Sh->Listnode(nil, acon.dir) :: nil);
+ ctxt.run(ref Sh->Listnode(nil, initscript) :: nil, 0);
+ initscript = nil;
+ }
+
+ # make sure the shell command is parsed only once.
+ cmd := sh->stringlist2list(argv);
+ if((hd argv) != nil && (hd argv)[0] == '{'){
+ (c, e) := sh->parse(hd argv);
+ if(c == nil){
+ sys->fprint(stderr(), "listen: %s\n", e);
+ sync <-= "parse error";
+ exit;
+ }
+ cmd = ref Sh->Listnode(c, hd argv) :: tl cmd;
+ }
+
+ sync <-= nil;
+ listench := chan of (int, Sys->Connection);
+ authch := chan of (string, Sys->Connection);
+ spawn listener(listench, acon, addr);
+ for (;;) {
+ user := "";
+ ccon: Sys->Connection;
+ alt {
+ (lok, c) := <-listench =>
+ if (lok == -1){
+ sync <-= "listen";
+ exit;
+ }
+ if (algs != nil) {
+ spawn authenticator(authch, c, algs, addr);
+ continue;
+ }
+ ccon = c;
+ (user, ccon) = <-authch =>
+ ;
+ }
+ if (user != nil)
+ ctxt.set("user", sh->stringlist2list(user :: nil));
+ ctxt.set("net", ref Sh->Listnode(nil, ccon.dir) :: nil);
+
+ # XXX could do this in a separate process too, to
+ # allow new connections to arrive and start authenticating
+ # while the shell command is still running.
+ sys->dup(ccon.dfd.fd, 0);
+ sys->dup(ccon.dfd.fd, 1);
+ ccon.dfd = ccon.cfd = nil;
+ ctxt.run(cmd, 0);
+ sys->dup(2, 0);
+ sys->dup(2, 1);
+ }
+}
+
+listener(listench: chan of (int, Sys->Connection), c: Sys->Connection, addr: string)
+{
+ for (;;) {
+ (ok, nc) := sys->listen(c);
+ if (ok == -1) {
+ sys->fprint(stderr(), "listen: listen error on '%s': %r\n", addr);
+ listench <-= (-1, nc);
+ exit;
+ }
+ if (verbose)
+ sys->fprint(stderr(), "listen: got connection on %s from %s",
+ addr, readfile(nc.dir + "/remote"));
+ nc.dfd = sys->open(nc.dir + "/data", Sys->ORDWR);
+ if (nc.dfd == nil)
+ sys->fprint(stderr(), "listen: cannot open %s: %r\n", nc.dir + "/data");
+ else{
+ if(nc.cfd != nil)
+ sys->fprint(nc.cfd, "keepalive");
+ listench <-= (ok, nc);
+ }
+ }
+}
+
+authenticator(authch: chan of (string, Sys->Connection),
+ c: Sys->Connection, algs: list of string, addr: string)
+{
+ err: string;
+ (c.dfd, err) = auth->server(algs, serverkey, c.dfd, 0);
+ if (c.dfd == nil) {
+ sys->fprint(stderr(), "listen: auth on %s failed: %s\n", addr, err);
+ return;
+ }
+ if (verbose)
+ sys->fprint(stderr(), "listen: authenticated on %s as %s\n", addr, err);
+ authch <-= (err, c);
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+user(): string
+{
+ u := readfile("/dev/user");
+ if (u == nil)
+ return "nobody";
+ return u;
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, sys->OREAD);
+ if(fd == nil)
+ return nil;
+
+ buf := array[1024] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return nil;
+
+ return string buf[0:n];
+}
+
+getalgs(): list of string
+{
+ sslctl := readfile("#D/clone");
+ if (sslctl == nil) {
+ sslctl = readfile("#D/ssl/clone");
+ if (sslctl == nil)
+ return nil;
+ sslctl = "#D/ssl/" + sslctl;
+ } else
+ sslctl = "#D/" + sslctl;
+ (nil, algs) := sys->tokenize(readfile(sslctl + "/encalgs") + " " + readfile(sslctl + "/hashalgs"), " \t\n");
+ return "none" :: algs;
+}
diff --git a/appl/cmd/lockfs.b b/appl/cmd/lockfs.b
new file mode 100644
index 00000000..1b958de2
--- /dev/null
+++ b/appl/cmd/lockfs.b
@@ -0,0 +1,773 @@
+implement Lockfs;
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+include "styxlib.m";
+ styxlib: Styxlib;
+ Dirtab, Styxserver, Chan,
+ devdir,
+ Eperm, Ebadfid, Eexists, Enotdir, Enotfound, Einuse: import styxlib;
+include "arg.m";
+include "keyring.m";
+ keyring: Keyring;
+include "security.m";
+ auth: Auth;
+
+Lockfs: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+ dirgen: fn(srv: ref Styxlib->Styxserver, c: ref Styxlib->Chan,
+ tab: array of Styxlib->Dirtab, i: int): (int, Sys->Dir);
+};
+
+Elocked: con "file is locked";
+
+devgen: Dirgenmod;
+
+Openreq: adt {
+ srv: ref Styxserver;
+ tag: int;
+ omode: int;
+ c: ref Chan;
+ uproc: Uproc;
+};
+
+Lockqueue: adt {
+ h: list of ref Openreq;
+ t: list of ref Openreq;
+ put: fn(q: self ref Lockqueue, s: ref Openreq);
+ get: fn(q: self ref Lockqueue): ref Openreq;
+ peek: fn(q: self ref Lockqueue): ref Openreq;
+ flush: fn(q: self ref Lockqueue, srv: ref Styxserver, tag: int);
+};
+
+Lockfile: adt {
+ waitq: ref Lockqueue;
+ fd: ref Sys->FD;
+ readers: int;
+ writers: int;
+ d: Sys->Dir;
+};
+
+Ureq: adt {
+ fname: string;
+ pick {
+ Open =>
+ omode: int;
+ Create =>
+ omode: int;
+ perm: int;
+ Remove =>
+ Wstat =>
+ dir: Sys->Dir;
+ }
+};
+
+Uproc: type chan of (ref Ureq, chan of (ref Sys->FD, string));
+
+maxqidpath := big 1;
+locks: list of ref Lockfile;
+lockdir: string;
+authinfo: ref Keyring->Authinfo;
+timefd: ref Sys->FD;
+
+MAXCONN: con 20;
+
+verbose := 0;
+
+usage()
+{
+ sys->fprint(stderr, "usage: lockfs [-A] [-a alg]... [-p addr] dir [mountpoint]\n");
+ raise "fail:usage";
+}
+
+badmodule(p: string)
+{
+ sys->fprint(stderr, "lockfs: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ styx = load Styx Styx->PATH;
+ if (styx == nil)
+ badmodule(Styx->PATH);
+ styx->init();
+ styxlib = load Styxlib Styxlib->PATH;
+ if (styxlib == nil)
+ badmodule(Styxlib->PATH);
+ styxlib->init(styx);
+ devgen = load Dirgenmod "$self";
+ if (devgen == nil)
+ badmodule("self as Dirgenmod");
+ timefd = sys->open("/dev/time", sys->OREAD);
+ if (timefd == nil) {
+ sys->fprint(stderr, "lockfs: cannot open /dev/time: %r\n");
+ raise "fail:no time";
+ }
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+ arg->init(argv);
+
+ addr := "";
+ doauth := 1;
+ algs: list of string;
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'p' =>
+ addr = arg->arg();
+ 'a' =>
+ alg := arg->arg();
+ if (alg == nil)
+ usage();
+ algs = alg :: algs;
+ 'A' =>
+ doauth = 0;
+ 'v' =>
+ verbose = 1;
+ * =>
+ usage();
+ }
+ }
+ argv = arg->argv();
+ if (argv == nil || (addr != nil && tl argv != nil))
+ usage();
+ if (addr == nil)
+ doauth = 0; # no authentication necessary for local mount
+ if (doauth) {
+ auth = load Auth Auth->PATH;
+ if (auth == nil)
+ badmodule(Auth->PATH);
+ if ((e := auth->init()) != nil) {
+ sys->fprint(stderr, "lockfs: cannot init auth: %s\n", e);
+ raise "fail:errors";
+ }
+ keyring = load Keyring Keyring->PATH;
+ if (keyring == nil)
+ badmodule(Keyring->PATH);
+ authinfo = keyring->readauthinfo("/usr/" + user() + "/keyring/default");
+ }
+
+ mountpoint := lockdir = hd argv;
+ if (tl argv != nil)
+ mountpoint = hd tl argv;
+ if (addr != nil) {
+ if (doauth && algs == nil)
+ algs = "none" :: nil; # XXX is this default a bad idea?
+ srvrq := chan of (ref Sys->FD, string, Uproc);
+ srvsync := chan of (int, string);
+ spawn listener(addr, srvrq, srvsync, algs);
+ (srvpid, err) := <-srvsync;
+ srvsync = nil;
+ if (srvpid == -1) {
+ sys->fprint(stderr, "lockfs: failed to start listener: %s\n", err);
+ raise "fail:errors";
+ }
+ sync := chan of int;
+ spawn server(srvrq, sync);
+ <-sync;
+ } else {
+ rq := chan of (ref Sys->FD, string, Uproc);
+ fds := array[2] of ref Sys->FD;
+ sys->pipe(fds);
+ sync := chan of int;
+ spawn server(rq, sync);
+ <-sync;
+ rq <-= (fds[0], "lock", nil);
+ rq <-= (nil, nil, nil);
+ if (sys->mount(fds[1], nil, mountpoint, Sys->MREPL | Sys->MCREATE, nil) == -1) {
+ sys->fprint(stderr, "lockfs: cannot mount: %r\n");
+ raise "fail:cannot mount";
+ }
+ }
+}
+
+server(srvrq: chan of (ref Sys->FD, string, Uproc), sync: chan of int)
+{
+ sys->pctl(Sys->FORKNS, nil);
+ sync <-= 1;
+ down := 0;
+ nclient := 0;
+ tchans := array[MAXCONN] of chan of ref Tmsg;
+ srv := array[MAXCONN] of ref Styxserver;
+ uprocs := array[MAXCONN] of Uproc;
+ lockinit();
+Service:
+ for (;;) alt {
+ (fd, reqstr, uprocch) := <-srvrq =>
+ if (fd == nil) {
+ if (verbose && reqstr != nil)
+ sys->print("lockfs: localserver going down (reason: %s)\n", reqstr);
+ down = 1;
+ } else {
+ if (verbose)
+ sys->print("lockfs: got new connection (s == '%s')\n", reqstr);
+ for (i := 0; i < len tchans; i++)
+ if (tchans[i] == nil) {
+ (tchans[i], srv[i]) = Styxserver.new(fd);
+ if(verbose)
+ sys->print("svc started\n");
+ uprocs[i] = uprocch;
+ break;
+ }
+ if (i == len tchans) {
+ sys->fprint(stderr, "lockfs: too many clients\n"); # XXX expand arrays
+ if (uprocch != nil)
+ uprocch <-= (nil, nil);
+ } else
+ nclient++;
+ }
+ (n, gm) := <-tchans =>
+ if (handletmsg(srv[n], gm, uprocs[n]) == -1) {
+ tchans[n] = nil;
+ srv[n] = nil;
+ if (uprocs[n] != nil) {
+ uprocs[n] <-= (nil, nil);
+ uprocs[n] = nil;
+ }
+ if (nclient-- <= 1 && down)
+ break Service;
+ }
+ }
+ if (verbose)
+ sys->print("lockfs: finished\n");
+}
+
+dirgen(nil: ref Styxserver, nil: ref Styxlib->Chan,
+ nil: array of Dirtab, s: int): (int, Sys->Dir)
+{
+ d: Sys->Dir;
+ ll := locks;
+ for (i := 0; i < s && ll != nil; i++)
+ ll = tl ll;
+ if (ll == nil)
+ return (-1, d);
+ return (1, (hd ll).d);
+}
+
+handletmsg(srv: ref Styxserver, gm: ref Tmsg, uproc: Uproc): int
+{
+{
+ if (gm == nil)
+ gm = ref Tmsg.Readerror(-1, "eof");
+ if(verbose)
+ sys->print("<- %s\n", gm.text());
+ pick m := gm {
+ Readerror =>
+ # could be more efficient...
+ for (cl := srv.chanlist(); cl != nil; cl = tl cl) {
+ c := hd cl;
+ for (ll := locks; ll != nil; ll = tl ll) {
+ if ((hd ll).d.qid.path == c.qid.path) {
+ l := hd ll;
+ l.waitq.flush(srv, -1);
+ if (c.open)
+ unlocked(l);
+ break;
+ }
+ }
+ }
+ if (m.error != "eof")
+ sys->fprint(stderr, "lockfs: read error: %s\n", m.error);
+ return -1;
+ Version =>
+ srv.devversion(m);
+ Auth =>
+ srv.devauth(m);
+ Walk =>
+ c := fid2chan(srv, m.fid);
+ qids: array of Sys->Qid;
+ cc := ref *c;
+ if (len m.names > 0) {
+ qids = array[1] of Sys->Qid; # it's just one level
+ if ((cc.qid.qtype & Sys->QTDIR) == 0) {
+ srv.reply(ref Rmsg.Error(m.tag, Enotdir));
+ break;
+ }
+ for (ll := locks; ll != nil; ll = tl ll)
+ if ((hd ll).d.name == m.names[0])
+ break;
+ if (ll == nil) {
+ srv.reply(ref Rmsg.Error(m.tag, Enotfound));
+ break;
+ }
+ d := (hd ll).d;
+ cc.qid = d.qid;
+ cc.path = d.name;
+ qids[0] = c.qid;
+ }
+ if(m.newfid != m.fid){
+ nc := srv.clone(cc, m.newfid);
+ if(nc == nil){
+ srv.reply(ref Rmsg.Error(m.tag, Einuse));
+ break;
+ }
+ }else{
+ c.qid = cc.qid;
+ c.path = cc.path;
+ }
+ srv.reply(ref Rmsg.Walk(m.tag, qids));
+ Open =>
+ c := fid2chan(srv, m.fid);
+ if (c.qid.qtype & Sys->QTDIR) {
+ srv.reply(ref Rmsg.Open(m.tag, c.qid, Styx->MAXFDATA));
+ break;
+ }
+ for (ll := locks; ll != nil; ll = tl ll)
+ if ((hd ll).d.qid.path == c.qid.path)
+ break;
+ if (ll == nil) {
+ srv.reply(ref Rmsg.Error(m.tag, Enotfound));
+ break;
+ }
+ l := hd ll;
+ req := ref Openreq(srv, m.tag, m.mode, c, uproc);
+ if (l.fd == nil || (m.mode == Sys->OREAD && l.writers == 0)) {
+ openlockfile(l, req);
+ } else {
+ l.waitq.put(req);
+ }
+ req = nil;
+ Create =>
+ c := fid2chan(srv, m.fid);
+ if ((c.qid.qtype & Sys->QTDIR) == 0) {
+ srv.reply(ref Rmsg.Error(m.tag, Enotdir));
+ break;
+ }
+ if (m.perm & Sys->DMDIR) {
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ break;
+ }
+ for (ll := locks; ll != nil; ll = tl ll)
+ if ((hd ll).d.name == m.name)
+ break;
+ if (ll != nil) {
+ srv.reply(ref Rmsg.Error(m.tag, Eexists));
+ break;
+ }
+ (fd, err) := create(uproc, lockdir + "/" + m.name, m.mode, m.perm);
+ if (fd == nil) {
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ (ok, d) := sys->fstat(fd);
+ if (ok == -1) {
+ srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ break;
+ }
+ l := ref Lockfile(ref Lockqueue, fd, 0, 0, d);
+ l.d.qid = (maxqidpath++, 0, Sys->QTFILE);
+ l.d.mtime = l.d.atime = now();
+ if (m.mode == Sys->OREAD)
+ l.readers = 1;
+ else
+ l.writers = 1;
+ locks = l :: locks;
+ c.qid.path = (hd locks).d.qid.path;
+ c.open = 1;
+ srv.reply(ref Rmsg.Create(m.tag, c.qid, Styx->MAXFDATA));
+ Read =>
+ c := fid2chan(srv, m.fid);
+ if (c.qid.qtype & Sys->QTDIR)
+ srv.devdirread(m, devgen, nil);
+ else {
+ l := qid2lock(c.qid);
+ if (l == nil)
+ srv.reply(ref Rmsg.Error(m.tag, Enotfound));
+ else {
+ d := array[m.count] of byte;
+ sys->seek(l.fd, m.offset, Sys->SEEKSTART);
+ n := sys->read(l.fd, d, m.count);
+ if (n == -1)
+ srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ else {
+ srv.reply(ref Rmsg.Read(m.tag, d[0:n]));
+ l.d.atime = now();
+ }
+ }
+ }
+ Write =>
+ c := fid2chan(srv, m.fid);
+ if (c.qid.qtype & Sys->QTDIR) {
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ break;
+ }
+ l := qid2lock(c.qid);
+ if (l == nil) {
+ srv.reply(ref Rmsg.Error(m.tag, Enotfound));
+ break;
+ }
+ sys->seek(l.fd, m.offset, Sys->SEEKSTART);
+ n := sys->write(l.fd, m.data, len m.data);
+ if (n == -1)
+ srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ else {
+ srv.reply(ref Rmsg.Write(m.tag, n));
+ nlength := m.offset + big n;
+ if (nlength > l.d.length)
+ l.d.length = nlength;
+ l.d.mtime = now();
+ l.d.qid.vers++;
+ }
+ Clunk =>
+ c := srv.devclunk(m);
+ if (c != nil && c.open && (l := qid2lock(c.qid)) != nil)
+ unlocked(l);
+ Flush =>
+ for (ll := locks; ll != nil; ll = tl ll)
+ (hd ll).waitq.flush(srv, m.tag);
+ srv.reply(ref Rmsg.Flush(m.tag));
+ Stat =>
+ srv.devstat(m, devgen, nil);
+ Remove =>
+ c := fid2chan(srv, m.fid);
+ srv.chanfree(c);
+ if (c.qid.qtype & Sys->QTDIR) {
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ break;
+ }
+ l := qid2lock(c.qid);
+ if (l == nil) {
+ srv.reply(ref Rmsg.Error(m.tag, Enotfound));
+ break;
+ }
+ if (l.fd != nil) {
+ srv.reply(ref Rmsg.Error(m.tag, Elocked));
+ break;
+ }
+ if ((err := remove(uproc, lockdir + "/" + l.d.name)) == nil) {
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ ll: list of ref Lockfile;
+ for (; locks != nil; locks = tl locks)
+ if (hd locks != l)
+ ll = hd locks :: ll;
+ locks = ll;
+ srv.reply(ref Rmsg.Remove(m.tag));
+ Wstat =>
+ c := fid2chan(srv, m.fid);
+ if (c.qid.qtype & Sys->QTDIR) {
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ break;
+ }
+ l := qid2lock(c.qid);
+ if (l == nil) {
+ srv.reply(ref Rmsg.Error(m.tag, Enotfound));
+ break;
+ }
+ if ((err := wstat(uproc, lockdir + "/" + l.d.name, m.stat)) != nil) {
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ (ok, d) := sys->stat(lockdir + "/" + m.stat.name);
+ if (ok == -1) {
+ srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ break;
+ }
+ d.qid = l.d.qid;
+ l.d = d;
+ srv.reply(ref Rmsg.Wstat(m.tag));
+ Attach =>
+ srv.devattach(m);
+ }
+ return 0;
+}
+exception e{
+ "panic:*" =>
+ sys->fprint(stderr, "lockfs: %s\n", e);
+ srv.reply(ref Rmsg.Error(gm.tag, e[len "panic:":]));
+ return 0;
+}
+}
+
+unlocked(l: ref Lockfile)
+{
+ if (l.readers > 0)
+ l.readers--;
+ else
+ l.writers--;
+ if (l.readers > 0)
+ return;
+ l.fd = nil;
+
+ # unblock all readers at the head of the queue.
+ # XXX should we queuejump other readers?
+ while ((nreq := l.waitq.peek()) != nil && l.writers == 0) {
+ if (nreq.omode != Sys->OREAD && l.readers > 0)
+ break;
+ openlockfile(l, nreq);
+ l.waitq.get();
+ }
+}
+
+openlockfile(l: ref Lockfile, req: ref Openreq): int
+{
+ err: string;
+ (l.fd, err) = open(req.uproc, lockdir + "/" + l.d.name, req.omode);
+ if (l.fd == nil) {
+ req.srv.reply(ref Rmsg.Error(req.tag, err));
+ return -1;
+ }
+ req.c.open = 1;
+ if (req.omode & Sys->OTRUNC)
+ l.d.length = big 0;
+ req.srv.reply(ref Rmsg.Open(req.tag, l.d.qid, Styx->MAXFDATA));
+ if (req.omode == Sys->OREAD)
+ l.readers++;
+ else
+ l.writers++;
+ return 0;
+}
+
+qid2lock(q: Sys->Qid): ref Lockfile
+{
+ for (ll := locks; ll != nil; ll = tl ll)
+ if ((hd ll).d.qid.path == q.path)
+ return hd ll;
+ return nil;
+}
+
+lockinit()
+{
+ fd := sys->open(lockdir, Sys->OREAD);
+ if (fd == nil)
+ return;
+
+ lockl: list of ref Lockfile;
+ # XXX if O(n²) behaviour is a problem, use Readdir module
+ for(;;){
+ (n, e) := sys->dirread(fd);
+ if(n <= 0)
+ break;
+ for (i := 0; i < n; i++) {
+ for (l := lockl; l != nil; l = tl l)
+ if ((hd l).d.name == e[i].name)
+ break;
+ if (l == nil) {
+ e[i].qid = (maxqidpath++, 0, Sys->QTFILE);
+ lockl = ref Lockfile(ref Lockqueue, nil, 0, 0, e[i]) :: lockl;
+ }
+ }
+ }
+ # remove all directories from list
+ for (locks = nil; lockl != nil; lockl = tl lockl)
+ if (((hd lockl).d.mode & Sys->DMDIR) == 0)
+ locks = hd lockl :: locks;
+}
+
+
+fid2chan(srv: ref Styxserver, fid: int): ref Chan
+{
+ c := srv.fidtochan(fid);
+ if (c == nil)
+ raise "panic:bad fid";
+ return c;
+}
+
+Lockqueue.put(q: self ref Lockqueue, s: ref Openreq)
+{
+ q.t = s :: q.t;
+}
+
+Lockqueue.get(q: self ref Lockqueue): ref Openreq
+{
+ s: ref Openreq;
+ if(q.h == nil)
+ (q.h, q.t) = (revrqlist(q.t), nil);
+
+ if(q.h != nil)
+ (s, q.h) = (hd q.h, tl q.h);
+
+ return s;
+}
+
+Lockqueue.peek(q: self ref Lockqueue): ref Openreq
+{
+ s := q.get();
+ if (s != nil)
+ q.h = s :: q.h;
+ return s;
+}
+
+doflush(l: list of ref Openreq, srv: ref Styxserver, tag: int): list of ref Openreq
+{
+ oldl := l;
+ nl: list of ref Openreq;
+ doneone := 0;
+ while (l != nil) {
+ oreq := hd l;
+ if (oreq.srv != srv || (tag != -1 && oreq.tag != tag))
+ nl = oreq :: nl;
+ else
+ doneone = 1;
+ l = tl l;
+ }
+ if (doneone)
+ return revrqlist(nl);
+ else
+ return oldl;
+}
+
+Lockqueue.flush(q: self ref Lockqueue, srv: ref Styxserver, tag: int)
+{
+ q.h = doflush(q.h, srv, tag);
+ q.t = doflush(q.t, srv, tag);
+}
+
+# or inline
+revrqlist(ls: list of ref Openreq) : list of ref Openreq
+{
+ rs: list of ref Openreq;
+ while(ls != nil){
+ rs = hd ls :: rs;
+ ls = tl ls;
+ }
+ return rs;
+}
+
+# addr should be, e.g. tcp!*!2345
+listener(addr: string, ch: chan of (ref Sys->FD, string, Uproc),
+ sync: chan of (int, string), algs: list of string)
+{
+ addr = netmkaddr(addr, "tcp", "33234");
+ (ok, c) := sys->announce(addr);;
+ if (ok == -1) {
+ sync <-= (-1, sys->sprint("cannot anounce on %s: %r", addr));
+ return;
+ }
+ sync <-= (sys->pctl(0, nil), nil);
+ for (;;) {
+ (n, nc) := sys->listen(c);
+ if (n == -1) {
+ ch <-= (nil, sys->sprint("listen failed: %r"), nil);
+ return;
+ }
+ dfd := sys->open(nc.dir + "/data", Sys->ORDWR);
+ if (dfd != nil) {
+ if (algs == nil)
+ ch <-= (dfd, nil, nil);
+ else
+ spawn authenticator(dfd, ch, algs);
+ }
+ }
+}
+
+# authenticate a connection, setting the user id appropriately,
+# and then act as a server, performing file operations
+# on behalf of the central process.
+authenticator(dfd: ref Sys->FD, ch: chan of (ref Sys->FD, string, Uproc), algs: list of string)
+{
+ (fd, err) := auth->server(algs, authinfo, dfd, 1);
+ if (fd == nil) {
+ if (verbose)
+ sys->fprint(stderr, "lockfs: authentication failed: %s\n", err);
+ return;
+ }
+ uproc := chan of (ref Ureq, chan of (ref Sys->FD, string));
+ ch <-= (fd, err, uproc);
+ for (;;) {
+ (req, reply) := <-uproc;
+ if (req == nil)
+ exit;
+ reply <-= doreq(req);
+ }
+}
+
+create(uproc: Uproc, file: string, omode: int, perm: int): (ref Sys->FD, string)
+{
+ return proxydoreq(uproc, ref Ureq.Create(file, omode, perm));
+}
+
+open(uproc: Uproc, file: string, omode: int): (ref Sys->FD, string)
+{
+ return proxydoreq(uproc, ref Ureq.Open(file, omode));
+}
+
+remove(uproc: Uproc, file: string): string
+{
+ return proxydoreq(uproc, ref Ureq.Remove(file)).t1;
+}
+
+wstat(uproc: Uproc, file: string, d: Sys->Dir): string
+{
+ return proxydoreq(uproc, ref Ureq.Wstat(file, d)).t1;
+}
+
+proxydoreq(uproc: Uproc, req: ref Ureq): (ref Sys->FD, string)
+{
+ if (uproc == nil)
+ return doreq(req);
+ reply := chan of (ref Sys->FD, string);
+ uproc <-= (req, reply);
+ return <-reply;
+}
+
+doreq(greq: ref Ureq): (ref Sys->FD, string)
+{
+ fd: ref Sys->FD;
+ err: string;
+ pick req := greq {
+ Open =>
+ if ((fd = sys->open(req.fname, req.omode)) == nil)
+ err = sys->sprint("%r");
+ Create =>
+ if ((fd = sys->create(req.fname, req.omode, req.perm)) == nil)
+ err = sys->sprint("%r");
+ Remove =>
+ if (sys->remove(req.fname) == -1)
+ err = sys->sprint("%r");
+ Wstat =>
+ if (sys->wstat(req.fname, req.dir) == -1)
+ err = sys->sprint("%r");
+ }
+ return (fd, err);
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, nil) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil){
+ sys->fprint(stderr, "lockfs: can't open /dev/user: %r\n");
+ raise "fail:no user";
+ }
+
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0) {
+ sys->fprint(stderr, "lockfs: failed to read /dev/user: %r\n");
+ raise "fail:no user";
+ }
+
+ return string buf[0:n];
+}
+
+now(): int
+{
+ buf := array[128] of byte;
+ sys->seek(timefd, big 0, 0);
+ if ((n := sys->read(timefd, buf, len buf)) < 0)
+ return 0;
+ return int (big string buf[0:n] / big 1000000);
+}
diff --git a/appl/cmd/logfile.b b/appl/cmd/logfile.b
new file mode 100644
index 00000000..6ec8369e
--- /dev/null
+++ b/appl/cmd/logfile.b
@@ -0,0 +1,259 @@
+implement Logfile;
+
+#
+# Copyright © 1999 Vita Nuova Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+
+Logfile: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+Fidrec: adt {
+ fid: int; # fid of read
+ rq: list of (int, Sys->Rread); # outstanding read requests
+ pos: int; # current position in the logfile
+};
+
+Circbuf: adt {
+ start: int;
+ data: array of byte;
+ new: fn(size: int): ref Circbuf;
+ put: fn(b: self ref Circbuf, d: array of byte): int;
+ get: fn(b: self ref Circbuf, s, n: int): (int, array of byte);
+};
+
+Fidhash: adt
+{
+ table: array of list of ref Fidrec;
+ get: fn(ht: self ref Fidhash, fid: int): ref Fidrec;
+ put: fn(ht: self ref Fidhash, fidrec: ref Fidrec);
+ del: fn(ht: self ref Fidhash, fidrec: ref Fidrec);
+ new: fn(): ref Fidhash;
+};
+
+usage()
+{
+ sys->fprint(stderr, "usage: logfile [-size] file\n");
+ raise "fail: usage";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ bufsize := Sys->ATOMICIO * 4;
+
+ if (argv != nil)
+ argv = tl argv;
+ if (argv != nil && len hd argv && (hd argv)[0] == '-' && len hd argv > 1) {
+ if ((bufsize = int ((hd argv)[1:])) <= 0) {
+ sys->fprint(stderr, "logfile: can't have a zero buffer size\n");
+ usage();
+ }
+ argv = tl argv;
+ }
+ if (argv == nil || tl argv != nil)
+ usage();
+ path := hd argv;
+
+ (dir, f) := pathsplit(path);
+ if (sys->bind("#s", dir, Sys->MBEFORE|Sys->MCREATE) == -1) {
+ sys->fprint(stderr, "logfile: bind #s failed: %r\n");
+ return;
+ }
+ fio := sys->file2chan(dir, f);
+ if (fio == nil) {
+ sys->fprint(stderr, "logfile: couldn't make %s: %r\n", path);
+ return;
+ }
+
+ spawn logserver(fio, bufsize);
+}
+
+logserver(fio: ref Sys->FileIO, bufsize: int)
+{
+ waitlist: list of ref Fidrec;
+ readers := Fidhash.new();
+ availcount := 0;
+ availchan := chan of int;
+ workchan := chan of (Sys->Rread, array of byte);
+ buf := Circbuf.new(bufsize);
+ for (;;) alt {
+ <-availchan =>
+ availcount++;
+ (off, count, fid, rc) := <-fio.read =>
+ r := readers.get(fid);
+ if (rc == nil) {
+ if (r != nil)
+ readers.del(r);
+ continue;
+ }
+ if (r == nil) {
+ r = ref Fidrec(fid, nil, buf.start);
+ if (r.pos < len buf.data)
+ r.pos = len buf.data; # first buffer's worth is garbage
+ readers.put(r);
+ }
+
+ (s, d) := buf.get(r.pos, count);
+ r.pos = s + len d;
+
+ if (d != nil) {
+ rc <-= (d, nil);
+ } else {
+ if (r.rq == nil)
+ waitlist = r :: waitlist;
+ r.rq = (count, rc) :: r.rq;
+ }
+
+ (off, data, fid, wc) := <-fio.write =>
+ if (wc == nil)
+ continue;
+ if ((n := buf.put(data)) < len data)
+ wc <-= (n, "write too long for buffer");
+ else
+ wc <-= (n, nil);
+
+ wl := waitlist;
+ for (waitlist = nil; wl != nil; wl = tl wl) {
+ r := hd wl;
+ if (availcount == 0) {
+ spawn worker(workchan, availchan);
+ availcount++;
+ }
+ (count, rc) := hd r.rq;
+ r.rq = tl r.rq;
+
+ # optimisation: if the read request wants exactly the data provided
+ # in the write request, then use the original data buffer.
+ s: int;
+ d: array of byte;
+ if (count >= n && r.pos == buf.start + len buf.data - n)
+ (s, d) = (r.pos, data);
+ else
+ (s, d) = buf.get(r.pos, count);
+ r.pos = s + len d;
+ workchan <-= (rc, d);
+ availcount--;
+ if (r.rq != nil)
+ waitlist = r :: waitlist;
+ d = nil;
+ }
+ data = nil;
+ wl = nil;
+ }
+}
+
+worker(work: chan of (Sys->Rread, array of byte), ready: chan of int)
+{
+ for (;;) {
+ (rc, data) := <-work; # blocks forever if the reading process is killed
+ rc <-= (data, nil);
+ (rc, data) = (nil, nil);
+ ready <-= 1;
+ }
+}
+
+Circbuf.new(size: int): ref Circbuf
+{
+ return ref Circbuf(0, array[size] of byte);
+}
+
+# return number of bytes actually written
+Circbuf.put(b: self ref Circbuf, d: array of byte): int
+{
+ blen := len b.data;
+ # if too big to fit in buffer, truncate the write.
+ if (len d > blen)
+ d = d[0:blen];
+ dlen := len d;
+
+ offset := b.start % blen;
+ if (offset + dlen <= blen) {
+ b.data[offset:] = d;
+ } else {
+ b.data[offset:] = d[0:blen - offset];
+ b.data[0:] = d[blen - offset:];
+ }
+ b.start += dlen;
+ return dlen;
+}
+
+# return (start, data)
+Circbuf.get(b: self ref Circbuf, s, n: int): (int, array of byte)
+{
+ # if the beginning's been overrun, start from the earliest place we can.
+ # we could put some indication of elided bytes in the buffer.
+ if (s < b.start)
+ s = b.start;
+ blen := len b.data;
+ if (s + n > b.start + blen)
+ n = b.start + blen - s;
+ if (n <= 0)
+ return (s, nil);
+ o := s % blen;
+ d := array[n] of byte;
+ if (o + n <= blen)
+ d[0:] = b.data[o:o+n];
+ else {
+ d[0:] = b.data[o:];
+ d[blen - o:] = b.data[0:o+n-blen];
+ }
+ return (s, d);
+}
+
+FIDHASHSIZE: con 32;
+
+Fidhash.new(): ref Fidhash
+{
+ return ref Fidhash(array[FIDHASHSIZE] of list of ref Fidrec);
+}
+
+# put an entry in the hash table.
+# assumes there is no current entry for the fid.
+Fidhash.put(ht: self ref Fidhash, f: ref Fidrec)
+{
+ slot := f.fid & (FIDHASHSIZE-1);
+ ht.table[slot] = f :: ht.table[slot];
+}
+
+Fidhash.get(ht: self ref Fidhash, fid: int): ref Fidrec
+{
+ for (l := ht.table[fid & (FIDHASHSIZE-1)]; l != nil; l = tl l)
+ if ((hd l).fid == fid)
+ return hd l;
+ return nil;
+}
+
+Fidhash.del(ht: self ref Fidhash, f: ref Fidrec)
+{
+ slot := f.fid & (FIDHASHSIZE-1);
+ nl: list of ref Fidrec;
+ for (l := ht.table[slot]; l != nil; l = tl l)
+ if ((hd l).fid != f.fid)
+ nl = (hd l) :: nl;
+ ht.table[slot] = nl;
+}
+
+pathsplit(p: string): (string, string)
+{
+ for (i := len p - 1; i >= 0; i--)
+ if (p[i] != '/')
+ break;
+ if (i < 0)
+ return (p, nil);
+ p = p[0:i+1];
+ for (i = len p - 1; i >=0; i--)
+ if (p[i] == '/')
+ break;
+ if (i < 0)
+ return (".", p);
+ return (p[0:i+1], p[i+1:]);
+}
+
diff --git a/appl/cmd/look.b b/appl/cmd/look.b
new file mode 100755
index 00000000..8465db45
--- /dev/null
+++ b/appl/cmd/look.b
@@ -0,0 +1,393 @@
+implement Look;
+
+#
+# Copyright © 2002 Lucent Technologies Inc.
+# transliteration of the Plan 9 command; subject to the Lucent Public License 1.02
+# -r option added by Caerwyn Jones to print a range
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+
+Look: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+filename := "/lib/words";
+dfile: ref Iobuf;
+bout: ref Iobuf;
+debug := 0;
+fold, direc, exact, iflag, range: int;
+rev := 1; # -1 for reverse-ordered file, not implemented
+tab := '\t';
+nflag := 0;
+entry: string;
+word: string;
+key: string;
+orig: string;
+targ: string;
+latin_fold_tab := array[64] of {
+ # Table to fold latin 1 characters to ASCII equivalents
+ # based at Rune value 0xc0
+ #
+ # À Á Â Ã Ä Å Æ Ç
+ # È É Ê Ë Ì Í Î Ï
+ # Ð Ñ Ò Ó Ô Õ Ö ×
+ # Ø Ù Ú Û Ü Ý Þ ß
+ # à á â ã ä å æ ç
+ # è é ê ë ì í î ï
+ # ð ñ ò ó ô õ ö ÷
+ # ø ù ú û ü ý þ ÿ
+ #
+ 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c',
+ 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i',
+ 'd', 'n', 'o', 'o', 'o', 'o', 'o', 0,
+ 'o', 'u', 'u', 'u', 'u', 'y', 0, 0,
+ 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c',
+ 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i',
+ 'd', 'n', 'o', 'o', 'o', 'o', 'o', 0,
+ 'o', 'u', 'u', 'u', 'u', 'y', 0, 'y',
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg := load Arg Arg->PATH;
+
+ arg->init(args);
+ arg->setusage(arg->progname()+" -[dfinx] [-r orig] [-t c] [string] [file]");
+ while((c := arg->opt()) != 0)
+ case c {
+ 'd' =>
+ direc++;
+ 'f' =>
+ fold++;
+ 'i' =>
+ iflag++;
+ 'n' =>
+ nflag = 1;
+ 't' =>
+ tab = (arg->earg())[0];
+ 'x' =>
+ exact++;
+ 'r' =>
+ range++;
+ orig = arg->earg();
+ targ = rcanon(orig);
+ * =>
+ sys->fprint(sys->fildes(2), "%s: bad option %c\n", arg->progname(), c);
+ sys->fprint(sys->fildes(2), "usage: %s -[dfinx] [-t c] [-r limit] [string] [file]\n", arg->progname());
+ raise "fail:usage";
+ }
+ args = arg->argv();
+ arg = nil;
+
+ bin := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if(!iflag)
+ if(args != nil){
+ orig = hd args;
+ args = tl args;
+ key = rcanon(orig);
+ }else
+ iflag++;
+ if(args == nil){
+ direc++;
+ fold++;
+ }else
+ filename = hd args;
+ if(debug)
+ sys->fprint(sys->fildes(2), "orig %s key %s %s\n", orig, key, filename);
+ dfile = bufio->open(filename, Sys->OREAD);
+ if(dfile == nil){
+ sys->fprint(sys->fildes(2), "look: can't open %s\n", filename);
+ raise "fail:no dictionary";
+ }
+ if(!iflag)
+ if(!locate() && !range && exact)
+ raise "fail:not found";
+ do{
+ if(iflag){
+ bout.flush();
+ if((orig = bin.gets('\n')) == nil)
+ exit;
+ key = rcanon(orig);
+ if(!locate())
+ continue;
+ }
+ if(range){
+ if(compare(key, word) <= 0 && compare(word, targ) <= 0)
+ bout.puts(entry);
+ }else if(!exact || !compare(word, key))
+ bout.puts(entry);
+ while((entry = dfile.gets('\n')) != nil){
+ word = rcanon(entry);
+ if(range)
+ n := compare(word, targ);
+ else
+ n = compare(key, word);
+ if(debug)
+ sys->print("compare %d\n", n);
+ case n {
+ -2 =>
+ if(range){
+ bout.puts(entry);
+ continue;
+ }
+ -1 =>
+ if(exact)
+ break;
+ if(!exact || !compare(word, key))
+ bout.puts(entry);
+ continue;
+ 0 =>
+ if(!exact || !compare(word, key))
+ bout.puts(entry);
+ continue;
+ }
+ break;
+ }
+ }while(iflag);
+ bout.flush();
+}
+
+locate(): int
+{
+ bot := big 0;
+ top := dfile.seek(big 0, 2);
+ mid: big;
+Search:
+ for(;;){
+ mid = (top+bot)/big 2;
+ if(debug)
+ sys->fprint(sys->fildes(2), "locate %bd %bd %bd\n", top, mid, bot);
+ dfile.seek(mid, 0);
+ c: int;
+ do
+ c = dfile.getc();
+ while(c >= 0 && c != '\n');
+ mid = dfile.offset();
+ if((entry = dfile.gets('\n')) == nil)
+ break;
+ word = rcanon(entry);
+ if(debug)
+ sys->fprint(sys->fildes(2), "mid %bd key: %s entry: %s\n", mid, key, word);
+ n := compare(key, word);
+ if(debug)
+ sys->fprint(sys->fildes(2), "compare: %d\n", n);
+ case n {
+ -2 or -1 or 0 =>
+ if(top <= mid)
+ break Search;
+ top = mid;
+ 1 or 2 =>
+ bot = mid;
+ }
+ }
+ if(debug)
+ sys->fprint(sys->fildes(2), "locate %bd %bd %bd\n", top, mid, bot);
+ bot = dfile.seek(big bot, 0);
+ while((entry = dfile.gets('\n')) != nil){
+ word = rcanon(entry);
+ if(debug)
+ sys->fprint(sys->fildes(2), "seekbot %bd key: %s entry: %s\n", bot, key, word);
+ n := compare(key, word);
+ if(debug)
+ sys->fprint(sys->fildes(2), "compare: %d\n", n);
+ case n {
+ -2 =>
+ return 0;
+ -1 =>
+ if(exact)
+ return 0;
+ return 1;
+ 0 =>
+ return 1;
+ 1 or 2 =>
+ continue;
+ }
+ }
+ return 0;
+}
+
+compare(s, t: string): int
+{
+ if(nflag)
+ return ncomp(s, t);
+ else
+ return acomp(s, t);
+}
+
+#
+# acomp(s, t) returns:
+# -2 if s strictly precedes t
+# -1 if s is a prefix of t
+# 0 if s is the same as t
+# 1 if t is a prefix of s
+# 2 if t strictly precedes s
+#
+acomp(s, t: string): int
+{
+ if(s == t)
+ return 0;
+ l := len s;
+ if(l > len t)
+ l = len t;
+ cs, ct: int;
+ for(i := 0; i < l; i++) {
+ cs = s[i];
+ ct = t[i];
+ if(cs != ct)
+ break;
+ }
+ if(i == len s)
+ return -1;
+ if(i == len t)
+ return 1;
+ if(cs < ct)
+ return -2;
+ return 2;
+}
+
+rcanon(s: string): string
+{
+ if(s != nil && s[len s - 1] == '\n')
+ s = s[0: len s - 1];
+ o := 0;
+ for(i := 0; i < len s && (r := s[i]) != tab; i++){
+ if(16rc0 <= r && r <= 16rff && (mr := latin_fold_tab[r-16rc0]) != 0)
+ r = mr;
+ if(direc)
+ if(!(isalnum(r) || r == ' ' || r == '\t'))
+ continue;
+ if(fold)
+ if(isupper(r))
+ r = tolower(r);
+ if(r != s[o]) # avoid copying s unless necessary
+ s[o] = r;
+ o++;
+ }
+ if(o != i)
+ return s[0:o];
+ return s;
+}
+
+sgn(v: int): int
+{
+ if(v < 0)
+ return -1;
+ if(v > 0)
+ return 1;
+ return 0;
+}
+
+ncomp(s: string, t: string): int
+{
+ while(len s > 0 && isspace(s[0]))
+ s = s[1:];
+ while(len t > 0 && isspace(t[0]))
+ t = t[1:];
+ ssgn := tsgn := -2*rev;
+ if(s != nil && s[0] == '-'){
+ s = s[1: ];
+ ssgn = -ssgn;
+ }
+ if(t != nil && t[0] == '-'){
+ t = t[1:];
+ tsgn = -tsgn;
+ }
+ for(i := 0; i < len s && isdigit(s[i]); i++)
+ ;
+ is := s[0:i];
+ js := s[i:];
+ for(i = 0; i < len t && isdigit(t[i]); i++)
+ ;
+ it := t[0:i];
+ jt := t[i:];
+ a := 0;
+ i = len is;
+ j := len it;
+ if(ssgn == tsgn){
+ while(j > 0 && i > 0)
+ if((b := it[--j] - is[--i]) != 0)
+ a = b;
+ }
+ while(i > 0)
+ if(is[--i] != '0')
+ return -ssgn;
+ while(j > 0)
+ if(it[--i] != '0')
+ return tsgn;
+ if(a)
+ return sgn(a)*ssgn;
+ s = js;
+ if(len s > 0 && s[0] == '.')
+ s = s[1: ];
+ t = jt;
+ if(len t > 0 && t[0] == '.')
+ t = t[1: ];
+ if(ssgn == tsgn)
+ while((len s > 0 && isdigit(s[0])) && (len t > 0 && isdigit(t[0]))){
+ if(a = t[0] - s[0])
+ return sgn(a)*ssgn;
+ s = s[1:];
+ t = t[1:];
+ }
+ for(; len s > 0 && isdigit(s[0]); s = s[1:])
+ if(s[0] != '0')
+ return -ssgn;
+ for(; len t > 0 && isdigit(t[0]); t = t[1:])
+ if(t[0] != '0')
+ return tsgn;
+ return 0;
+}
+
+isupper(c: int): int
+{
+ return c >= 'A' && c <= 'Z';
+}
+
+islower(c: int): int
+{
+ return c >= 'a' && c <= 'z';
+}
+
+isalpha(c: int): int
+{
+ return islower(c) || isupper(c);
+}
+
+islatin1(c: int): int
+{
+ return c >= 16rC0 && c <= 16rFF;
+}
+
+isdigit(c: int): int
+{
+ return c >= '0' && c <= '9';
+}
+
+isalnum(c: int): int
+{
+ return isdigit(c) || islower(c) || isupper(c);
+}
+
+isspace(c: int): int
+{
+ return c == ' ' || c == '\t' || c >= 16r0A && c <= 16r0D;
+}
+
+tolower(c: int): int
+{
+ return c-'A'+'a';
+}
diff --git a/appl/cmd/lookman.b b/appl/cmd/lookman.b
new file mode 100644
index 00000000..53557c8b
--- /dev/null
+++ b/appl/cmd/lookman.b
@@ -0,0 +1,250 @@
+implement Lookman;
+include "sys.m";
+include "bufio.m";
+include "draw.m";
+
+
+Lookman : module {
+ init : fn (ctxt : ref Draw->Context, argv : list of string);
+};
+
+sys : Sys;
+bufio : Bufio;
+Iobuf : import bufio;
+
+ctype := array [256] of { * => byte 0 };
+
+MANINDEX : con "/man/index";
+
+init(nil : ref Draw->Context, argv : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ if (bufio == nil)
+ raise "init:fail";
+
+ # setup our char conversion table
+ # map upper-case to lower-case
+ for (i := 'A'; i <= 'Z'; i++)
+ ctype[i] = byte ((i - 'A') + 'a');
+
+ # only allow the following chars
+ okchars := "abcdefghijklmnopqrstuvwxyz0123456789+.:½ ";
+ for (i = 0; i < len okchars; i++) {
+ ch := okchars[i];
+ ctype[ch] = byte ch;
+ }
+
+ stdout := bufio->fopen(sys->fildes(1), Sys->OWRITE);
+
+ argv = tl argv;
+ paths := lookup(argv);
+ for (; paths != nil; paths = tl paths)
+ stdout.puts(sys->sprint("%s\n", hd paths));
+ stdout.flush();
+}
+
+lookup(words : list of string) : list of string
+{
+ # open the index file
+ manindex := bufio->open(MANINDEX, Sys->OREAD);
+ if (manindex == nil) {
+ sys->print("cannot open %s: %r\n", MANINDEX);
+ return nil;
+ }
+
+ # convert to lower-case and discard funny chars
+ keywords : list of string;
+ for (; words != nil; words = tl words) {
+ word := hd words;
+ kw := "";
+ for (i := 0; i < len word; i++) {
+ ch := word[i];
+ if (ch < len ctype && ctype[ch] != byte 0)
+ kw[len kw] = int ctype[ch];
+ }
+ if (kw != "")
+ keywords = kw :: keywords;
+ }
+
+ if (keywords == nil)
+ return nil;
+
+ keywords = sortuniq(keywords);
+ matches : list of list of string;
+
+ for (; keywords != nil; keywords = tl keywords) {
+ kw := hd keywords;
+ matchlist := look(manindex, '\t', kw);
+ pathlist : list of string = nil;
+ for (; matchlist != nil; matchlist = tl matchlist) {
+ line := hd matchlist;
+ (n, toks) := sys->tokenize(line, "\t");
+ if (n != 2)
+ continue;
+ pathlist = hd tl toks :: pathlist;
+ }
+ if (pathlist != nil)
+ matches = pathlist :: matches;
+ }
+
+ return intersect(matches);
+}
+
+getentry(iob : ref Iobuf) : (string, string)
+{
+ while ((s := iob.gets('\n')) != nil) {
+ if (s[len s -1] == '\n')
+ s = s[0:len s -1];
+ if (s == nil)
+ continue;
+ (n, toks) := sys->tokenize(s, "\t");
+ if (n != 2)
+ continue;
+ return (hd toks, hd tl toks);
+ }
+ return (nil, nil);
+}
+
+sortuniq(strlist : list of string) : list of string
+{
+ strs := array [len strlist] of string;
+ for (i := 0; strlist != nil; (i, strlist) = (i+1, tl strlist))
+ strs[i] = hd strlist;
+
+ # simple sort (greatest first)
+ for (i = 0; i < len strs - 1; i++) {
+ for (j := i+1; j < len strs; j++)
+ if (strs[i] < strs[j])
+ (strs[i], strs[j]) = (strs[j], strs[i]);
+ }
+
+ # construct list (result is ascending)
+ r : list of string;
+ prev := "";
+ for (i = 0; i < len strs; i++) {
+ if (strs[i] != prev) {
+ r = strs[i] :: r;
+ prev = strs[i];
+ }
+ }
+ return r;
+}
+
+intersect(strlists : list of list of string) : list of string
+{
+ if (strlists == nil)
+ return nil;
+
+ okl := hd strlists;
+ for (strlists = tl strlists; okl != nil && strlists != nil; strlists = tl strlists) {
+ find := hd strlists;
+ found : list of string = nil;
+ for (; okl != nil; okl = tl okl) {
+ ok := hd okl;
+ for (scanl := find; scanl != nil; scanl = tl scanl) {
+ scan := hd scanl;
+ if (scan == ok) {
+ found = ok :: found;
+ break;
+ }
+ }
+ }
+ okl = found;
+ }
+ return sortuniq(okl);
+}
+
+# binary search for key in f.
+# based on Plan 9 look.c
+#
+look(f: ref Iobuf, sep: int, key: string): list of string
+{
+ bot := mid := 0;
+ top := int f.seek(big 0, Sys->SEEKEND);
+ key = canon(key, sep);
+
+ for (;;) {
+ mid = (top + bot) / 2;
+ f.seek(big mid, Sys->SEEKSTART);
+ c: int;
+ do {
+ c = f.getb();
+ mid++;
+ } while (c != Bufio->EOF && c != Bufio->ERROR && c != '\n');
+ (entry, eof) := getword(f);
+ if (entry == nil && eof)
+ break;
+ entry = canon(entry, sep);
+ case comparewords(key, entry) {
+ -2 or -1 or 0 =>
+ if (top <= mid)
+ break;
+ top = mid;
+ continue;
+ 1 or 2 =>
+ bot = mid;
+ continue;
+ }
+ break;
+ }
+ matchlist : list of string;
+ f.seek(big bot, Sys->SEEKSTART);
+ for (;;) {
+ (entry, eof) := getword(f);
+ if (entry == nil && eof)
+ return matchlist;
+ word := canon(entry, sep);
+ case comparewords(key, word) {
+ -1 or 0 =>
+ matchlist = entry :: matchlist;
+ continue;
+ 1 or 2 =>
+ continue;
+ }
+ break;
+ }
+ return matchlist;
+}
+
+comparewords(s, t: string): int
+{
+ if (s == t)
+ return 0;
+ i := 0;
+ for (; i < len s && i < len t && s[i] == t[i]; i++)
+ ;
+ if (i >= len s)
+ return -1;
+ if (i >= len t)
+ return 1;
+ if (s[i] < t[i])
+ return -2;
+ return 2;
+}
+
+getword(f: ref Iobuf): (string, int)
+{
+ ret := "";
+ for (;;) {
+ c := f.getc();
+ if (c == Bufio->EOF || c == Bufio->ERROR)
+ return (ret, 0);
+ if (c == '\n')
+ break;
+ ret[len ret] = c;
+ }
+ return (ret, 1);
+}
+
+canon(s: string, sep: int): string
+{
+ if (sep < 0)
+ return s;
+ i := 0;
+ for (; i < len s; i++)
+ if (s[i] == sep)
+ break;
+ return s[0:i];
+}
diff --git a/appl/cmd/ls.b b/appl/cmd/ls.b
new file mode 100644
index 00000000..3d27d59e
--- /dev/null
+++ b/appl/cmd/ls.b
@@ -0,0 +1,318 @@
+implement Ls;
+
+include "sys.m";
+ sys: Sys;
+ FD, Dir: import Sys;
+
+include "draw.m";
+ Context: import Draw;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "readdir.m";
+ readdir: Readdir;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+
+Ls: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+PREFIX: con 16r40000000;
+
+dopt := 0;
+eopt := 0;
+lopt := 0;
+mopt := 0;
+nopt := 0;
+popt := 0;
+qopt := 0;
+sopt := 0;
+topt := 0;
+uopt := 0;
+Topt := 0;
+now: int;
+sortby: int;
+
+out: ref Bufio->Iobuf;
+stderr: ref FD;
+
+dwIndex: int;
+dwQueue: array of Dir;
+
+badmodule(p: string)
+{
+ sys->fprint(stderr, "ls: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init(nil: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ badmodule(Bufio->PATH);
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ badmodule(Readdir->PATH);
+ str = load String String->PATH;
+ if(str == nil)
+ badmodule(String->PATH);
+
+ stderr = sys->fildes(2);
+ out = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ rev := 0;
+ sortby = Readdir->NAME;
+ compact := 0;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ badmodule(Arg->PATH);
+ arg->init(argv);
+ while((o := arg->opt()) != 0){
+ case o {
+ 'l' =>
+ lopt++;
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ badmodule(Daytime->PATH);
+ now = daytime->now();
+ 'p' =>
+ popt++;
+ 'q' =>
+ qopt++;
+ 'd' =>
+ dopt++;
+ 'e' =>
+ eopt++;
+ 'm' =>
+ mopt++;
+ 'n' =>
+ nopt++;
+ 'k' =>
+ sopt++;
+ 't' =>
+ topt++;
+ 'u' =>
+ uopt++;
+ 's' =>
+ sortby = Readdir->SIZE;
+ 'c' =>
+ compact = Readdir->COMPACT;
+ 'r' =>
+ rev = Readdir->DESCENDING;
+ 'T' =>
+ Topt++;
+ * =>
+ sys->fprint(stderr, "usage: ls [-delmnpqrstucT] [files]\n");
+ raise "fail:usage";
+ }
+ }
+ argv = arg->argv();
+ arg = nil;
+
+ if(nopt == 0) {
+ if(topt){
+ if(uopt)
+ sortby = Readdir->ATIME;
+ else
+ sortby = Readdir->MTIME;
+ }
+ } else
+ sortby = Readdir->NONE;
+ sortby |= rev|compact;
+
+ if(argv == nil) {
+ argv = list of {"."};
+ popt++;
+ }
+
+ for(; argv != nil; argv = tl argv)
+ ls(hd argv);
+ delayWrite();
+ out.flush();
+}
+
+ls(file: string)
+{
+ dir: Dir;
+ ok: int;
+
+ (ok, dir) = sys->stat(file);
+ if(ok == -1) {
+ sys->fprint(stderr, "ls: stat %s: %r\n", file);
+ return;
+ }
+ if(dopt || (dir.mode & Sys->DMDIR) == 0) {
+ # delay write: save it in the queue to sort by sortby
+ if(dwIndex == 0)
+ dwQueue = array[30] of Dir;
+ else if(len dwQueue == dwIndex) {
+ # expand dwQueue
+ tmp := array[2 * dwIndex] of Dir;
+ tmp[0:] = dwQueue;
+ dwQueue = tmp;
+ }
+ (dirname, filename) := str->splitstrr(file, "/");
+ if(dirname != "") {
+ dir.name = dirname + filename;
+ dir.dev |= PREFIX;
+ }
+ dwQueue[dwIndex++] = dir;
+ return;
+ }
+
+ delayWrite();
+
+ (d, n) := readdir->init(file, sortby);
+ if( n < 0)
+ sys->fprint(stderr, "ls: Readdir: %s: %r\n", file);
+ else
+ lsprint(file, d[0:n]);
+}
+
+delayWrite()
+{
+ if(dwIndex == 0)
+ return;
+
+ a := array[dwIndex] of ref Dir;
+ for (i := 0; i < dwIndex; i++)
+ a[i] = ref dwQueue[i];
+ (b, n) := readdir->sortdir(a, sortby);
+
+ lsprint("", b[0:n]);
+
+ # reset dwIndex
+ dwIndex = 0;
+ dwQueue = nil;
+}
+
+Widths: adt {
+ vers, dev, uid, gid, muid, length, size: int;
+};
+
+dowidths(dir: array of ref Dir): ref Widths
+{
+ w := Widths(0, 0, 0, 0, 0, 0, 0);
+ for (i := 0; i < len dir; i++) {
+ n: int;
+ d := dir[i];
+ if(sopt)
+ if((n = len string ((d.length+big 1023)/big 1024)) > w.size)
+ w.size = n;
+ if(mopt)
+ if((n = len d.muid+2) > w.muid)
+ w.muid = n;
+ if(qopt)
+ if((n = len string d.qid.vers) > w.vers)
+ w.vers = n;
+ if(lopt) {
+ if((n = len string (d.dev & ~PREFIX)) > w.dev)
+ w.dev = n;
+ if((n = len d.uid) > w.uid)
+ w.uid = n;
+ if((n = len d.gid) > w.gid)
+ w.gid = n;
+ if((n = len string d.length) > w.length)
+ w.length = n;
+ }
+ }
+ return ref w;
+}
+
+
+lsprint(dirname: string, dir: array of ref Dir)
+{
+ w := dowidths(dir);
+
+ for (i := 0; i < len dir; i++)
+ lslineprint(dirname, dir[i].name, dir[i], w);
+}
+
+lslineprint(dirname, name: string, dir: ref Dir, w: ref Widths)
+{
+ if(sopt)
+ out.puts(sys->sprint("%*bd ", w.size, (dir.length+big 1023)/big 1024));
+ if(mopt){
+ out.puts(sys->sprint("[%s] ", dir.muid));
+ for(i := len dir.muid+2; i < w.muid; i++)
+ out.putc(' ');
+ }
+ if(qopt)
+ out.puts(sys->sprint("(%.16bux %*ud %.2ux) ", dir.qid.path, w.vers, dir.qid.vers, dir.qid.qtype));
+ if(Topt){
+ if(dir.mode & Sys->DMTMP)
+ out.puts("t ");
+ else
+ out.puts("- ");
+ }
+
+ file := name;
+ pf := dir.dev & PREFIX;
+ dir.dev &= ~PREFIX;
+ if(popt) {
+ if(pf)
+ (nil, file) = str->splitstrr(dir.name, "/");
+ else
+ file = dir.name;
+ } else if(dirname != "") {
+ if(dirname[len dirname-1] == '/')
+ file = dirname + file;
+ else
+ file = dirname + "/" + file;
+ }
+
+
+ if(lopt) {
+ time := dir.mtime;
+ if(uopt)
+ time = dir.atime;
+ if(eopt)
+ out.puts(sys->sprint("%s %c %*d %*s %*s %*bud %d %s\n",
+ modes(dir.mode), dir.dtype, w.dev, dir.dev,
+ -w.uid, dir.uid, -w.gid, dir.gid, w.length, dir.length,
+ time, file));
+ else
+ out.puts(sys->sprint("%s %c %*d %*s %*s %*bud %s %s\n",
+ modes(dir.mode), dir.dtype, w.dev, dir.dev,
+ -w.uid, dir.uid, -w.gid, dir.gid, w.length, dir.length,
+ daytime->filet(now, time), file));
+ } else
+ out.puts(file+"\n");
+}
+
+mtab := array[] of {
+ "---", "--x", "-w-", "-wx",
+ "r--", "r-x", "rw-", "rwx"
+};
+
+modes(mode: int): string
+{
+ s: string;
+
+ if(mode & Sys->DMDIR)
+ s = "d";
+ else if(mode & Sys->DMAPPEND)
+ s = "a";
+ else if(mode & Sys->DMAUTH)
+ s = "A";
+ else
+ s = "-";
+ if(mode & Sys->DMEXCL)
+ s += "l";
+ else
+ s += "-";
+ s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7];
+ return s;
+}
+
diff --git a/appl/cmd/lstar.b b/appl/cmd/lstar.b
new file mode 100644
index 00000000..fddd19d2
--- /dev/null
+++ b/appl/cmd/lstar.b
@@ -0,0 +1,120 @@
+implement lstar;
+
+include "sys.m";
+ sys: Sys;
+ print, sprint, fprint: import sys;
+ stdin, stderr: ref sys->FD;
+include "draw.m";
+
+TBLOCK: con 512; # tar logical blocksize
+Header: adt{
+ name: string;
+ size: int;
+ mtime: int;
+ skip: int;
+};
+
+lstar: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Error(mess: string){
+ fprint(stderr,"lstar: %s: %r\n",mess);
+ exit;
+}
+
+
+NBLOCK: con 20; # blocking factor for efficient read
+tarbuf := array[NBLOCK*TBLOCK] of byte; # static buffer
+nblock := NBLOCK; # how many blocks of data are in tarbuf
+recno := NBLOCK; # how many blocks in tarbuf have been consumed
+getblock():array of byte{
+ if(recno>=nblock){
+ i := sys->read(stdin,tarbuf,TBLOCK*NBLOCK);
+ if(i==0)
+ return tarbuf[0:0];
+ if(i<0)
+ Error("read error");
+ if(i%TBLOCK!=0)
+ Error("blocksize error");
+ nblock = i/TBLOCK;
+ recno = 0;
+ }
+ recno++;
+ return tarbuf[(recno-1)*TBLOCK:recno*TBLOCK];
+}
+
+octal(b:array of byte):int{
+ sum := 0;
+ for(i:=0; i<len b; i++){
+ bi := int b[i];
+ if(bi==' ') continue;
+ if(bi==0) break;
+ sum = 8*sum + bi-'0';
+ }
+ return sum;
+}
+
+nullterm(b:array of byte):string{
+ for(i:=0; i<len b; i++)
+ if(b[i]==byte 0) break;
+ return string b[0:i];
+}
+
+getdir():ref Header{
+ dblock := getblock();
+ if(len dblock==0)
+ return nil;
+ if(dblock[0]==byte 0)
+ return nil;
+
+ name := nullterm(dblock[0:100]);
+ if(int dblock[345]!=0)
+ name = nullterm(dblock[345:500])+"/"+name;
+
+ magic := string(dblock[257:262]);
+ if(magic[0]!=0 && magic!="ustar")
+ Error("bad magic "+name);
+ chksum := octal(dblock[148:156]);
+ for(ci:=148; ci<156; ci++) dblock[ci] = byte ' ';
+ for(i:=0; i<TBLOCK; i++)
+ chksum -= int dblock[i];
+ if(chksum!=0)
+ Error("directory checksum error "+name);
+
+ skip := 1;
+ size := 0;
+ mtime := 0;
+ case int dblock[156]{
+ '0' or '5' or '7' or 0 =>
+ skip = 0;
+ size = octal(dblock[124:136]);
+ mtime = octal(dblock[136:148]);
+ '1' =>
+ fprint(stderr,"skipping link %s -> %s\n",name,string(dblock[157:257]));
+ '2' or 's' =>
+ fprint(stderr,"skipping symlink %s\n",name);
+ '3' or '4' or '6' =>
+ fprint(stderr,"skipping special file %s\n",name);
+ * =>
+ Error(sprint("unrecognized typeflag %d for %s",int dblock[156],name));
+ }
+ return ref Header(name,size,mtime,skip);
+}
+
+
+init(nil: ref Draw->Context, nil: list of string){
+ sys = load Sys Sys->PATH;
+ stdin = sys->fildes(0);
+ stderr = sys->fildes(2);
+ ofile: ref sys->FD;
+
+ while((file := getdir())!=nil){
+ bytes := file.size;
+ blocks := (bytes+TBLOCK-1)/TBLOCK;
+ for(; blocks>0; blocks--)
+ getblock();
+ print("%s %d %d 0\n",file.name,file.mtime,file.size);
+ ofile = nil;
+ }
+}
diff --git a/appl/cmd/man.b b/appl/cmd/man.b
new file mode 100644
index 00000000..f0bc24b3
--- /dev/null
+++ b/appl/cmd/man.b
@@ -0,0 +1,199 @@
+implement Man, Command;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "filepat.m";
+include "bufio.m";
+include "man.m";
+
+Command: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+MANPATH: con "/man/";
+PATHDEPTH: con 1;
+
+indices: list of (string, list of (string, string));
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "Usage: man [-f] [0-9] ... name ...\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr := sys->fildes(2);
+ man2txt := load Command "/dis/man2txt.dis";
+ if (man2txt == nil) {
+ sys->fprint(stderr, "man: cannot load /dis/man2txt.dis: %r\n");
+ raise "fail:bad module";
+ }
+
+ argv = tl argv;
+ sections: list of string;
+ fflag := 0;
+ for (; argv != nil; argv = tl argv) {
+ arg := hd argv;
+ if (arg == nil)
+ continue;
+ if (arg == "-f") {
+ argv = tl argv;
+ if (argv == nil || sections != nil)
+ usage();
+ fflag = 1;
+ break;
+ }
+
+ if (!isint(arg))
+ break;
+ sections = arg :: sections;
+ }
+ if (argv == nil)
+ usage();
+
+ paths := argv;
+ if (!fflag) {
+ err := loadsections(sections);
+ if (err != nil) {
+ sys->fprint(stderr, "%s\n", err);
+ raise "fail:error";
+ }
+ files := getfiles(sections, argv);
+ paths = nil;
+ for (; files != nil; files = tl files) {
+ (nil, nil, path) := hd files;
+ paths = path :: paths;
+ }
+ paths = sortuniq(paths);
+ }
+ man2txt->init(nil, "man2txt" :: paths);
+}
+
+loadsections(scanlist: list of string): string
+{
+ sys = load Sys Sys->PATH;
+ bufio := load Bufio Bufio->PATH;
+ Iobuf: import bufio;
+
+ if (bufio == nil)
+ return sys->sprint("cannot load %s: %r", Bufio->PATH);
+
+ indexpaths: list of string;
+ if (scanlist == nil) {
+ filepat := load Filepat Filepat->PATH;
+ if (filepat == nil)
+ return sys->sprint("cannot load %s: %r", Filepat->PATH);
+
+ indexpaths = filepat->expand(MANPATH + "[0-9]*/INDEX");
+ if (indexpaths == nil)
+ return sys->sprint("cannot find man pages");
+ } else {
+ for (; scanlist != nil; scanlist = tl scanlist)
+ indexpaths = MANPATH + string hd scanlist + "/INDEX" :: indexpaths;
+ indexpaths = sortuniq(indexpaths);
+ }
+
+ sections: list of string;
+ for (; indexpaths != nil; indexpaths = tl indexpaths) {
+ path := hd indexpaths;
+ (n, toks) := sys->tokenize(path, "/");
+ for (d := 0; d < PATHDEPTH; d++)
+ toks = tl toks;
+ sections = hd toks :: sections;
+ }
+
+ for (sl := sections; sl != nil; sl = tl sl) {
+ section := hd sl;
+ path := MANPATH + string section + "/INDEX";
+ iob := bufio->open(path, Sys->OREAD);
+ if (iob == nil)
+ continue;
+ pairs: list of (string, string) = nil;
+
+ while((s := iob.gets('\n')) != nil) {
+ if (s[len s - 1] == '\n')
+ s = s[0:len s - 1];
+ (n, toks) := sys->tokenize(s, " ");
+ if (n != 2)
+ continue;
+ pairs = (hd toks, hd tl toks) :: pairs;
+ }
+ iob.close();
+ indices = (section, pairs) :: indices;
+ }
+ return nil;
+}
+
+getfiles(sections: list of string, keys: list of string): list of (int, string, string)
+{
+ ixl: list of (string, list of (string, string));
+
+ if (sections == nil)
+ ixl = indices;
+ else {
+ for (; sections != nil; sections = tl sections) {
+ section := hd sections;
+ for (il := indices; il != nil; il = tl il) {
+ (s, mapl) := hd il;
+ if (s == section) {
+ ixl = (s, mapl) :: ixl;
+ break;
+ }
+ }
+ }
+ }
+ paths: list of (int, string, string);
+ for(keyl := keys; keyl != nil; keyl = tl keyl){
+ for (; ixl != nil; ixl = tl ixl) {
+ for ((s, mapl) := hd ixl; mapl != nil; mapl = tl mapl) {
+ (kw, file) := hd mapl;
+ if (hd keyl == kw) {
+ p := MANPATH + s + "/" + file;
+ paths = (int s, kw, p) :: paths;
+ }
+ }
+ # allow files not in the index
+ if(paths == nil || (hd paths).t0 != int s || (hd paths).t1 != hd keyl){
+ p := MANPATH + string s + "/" + hd keyl;
+ if(sys->stat(p).t0 != -1)
+ paths = (int s, hd keyl, p) :: paths;
+ }
+ }
+ }
+ return paths;
+}
+
+sortuniq(strlist: list of string): list of string
+{
+ strs := array [len strlist] of string;
+ for (i := 0; strlist != nil; (i, strlist) = (i+1, tl strlist))
+ strs[i] = hd strlist;
+
+ # simple sort (greatest first)
+ for (i = 0; i < len strs - 1; i++) {
+ for (j := i+1; j < len strs; j++)
+ if (strs[i] < strs[j])
+ (strs[i], strs[j]) = (strs[j], strs[i]);
+ }
+
+ # construct list (result is ascending)
+ r: list of string;
+ prev := "";
+ for (i = 0; i < len strs; i++) {
+ if (strs[i] != prev) {
+ r = strs[i] :: r;
+ prev = strs[i];
+ }
+ }
+ return r;
+}
+
+isint(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] < '0' || s[i] > '9')
+ return 0;
+ return 1;
+}
diff --git a/appl/cmd/man2txt.b b/appl/cmd/man2txt.b
new file mode 100644
index 00000000..1a82027f
--- /dev/null
+++ b/appl/cmd/man2txt.b
@@ -0,0 +1,79 @@
+implement Man2txt;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "man.m";
+
+Man2txt: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+W: adt {
+ textwidth: fn(w: self ref W, text: Parseman->Text): int;
+};
+
+output: ref Iobuf;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) {
+ sys->print("cannot load Bufio module: %r\n");
+ raise "fail:init";
+ }
+
+ stdout := sys->fildes(1);
+ output = bufio->fopen(stdout, Sys->OWRITE);
+
+ parser := load Parseman Parseman->PATH;
+ parser->init();
+
+ argv = tl argv;
+ for (; argv != nil ; argv = tl argv) {
+ fname := hd argv;
+ fd := sys->open(fname, Sys->OREAD);
+ if (fd == nil) {
+ sys->print("cannot open %s: %r\n", fname);
+ continue;
+ }
+ m := Parseman->Metrics(65, 1, 1, 1, 1, 5, 2);
+
+ datachan := chan of list of (int, Parseman->Text);
+ w: ref W;
+ spawn parser->parseman(fd, m, 1, w, datachan);
+ for (;;) {
+ line := <- datachan;
+ if (line == nil)
+ break;
+ setline(line);
+ }
+ output.flush();
+ }
+ output.close();
+}
+
+W.textwidth(nil: self ref W, text: Parseman->Text): int
+{
+ return len text.text;
+}
+
+setline(line: list of (int, Parseman->Text))
+{
+#return;
+ offset := 0;
+ for (; line != nil; line = tl line) {
+ (indent, txt) := hd line;
+ while (offset < indent) {
+ output.putc(' ');
+ offset++;
+ }
+ output.puts(txt.text);
+ offset += len txt.text;
+ }
+ output.putc('\n');
+}
diff --git a/appl/cmd/manufacture.b b/appl/cmd/manufacture.b
new file mode 100644
index 00000000..7be96de3
--- /dev/null
+++ b/appl/cmd/manufacture.b
@@ -0,0 +1,42 @@
+implement Manufacture;
+
+include "sys.m";
+FD, Dir: import Sys;
+sys: Sys;
+
+include "draw.m";
+draw: Draw;
+Context, Display, Font, Screen, Image, Point, Rect: import draw;
+
+Manufacture: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+stderr: ref FD;
+
+init(nil: ref Context, argv: list of string)
+{
+ s: string;
+ argv0: string;
+
+ argv0 = hd argv;
+ argv = tl argv;
+ sys = load Sys Sys->PATH;
+
+ stderr = sys->fildes(2);
+
+ fd := sys->create("/nvfs/ID", sys->OWRITE, 8r666);
+ if(fd == nil){
+ sys->fprint(stderr, "manufacture: can't create /nvfs/ID: %r\n");
+ return;
+ }
+
+ while(argv != nil) {
+ s = hd argv;
+ sys->fprint(fd, "%s", s);
+ argv = tl argv;
+ if(argv != nil)
+ sys->fprint(fd, " ");
+ }
+}
diff --git a/appl/cmd/mash/builtins.b b/appl/cmd/mash/builtins.b
new file mode 100644
index 00000000..b4374581
--- /dev/null
+++ b/appl/cmd/mash/builtins.b
@@ -0,0 +1,347 @@
+implement Mashbuiltin;
+
+#
+# "builtins" builtin, defines:
+#
+# env - print environment or individual elements
+# eval - interpret arguments as mash input
+# exit - exit toplevel, eval or subshell
+# load - load a builtin
+# prompt - print or set prompt
+# quote - print arguments quoted as input for mash
+# run - interpret a file as mash input
+# status - report existence of error output
+# time - time the execution of a command
+# whatis - print variable, function and builtin
+#
+
+include "mash.m";
+include "mashparse.m";
+
+mashlib: Mashlib;
+
+Cmd, Env, Stab: import mashlib;
+sys, bufio: import mashlib;
+
+Iobuf: import bufio;
+
+#
+# Interface to catch the use as a command.
+#
+init(nil: ref Draw->Context, nil: list of string)
+{
+ ssys := load Sys Sys->PATH;
+ ssys->fprint(ssys->fildes(2), "builtins: cannot run as a command\n");
+ raise "fail: error";
+}
+
+#
+# Used by whatis.
+#
+name(): string
+{
+ return "builtins";
+}
+
+#
+# Install commands.
+#
+mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env)
+{
+ mashlib = lib;
+ e.defbuiltin("env", this);
+ e.defbuiltin("eval", this);
+ e.defbuiltin("exit", this);
+ e.defbuiltin("load", this);
+ e.defbuiltin("prompt", this);
+ e.defbuiltin("quote", this);
+ e.defbuiltin("run", this);
+ e.defbuiltin("status", this);
+ e.defbuiltin("time", this);
+ e.defbuiltin("whatis", this);
+}
+
+#
+# Execute a builtin.
+#
+mashcmd(e: ref Env, l: list of string)
+{
+ case hd l {
+ "env" =>
+ l = tl l;
+ if (l == nil) {
+ out := e.outfile();
+ if (out == nil)
+ return;
+ prsymbs(out, e.global, "=");
+ prsymbs(out, e.local, ":=");
+ out.close();
+ } else
+ e.usage("env");
+ "eval" =>
+ eval(e, tl l);
+ "exit" =>
+ raise mashlib->EXIT;
+ "load" =>
+ l = tl l;
+ if (len l == 1)
+ e.doload(hd l);
+ else
+ e.usage("load file");
+ "prompt" =>
+ l = tl l;
+ case len l {
+ 0 =>
+ mashlib->prprompt(0);
+ 1 =>
+ mashlib->prompt = hd l;
+ 2 =>
+ mashlib->prompt = hd l;
+ mashlib->contin = hd tl l;
+ * =>
+ e.usage("prompt [string]");
+ }
+ "quote" =>
+ l = tl l;
+ if (l != nil) {
+ out := e.outfile();
+ if (out == nil)
+ return;
+ f := 0;
+ while (l != nil) {
+ if (f)
+ out.putc(' ');
+ else
+ f = 1;
+ out.puts(mashlib->quote(hd l));
+ l = tl l;
+ }
+ out.putc('\n');
+ out.close();
+ }
+ "run" =>
+ if (!run(e, tl l))
+ e.usage("run [-] [-denx] file [arg ...]");
+ "status" =>
+ l = tl l;
+ if (l != nil)
+ status(e, l);
+ else
+ e.usage("status cmd [arg ...]");
+ "time" =>
+ l = tl l;
+ if (l != nil)
+ time(e, l);
+ else
+ e.usage("time cmd [arg ...]");
+ "whatis" =>
+ l = tl l;
+ if (l != nil) {
+ out := e.outfile();
+ if (out == nil)
+ return;
+ while (l != nil) {
+ whatis(e, out, hd l);
+ l = tl l;
+ }
+ out.close();
+ }
+ }
+}
+
+#
+# Print a variable and its value.
+#
+prone(out: ref Iobuf, eq, s: string, v: list of string)
+{
+ out.puts(s);
+ out.putc(' ');
+ out.puts(eq);
+ if (v != mashlib->empty) {
+ do {
+ out.putc(' ');
+ out.puts(mashlib->quote(hd v));
+ v = tl v;
+ } while (v != nil);
+ }
+ out.puts(";\n");
+}
+
+#
+# Print the contents of a symbol table.
+#
+prsymbs(out: ref Iobuf, t: ref Stab, eq: string)
+{
+ if (t == nil)
+ return;
+ for (l := t.all(); l != nil; l = tl l) {
+ s := hd l;
+ v := s.value;
+ if (v != nil)
+ prone(out, eq, s.name, v);
+ }
+}
+
+#
+# Print variables, functions and builtins.
+#
+whatis(e: ref Env, out: ref Iobuf, s: string)
+{
+ f := 0;
+ v := e.global.find(s);
+ if (v != nil) {
+ if (v.value != nil)
+ prone(out, "=", s, v.value);
+ if (v.func != nil) {
+ out.puts("fn ");
+ out.puts(s);
+ out.puts(" { ");
+ out.puts(v.func.text());
+ out.puts(" };\n");
+ }
+ if (v.builtin != nil) {
+ out.puts("load ");
+ out.puts(v.builtin->name());
+ out.puts("; ");
+ out.puts(s);
+ out.puts(";\n");
+ }
+ f = 1;
+ }
+ if (e.local != nil) {
+ v = e.local.find(s);
+ if (v != nil) {
+ prone(out, ":=", s, v.value);
+ f = 1;
+ }
+ }
+ if (!f) {
+ out.puts(s);
+ out.puts(": not found\n");
+ }
+}
+
+#
+# Catenate arguments and interpret as mash input.
+#
+eval(e: ref Env, l: list of string)
+{
+ s: string;
+ while (l != nil) {
+ s = s + " " + hd l;
+ l = tl l;
+ }
+ e = e.copy();
+ e.flags &= ~mashlib->EInter;
+ e.sopen(s);
+ mashlib->parse->parse(e);
+}
+
+#
+# Interpret file as mash input.
+#
+run(e: ref Env, l: list of string): int
+{
+ f := 0;
+ if (l == nil)
+ return 0;
+ e = e.copy();
+ s := hd l;
+ while (s[0] == '-') {
+ if (s == "-")
+ f = 1;
+ else {
+ for (i := 1; i < len s; i++) {
+ case s[i] {
+ 'd' =>
+ e.flags |= mashlib->EDumping;
+ 'e' =>
+ e.flags |= mashlib->ERaise;
+ 'n' =>
+ e.flags |= mashlib->ENoxeq;
+ 'x' =>
+ e.flags |= mashlib->EEcho;
+ * =>
+ return 0;
+ }
+ }
+ }
+ l = tl l;
+ if (l == nil)
+ return 0;
+ s = hd l;
+ }
+ fd := sys->open(s, Sys->OREAD);
+ if (fd == nil) {
+ err := mashlib->errstr();
+ if (mashlib->nonexistent(err) && s[0] != '/' && s[0:2] != "./") {
+ fd = sys->open(mashlib->LIB + s, Sys->OREAD);
+ if (fd == nil)
+ err = mashlib->errstr();
+ else
+ s = mashlib->LIB + s;
+ }
+ if (fd == nil) {
+ if (!f)
+ e.report(s + ": " + err);
+ return 1;
+ }
+ }
+ e.local = Stab.new();
+ e.local.assign(mashlib->ARGS, tl l);
+ e.flags &= ~mashlib->EInter;
+ e.fopen(fd, s);
+ mashlib->parse->parse(e);
+ return 1;
+}
+
+#
+# Run a command and report true on no error output.
+#
+status(e: ref Env, l: list of string)
+{
+ in := child(e, l);
+ if (in == nil)
+ return;
+ b := array[256] of byte;
+ n := sys->read(in, b, len b);
+ if (n != 0) {
+ while (n > 0)
+ n = sys->read(in, b, len b);
+ if (n < 0)
+ e.couldnot("read", "pipe");
+ } else
+ e.output(Mashlib->TRUE);
+}
+
+#
+# Status env child.
+#
+child(e: ref Env, l: list of string): ref Sys->FD
+{
+ e = e.copy();
+ fds := e.pipe();
+ if (fds == nil)
+ return nil;
+ if (sys->dup(fds[0].fd, 2) < 0) {
+ e.couldnot("dup", "pipe");
+ return nil;
+ }
+ t := e.stderr;
+ e.stderr = fds[0];
+ e.runit(l, nil, nil, 0);
+ e.stderr = t;
+ sys->dup(t.fd, 2);
+ return fds[1];
+}
+
+#
+# Time the execution of a command.
+#
+time(e: ref Env, l: list of string)
+{
+ t1 := sys->millisec();
+ e.runit(l, nil, nil, 1);
+ t2 := sys->millisec();
+ sys->fprint(e.stderr, "%.4g\n", real (t2 - t1) / 1000.0);
+}
diff --git a/appl/cmd/mash/depends.b b/appl/cmd/mash/depends.b
new file mode 100644
index 00000000..dfb14ef5
--- /dev/null
+++ b/appl/cmd/mash/depends.b
@@ -0,0 +1,228 @@
+#
+# Dependency/rule routines.
+#
+
+DHASH: con 127; # dephash size
+
+#
+# Initialize. "make -clear" calls this.
+#
+initdep()
+{
+ dephash = array[DHASH] of list of ref Target;
+ rules = nil;
+}
+
+#
+# Lookup a target in dephash, maybe add it.
+#
+target(s: string, insert: int): ref Target
+{
+ h := hash->fun1(s, DHASH);
+ l := dephash[h];
+ while (l != nil) {
+ if ((hd l).target == s)
+ return hd l;
+ l = tl l;
+ }
+ if (!insert)
+ return nil;
+ t := ref Target(s, nil);
+ dephash[h] = t :: dephash[h];
+ return t;
+}
+
+adddep(s: string, d: ref Depend)
+{
+ t := target(s, 1);
+ t.depends = d :: t.depends;
+}
+
+#
+# Dependency (:) command.
+# Evaluate lhs and rhs, make dependency, and add to the targets.
+#
+Cmd.depend(c: self ref Cmd, e: ref Env)
+{
+ if ((e.flags & ETop) == 0) {
+ e.report("dependency not at top level");
+ return;
+ }
+ if (dephash == nil)
+ initdep();
+ w := pass1(e, c.words);
+ if (w == nil)
+ return;
+ l := pass2(e, w);
+ if (l == nil)
+ return;
+ r: list of string;
+ if (c.left.words != nil) {
+ w = pass1(e, c.left.words);
+ if (w == nil)
+ return;
+ r = pass2(e, w);
+ if (r == nil)
+ return;
+ }
+ d := ref Depend(l, r, c.left.op, c.left.left, 0);
+ while (l != nil) {
+ adddep(hd l, d);
+ l = tl l;
+ }
+}
+
+#
+# Evaluate rule lhs and break into path components.
+#
+rulelhs(e: ref Env, i: ref Item): ref Lhs
+{
+ i = i.ieval1(e);
+ if (i == nil)
+ return nil;
+ (s, l, nil) := i.ieval2(e);
+ if (l != nil) {
+ e.report("rule pattern evaluates to a list");
+ return nil;
+ }
+ if (s == nil) {
+ e.report("rule pattern evaluates to nil");
+ return nil;
+ }
+ (n, p) := sys->tokenize(s, "/");
+ return ref Lhs(s, p, n);
+}
+
+#
+# Rule (:~) command.
+# First pass of rhs evaluation is done here.
+#
+Cmd.rule(c: self ref Cmd, e: ref Env)
+{
+ if (e.flags & ETop) {
+ l := rulelhs(e, c.item);
+ if (l == nil)
+ return;
+ r := c.left.item.ieval1(e);
+ if (r == nil)
+ return;
+ rules = ref Rule(l, r, c.left.op, c.left.left) :: rules;
+ } else
+ e.report("rule not at top level");
+}
+
+Target.find(s: string): ref Target
+{
+ if (dephash == nil)
+ return nil;
+ return target(s, 0);
+}
+
+#
+# Match a path element.
+#
+matchelem(p, s: string): int
+{
+ m := len p;
+ n := len s;
+ if (m == n && p == s)
+ return 1;
+ for (i := 0; i < m; i++) {
+ if (p[i] == '*') {
+ j := i + 1;
+ if (j == m)
+ return 1;
+ q := p[j:];
+ do {
+ if (matchelem(q, s[i:]))
+ return 1;
+ } while (++i < n);
+ return 0;
+ } else if (i >= n || p[i] != s[i])
+ return 0;
+ }
+ return 0;
+}
+
+#
+# Match a path element and return a list of sub-matches.
+#
+matches(p, s: string): (int, list of string)
+{
+ m := len p;
+ n := len s;
+ for (i := 0; i < m; i++) {
+ if (p[i] == '*') {
+ j := i + 1;
+ if (j == m)
+ return (1, s[i:] :: nil);
+ q := p[j:];
+ do {
+ (r, l) := matches(q, s[i:]);
+ if (r)
+ return (1, s[j - 1: i] :: l);
+ } while (++i < n);
+ return (0, nil);
+ } else if (i >= n || p[i] != s[i])
+ return (0, nil);
+ }
+ return (m == n, nil);
+}
+
+#
+# Rule match.
+#
+Rule.match(r: self ref Rule, a, n: int, t: list of string): int
+{
+ l := r.lhs;
+ if (l.count != n || (l.text[0] == '/') != a)
+ return 0;
+ for (e := l.elems; e != nil; e = tl e) {
+ if (!matchelem(hd e, hd t))
+ return 0;
+ t = tl t;
+ }
+ return 1;
+}
+
+#
+# Rule match with array of sub-matches.
+#
+Rule.matches(r: self ref Rule, t: list of string): array of string
+{
+ m: list of list of string;
+ c := 1;
+ for (e := r.lhs.elems; e != nil; e = tl e) {
+ (x, l) := matches(hd e, hd t);
+ if (!x)
+ return nil;
+ if (l != nil) {
+ c += len l;
+ m = revstrs(l) :: m;
+ }
+ t = tl t;
+ }
+ a := array[c] of string;
+ while (m != nil) {
+ for (l := hd m; l != nil; l = tl l)
+ a[--c] = hd l;
+ m = tl m;
+ }
+ return a;
+}
+
+#
+# Return list of rules that match a string.
+#
+rulematch(s: string): list of ref Rule
+{
+ m: list of ref Rule;
+ a := s[0] == '/';
+ (n, t) := sys->tokenize(s, "/");
+ for (l := rules; l != nil; l = tl l) {
+ r := hd l;
+ if (r.match(a, n, t))
+ m = r :: m;
+ }
+ return m;
+}
diff --git a/appl/cmd/mash/dump.b b/appl/cmd/mash/dump.b
new file mode 100644
index 00000000..ed4b6309
--- /dev/null
+++ b/appl/cmd/mash/dump.b
@@ -0,0 +1,199 @@
+#
+# Output routines.
+#
+
+#
+# Echo list of strings.
+#
+echo(e: ref Env, s: list of string)
+{
+ out := e.outfile();
+ if (out == nil)
+ return;
+ out.putc('+');
+ for (t := s; t != nil; t = tl t) {
+ out.putc(' ');
+ out.puts(hd t);
+ }
+ out.putc('\n');
+ out.close();
+}
+
+#
+# Return text representation of Word/Item/Cmd.
+#
+
+Word.word(w: self ref Word, d: string): string
+{
+ if (w == nil)
+ return nil;
+ if (d != nil)
+ return d + w.text;
+ if (w.flags & Wquoted)
+ return enquote(w.text);
+ return w.text;
+}
+
+Item.text(i: self ref Item): string
+{
+ if (i == nil)
+ return nil;
+ case i.op {
+ Icaret =>
+ return i.left.text() + " ^ " + i.right.text();
+ Iicaret =>
+ return i.left.text() + i.right.text();
+ Idollarq =>
+ return i.word.word("$\"");
+ Idollar or Imatch =>
+ return i.word.word("$");
+ Iword =>
+ return i.word.word(nil);
+ Iexpr =>
+ return "(" + i.cmd.text() + ")";
+ Ibackq =>
+ return "`" + group(i.cmd);
+ Iquote =>
+ return "\"" + group(i.cmd);
+ Iinpipe =>
+ return "<" + group(i.cmd);
+ Ioutpipe =>
+ return ">" + group(i.cmd);
+ * =>
+ return "?" + string i.op;
+ }
+}
+
+words(l: list of ref Item): string
+{
+ s: string;
+ while (l != nil) {
+ if (s == nil)
+ s = (hd l).text();
+ else
+ s = s + " " + (hd l).text();
+ l = tl l;
+ }
+ return s;
+}
+
+redir(s: string, c: ref Cmd): string
+{
+ if (c == nil)
+ return s;
+ for (l := c.redirs; l != nil; l = tl l) {
+ r := hd l;
+ s = s + " " + rdsymbs[r.op] + " " + r.word.text();
+ }
+ return s;
+}
+
+cmd2in(c: ref Cmd, s: string): string
+{
+ return c.left.text() + " " + s + " " + c.right.text();
+}
+
+group(c: ref Cmd): string
+{
+ if (c == nil)
+ return "{ }";
+ return redir("{ " + c.text() + " }", c);
+}
+
+sequence(c: ref Cmd): string
+{
+ s: string;
+ do {
+ r := c.right;
+ t := ";";
+ if (r.op == Casync) {
+ r = r.left;
+ t = "&";
+ }
+ if (s == nil)
+ s = r.text() + t;
+ else
+ s = r.text() + t + " " + s;
+ c = c.left;
+ } while (c != nil);
+ return s;
+}
+
+Cmd.text(c: self ref Cmd): string
+{
+ if (c == nil)
+ return nil;
+ case c.op {
+ Csimple =>
+ return redir(words(c.words), c);
+ Cseq =>
+ return sequence(c);
+ Cfor =>
+ return "for (" + c.item.text() + " in " + words(c.words) + ") " + c.left.text();
+ Cif =>
+ return "if (" + c.left.text() +") " + c.right.text();
+ Celse =>
+ return c.left.text() +" else " + c.right.text();
+ Cwhile =>
+ return "while (" + c.left.text() +") " + c.right.text();
+ Ccase =>
+ return redir("case " + c.left.text() + " { " + c.right.text() + "}", c);
+ Ccases =>
+ s := c.left.text();
+ if (s[len s - 1] != '&')
+ return s + "; " + c.right.text();
+ return s + " " + c.right.text();
+ Cmatched =>
+ return cmd2in(c, "=>");
+ Cdefeq =>
+ return c.item.text() + " := " + words(c.words);
+ Ceq =>
+ return c.item.text() + " = " + words(c.words);
+ Cfn =>
+ return "fn " + c.item.text() + " " + group(c.left);
+ Crescue =>
+ return "rescue " + c.item.text() + " " + group(c.left);
+ Casync =>
+ return c.left.text() + "&";
+ Cgroup =>
+ return group(c.left);
+ Clistgroup =>
+ return ":" + group(c.left);
+ Csubgroup =>
+ return "@" + group(c.left);
+ Cnop =>
+ return nil;
+ Cword =>
+ return c.item.text();
+ Ccaret =>
+ return cmd2in(c, "^");
+ Chd =>
+ return "hd " + c.left.text();
+ Clen =>
+ return "len " + c.left.text();
+ Cnot =>
+ return "!" + c.left.text();
+ Ctl =>
+ return "tl " + c.left.text();
+ Ccons =>
+ return cmd2in(c, "::");
+ Ceqeq =>
+ return cmd2in(c, "==");
+ Cnoteq =>
+ return cmd2in(c, "!=");
+ Cmatch =>
+ return cmd2in(c, "~");
+ Cpipe =>
+ return cmd2in(c, "|");
+ Cdepend =>
+ return words(c.words) + " : " + words(c.left.words) + " " + c.left.text();
+ Crule =>
+ return c.item.text() + " :~ " + c.left.item.text() + " " + c.left.text();
+ * =>
+ if (c.op >= Cprivate)
+ return "Priv+" + string (c.op - Cprivate);
+ else
+ return "?" + string c.op;
+ }
+ return nil;
+}
diff --git a/appl/cmd/mash/exec.b b/appl/cmd/mash/exec.b
new file mode 100644
index 00000000..faa003b3
--- /dev/null
+++ b/appl/cmd/mash/exec.b
@@ -0,0 +1,401 @@
+#
+# Manage the execution of a command.
+#
+
+srv: string; # srv file proto
+nsrv: int = 0; # srv file unique id
+
+#
+# Return error string.
+#
+errstr(): string
+{
+ return sys->sprint("%r");
+}
+
+#
+# Server thread for servefd.
+#
+server(c: ref Sys->FileIO, fd: ref Sys->FD, write: int)
+{
+ a: array of byte;
+ if (!write)
+ a = array[Sys->ATOMICIO] of byte;
+ for (;;) {
+ alt {
+ (nil, b, nil, wc) := <- c.write =>
+ if (wc == nil)
+ return;
+ if (!write) {
+ wc <- = (0, EPIPE);
+ return;
+ }
+ r := sys->write(fd, b, len b);
+ if (r < 0) {
+ wc <- = (0, errstr());
+ return;
+ }
+ wc <- = (r, nil);
+ (nil, n, nil, rc) := <- c.read =>
+ if (rc == nil)
+ return;
+ if (write) {
+ rc <- = (array[0] of byte, nil);
+ return;
+ }
+ if (n > Sys->ATOMICIO)
+ n = Sys->ATOMICIO;
+ r := sys->read(fd, a, n);
+ if (r < 0) {
+ rc <- = (nil, errstr());
+ return;
+ }
+ rc <- = (a[0:r], nil);
+ }
+ }
+}
+
+#
+# Serve FD as a #s file. Used to implement generators.
+#
+Env.servefd(e: self ref Env, fd: ref Sys->FD, write: int): string
+{
+ (s, c) := e.servefile(nil);
+ spawn server(c, fd, write);
+ return s;
+}
+
+#
+# Generate name and FileIO adt for a served filed.
+#
+Env.servefile(e: self ref Env, n: string): (string, ref Sys->FileIO)
+{
+ c: ref Sys->FileIO;
+ s: string;
+ if (srv == nil) {
+ (ok, d) := sys->stat(CHAN);
+ if (ok < 0)
+ e.couldnot("stat", CHAN);
+ if (d.dtype != 's') {
+ if (sys->bind("#s", CHAN, Sys->MBEFORE) < 0)
+ e.couldnot("bind", CHAN);
+ }
+ srv = "mash." + string sys->pctl(0, nil);
+ }
+ retry := 0;
+ for (;;) {
+ if (retry || n == nil)
+ s = srv + "." + string nsrv++;
+ else
+ s = n;
+ c = sys->file2chan(CHAN, s);
+ s = CHAN + "/" + s;
+ if (c == nil) {
+ if (retry || n == nil || errstr() != EEXISTS)
+ e.couldnot("file2chan", s);
+ retry = 1;
+ continue;
+ }
+ break;
+ }
+ if (n != nil)
+ n = CHAN + "/" + n;
+ else
+ n = s;
+ if (retry && sys->bind(s, n, Sys->MREPL) < 0)
+ e.couldnot("bind", n);
+ return (n, c);
+}
+
+#
+# Shorthand for string output.
+#
+Env.output(e: self ref Env, s: string)
+{
+ if (s == nil)
+ return;
+ out := e.outfile();
+ if (out == nil)
+ return;
+ out.puts(s);
+ out.close();
+}
+
+#
+# Return Iobuf for stdout.
+#
+Env.outfile(e: self ref Env): ref Bufio->Iobuf
+{
+ fd := e.out;
+ if (fd == nil)
+ fd = sys->fildes(1);
+ out := bufio->fopen(fd, Bufio->OWRITE);
+ if (out == nil)
+ e.report(sys->sprint("fopen failed: %r"));
+ return out;
+}
+
+#
+# Return FD for /dev/null.
+#
+Env.devnull(e: self ref Env): ref Sys->FD
+{
+ fd := sys->open(DEVNULL, Sys->OREAD);
+ if (fd == nil)
+ e.couldnot("open", DEVNULL);
+ return fd;
+}
+
+#
+# Make a pipe.
+#
+Env.pipe(e: self ref Env): array of ref Sys->FD
+{
+ fds := array[2] of ref Sys->FD;
+ if (sys->pipe(fds) < 0) {
+ e.report(sys->sprint("pipe failed: %r"));
+ return nil;
+ }
+ return fds;
+}
+
+#
+# Open wait file for an env.
+#
+waitfd(e: ref Env)
+{
+ w := "#p/" + string sys->pctl(0, nil) + "/wait";
+ fd := sys->open(w, sys->OREAD);
+ if (fd == nil)
+ e.couldnot("open", w);
+ e.wait = fd;
+}
+
+#
+# Wait for a thread. Perhaps propagate exception or exit.
+#
+waitfor(e: ref Env, pid: int, wc: chan of int, ec, xc: chan of string)
+{
+ if (ec != nil || xc != nil) {
+ spawn waiter(e, pid, wc);
+ if (ec == nil)
+ ec = chan of string;
+ if (xc == nil)
+ xc = chan of string;
+ alt {
+ <-wc =>
+ return;
+ x := <-ec =>
+ <-wc;
+ exitmash();
+ x := <-xc =>
+ <-wc;
+ s := x;
+ if (len s < FAILLEN || s[0:FAILLEN] != FAIL)
+ s = FAIL + s;
+ raise s;
+ }
+ } else
+ waiter(e, pid, nil);
+}
+
+#
+# Wait for a specific pid.
+#
+waiter(e: ref Env, pid: int, wc: chan of int)
+{
+ buf := array[sys->WAITLEN] of byte;
+ for(;;) {
+ n := sys->read(e.wait, buf, len buf);
+ if (n < 0) {
+ e.report(sys->sprint("read wait: %r\n"));
+ break;
+ }
+ status := string buf[0:n];
+ if (status[len status - 1] != ':')
+ sys->fprint(e.stderr, "%s\n", status);
+ who := int status;
+ if (who != 0 && who == pid)
+ break;
+ }
+ if (wc != nil)
+ wc <-= 0;
+}
+
+#
+# Preparse IO for a new thread.
+# Make a new FD group and redirect stdin/stdout.
+#
+prepareio(in, out: ref sys->FD): (int, ref Sys->FD)
+{
+ fds := list of { 0, 1, 2};
+ if (in != nil)
+ fds = in.fd :: fds;
+ if (out != nil)
+ fds = out.fd :: fds;
+ pid := sys->pctl(sys->NEWFD, fds);
+ console := sys->fildes(2);
+ if (in != nil) {
+ sys->dup(in.fd, 0);
+ in = nil;
+ }
+ if (out != nil) {
+ sys->dup(out.fd, 1);
+ out = nil;
+ }
+ return (pid, console);
+}
+
+#
+# Add ".dis" to a command if missing.
+#
+dis(s: string): string
+{
+ if (len s < 4 || s[len s - 4:] != ".dis")
+ return s + ".dis";
+ return s;
+}
+
+#
+# Load a builtin.
+#
+Env.doload(e: self ref Env, s: string)
+{
+ file := dis(s);
+ l := load Mashbuiltin file;
+ if (l == nil) {
+ err := errstr();
+ if (nonexistent(err) && file[0] != '/' && file[0:2] != "./") {
+ l = load Mashbuiltin LIB + file;
+ if (l == nil)
+ err = errstr();
+ }
+ if (l == nil) {
+ e.report(s + ": " + err);
+ return;
+ }
+ }
+ l->mashinit("load" :: s :: nil, lib, l, e);
+}
+
+#
+# Execute a spawned thread (dis module or builtin).
+#
+mkprog(args: list of string, e: ref Env, in, out: ref Sys->FD, wc: chan of int, ec, xc: chan of string)
+{
+ (pid, console) := prepareio(in, out);
+ wc <-= pid;
+ if (pid < 0)
+ return;
+ cmd := hd args;
+ {
+ b := e.builtin(cmd);
+ if (b != nil) {
+ e = e.copy();
+ e.in = in;
+ e.out = out;
+ e.stderr = console;
+ e.wait = nil;
+ b->mashcmd(e, args);
+ } else {
+ file := dis(cmd);
+ c := load Command file;
+ if (c == nil) {
+ err := errstr();
+ if (nonexistent(err) && file[0] != '/' && file[0:2] != "./") {
+ c = load Command "/dis/" + file;
+ if (c == nil)
+ err = errstr();
+ }
+ if (c == nil) {
+ sys->fprint(console, "%s: %s\n", file, err);
+ return;
+ }
+ }
+ c->init(gctxt, args);
+ }
+ }exception x{
+ FAILPAT =>
+ if (xc != nil)
+ xc <-= x;
+ # the command failure should be propagated silently to
+ # a higher level, where $status can be set.. - wrtp.
+ #else
+ # sys->fprint(console, "%s: %s\n", cmd, x.name);
+ exit;
+ EPIPE =>
+ if (xc != nil)
+ xc <-= x;
+ #else
+ # sys->fprint(console, "%s: %s\n", cmd, x.name);
+ exit;
+ EXIT =>
+ if (ec != nil)
+ ec <-= x;
+ exit;
+ }
+}
+
+#
+# Open/create files for redirection.
+#
+redirect(e: ref Env, f: array of string, in, out: ref Sys->FD): (int, ref Sys->FD, ref Sys->FD)
+{
+ s: string;
+ err := 0;
+ if (f[Rinout] != nil) {
+ s = f[Rinout];
+ in = sys->open(s, Sys->ORDWR);
+ if (in == nil) {
+ sys->fprint(e.stderr, "%s: %r\n", s);
+ err = 1;
+ }
+ out = in;
+ } else if (f[Rin] != nil) {
+ s = f[Rin];
+ in = sys->open(s, Sys->OREAD);
+ if (in == nil) {
+ sys->fprint(e.stderr, "%s: %r\n", s);
+ err = 1;
+ }
+ }
+ if (f[Rout] != nil || f[Rappend] != nil) {
+ if (f[Rappend] != nil) {
+ s = f[Rappend];
+ out = sys->open(s, Sys->OWRITE);
+ if (out != nil)
+ sys->seek(out, big 0, Sys->SEEKEND);
+ } else {
+ s = f[Rout];
+ out = nil;
+ }
+ if (out == nil) {
+ out = sys->create(s, Sys->OWRITE, 8r666);
+ if (out == nil) {
+ sys->fprint(e.stderr, "%s: %r\n", s);
+ err = 1;
+ }
+ }
+ }
+ if (err)
+ return (0, nil, nil);
+ return (1, in, out);
+}
+
+#
+# Spawn a command and maybe wait for it.
+#
+exec(a: list of string, e: ref Env, infd, outfd: ref Sys->FD, wait: int)
+{
+ if (wait && e.wait == nil)
+ waitfd(e);
+ wc := chan of int;
+ if (wait && (e.flags & ERaise))
+ xc := chan of string;
+ if (wait && (e.flags & ETop))
+ ec := chan of string;
+ spawn mkprog(a, e, infd, outfd, wc, ec, xc);
+ pid := <-wc;
+ if (wait)
+ waitfor(e, pid, wc, ec, xc);
+}
diff --git a/appl/cmd/mash/expr.b b/appl/cmd/mash/expr.b
new file mode 100644
index 00000000..00e45069
--- /dev/null
+++ b/appl/cmd/mash/expr.b
@@ -0,0 +1,158 @@
+#
+# Expression evaluation.
+#
+
+#
+# Filename pattern matching.
+#
+glob(e: ref Env, s: string): (string, list of string)
+{
+ if (filepat == nil) {
+ filepat = load Filepat Filepat->PATH;
+ if (filepat == nil)
+ e.couldnot("load", Filepat->PATH);
+ }
+ l := filepat->expand(s);
+ if (l != nil)
+ return (nil, l);
+ return (s, nil);
+}
+
+#
+# RE pattern matching.
+#
+match(s1, s2: string): int
+{
+ (re, nil) := regex->compile(s2, 0);
+ return regex->execute(re, s1) != nil;
+}
+
+#
+# RE match of two lists. Two non-singleton lists never match.
+#
+match2(e: ref Env, s1: string, l1: list of string, s2: string, l2: list of string): int
+{
+ if (regex == nil) {
+ regex = load Regex Regex->PATH;
+ if (regex == nil)
+ e.couldnot("load", Regex->PATH);
+ }
+ if (s1 != nil) {
+ if (s2 != nil)
+ return match(s1, s2);
+ while (l2 != nil) {
+ if (match(s1, hd l2))
+ return 1;
+ l2 = tl l2;
+ }
+ } else if (l1 != nil) {
+ if (s2 == nil)
+ return 0;
+ while (l1 != nil) {
+ if (match(hd l1, s2))
+ return 1;
+ l1 = tl l1;
+ }
+ } else if (s2 != nil)
+ return match(nil, s2);
+ else if (l2 != nil) {
+ while (l2 != nil) {
+ if (match(nil, hd l2))
+ return 1;
+ l2 = tl l2;
+ }
+ } else
+ return 1;
+ return 0;
+}
+
+#
+# Test list equality. Same length and identical members.
+#
+eqlist(l1, l2: list of string): int
+{
+ while (l1 != nil && l2 != nil) {
+ if (hd l1 != hd l2)
+ return 0;
+ l1 = tl l1;
+ l2 = tl l2;
+ }
+ return l1 == nil && l2 == nil;
+}
+
+#
+# Equality operator.
+#
+Cmd.evaleq(c: self ref Cmd, e: ref Env): int
+{
+ (s1, l1, nil) := c.left.eeval2(e);
+ (s2, l2, nil) := c.right.eeval2(e);
+ if (s1 != nil)
+ return s1 == s2;
+ if (l1 != nil)
+ return eqlist(l1, l2);
+ return s2 == nil && l2 == nil;
+}
+
+#
+# Match operator.
+#
+Cmd.evalmatch(c: self ref Cmd, e: ref Env): int
+{
+ (s1, l1, nil) := c.left.eeval2(e);
+ (s2, l2, nil) := c.right.eeval2(e);
+ return match2(e, s1, l1, s2, l2);
+}
+
+#
+# Catenation operator.
+#
+Item.caret(i: self ref Item, e: ref Env): (string, list of string, int)
+{
+ (s1, l1, x1) := i.left.ieval2(e);
+ (s2, l2, x2) := i.right.ieval2(e);
+ return caret(s1, l1, x1, s2, l2, x2);
+}
+
+#
+# Caret of lists. A singleton distributes. Otherwise pairwise, padded with nils.
+#
+caret(s1: string, l1: list of string, x1: int, s2: string, l2: list of string, x2: int): (string, list of string, int)
+{
+ l: list of string;
+ if (s1 != nil) {
+ if (s2 != nil)
+ return (s1 + s2, nil, x1 | x2);
+ if (l2 == nil)
+ return (s1, nil, x1);
+ while (l2 != nil) {
+ l = (s1 + hd l2) :: l;
+ l2 = tl l2;
+ }
+ } else if (s2 != nil) {
+ if (l1 == nil)
+ return (s2, nil, x2);
+ while (l1 != nil) {
+ l = (hd l1 + s2) :: l;
+ l1 = tl l1;
+ }
+ } else if (l1 != nil) {
+ if (l2 == nil)
+ return (nil, l1, 0);
+ while (l1 != nil || l2 != nil) {
+ if (l1 != nil) {
+ s1 = hd l1;
+ l1 = tl l1;
+ } else
+ s1 = nil;
+ if (l2 != nil) {
+ s2 = hd l2;
+ l2 = tl l2;
+ } else
+ s2 = nil;
+ l = (s1 + s2) :: l;
+ }
+ } else if (l2 != nil)
+ return (nil, l2, 0);
+ return (nil, revstrs(l), 0);
+}
diff --git a/appl/cmd/mash/eyacc.b b/appl/cmd/mash/eyacc.b
new file mode 100644
index 00000000..96b6e412
--- /dev/null
+++ b/appl/cmd/mash/eyacc.b
@@ -0,0 +1,2785 @@
+implement Yacc;
+
+include "sys.m";
+ sys: Sys;
+ print, fprint, sprint: import sys;
+ UTFmax: import Sys;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "draw.m";
+
+Yacc: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Arg: adt
+{
+ argv: list of string;
+ c: int;
+ opts: string;
+
+ init: fn(argv: list of string): ref Arg;
+ opt: fn(arg: self ref Arg): int;
+ arg: fn(arg: self ref Arg): string;
+};
+
+PARSER: con "./eyaccpar";
+OFILE: con "tab.b";
+FILEU: con "output";
+FILED: con "tab.m";
+FILEDEBUG: con "debug";
+
+# the following are adjustable
+# according to memory size
+ACTSIZE: con 30000;
+NSTATES: con 2000;
+TEMPSIZE: con 2000;
+
+SYMINC: con 50; # increase for non-term or term
+RULEINC: con 50; # increase for max rule length prodptr[i]
+PRODINC: con 100; # increase for productions prodptr
+WSETINC: con 50; # increase for working sets wsets
+STATEINC: con 200; # increase for states statemem
+
+NAMESIZE: con 50;
+NTYPES: con 63;
+ISIZE: con 400;
+
+PRIVATE: con 16rE000; # unicode private use
+
+# relationships which must hold:
+# TEMPSIZE >= NTERMS + NNONTERM + 1
+# TEMPSIZE >= NSTATES
+#
+
+NTBASE: con 8r10000;
+ERRCODE: con 8190;
+ACCEPTCODE: con 8191;
+YYLEXUNK: con 3;
+TOKSTART: con 4; #index of first defined token
+
+# no, left, right, binary assoc.
+NOASC, LASC, RASC, BASC: con iota;
+
+# flags for state generation
+DONE, MUSTDO, MUSTLOOKAHEAD: con iota;
+
+# flags for a rule having an action, and being reduced
+ACTFLAG: con 16r4;
+REDFLAG: con 16r8;
+
+# output parser flags
+YYFLAG1: con -1000;
+
+# parse tokens
+IDENTIFIER, MARK, TERM, LEFT, RIGHT, BINARY, PREC, LCURLY, IDENTCOLON, NUMBER, START, TYPEDEF, TYPENAME, MODULE: con PRIVATE+iota;
+
+ENDFILE: con 0;
+
+EMPTY: con 1;
+WHOKNOWS: con 0;
+OK: con 1;
+NOMORE: con -1000;
+
+# macros for getting associativity and precedence levels
+ASSOC(i: int): int
+{
+ return i & 3;
+}
+
+PLEVEL(i: int): int
+{
+ return (i >> 4) & 16r3f;
+}
+
+TYPE(i: int): int
+{
+ return (i >> 10) & 16r3f;
+}
+
+# macros for setting associativity and precedence levels
+SETASC(i, j: int): int
+{
+ return i | j;
+}
+
+SETPLEV(i, j: int): int
+{
+ return i | (j << 4);
+}
+
+SETTYPE(i, j: int): int
+{
+ return i | (j << 10);
+}
+
+# I/O descriptors
+stderr: ref Sys->FD;
+fdefine: ref Iobuf; # file for module definition
+fdebug: ref Iobuf; # y.debug for strings for debugging
+ftable: ref Iobuf; # y.tab.c file
+finput: ref Iobuf; # input file
+foutput: ref Iobuf; # y.output file
+
+CodeData, CodeMod, CodeAct: con iota;
+NCode: con 8192;
+
+Code: adt
+{
+ kind: int;
+ data: array of byte;
+ ndata: int;
+ next: cyclic ref Code;
+};
+
+codehead: ref Code;
+codetail: ref Code;
+
+modname: string; # name of module
+
+# communication variables between various I/O routines
+infile: string; # input file name
+numbval: int; # value of an input number
+tokname: string; # input token name, slop for runes and 0
+
+# structure declarations
+Lkset: type array of int;
+
+Pitem: adt
+{
+ prod: array of int;
+ off: int; # offset within the production
+ first: int; # first term or non-term in item
+ prodno: int; # production number for sorting
+};
+
+Item: adt
+{
+ pitem: Pitem;
+ look: Lkset;
+};
+
+Symb: adt
+{
+ name: string;
+ value: int;
+};
+
+Wset: adt
+{
+ pitem: Pitem;
+ flag: int;
+ ws: Lkset;
+};
+
+ # storage of names
+
+parser := PARSER;
+yydebug: string;
+
+ # storage of types
+ntypes: int; # number of types defined
+typeset := array[NTYPES] of string; # pointers to type tags
+
+ # token information
+
+ntokens := 0; # number of tokens
+tokset: array of Symb;
+toklev: array of int; # vector with the precedence of the terminals
+
+ # nonterminal information
+
+nnonter := -1; # the number of nonterminals
+nontrst: array of Symb;
+start: int; # start symbol
+
+ # state information
+
+nstate := 0; # number of states
+pstate := array[NSTATES+2] of int; # index into statemem to the descriptions of the states
+statemem : array of Item;
+tystate := array[NSTATES] of int; # contains type information about the states
+tstates : array of int; # states generated by terminal gotos
+ntstates : array of int; # states generated by nonterminal gotos
+mstates := array[NSTATES] of {* => 0}; # chain of overflows of term/nonterm generation lists
+lastred: int; # number of last reduction of a state
+defact := array[NSTATES] of int; # default actions of states
+
+ # lookahead set information
+
+lkst: array of Lkset;
+nolook := 0; # flag to turn off lookahead computations
+tbitset := 0; # size of lookahead sets
+clset: Lkset; # temporary storage for lookahead computations
+
+ # working set information
+
+wsets: array of Wset;
+cwp: int;
+
+ # storage for action table
+
+amem: array of int; # action table storage
+memp: int; # next free action table position
+indgo := array[NSTATES] of int; # index to the stored goto table
+
+ # temporary vector, indexable by states, terms, or ntokens
+
+temp1 := array[TEMPSIZE] of int; # temporary storage, indexed by terms + ntokens or states
+lineno := 1; # current input line number
+fatfl := 1; # if on, error is fatal
+nerrors := 0; # number of errors
+
+ # assigned token type values
+extval := 0;
+
+ytabc := OFILE; # name of y.tab.c
+
+ # grammar rule information
+
+nprod := 1; # number of productions
+prdptr: array of array of int; # pointers to descriptions of productions
+levprd: array of int; # precedence levels for the productions
+rlines: array of int; # line number for this rule
+
+
+ # statistics collection variables
+
+zzgoent := 0;
+zzgobest := 0;
+zzacent := 0;
+zzexcp := 0;
+zzclose := 0;
+zzrrconf := 0;
+zzsrconf := 0;
+zzstate := 0;
+
+ # optimizer arrays
+yypgo: array of array of int;
+optst: array of array of int;
+ggreed: array of int;
+pgo: array of int;
+
+maxspr: int; # maximum spread of any entry
+maxoff: int; # maximum offset into a array
+maxa: int;
+
+ # storage for information about the nonterminals
+
+pres: array of array of array of int; # vector of pointers to productions yielding each nonterminal
+pfirst: array of Lkset;
+pempty: array of int; # vector of nonterminals nontrivially deriving e
+ # random stuff picked out from between functions
+
+indebug := 0; # debugging flag for cpfir
+pidebug := 0; # debugging flag for putitem
+gsdebug := 0; # debugging flag for stagen
+cldebug := 0; # debugging flag for closure
+pkdebug := 0; # debugging flag for apack
+g2debug := 0; # debugging for go2gen
+adb := 0; # debugging for callopt
+
+Resrv : adt
+{
+ name: string;
+ value: int;
+};
+
+resrv := array[] of {
+ Resrv("binary", BINARY),
+ Resrv("module", MODULE),
+ Resrv("left", LEFT),
+ Resrv("nonassoc", BINARY),
+ Resrv("prec", PREC),
+ Resrv("right", RIGHT),
+ Resrv("start", START),
+ Resrv("term", TERM),
+ Resrv("token", TERM),
+ Resrv("type", TYPEDEF),};
+
+zznewstate := 0;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ stderr = sys->fildes(2);
+
+ setup(argv); # initialize and read productions
+
+ tbitset = (ntokens+32)/32;
+ cpres(); # make table of which productions yield a given nonterminal
+ cempty(); # make a table of which nonterminals can match the empty string
+ cpfir(); # make a table of firsts of nonterminals
+
+ stagen(); # generate the states
+
+ yypgo = array[nnonter+1] of array of int;
+ optst = array[nstate] of array of int;
+ output(); # write the states and the tables
+ go2out();
+
+ hideprod();
+ summary();
+
+ callopt();
+
+ others();
+
+ bufio->flush();
+}
+
+setup(argv: list of string)
+{
+ j, ty: int;
+
+ ytab := 0;
+ vflag := 0;
+ dflag := 0;
+ stem := 0;
+ stemc := "y";
+ foutput = nil;
+ fdefine = nil;
+ fdebug = nil;
+ arg := Arg.init(argv);
+ while(c := arg.opt()){
+ case c{
+ 'v' or 'V' =>
+ vflag++;
+ 'D' =>
+ yydebug = arg.arg();
+ 'd' =>
+ dflag++;
+ 'o' =>
+ ytab++;
+ ytabc = arg.arg();
+ 's' =>
+ stem++;
+ stemc = arg.arg();
+ * =>
+ usage();
+ }
+ }
+ argv = arg.argv;
+ if(len argv != 1)
+ usage();
+ infile = hd argv;
+ finput = bufio->open(infile, Bufio->OREAD);
+ if(finput == nil)
+ error("cannot open '"+infile+"'");
+
+ openup(stemc, dflag, vflag, ytab, ytabc);
+
+ defin(0, "$end");
+ extval = PRIVATE; # tokens start in unicode 'private use'
+ defin(0, "error");
+ defin(1, "$accept");
+ defin(0, "$unk");
+ i := 0;
+
+ for(t := gettok(); t != MARK && t != ENDFILE; )
+ case t {
+ ';' =>
+ t = gettok();
+
+ START =>
+ if(gettok() != IDENTIFIER)
+ error("bad %%start construction");
+ start = chfind(1, tokname);
+ t = gettok();
+
+ TYPEDEF =>
+ if(gettok() != TYPENAME)
+ error("bad syntax in %%type");
+ ty = numbval;
+ for(;;) {
+ t = gettok();
+ case t {
+ IDENTIFIER =>
+ if((t=chfind(1, tokname)) < NTBASE) {
+ j = TYPE(toklev[t]);
+ if(j != 0 && j != ty)
+ error("type redeclaration of token "+
+ tokset[t].name);
+ else
+ toklev[t] = SETTYPE(toklev[t], ty);
+ } else {
+ j = nontrst[t-NTBASE].value;
+ if(j != 0 && j != ty)
+ error("type redeclaration of nonterminal "+
+ nontrst[t-NTBASE].name);
+ else
+ nontrst[t-NTBASE].value = ty;
+ }
+ continue;
+ ',' =>
+ continue;
+ ';' =>
+ t = gettok();
+ }
+ break;
+ }
+
+ MODULE =>
+ cpymodule();
+ t = gettok();
+
+ LEFT or BINARY or RIGHT or TERM =>
+ # nonzero means new prec. and assoc.
+ lev := t-TERM;
+ if(lev)
+ i++;
+ ty = 0;
+
+ # get identifiers so defined
+ t = gettok();
+
+ # there is a type defined
+ if(t == TYPENAME) {
+ ty = numbval;
+ t = gettok();
+ }
+ for(;;) {
+ case t {
+ ',' =>
+ t = gettok();
+ continue;
+
+ ';' =>
+ break;
+
+ IDENTIFIER =>
+ j = chfind(0, tokname);
+ if(j >= NTBASE)
+ error(tokname+" defined earlier as nonterminal");
+ if(lev) {
+ if(ASSOC(toklev[j]))
+ error("redeclaration of precedence of "+tokname);
+ toklev[j] = SETASC(toklev[j], lev);
+ toklev[j] = SETPLEV(toklev[j], i);
+ }
+ if(ty) {
+ if(TYPE(toklev[j]))
+ error("redeclaration of type of "+tokname);
+ toklev[j] = SETTYPE(toklev[j],ty);
+ }
+ t = gettok();
+ if(t == NUMBER) {
+ tokset[j].value = numbval;
+ t = gettok();
+ }
+ continue;
+ }
+ break;
+ }
+
+ LCURLY =>
+ cpycode();
+ t = gettok();
+
+ * =>
+ error("syntax error");
+ }
+ if(t == ENDFILE)
+ error("unexpected EOF before %%");
+ if(modname == nil)
+ error("missing %module specification");
+
+ moreprod();
+ prdptr[0] = array[4] of {
+ NTBASE, # added production
+ start, # if start is 0, we will overwrite with the lhs of the first rule
+ 1,
+ 0
+ };
+ nprod = 1;
+ curprod := array[RULEINC] of int;
+ t = gettok();
+ if(t != IDENTCOLON)
+ error("bad syntax on first rule");
+
+ if(!start)
+ prdptr[0][1] = chfind(1, tokname);
+
+ # read rules
+ # put into prdptr array in the format
+ # target
+ # followed by id's of terminals and non-terminals
+ # followd by -nprod
+ while(t != MARK && t != ENDFILE) {
+ mem := 0;
+ # process a rule
+ rlines[nprod] = lineno;
+ if(t == '|')
+ curprod[mem++] = prdptr[nprod-1][0];
+ else if(t == IDENTCOLON) {
+ curprod[mem] = chfind(1, tokname);
+ if(curprod[mem] < NTBASE)
+ error("token illegal on LHS of grammar rule");
+ mem++;
+ } else
+ error("illegal rule: missing semicolon or | ?");
+
+ # read rule body
+ t = gettok();
+
+ for(;;){
+ while(t == IDENTIFIER) {
+ curprod[mem] = chfind(1, tokname);
+ if(curprod[mem] < NTBASE)
+ levprd[nprod] = toklev[curprod[mem]];
+ mem++;
+ if(mem >= len curprod){
+ ncurprod := array[mem+RULEINC] of int;
+ ncurprod[0:] = curprod;
+ curprod = ncurprod;
+ }
+ t = gettok();
+ }
+ if(t == PREC) {
+ if(gettok() != IDENTIFIER)
+ error("illegal %%prec syntax");
+ j = chfind(2, tokname);
+ if(j >= NTBASE)
+ error("nonterminal "+nontrst[j-NTBASE].name+" illegal after %%prec");
+ levprd[nprod] = toklev[j];
+ t = gettok();
+ }
+ if(t != '=')
+ break;
+ levprd[nprod] |= ACTFLAG;
+ addcode(CodeAct, "\n"+string nprod+"=>");
+ cpyact(curprod, mem);
+
+ # action within rule...
+ if((t=gettok()) == IDENTIFIER) {
+ # make it a nonterminal
+ j = chfind(1, "$$"+string nprod);
+
+ #
+ # the current rule will become rule number nprod+1
+ # enter null production for action
+ #
+ prdptr[nprod] = array[2] of {j, -nprod};
+
+ # update the production information
+ nprod++;
+ moreprod();
+ levprd[nprod] = levprd[nprod-1] & ~ACTFLAG;
+ levprd[nprod-1] = ACTFLAG;
+ rlines[nprod] = lineno;
+
+ # make the action appear in the original rule
+ curprod[mem++] = j;
+ if(mem >= len curprod){
+ ncurprod := array[mem+RULEINC] of int;
+ ncurprod[0:] = curprod;
+ curprod = ncurprod;
+ }
+ }
+ }
+
+ while(t == ';')
+ t = gettok();
+ curprod[mem++] = -nprod;
+
+ # check that default action is reasonable
+ if(ntypes && !(levprd[nprod]&ACTFLAG) && nontrst[curprod[0]-NTBASE].value) {
+ # no explicit action, LHS has value
+
+ tempty := curprod[1];
+ if(tempty < 0)
+ error("must return a value, since LHS has a type");
+ else
+ if(tempty >= NTBASE)
+ tempty = nontrst[tempty-NTBASE].value;
+ else
+ tempty = TYPE(toklev[tempty]);
+ if(tempty != nontrst[curprod[0]-NTBASE].value)
+ error("default action causes potential type clash");
+ else{
+ addcodec(CodeAct, '\n');
+ addcode(CodeAct, string nprod);
+ addcode(CodeAct, "=>\ne.yyval.");
+ addcode(CodeAct, typeset[tempty]);
+ addcode(CodeAct, " = yys[yyp+1].yyv.");
+ addcode(CodeAct, typeset[tempty]);
+ addcodec(CodeAct, ';');
+ }
+ }
+ moreprod();
+ prdptr[nprod] = array[mem] of int;
+ prdptr[nprod][0:] = curprod[:mem];
+ nprod++;
+ moreprod();
+ levprd[nprod] = 0;
+ }
+
+ #
+ # end of all rules
+ # dump out the prefix code
+ #
+ ftable.puts("implement ");
+ ftable.puts(modname);
+ ftable.puts(";\n");
+
+ dumpcode(CodeMod);
+ dumpmod();
+ dumpcode(CodeAct);
+
+ ftable.puts("YYEOFCODE: con 1;\n");
+ ftable.puts("YYERRCODE: con 2;\n");
+ ftable.puts("YYMAXDEPTH: con 200;\n"); # was 150
+# ftable.puts("yyval: YYSTYPE;\n");
+
+ #
+ # copy any postfix code
+ #
+ if(t == MARK) {
+ ftable.puts("\n#line\t");
+ ftable.puts(string lineno);
+ ftable.puts("\t\"");
+ ftable.puts(infile);
+ ftable.puts("\"\n");
+ while((c=finput.getc()) != Bufio->EOF)
+ ftable.putc(c);
+ }
+ finput.close();
+}
+
+#
+# allocate enough room to hold another production
+#
+moreprod()
+{
+ n := len prdptr;
+ if(nprod < n)
+ return;
+ n += PRODINC;
+ aprod := array[n] of array of int;
+ aprod[0:] = prdptr;
+ prdptr = aprod;
+
+ alevprd := array[n] of int;
+ alevprd[0:] = levprd;
+ levprd = alevprd;
+
+ arlines := array[n] of int;
+ arlines[0:] = rlines;
+ rlines = arlines;
+}
+
+#
+# define s to be a terminal if t=0
+# or a nonterminal if t=1
+#
+defin(nt: int, s: string): int
+{
+ val := 0;
+ if(nt) {
+ nnonter++;
+ if(nnonter >= len nontrst){
+ anontrst := array[nnonter + SYMINC] of Symb;
+ anontrst[0:] = nontrst;
+ nontrst = anontrst;
+ }
+ nontrst[nnonter] = Symb(s, 0);
+ return NTBASE + nnonter;
+ }
+
+ # must be a token
+ ntokens++;
+ if(ntokens >= len tokset){
+ atokset := array[ntokens + SYMINC] of Symb;
+ atokset[0:] = tokset;
+ tokset = atokset;
+
+ atoklev := array[ntokens + SYMINC] of int;
+ atoklev[0:] = toklev;
+ toklev = atoklev;
+ }
+ tokset[ntokens].name = s;
+ toklev[ntokens] = 0;
+
+ # establish value for token
+ # single character literal
+ if(s[0] == ' ' && len s == 1+1){
+ val = s[1];
+ }else if(s[0] == ' ' && s[1] == '\\') { # escape sequence
+ if(len s == 2+1) {
+ # single character escape sequence
+ case s[2] {
+ '\'' => val = '\'';
+ '"' => val = '"';
+ '\\' => val = '\\';
+ 'a' => val = '\a';
+ 'b' => val = '\b';
+ 'n' => val = '\n';
+ 'r' => val = '\r';
+ 't' => val = '\t';
+ 'v' => val = '\v';
+ * =>
+ error("invalid escape "+s[1:3]);
+ }
+ }else if(s[2] == 'u' && len s == 2+1+4) { # \unnnn sequence
+ val = 0;
+ s = s[3:];
+ while(s != ""){
+ c := s[0];
+ if(c >= '0' && c <= '9')
+ c -= '0';
+ else if(c >= 'a' && c <= 'f')
+ c -= 'a' - 10;
+ else if(c >= 'A' && c <= 'F')
+ c -= 'A' - 10;
+ else
+ error("illegal \\unnnn construction");
+ val = val * 16 + c;
+ s = s[1:];
+ }
+ if(val == 0)
+ error("'\\u0000' is illegal");
+ }else
+ error("unknown escape");
+ }else
+ val = extval++;
+
+ tokset[ntokens].value = val;
+ return ntokens;
+}
+
+peekline := 0;
+gettok(): int
+{
+ i, match, c: int;
+
+ tokname = "";
+ for(;;){
+ reserve := 0;
+ lineno += peekline;
+ peekline = 0;
+ c = finput.getc();
+ while(c == ' ' || c == '\n' || c == '\t' || c == '\v' || c == '\r') {
+ if(c == '\n')
+ lineno++;
+ c = finput.getc();
+ }
+
+ # skip comment
+ if(c != '#')
+ break;
+ lineno += skipcom();
+ }
+ case c {
+ Bufio->EOF =>
+ return ENDFILE;
+
+ '{' =>
+ finput.ungetc();
+ return '=';
+
+ '<' =>
+ # get, and look up, a type name (union member name)
+ i = 0;
+ while((c=finput.getc()) != '>' && c != Bufio->EOF && c != '\n')
+ tokname[i++] = c;
+ if(c != '>')
+ error("unterminated < ... > clause");
+ for(i=1; i<=ntypes; i++)
+ if(typeset[i] == tokname) {
+ numbval = i;
+ return TYPENAME;
+ }
+ ntypes++;
+ numbval = ntypes;
+ typeset[numbval] = tokname;
+ return TYPENAME;
+
+ '"' or '\'' =>
+ match = c;
+ tokname[0] = ' ';
+ i = 1;
+ for(;;) {
+ c = finput.getc();
+ if(c == '\n' || c == Bufio->EOF)
+ error("illegal or missing ' or \"" );
+ if(c == '\\') {
+ tokname[i++] = '\\';
+ c = finput.getc();
+ } else if(c == match)
+ return IDENTIFIER;
+ tokname[i++] = c;
+ }
+
+ '%' =>
+ case c = finput.getc(){
+ '%' => return MARK;
+ '=' => return PREC;
+ '{' => return LCURLY;
+ }
+
+ getword(c);
+ # find a reserved word
+ for(c=0; c < len resrv; c++)
+ if(tokname == resrv[c].name)
+ return resrv[c].value;
+ error("invalid escape, or illegal reserved word: "+tokname);
+
+ '0' to '9' =>
+ numbval = c - '0';
+ while(isdigit(c = finput.getc()))
+ numbval = numbval*10 + c-'0';
+ finput.ungetc();
+ return NUMBER;
+
+ * =>
+ if(isword(c) || c=='.' || c=='$')
+ getword(c);
+ else
+ return c;
+ }
+
+ # look ahead to distinguish IDENTIFIER from IDENTCOLON
+ c = finput.getc();
+ while(c == ' ' || c == '\t'|| c == '\n' || c == '\v' || c == '\r' || c == '#') {
+ if(c == '\n')
+ peekline++;
+ # look for comments
+ if(c == '#')
+ peekline += skipcom();
+ c = finput.getc();
+ }
+ if(c == ':')
+ return IDENTCOLON;
+ finput.ungetc();
+ return IDENTIFIER;
+}
+
+getword(c: int)
+{
+ i := 0;
+ while(isword(c) || isdigit(c) || c == '_' || c=='.' || c=='$') {
+ tokname[i++] = c;
+ c = finput.getc();
+ }
+ finput.ungetc();
+}
+
+#
+# determine the type of a symbol
+#
+fdtype(t: int): int
+{
+ v : int;
+ s: string;
+
+ if(t >= NTBASE) {
+ v = nontrst[t-NTBASE].value;
+ s = nontrst[t-NTBASE].name;
+ } else {
+ v = TYPE(toklev[t]);
+ s = tokset[t].name;
+ }
+ if(v <= 0)
+ error("must specify type for "+s);
+ return v;
+}
+
+chfind(t: int, s: string): int
+{
+ if(s[0] == ' ')
+ t = 0;
+ for(i:=0; i<=ntokens; i++)
+ if(s == tokset[i].name)
+ return i;
+ for(i=0; i<=nnonter; i++)
+ if(s == nontrst[i].name)
+ return NTBASE+i;
+
+ # cannot find name
+ if(t > 1)
+ error(s+" should have been defined earlier");
+ return defin(t, s);
+}
+
+#
+# saves module definition in Code
+#
+cpymodule()
+{
+ if(gettok() != IDENTIFIER)
+ error("bad %%module construction");
+ if(modname != nil)
+ error("duplicate %%module construction");
+ modname = tokname;
+
+ level := 0;
+ for(;;) {
+ if((c:=finput.getc()) == Bufio->EOF)
+ error("EOF encountered while processing %%module");
+ case c {
+ '\n' =>
+ lineno++;
+ '{' =>
+ level++;
+ if(level == 1)
+ continue;
+ '}' =>
+ level--;
+
+ # we are finished copying
+ if(level == 0)
+ return;
+ }
+ addcodec(CodeMod, c);
+ }
+}
+
+#
+# saves code between %{ and %}
+#
+cpycode()
+{
+ c := finput.getc();
+ if(c == '\n') {
+ c = finput.getc();
+ lineno++;
+ }
+ addcode(CodeData, "\n#line\t" + string lineno + "\t\"" + infile + "\"\n");
+ while(c != Bufio->EOF) {
+ if(c == '%') {
+ if((c=finput.getc()) == '}')
+ return;
+ addcodec(CodeData, '%');
+ }
+ addcodec(CodeData, c);
+ if(c == '\n')
+ lineno++;
+ c = finput.getc();
+ }
+ error("eof before %%}");
+}
+
+addcode(k: int, s: string)
+{
+ for(i := 0; i < len s; i++)
+ addcodec(k, s[i]);
+}
+
+addcodec(k, c: int)
+{
+ if(codehead == nil
+ || k != codetail.kind
+ || codetail.ndata >= NCode){
+ cd := ref Code(k, array[NCode+UTFmax] of byte, 0, nil);
+ if(codehead == nil)
+ codehead = cd;
+ else
+ codetail.next = cd;
+ codetail = cd;
+ }
+
+ codetail.ndata += sys->char2byte(c, codetail.data, codetail.ndata);
+}
+
+dumpcode(til: int)
+{
+ for(; codehead != nil; codehead = codehead.next){
+ if(codehead.kind == til)
+ return;
+ if(ftable.write(codehead.data, codehead.ndata) != codehead.ndata)
+ error("can't write output file");
+ }
+}
+
+#
+# write out the module declaration and any token info
+#
+dumpmod()
+{
+ if(fdefine != nil) {
+ fdefine.puts(modname);
+ fdefine.puts(": module {\n");
+ }
+ ftable.puts(modname);
+ ftable.puts(": module {\n");
+
+ for(; codehead != nil; codehead = codehead.next){
+ if(codehead.kind != CodeMod)
+ break;
+ if(ftable.write(codehead.data, codehead.ndata) != codehead.ndata)
+ error("can't write output file");
+ if(fdefine != nil && fdefine.write(codehead.data, codehead.ndata) != codehead.ndata)
+ error("can't write define file");
+ }
+
+ for(i:=TOKSTART; i<=ntokens; i++) {
+ # non-literals
+ c := tokset[i].name[0];
+ if(c != ' ' && c != '$') {
+ s := tokset[i].name+": con "+string tokset[i].value+";\n";
+ ftable.puts(s);
+ if(fdefine != nil)
+ fdefine.puts(s);
+ }
+ }
+
+ if(fdefine != nil)
+ fdefine.puts("};\n");
+ ftable.puts("\n};\n");
+
+ if(fdebug != nil) {
+ fdebug.puts("yytoknames = array[] of {\n");
+ for(i=1; i<=ntokens; i++) {
+ if(tokset[i].name != nil)
+ fdebug.puts("\t\""+chcopy(tokset[i].name)+"\",\n");
+ else
+ fdebug.puts("\t\"\",\n");
+ }
+ fdebug.puts("};\n");
+ }
+}
+
+#
+# skip over comments
+# skipcom is called after reading a '#'
+#
+skipcom(): int
+{
+ c := finput.getc();
+ while(c != Bufio->EOF) {
+ if(c == '\n')
+ return 1;
+ c = finput.getc();
+ }
+ error("EOF inside comment");
+ return 0;
+}
+
+#
+# copy limbo action to the next ; or closing }
+#
+cpyact(curprod: array of int, max: int)
+{
+ addcode(CodeAct, "\n#line\t");
+ addcode(CodeAct, string lineno);
+ addcode(CodeAct, "\t\"");
+ addcode(CodeAct, infile);
+ addcode(CodeAct, "\"\n");
+
+ brac := 0;
+
+loop: for(;;){
+ c := finput.getc();
+ swt: case c {
+ ';' =>
+ if(brac == 0) {
+ addcodec(CodeAct, c);
+ return;
+ }
+
+ '{' =>
+ brac++;
+
+ '$' =>
+ s := 1;
+ tok := -1;
+ c = finput.getc();
+
+ # type description
+ if(c == '<') {
+ finput.ungetc();
+ if(gettok() != TYPENAME)
+ error("bad syntax on $<ident> clause");
+ tok = numbval;
+ c = finput.getc();
+ }
+ if(c == '$') {
+ addcode(CodeAct, "e.yyval");
+
+ # put out the proper tag...
+ if(ntypes) {
+ if(tok < 0)
+ tok = fdtype(curprod[0]);
+ addcode(CodeAct, "."+typeset[tok]);
+ }
+ continue loop;
+ }
+ if(c == '-') {
+ s = -s;
+ c = finput.getc();
+ }
+ j := 0;
+ if(isdigit(c)) {
+ while(isdigit(c)) {
+ j = j*10 + c-'0';
+ c = finput.getc();
+ }
+ finput.ungetc();
+ j = j*s;
+ if(j >= max)
+ error("Illegal use of $" + string j);
+ }else if(isword(c) || c == '_' || c == '.') {
+ # look for $name
+ finput.ungetc();
+ if(gettok() != IDENTIFIER)
+ error("$ must be followed by an identifier");
+ tokn := chfind(2, tokname);
+ fnd := -1;
+ if((c = finput.getc()) != '@')
+ finput.ungetc();
+ else if(gettok() != NUMBER)
+ error("@ must be followed by number");
+ else
+ fnd = numbval;
+ for(j=1; j<max; j++){
+ if(tokn == curprod[j]) {
+ fnd--;
+ if(fnd <= 0)
+ break;
+ }
+ }
+ if(j >= max)
+ error("$name or $name@number not found");
+ }else{
+ addcodec(CodeAct, '$');
+ if(s < 0)
+ addcodec(CodeAct, '-');
+ finput.ungetc();
+ continue loop;
+ }
+ addcode(CodeAct, "yys[yypt-" + string(max-j-1) + "].yyv");
+
+ # put out the proper tag
+ if(ntypes) {
+ if(j <= 0 && tok < 0)
+ error("must specify type of $" + string j);
+ if(tok < 0)
+ tok = fdtype(curprod[j]);
+ addcodec(CodeAct, '.');
+ addcode(CodeAct, typeset[tok]);
+ }
+ continue loop;
+
+ '}' =>
+ brac--;
+ if(brac)
+ break;
+ addcodec(CodeAct, c);
+ return;
+
+ '#' =>
+ # a comment
+ addcodec(CodeAct, c);
+ c = finput.getc();
+ while(c != Bufio->EOF) {
+ if(c == '\n') {
+ lineno++;
+ break swt;
+ }
+ addcodec(CodeAct, c);
+ c = finput.getc();
+ }
+ error("EOF inside comment");
+
+ '\''or '"' =>
+ # character string or constant
+ match := c;
+ addcodec(CodeAct, c);
+ while(c = finput.getc()) {
+ if(c == '\\') {
+ addcodec(CodeAct, c);
+ c = finput.getc();
+ if(c == '\n')
+ lineno++;
+ } else if(c == match)
+ break swt;
+ if(c == '\n')
+ error("newline in string or char const.");
+ addcodec(CodeAct, c);
+ }
+ error("EOF in string or character constant");
+
+ Bufio->EOF =>
+ error("action does not terminate");
+
+ '\n' =>
+ lineno++;
+ }
+
+ addcodec(CodeAct, c);
+ }
+}
+
+openup(stem: string, dflag, vflag, ytab: int, ytabc: string)
+{
+ buf: string;
+ if(vflag) {
+ buf = stem + "." + FILEU;
+ foutput = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(foutput == nil)
+ error("can't create " + buf);
+ }
+ if(yydebug != nil) {
+ buf = stem + "." + FILEDEBUG;
+ fdebug = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(fdebug == nil)
+ error("can't create " + buf);
+ }
+ if(dflag) {
+ buf = stem + "." + FILED;
+ fdefine = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(fdefine == nil)
+ error("can't create " + buf);
+ }
+ if(ytab == 0)
+ buf = stem + "." + OFILE;
+ else
+ buf = ytabc;
+ ftable = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(ftable == nil)
+ error("can't create file " + buf);
+}
+
+#
+# return a pointer to the name of symbol i
+#
+symnam(i: int): string
+{
+ s: string;
+ if(i >= NTBASE)
+ s = nontrst[i-NTBASE].name;
+ else
+ s = tokset[i].name;
+ if(s[0] == ' ')
+ s = s[1:];
+ return s;
+}
+
+#
+# write out error comment
+#
+error(s: string)
+{
+ nerrors++;
+ fprint(stderr, "\n fatal error: %s, %s:%d\n", s, infile, lineno);
+ if(!fatfl)
+ return;
+ summary();
+ exit;
+# exits("error");
+}
+
+#
+# set elements 0 through n-1 to c
+#
+aryfil(v: array of int, n, c: int)
+{
+ for(i:=0; i<n; i++)
+ v[i] = c;
+}
+
+#
+# compute an array with the beginnings of productions yielding given nonterminals
+# The array pres points to these lists
+# the array pyield has the lists: the total size is only NPROD+1
+#
+cpres()
+{
+ pres = array[nnonter+1] of array of array of int;
+ curres := array[nprod] of array of int;
+ for(i:=0; i<=nnonter; i++) {
+ n := 0;
+ c := i+NTBASE;
+ fatfl = 0; # make undefined symbols nonfatal
+ for(j:=0; j<nprod; j++)
+ if(prdptr[j][0] == c)
+ curres[n++] = prdptr[j][1:];
+ if(n == 0)
+ error("nonterminal " + nontrst[i].name + " not defined!");
+ else{
+ pres[i] = array[n] of array of int;
+ pres[i][0:] = curres[:n];
+ }
+ }
+ fatfl = 1;
+ if(nerrors) {
+ summary();
+ exit; #exits("error");
+ }
+}
+
+dumppres()
+{
+ for(i := 0; i <= nnonter; i++){
+ print("nonterm %d\n", i);
+ curres := pres[i];
+ for(j := 0; j < len curres; j++){
+ print("\tproduction %d:", j);
+ prd := curres[j];
+ for(k := 0; k < len prd; k++)
+ print(" %d", prd[k]);
+ print("\n");
+ }
+ }
+}
+
+#
+# mark nonterminals which derive the empty string
+# also, look for nonterminals which don't derive any token strings
+#
+cempty()
+{
+ i, p, np: int;
+ prd: array of int;
+
+ pempty = array[nnonter+1] of int;
+
+ # first, use the array pempty to detect productions that can never be reduced
+ # set pempty to WHONOWS
+ aryfil(pempty, nnonter+1, WHOKNOWS);
+
+ # now, look at productions, marking nonterminals which derive something
+more: for(;;){
+ for(i=0; i<nprod; i++) {
+ prd = prdptr[i];
+ if(pempty[prd[0] - NTBASE])
+ continue;
+ np = len prd - 1;
+ for(p = 1; p < np; p++)
+ if(prd[p] >= NTBASE && pempty[prd[p]-NTBASE] == WHOKNOWS)
+ break;
+ # production can be derived
+ if(p == np) {
+ pempty[prd[0]-NTBASE] = OK;
+ continue more;
+ }
+ }
+ break;
+ }
+
+ # now, look at the nonterminals, to see if they are all OK
+ for(i=0; i<=nnonter; i++) {
+ # the added production rises or falls as the start symbol ...
+ if(i == 0)
+ continue;
+ if(pempty[i] != OK) {
+ fatfl = 0;
+ error("nonterminal " + nontrst[i].name + " never derives any token string");
+ }
+ }
+
+ if(nerrors) {
+ summary();
+ exit; #exits("error");
+ }
+
+ # now, compute the pempty array, to see which nonterminals derive the empty string
+ # set pempty to WHOKNOWS
+ aryfil(pempty, nnonter+1, WHOKNOWS);
+
+ # loop as long as we keep finding empty nonterminals
+
+again: for(;;){
+ next: for(i=1; i<nprod; i++) {
+ # not known to be empty
+ prd = prdptr[i];
+ if(pempty[prd[0]-NTBASE] != WHOKNOWS)
+ continue;
+ np = len prd - 1;
+ for(p = 1; p < np; p++)
+ if(prd[p] < NTBASE || pempty[prd[p]-NTBASE] != EMPTY)
+ continue next;
+
+ # we have a nontrivially empty nonterminal
+ pempty[prd[0]-NTBASE] = EMPTY;
+ # got one ... try for another
+ continue again;
+ }
+ return;
+ }
+}
+
+dumpempty()
+{
+ for(i := 0; i <= nnonter; i++)
+ if(pempty[i] == EMPTY)
+ print("non-term %d %s matches empty\n", i, symnam(i+NTBASE));
+}
+
+#
+# compute an array with the first of nonterminals
+#
+cpfir()
+{
+ s, n, p, np, ch: int;
+ curres: array of array of int;
+ prd: array of int;
+
+ wsets = array[nnonter+WSETINC] of Wset;
+ pfirst = array[nnonter+1] of Lkset;
+ for(i:=0; i<=nnonter; i++) {
+ wsets[i].ws = mkset();
+ pfirst[i] = mkset();
+ curres = pres[i];
+ n = len curres;
+ # initially fill the sets
+ for(s = 0; s < n; s++) {
+ prd = curres[s];
+ np = len prd - 1;
+ for(p = 0; p < np; p++) {
+ ch = prd[p];
+ if(ch < NTBASE) {
+ setbit(pfirst[i], ch);
+ break;
+ }
+ if(!pempty[ch-NTBASE])
+ break;
+ }
+ }
+ }
+
+ # now, reflect transitivity
+ changes := 1;
+ while(changes) {
+ changes = 0;
+ for(i=0; i<=nnonter; i++) {
+ curres = pres[i];
+ n = len curres;
+ for(s = 0; s < n; s++) {
+ prd = curres[s];
+ np = len prd - 1;
+ for(p = 0; p < np; p++) {
+ ch = prd[p] - NTBASE;
+ if(ch < 0)
+ break;
+ changes |= setunion(pfirst[i], pfirst[ch]);
+ if(!pempty[ch])
+ break;
+ }
+ }
+ }
+ }
+
+ if(!indebug)
+ return;
+ if(foutput != nil){
+ for(i=0; i<=nnonter; i++) {
+ foutput.putc('\n');
+ foutput.puts(nontrst[i].name);
+ foutput.puts(": ");
+ prlook(pfirst[i]);
+ foutput.putc(' ');
+ foutput.puts(string pempty[i]);
+ foutput.putc('\n');
+ }
+ }
+}
+
+#
+# generate the states
+#
+stagen()
+{
+ # initialize
+ nstate = 0;
+ tstates = array[ntokens+1] of {* => 0}; # states generated by terminal gotos
+ ntstates = array[nnonter+1] of {* => 0};# states generated by nonterminal gotos
+ amem = array[ACTSIZE] of {* => 0};
+ memp = 0;
+
+ clset = mkset();
+ pstate[0] = pstate[1] = 0;
+ aryfil(clset, tbitset, 0);
+ putitem(Pitem(prdptr[0], 0, 0, 0), clset);
+ tystate[0] = MUSTDO;
+ nstate = 1;
+ pstate[2] = pstate[1];
+
+ #
+ # now, the main state generation loop
+ # first pass generates all of the states
+ # later passes fix up lookahead
+ # could be sped up a lot by remembering
+ # results of the first pass rather than recomputing
+ #
+ first := 1;
+ for(more := 1; more; first = 0){
+ more = 0;
+ for(i:=0; i<nstate; i++) {
+ if(tystate[i] != MUSTDO)
+ continue;
+
+ tystate[i] = DONE;
+ aryfil(temp1, nnonter+1, 0);
+
+ # take state i, close it, and do gotos
+ closure(i);
+
+ # generate goto's
+ for(p:=0; p<cwp; p++) {
+ pi := wsets[p];
+ if(pi.flag)
+ continue;
+ wsets[p].flag = 1;
+ c := pi.pitem.first;
+ if(c <= 1) {
+ if(pstate[i+1]-pstate[i] <= p)
+ tystate[i] = MUSTLOOKAHEAD;
+ continue;
+ }
+ # do a goto on c
+ putitem(wsets[p].pitem, wsets[p].ws);
+ for(q:=p+1; q<cwp; q++) {
+ # this item contributes to the goto
+ if(c == wsets[q].pitem.first) {
+ putitem(wsets[q].pitem, wsets[q].ws);
+ wsets[q].flag = 1;
+ }
+ }
+
+ if(c < NTBASE)
+ state(c); # register new state
+ else
+ temp1[c-NTBASE] = state(c);
+ }
+
+ if(gsdebug && foutput != nil) {
+ foutput.puts(string i + ": ");
+ for(j:=0; j<=nnonter; j++)
+ if(temp1[j])
+ foutput.puts(nontrst[j].name + " " + string temp1[j] + ", ");
+ foutput.putc('\n');
+ }
+
+ if(first)
+ indgo[i] = apack(temp1[1:], nnonter-1) - 1;
+
+ more++;
+ }
+ }
+}
+
+#
+# generate the closure of state i
+#
+closure(i: int)
+{
+ zzclose++;
+
+ # first, copy kernel of state i to wsets
+ cwp = 0;
+ q := pstate[i+1];
+ for(p:=pstate[i]; p<q; p++) {
+ wsets[cwp].pitem = statemem[p].pitem;
+ wsets[cwp].flag = 1; # this item must get closed
+ wsets[cwp].ws[0:] = statemem[p].look;
+ cwp++;
+ }
+
+ # now, go through the loop, closing each item
+ work := 1;
+ while(work) {
+ work = 0;
+ for(u:=0; u<cwp; u++) {
+ if(wsets[u].flag == 0)
+ continue;
+ # dot is before c
+ c := wsets[u].pitem.first;
+ if(c < NTBASE) {
+ wsets[u].flag = 0;
+ # only interesting case is where . is before nonterminal
+ continue;
+ }
+
+ # compute the lookahead
+ aryfil(clset, tbitset, 0);
+
+ # find items involving c
+ for(v:=u; v<cwp; v++) {
+ if(wsets[v].flag != 1
+ || wsets[v].pitem.first != c)
+ continue;
+ pi := wsets[v].pitem.prod;
+ ipi := wsets[v].pitem.off + 1;
+
+ wsets[v].flag = 0;
+ if(nolook)
+ continue;
+ while((ch := pi[ipi++]) > 0) {
+ # terminal symbol
+ if(ch < NTBASE) {
+ setbit(clset, ch);
+ break;
+ }
+ # nonterminal symbol
+ setunion(clset, pfirst[ch-NTBASE]);
+ if(!pempty[ch-NTBASE])
+ break;
+ }
+ if(ch <= 0)
+ setunion(clset, wsets[v].ws);
+ }
+
+ #
+ # now loop over productions derived from c
+ #
+ curres := pres[c - NTBASE];
+ n := len curres;
+ # initially fill the sets
+ nexts: for(s := 0; s < n; s++) {
+ prd := curres[s];
+ #
+ # put these items into the closure
+ # is the item there
+ #
+ for(v=0; v<cwp; v++) {
+ # yes, it is there
+ if(wsets[v].pitem.off == 0
+ && wsets[v].pitem.prod == prd) {
+ if(!nolook && setunion(wsets[v].ws, clset))
+ wsets[v].flag = work = 1;
+ continue nexts;
+ }
+ }
+
+ # not there; make a new entry
+ if(cwp >= len wsets){
+ awsets := array[cwp + WSETINC] of Wset;
+ awsets[0:] = wsets;
+ wsets = awsets;
+ }
+ wsets[cwp].pitem = Pitem(prd, 0, prd[0], -prd[len prd-1]);
+ wsets[cwp].flag = 1;
+ wsets[cwp].ws = mkset();
+ if(!nolook) {
+ work = 1;
+ wsets[cwp].ws[0:] = clset;
+ }
+ cwp++;
+ }
+ }
+ }
+
+ # have computed closure; flags are reset; return
+ if(cldebug && foutput != nil) {
+ foutput.puts("\nState " + string i + ", nolook = " + string nolook + "\n");
+ for(u:=0; u<cwp; u++) {
+ if(wsets[u].flag)
+ foutput.puts("flag set!\n");
+ wsets[u].flag = 0;
+ foutput.putc('\t');
+ foutput.puts(writem(wsets[u].pitem));
+ prlook(wsets[u].ws);
+ foutput.putc('\n');
+ }
+ }
+}
+
+#
+# sorts last state,and sees if it equals earlier ones. returns state number
+#
+state(c: int): int
+{
+ zzstate++;
+ p1 := pstate[nstate];
+ p2 := pstate[nstate+1];
+ if(p1 == p2)
+ return 0; # null state
+ # sort the items
+ k, l: int;
+ for(k = p1+1; k < p2; k++) { # make k the biggest
+ for(l = k; l > p1; l--) {
+ if(statemem[l].pitem.prodno < statemem[l-1].pitem.prodno
+ || statemem[l].pitem.prodno == statemem[l-1].pitem.prodno
+ && statemem[l].pitem.off < statemem[l-1].pitem.off) {
+ s := statemem[l];
+ statemem[l] = statemem[l-1];
+ statemem[l-1] = s;
+ }else
+ break;
+ }
+ }
+
+ size1 := p2 - p1; # size of state
+
+ if(c >= NTBASE)
+ i := ntstates[c-NTBASE];
+ else
+ i = tstates[c];
+
+look: for(; i != 0; i = mstates[i]) {
+ # get ith state
+ q1 := pstate[i];
+ q2 := pstate[i+1];
+ size2 := q2 - q1;
+ if(size1 != size2)
+ continue;
+ k = p1;
+ for(l = q1; l < q2; l++) {
+ if(statemem[l].pitem.prod != statemem[k].pitem.prod
+ || statemem[l].pitem.off != statemem[k].pitem.off)
+ continue look;
+ k++;
+ }
+
+ # found it
+ pstate[nstate+1] = pstate[nstate]; # delete last state
+ # fix up lookaheads
+ if(nolook)
+ return i;
+ k = p1;
+ for(l = q1; l < q2; l++) {
+ if(setunion(statemem[l].look, statemem[k].look))
+ tystate[i] = MUSTDO;
+ k++;
+ }
+ return i;
+ }
+ # state is new
+ zznewstate++;
+ if(nolook)
+ error("yacc state/nolook error");
+ pstate[nstate+2] = p2;
+ if(nstate+1 >= NSTATES)
+ error("too many states");
+ if(c >= NTBASE) {
+ mstates[nstate] = ntstates[c-NTBASE];
+ ntstates[c-NTBASE] = nstate;
+ } else {
+ mstates[nstate] = tstates[c];
+ tstates[c] = nstate;
+ }
+ tystate[nstate] = MUSTDO;
+ return nstate++;
+}
+
+putitem(p: Pitem, set: Lkset)
+{
+ p.off++;
+ p.first = p.prod[p.off];
+
+ if(pidebug && foutput != nil)
+ foutput.puts("putitem(" + writem(p) + "), state " + string nstate + "\n");
+ j := pstate[nstate+1];
+ if(j >= len statemem){
+ asm := array[j + STATEINC] of Item;
+ asm[0:] = statemem;
+ statemem = asm;
+ }
+ statemem[j].pitem = p;
+ if(!nolook){
+ s := mkset();
+ s[0:] = set;
+ statemem[j].look = s;
+ }
+ j++;
+ pstate[nstate+1] = j;
+}
+
+#
+# creates output string for item pointed to by pp
+#
+writem(pp: Pitem): string
+{
+ i: int;
+ p := pp.prod;
+ q := chcopy(nontrst[prdptr[pp.prodno][0]-NTBASE].name) + ": ";
+ npi := pp.off;
+ pi := p == prdptr[pp.prodno];
+ for(;;){
+ c := ' ';
+ if(pi == npi)
+ c = '.';
+ q[len q] = c;
+ i = p[pi++];
+ if(i <= 0)
+ break;
+ q += chcopy(symnam(i));
+ }
+
+ # an item calling for a reduction
+ i = p[npi];
+ if(i < 0)
+ q += " (" + string -i + ")";
+ return q;
+}
+
+#
+# pack state i from temp1 into amem
+#
+apack(p: array of int, n: int): int
+{
+ #
+ # we don't need to worry about checking because
+ # we will only look at entries known to be there...
+ # eliminate leading and trailing 0's
+ #
+ off := 0;
+ for(pp := 0; pp <= n && p[pp] == 0; pp++)
+ off--;
+ # no actions
+ if(pp > n)
+ return 0;
+ for(; n > pp && p[n] == 0; n--)
+ ;
+ p = p[pp:n+1];
+
+ # now, find a place for the elements from p to q, inclusive
+ r := len amem - len p;
+nextk: for(rr := 0; rr <= r; rr++) {
+ qq := rr;
+ for(pp = 0; pp < len p; pp++) {
+ if(p[pp] != 0)
+ if(p[pp] != amem[qq] && amem[qq] != 0)
+ continue nextk;
+ qq++;
+ }
+
+ # we have found an acceptable k
+ if(pkdebug && foutput != nil)
+ foutput.puts("off = " + string(off+rr) + ", k = " + string rr + "\n");
+ qq = rr;
+ for(pp = 0; pp < len p; pp++) {
+ if(p[pp]) {
+ if(qq > memp)
+ memp = qq;
+ amem[qq] = p[pp];
+ }
+ qq++;
+ }
+ if(pkdebug && foutput != nil) {
+ for(pp = 0; pp <= memp; pp += 10) {
+ foutput.putc('\t');
+ for(qq = pp; qq <= pp+9; qq++)
+ foutput.puts(string amem[qq] + " ");
+ foutput.putc('\n');
+ }
+ }
+ return off + rr;
+ }
+ error("no space in action table");
+ return 0;
+}
+
+#
+# print the output for the states
+#
+output()
+{
+ c, u, v: int;
+
+ ftable.puts("yyexca := array[] of {");
+ if(fdebug != nil)
+ fdebug.puts("yystates = array [] of {\n");
+
+ noset := mkset();
+
+ # output the stuff for state i
+ for(i:=0; i<nstate; i++) {
+ nolook = tystate[i]!=MUSTLOOKAHEAD;
+ closure(i);
+
+ # output actions
+ nolook = 1;
+ aryfil(temp1, ntokens+nnonter+1, 0);
+ for(u=0; u<cwp; u++) {
+ c = wsets[u].pitem.first;
+ if(c > 1 && c < NTBASE && temp1[c] == 0) {
+ for(v=u; v<cwp; v++)
+ if(c == wsets[v].pitem.first)
+ putitem(wsets[v].pitem, noset);
+ temp1[c] = state(c);
+ } else
+ if(c > NTBASE && temp1[(c -= NTBASE) + ntokens] == 0)
+ temp1[c+ntokens] = amem[indgo[i]+c];
+ }
+ if(i == 1)
+ temp1[1] = ACCEPTCODE;
+
+ # now, we have the shifts; look at the reductions
+ lastred = 0;
+ for(u=0; u<cwp; u++) {
+ c = wsets[u].pitem.first;
+
+ # reduction
+ if(c > 0)
+ continue;
+ lastred = -c;
+ us := wsets[u].ws;
+ for(k:=0; k<=ntokens; k++) {
+ if(!bitset(us, k))
+ continue;
+ if(temp1[k] == 0)
+ temp1[k] = c;
+ else
+ if(temp1[k] < 0) { # reduce/reduce conflict
+ if(foutput != nil)
+ foutput.puts(
+ "\n" + string i + ": reduce/reduce conflict (red'ns "
+ + string -temp1[k] + " and " + string lastred + " ) on " + symnam(k));
+ if(-temp1[k] > lastred)
+ temp1[k] = -lastred;
+ zzrrconf++;
+ } else
+ # potential shift/reduce conflict
+ precftn(lastred, k, i);
+ }
+ }
+ wract(i);
+ }
+
+ if(fdebug != nil)
+ fdebug.puts("};\n");
+ ftable.puts("};\n");
+ ftable.puts("YYNPROD: con " + string nprod + ";\n");
+ ftable.puts("YYPRIVATE: con " + string PRIVATE + ";\n");
+ ftable.puts("yytoknames: array of string;\n");
+ ftable.puts("yystates: array of string;\n");
+ if(yydebug != nil){
+ ftable.puts("include \"y.debug\";\n");
+ ftable.puts("yydebug: con " + yydebug + ";\n");
+ }else{
+ ftable.puts("yydebug: con 0;\n");
+ }
+}
+
+#
+# decide a shift/reduce conflict by precedence.
+# r is a rule number, t a token number
+# the conflict is in state s
+# temp1[t] is changed to reflect the action
+#
+precftn(r, t, s: int)
+{
+ action: int;
+
+ lp := levprd[r];
+ lt := toklev[t];
+ if(PLEVEL(lt) == 0 || PLEVEL(lp) == 0) {
+
+ # conflict
+ if(foutput != nil)
+ foutput.puts(
+ "\n" + string s + ": shift/reduce conflict (shift "
+ + string temp1[t] + "(" + string PLEVEL(lt) + "), red'n "
+ + string r + "(" + string PLEVEL(lp) + ")) on " + symnam(t));
+ zzsrconf++;
+ return;
+ }
+ if(PLEVEL(lt) == PLEVEL(lp))
+ action = ASSOC(lt);
+ else if(PLEVEL(lt) > PLEVEL(lp))
+ action = RASC; # shift
+ else
+ action = LASC; # reduce
+ case action{
+ BASC => # error action
+ temp1[t] = ERRCODE;
+ LASC => # reduce
+ temp1[t] = -r;
+ }
+}
+
+#
+# output state i
+# temp1 has the actions, lastred the default
+#
+wract(i: int)
+{
+ p, p1: int;
+
+ # find the best choice for lastred
+ lastred = 0;
+ ntimes := 0;
+ for(j:=0; j<=ntokens; j++) {
+ if(temp1[j] >= 0)
+ continue;
+ if(temp1[j]+lastred == 0)
+ continue;
+ # count the number of appearances of temp1[j]
+ count := 0;
+ tred := -temp1[j];
+ levprd[tred] |= REDFLAG;
+ for(p=0; p<=ntokens; p++)
+ if(temp1[p]+tred == 0)
+ count++;
+ if(count > ntimes) {
+ lastred = tred;
+ ntimes = count;
+ }
+ }
+
+ #
+ # for error recovery, arrange that, if there is a shift on the
+ # error recovery token, `error', that the default be the error action
+ #
+ if(temp1[2] > 0)
+ lastred = 0;
+
+ # clear out entries in temp1 which equal lastred
+ # count entries in optst table
+ n := 0;
+ for(p=0; p<=ntokens; p++) {
+ p1 = temp1[p];
+ if(p1+lastred == 0)
+ temp1[p] = p1 = 0;
+ if(p1 > 0 && p1 != ACCEPTCODE && p1 != ERRCODE)
+ n++;
+ }
+
+ wrstate(i);
+ defact[i] = lastred;
+ flag := 0;
+ os := array[n*2] of int;
+ n = 0;
+ for(p=0; p<=ntokens; p++) {
+ if((p1=temp1[p]) != 0) {
+ if(p1 < 0) {
+ p1 = -p1;
+ } else if(p1 == ACCEPTCODE) {
+ p1 = -1;
+ } else if(p1 == ERRCODE) {
+ p1 = 0;
+ } else {
+ os[n++] = p;
+ os[n++] = p1;
+ zzacent++;
+ continue;
+ }
+ if(flag++ == 0)
+ ftable.puts("-1, " + string i + ",\n");
+ ftable.puts("\t" + string p + ", " + string p1 + ",\n");
+ zzexcp++;
+ }
+ }
+ if(flag) {
+ defact[i] = -2;
+ ftable.puts("\t-2, " + string lastred + ",\n");
+ }
+ optst[i] = os;
+}
+
+#
+# writes state i
+#
+wrstate(i: int)
+{
+ j0, j1, u: int;
+ pp, qq: int;
+
+ if(fdebug != nil) {
+ if(lastred) {
+ fdebug.puts(" nil, #" + string i + "\n");
+ } else {
+ fdebug.puts(" \"");
+ qq = pstate[i+1];
+ for(pp=pstate[i]; pp<qq; pp++){
+ fdebug.puts(writem(statemem[pp].pitem));
+ fdebug.puts("\\n");
+ }
+ if(tystate[i] == MUSTLOOKAHEAD)
+ for(u = pstate[i+1] - pstate[i]; u < cwp; u++)
+ if(wsets[u].pitem.first < 0){
+ fdebug.puts(writem(wsets[u].pitem));
+ fdebug.puts("\\n");
+ }
+ fdebug.puts("\", #" + string i + "/\n");
+ }
+ }
+ if(foutput == nil)
+ return;
+ foutput.puts("\nstate " + string i + "\n");
+ qq = pstate[i+1];
+ for(pp=pstate[i]; pp<qq; pp++){
+ foutput.putc('\t');
+ foutput.puts(writem(statemem[pp].pitem));
+ foutput.putc('\n');
+ }
+ if(tystate[i] == MUSTLOOKAHEAD) {
+ # print out empty productions in closure
+ for(u = pstate[i+1] - pstate[i]; u < cwp; u++) {
+ if(wsets[u].pitem.first < 0) {
+ foutput.putc('\t');
+ foutput.puts(writem(wsets[u].pitem));
+ foutput.putc('\n');
+ }
+ }
+ }
+
+ # check for state equal to another
+ for(j0=0; j0<=ntokens; j0++)
+ if((j1=temp1[j0]) != 0) {
+ foutput.puts("\n\t" + symnam(j0) + " ");
+ # shift, error, or accept
+ if(j1 > 0) {
+ if(j1 == ACCEPTCODE)
+ foutput.puts("accept");
+ else if(j1 == ERRCODE)
+ foutput.puts("error");
+ else
+ foutput.puts("shift "+string j1);
+ } else
+ foutput.puts("reduce " + string -j1 + " (src line " + string rlines[-j1] + ")");
+ }
+
+ # output the final production
+ if(lastred)
+ foutput.puts("\n\t. reduce " + string lastred + " (src line " + string rlines[lastred] + ")\n\n");
+ else
+ foutput.puts("\n\t. error\n\n");
+
+ # now, output nonterminal actions
+ j1 = ntokens;
+ for(j0 = 1; j0 <= nnonter; j0++) {
+ j1++;
+ if(temp1[j1])
+ foutput.puts("\t" + symnam(j0+NTBASE) + " goto " + string temp1[j1] + "\n");
+ }
+}
+
+#
+# output the gotos for the nontermninals
+#
+go2out()
+{
+ for(i := 1; i <= nnonter; i++) {
+ go2gen(i);
+
+ # find the best one to make default
+ best := -1;
+ times := 0;
+
+ # is j the most frequent
+ for(j := 0; j < nstate; j++) {
+ if(tystate[j] == 0)
+ continue;
+ if(tystate[j] == best)
+ continue;
+
+ # is tystate[j] the most frequent
+ count := 0;
+ cbest := tystate[j];
+ for(k := j; k < nstate; k++)
+ if(tystate[k] == cbest)
+ count++;
+ if(count > times) {
+ best = cbest;
+ times = count;
+ }
+ }
+
+ # best is now the default entry
+ zzgobest += times-1;
+ n := 0;
+ for(j = 0; j < nstate; j++)
+ if(tystate[j] != 0 && tystate[j] != best)
+ n++;
+ goent := array[2*n+1] of int;
+ n = 0;
+ for(j = 0; j < nstate; j++)
+ if(tystate[j] != 0 && tystate[j] != best) {
+ goent[n++] = j;
+ goent[n++] = tystate[j];
+ zzgoent++;
+ }
+
+ # now, the default
+ if(best == -1)
+ best = 0;
+ zzgoent++;
+ goent[n] = best;
+ yypgo[i] = goent;
+ }
+}
+
+#
+# output the gotos for nonterminal c
+#
+go2gen(c: int)
+{
+ i, cc, p, q: int;
+
+ # first, find nonterminals with gotos on c
+ aryfil(temp1, nnonter+1, 0);
+ temp1[c] = 1;
+ work := 1;
+ while(work) {
+ work = 0;
+ for(i=0; i<nprod; i++) {
+ # cc is a nonterminal with a goto on c
+ cc = prdptr[i][1]-NTBASE;
+ if(cc >= 0 && temp1[cc] != 0) {
+ # thus, the left side of production i does too
+ cc = prdptr[i][0]-NTBASE;
+ if(temp1[cc] == 0) {
+ work = 1;
+ temp1[cc] = 1;
+ }
+ }
+ }
+ }
+
+ # now, we have temp1[c] = 1 if a goto on c in closure of cc
+ if(g2debug && foutput != nil) {
+ foutput.puts(nontrst[c].name);
+ foutput.puts(": gotos on ");
+ for(i=0; i<=nnonter; i++)
+ if(temp1[i]){
+ foutput.puts(nontrst[i].name);
+ foutput.putc(' ');
+ }
+ foutput.putc('\n');
+ }
+
+ # now, go through and put gotos into tystate
+ aryfil(tystate, nstate, 0);
+ for(i=0; i<nstate; i++) {
+ q = pstate[i+1];
+ for(p=pstate[i]; p<q; p++) {
+ if((cc = statemem[p].pitem.first) >= NTBASE) {
+ # goto on c is possible
+ if(temp1[cc-NTBASE]) {
+ tystate[i] = amem[indgo[i]+c];
+ break;
+ }
+ }
+ }
+ }
+}
+
+#
+# in order to free up the mem and amem arrays for the optimizer,
+# and still be able to output yyr1, etc., after the sizes of
+# the action array is known, we hide the nonterminals
+# derived by productions in levprd.
+#
+hideprod()
+{
+ j := 0;
+ levprd[0] = 0;
+ for(i:=1; i<nprod; i++) {
+ if(!(levprd[i] & REDFLAG)) {
+ j++;
+ if(foutput != nil) {
+ foutput.puts("Rule not reduced: ");
+ foutput.puts(writem(Pitem(prdptr[i], 0, 0, i)));
+ foutput.putc('\n');
+ }
+ }
+ levprd[i] = prdptr[i][0] - NTBASE;
+ }
+ if(j)
+ print("%d rules never reduced\n", j);
+}
+
+callopt()
+{
+ j, k, p, q: int;
+ v: array of int;
+
+ pgo = array[nnonter+1] of int;
+ pgo[0] = 0;
+ maxoff = 0;
+ maxspr = 0;
+ for(i := 0; i < nstate; i++) {
+ k = 32000;
+ j = 0;
+ v = optst[i];
+ q = len v;
+ for(p = 0; p < q; p += 2) {
+ if(v[p] > j)
+ j = v[p];
+ if(v[p] < k)
+ k = v[p];
+ }
+ # nontrivial situation
+ if(k <= j) {
+ # j is now the range
+# j -= k; # call scj
+ if(k > maxoff)
+ maxoff = k;
+ }
+ tystate[i] = q + 2*j;
+ if(j > maxspr)
+ maxspr = j;
+ }
+
+ # initialize ggreed table
+ ggreed = array[nnonter+1] of int;
+ for(i = 1; i <= nnonter; i++) {
+ ggreed[i] = 1;
+ j = 0;
+
+ # minimum entry index is always 0
+ v = yypgo[i];
+ q = len v - 1;
+ for(p = 0; p < q ; p += 2) {
+ ggreed[i] += 2;
+ if(v[p] > j)
+ j = v[p];
+ }
+ ggreed[i] = ggreed[i] + 2*j;
+ if(j > maxoff)
+ maxoff = j;
+ }
+
+ # now, prepare to put the shift actions into the amem array
+ for(i = 0; i < ACTSIZE; i++)
+ amem[i] = 0;
+ maxa = 0;
+ for(i = 0; i < nstate; i++) {
+ if(tystate[i] == 0 && adb > 1)
+ ftable.puts("State " + string i + ": null\n");
+ indgo[i] = YYFLAG1;
+ }
+ while((i = nxti()) != NOMORE)
+ if(i >= 0)
+ stin(i);
+ else
+ gin(-i);
+
+ # print amem array
+ if(adb > 2)
+ for(p = 0; p <= maxa; p += 10) {
+ ftable.puts(string p + " ");
+ for(i = 0; i < 10; i++)
+ ftable.puts(string amem[p+i] + " ");
+ ftable.putc('\n');
+ }
+
+ aoutput();
+ osummary();
+}
+
+#
+# finds the next i
+#
+nxti(): int
+{
+ max := 0;
+ maxi := 0;
+ for(i := 1; i <= nnonter; i++)
+ if(ggreed[i] >= max) {
+ max = ggreed[i];
+ maxi = -i;
+ }
+ for(i = 0; i < nstate; i++)
+ if(tystate[i] >= max) {
+ max = tystate[i];
+ maxi = i;
+ }
+ if(max == 0)
+ return NOMORE;
+ return maxi;
+}
+
+gin(i: int)
+{
+ s: int;
+
+ # enter gotos on nonterminal i into array amem
+ ggreed[i] = 0;
+
+ q := yypgo[i];
+ nq := len q - 1;
+ # now, find amem place for it
+nextgp: for(p := 0; p < ACTSIZE; p++) {
+ if(amem[p])
+ continue;
+ for(r := 0; r < nq; r += 2) {
+ s = p + q[r] + 1;
+ if(s > maxa){
+ maxa = s;
+ if(maxa >= ACTSIZE)
+ error("a array overflow");
+ }
+ if(amem[s])
+ continue nextgp;
+ }
+ # we have found amem spot
+ amem[p] = q[nq];
+ if(p > maxa)
+ maxa = p;
+ for(r = 0; r < nq; r += 2) {
+ s = p + q[r] + 1;
+ amem[s] = q[r+1];
+ }
+ pgo[i] = p;
+ if(adb > 1)
+ ftable.puts("Nonterminal " + string i + ", entry at " + string pgo[i] + "\n");
+ return;
+ }
+ error("cannot place goto " + string i + "\n");
+}
+
+stin(i: int)
+{
+ s: int;
+
+ tystate[i] = 0;
+
+ # enter state i into the amem array
+ q := optst[i];
+ nq := len q;
+ # find an acceptable place
+nextn: for(n := -maxoff; n < ACTSIZE; n++) {
+ flag := 0;
+ for(r := 0; r < nq; r += 2) {
+ s = q[r] + n;
+ if(s < 0 || s > ACTSIZE)
+ continue nextn;
+ if(amem[s] == 0)
+ flag++;
+ else if(amem[s] != q[r+1])
+ continue nextn;
+ }
+
+ # check the position equals another only if the states are identical
+ for(j:=0; j<nstate; j++) {
+ if(indgo[j] == n) {
+
+ # we have some disagreement
+ if(flag)
+ continue nextn;
+ if(nq == len optst[j]) {
+
+ # states are equal
+ indgo[i] = n;
+ if(adb > 1)
+ ftable.puts("State " + string i + ": entry at "
+ + string n + " equals state " + string j + "\n");
+ return;
+ }
+
+ # we have some disagreement
+ continue nextn;
+ }
+ }
+
+ for(r = 0; r < nq; r += 2) {
+ s = q[r] + n;
+ if(s > maxa)
+ maxa = s;
+ if(amem[s] != 0 && amem[s] != q[r+1])
+ error("clobber of a array, pos'n " + string s + ", by " + string q[r+1] + "");
+ amem[s] = q[r+1];
+ }
+ indgo[i] = n;
+ if(adb > 1)
+ ftable.puts("State " + string i + ": entry at " + string indgo[i] + "\n");
+ return;
+ }
+ error("Error; failure to place state " + string i + "\n");
+}
+
+#
+# this version is for limbo
+# write out the optimized parser
+#
+aoutput()
+{
+ ftable.puts("YYLAST:\tcon "+string (maxa+1)+";\n");
+ arout("yyact", amem, maxa+1);
+ arout("yypact", indgo, nstate);
+ arout("yypgo", pgo, nnonter+1);
+}
+
+#
+# put out other arrays, copy the parsers
+#
+others()
+{
+ finput = bufio->open(parser, Bufio->OREAD);
+ if(finput == nil)
+ error("cannot find parser " + parser);
+ arout("yyr1", levprd, nprod);
+ aryfil(temp1, nprod, 0);
+
+ #
+ #yyr2 is the number of rules for each production
+ #
+ for(i:=1; i<nprod; i++)
+ temp1[i] = len prdptr[i] - 2;
+ arout("yyr2", temp1, nprod);
+
+ aryfil(temp1, nstate, -1000);
+ for(i=0; i<=ntokens; i++)
+ for(j:=tstates[i]; j!=0; j=mstates[j])
+ temp1[j] = i;
+ for(i=0; i<=nnonter; i++)
+ for(j=ntstates[i]; j!=0; j=mstates[j])
+ temp1[j] = -i;
+ arout("yychk", temp1, nstate);
+ arout("yydef", defact, nstate);
+
+ # put out token translation tables
+ # table 1 has 0-256
+ aryfil(temp1, 256, 0);
+ c := 0;
+ for(i=1; i<=ntokens; i++) {
+ j = tokset[i].value;
+ if(j >= 0 && j < 256) {
+ if(temp1[j]) {
+ print("yacc bug -- cant have 2 different Ts with same value\n");
+ print(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name);
+ nerrors++;
+ }
+ temp1[j] = i;
+ if(j > c)
+ c = j;
+ }
+ }
+ for(i = 0; i <= c; i++)
+ if(temp1[i] == 0)
+ temp1[i] = YYLEXUNK;
+ arout("yytok1", temp1, c+1);
+
+ # table 2 has PRIVATE-PRIVATE+256
+ aryfil(temp1, 256, 0);
+ c = 0;
+ for(i=1; i<=ntokens; i++) {
+ j = tokset[i].value - PRIVATE;
+ if(j >= 0 && j < 256) {
+ if(temp1[j]) {
+ print("yacc bug -- cant have 2 different Ts with same value\n");
+ print(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name);
+ nerrors++;
+ }
+ temp1[j] = i;
+ if(j > c)
+ c = j;
+ }
+ }
+ arout("yytok2", temp1, c+1);
+
+ # table 3 has everything else
+ ftable.puts("yytok3 := array[] of {\n");
+ c = 0;
+ for(i=1; i<=ntokens; i++) {
+ j = tokset[i].value;
+ if(j >= 0 && j < 256)
+ continue;
+ if(j >= PRIVATE && j < 256+PRIVATE)
+ continue;
+
+ ftable.puts(sprint("%4d,%4d,", j, i));
+ c++;
+ if(c%5 == 0)
+ ftable.putc('\n');
+ }
+ ftable.puts(sprint("%4d\n};\n", 0));
+
+ # copy parser text
+ while((c=finput.getc()) != Bufio->EOF) {
+ if(c == '$') {
+ if((c = finput.getc()) != 'A')
+ ftable.putc('$');
+ else { # copy actions
+ if(codehead == nil)
+ ftable.puts("* => ;");
+ else
+ dumpcode(-1);
+ c = finput.getc();
+ }
+ }
+ ftable.putc(c);
+ }
+ ftable.close();
+}
+
+arout(s: string, v: array of int, n: int)
+{
+ ftable.puts(s+" := array[] of {");
+ for(i := 0; i < n; i++) {
+ if(i%10 == 0)
+ ftable.putc('\n');
+ ftable.puts(sprint("%4d", v[i]));
+ ftable.putc(',');
+ }
+ ftable.puts("\n};\n");
+}
+
+#
+# output the summary on y.output
+#
+summary()
+{
+ if(foutput != nil) {
+ foutput.puts("\n" + string ntokens + " terminals, " + string(nnonter + 1) + " nonterminals\n");
+ foutput.puts("" + string nprod + " grammar rules, " + string nstate + "/" + string NSTATES + " states\n");
+ foutput.puts("" + string zzsrconf + " shift/reduce, " + string zzrrconf + " reduce/reduce conflicts reported\n");
+ foutput.puts("" + string len wsets + " working sets used\n");
+ foutput.puts("memory: parser " + string memp + "/" + string ACTSIZE + "\n");
+ foutput.puts(string (zzclose - 2*nstate) + " extra closures\n");
+ foutput.puts(string zzacent + " shift entries, " + string zzexcp + " exceptions\n");
+ foutput.puts(string zzgoent + " goto entries\n");
+ foutput.puts(string zzgobest + " entries saved by goto default\n");
+ }
+ if(zzsrconf != 0 || zzrrconf != 0) {
+ print("\nconflicts: ");
+ if(zzsrconf)
+ print("%d shift/reduce", zzsrconf);
+ if(zzsrconf && zzrrconf)
+ print(", ");
+ if(zzrrconf)
+ print("%d reduce/reduce", zzrrconf);
+ print("\n");
+ }
+ if(fdefine != nil)
+ fdefine.close();
+}
+
+#
+# write optimizer summary
+#
+osummary()
+{
+ if(foutput == nil)
+ return;
+ i := 0;
+ for(p := maxa; p >= 0; p--)
+ if(amem[p] == 0)
+ i++;
+
+ foutput.puts("Optimizer space used: output " + string (maxa+1) + "/" + string ACTSIZE + "\n");
+ foutput.puts(string(maxa+1) + " table entries, " + string i + " zero\n");
+ foutput.puts("maximum spread: " + string maxspr + ", maximum offset: " + string maxoff + "\n");
+}
+
+#
+# copies and protects "'s in q
+#
+chcopy(q: string): string
+{
+ s := "";
+ j := 0;
+ for(i := 0; i < len q; i++) {
+ if(q[i] == '"') {
+ s += q[j:i] + "\\";
+ j = i;
+ }
+ }
+ return s + q[j:i];
+}
+
+usage()
+{
+ fprint(stderr, "usage: yacc [-vd] [-Dn] [-o output] [-s stem] file\n");
+ exit;
+}
+
+bitset(set: Lkset, bit: int): int
+{
+ return set[bit>>5] & (1<<(bit&31));
+}
+
+setbit(set: Lkset, bit: int): int
+{
+ return set[bit>>5] |= (1<<(bit&31));
+}
+
+mkset(): Lkset
+{
+ return array[tbitset] of {* => 0};
+}
+
+#
+# set a to the union of a and b
+# return 1 if b is not a subset of a, 0 otherwise
+#
+setunion(a, b: array of int): int
+{
+ sub := 0;
+ for(i:=0; i<tbitset; i++) {
+ x := a[i];
+ y := x | b[i];
+ a[i] = y;
+ if(y != x)
+ sub = 1;
+ }
+ return sub;
+}
+
+prlook(p: Lkset)
+{
+ if(p == nil){
+ foutput.puts("\tNULL");
+ return;
+ }
+ foutput.puts(" { ");
+ for(j:=0; j<=ntokens; j++){
+ if(bitset(p, j)){
+ foutput.puts(symnam(j));
+ foutput.putc(' ');
+ }
+ }
+ foutput.putc('}');
+}
+
+#
+# utility routines
+#
+isdigit(c: int): int
+{
+ return c >= '0' && c <= '9';
+}
+
+isword(c: int): int
+{
+ return c >= 16ra0 || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
+}
+
+mktemp(t: string): string
+{
+ return t;
+}
+
+#
+# arg processing
+#
+Arg.init(argv: list of string): ref Arg
+{
+ if(argv != nil)
+ argv = tl argv;
+ return ref Arg(argv, 0, "");
+}
+
+Arg.opt(arg: self ref Arg): int
+{
+ opts := arg.opts;
+ if(opts != ""){
+ arg.c = opts[0];
+ arg.opts = opts[1:];
+ return arg.c;
+ }
+ argv := arg.argv;
+ if(argv == nil)
+ return arg.c = 0;
+ opts = hd argv;
+ if(len opts < 2 || opts[0] != '-')
+ return arg.c = 0;
+ arg.argv = tl argv;
+ if(opts == "--")
+ return arg.c = 0;
+ arg.opts = opts[2:];
+ return arg.c = opts[1];
+}
+
+Arg.arg(arg: self ref Arg): string
+{
+ s := arg.opts;
+ arg.opts = "";
+ if(s != "")
+ return s;
+ argv := arg.argv;
+ if(argv == nil)
+ return "";
+ arg.argv = tl argv;
+ return hd argv;
+}
diff --git a/appl/cmd/mash/eyaccpar b/appl/cmd/mash/eyaccpar
new file mode 100644
index 00000000..2bbb0355
--- /dev/null
+++ b/appl/cmd/mash/eyaccpar
@@ -0,0 +1,223 @@
+YYFLAG: con -1000;
+
+# parser for yacc output
+YYENV: adt
+{
+ yylval: ref YYSTYPE; # lexical value
+ yyval: YYSTYPE; # goto value
+ yyenv: YYETYPE; # useer environment
+ yynerrs: int; # number of errors
+ yyerrflag: int; # error recovery flag
+ yysys: Sys;
+ yystderr: ref Sys->FD;
+};
+
+yytokname(yyc: int): string
+{
+ if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil)
+ return yytoknames[yyc-1];
+ return "<"+string yyc+">";
+}
+
+yystatname(yys: int): string
+{
+ if(yys >= 0 && yys < len yystates && yystates[yys] != nil)
+ return yystates[yys];
+ return "<"+string yys+">\n";
+}
+
+yylex1(e: ref YYENV): int
+{
+ c, yychar : int;
+ yychar = yyelex(e);
+ if(yychar <= 0)
+ c = yytok1[0];
+ else if(yychar < len yytok1)
+ c = yytok1[yychar];
+ else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2)
+ c = yytok2[yychar-YYPRIVATE];
+ else{
+ n := len yytok3;
+ c = 0;
+ for(i := 0; i < n; i+=2) {
+ if(yytok3[i+0] == yychar) {
+ c = yytok3[i+1];
+ break;
+ }
+ }
+ if(c == 0)
+ c = yytok2[1]; # unknown char
+ }
+ if(yydebug >= 3)
+ e.yysys->fprint(e.yystderr, "lex %.4ux %s\n", yychar, yytokname(c));
+ return c;
+}
+
+YYS: adt
+{
+ yyv: YYSTYPE;
+ yys: int;
+};
+
+yyparse(): int
+{
+ return yyeparse(nil);
+}
+
+yyeparse(e: ref YYENV): int
+{
+ if(e == nil)
+ e = ref YYENV;
+ if(e.yylval == nil)
+ e.yylval = ref YYSTYPE;
+ if(e.yysys == nil) {
+ e.yysys = load Sys "$Sys";
+ e.yystderr = e.yysys->fildes(2);
+ }
+
+ yys := array[YYMAXDEPTH] of YYS;
+
+ yystate := 0;
+ yychar := -1;
+ e.yynerrs = 0;
+ e.yyerrflag = 0;
+ yyp := -1;
+ yyn := 0;
+
+yystack:
+ for(;;){
+ # put a state and value onto the stack
+ if(yydebug >= 4)
+ e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+ yyp++;
+ if(yyp >= YYMAXDEPTH) {
+ yyerror(e, "yacc stack overflow");
+ yyn = 1;
+ break yystack;
+ }
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = e.yyval;
+
+ for(;;){
+ yyn = yypact[yystate];
+ if(yyn > YYFLAG) { # simple state
+ if(yychar < 0)
+ yychar = yylex1(e);
+ yyn += yychar;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yyn = yyact[yyn];
+ if(yychk[yyn] == yychar) { # valid shift
+ yychar = -1;
+ yyp++;
+ if(yyp >= YYMAXDEPTH) {
+ yyerror(e, "yacc stack overflow");
+ yyn = 1;
+ break yystack;
+ }
+ yystate = yyn;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = *e.yylval;
+ if(e.yyerrflag > 0)
+ e.yyerrflag--;
+ if(yydebug >= 4)
+ e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+ continue;
+ }
+ }
+ }
+
+ # default state action
+ yyn = yydef[yystate];
+ if(yyn == -2) {
+ if(yychar < 0)
+ yychar = yylex1(e);
+
+ # look through exception table
+ for(yyxi:=0;; yyxi+=2)
+ if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate)
+ break;
+ for(yyxi += 2;; yyxi += 2) {
+ yyn = yyexca[yyxi];
+ if(yyn < 0 || yyn == yychar)
+ break;
+ }
+ yyn = yyexca[yyxi+1];
+ if(yyn < 0){
+ yyn = 0;
+ break yystack;
+ }
+ }
+
+ if(yyn != 0)
+ break;
+
+ # error ... attempt to resume parsing
+ if(e.yyerrflag == 0) { # brand new error
+ yyerror(e, "syntax error");
+ e.yynerrs++;
+ if(yydebug >= 1) {
+ e.yysys->fprint(e.yystderr, "%s", yystatname(yystate));
+ e.yysys->fprint(e.yystderr, "saw %s\n", yytokname(yychar));
+ }
+ }
+
+ if(e.yyerrflag != 3) { # incompletely recovered error ... try again
+ e.yyerrflag = 3;
+
+ # find a state where "error" is a legal shift action
+ while(yyp >= 0) {
+ yyn = yypact[yys[yyp].yys] + YYERRCODE;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yystate = yyact[yyn]; # simulate a shift of "error"
+ if(yychk[yystate] == YYERRCODE) {
+ yychar = -1;
+ continue yystack;
+ }
+ }
+
+ # the current yyp has no shift on "error", pop stack
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "error recovery pops state %d, uncovers %d\n",
+ yys[yyp].yys, yys[yyp-1].yys );
+ yyp--;
+ }
+ # there is no state on the stack with an error shift ... abort
+ yyn = 1;
+ break yystack;
+ }
+
+ # no shift yet; clobber input char
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "error recovery discards %s\n", yytokname(yychar));
+ if(yychar == YYEOFCODE) {
+ yyn = 1;
+ break yystack;
+ }
+ yychar = -1;
+ # try again in the same state
+ }
+
+ # reduction by production yyn
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+ yypt := yyp;
+ yyp -= yyr2[yyn];
+# yyval = yys[yyp+1].yyv;
+ yym := yyn;
+
+ # consult goto table to find next state
+ yyn = yyr1[yyn];
+ yyg := yypgo[yyn];
+ yyj := yyg + yys[yyp].yys + 1;
+
+ if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+ yystate = yyact[yyg];
+ case yym {
+ $A
+ }
+ }
+
+ return yyn;
+}
diff --git a/appl/cmd/mash/history.b b/appl/cmd/mash/history.b
new file mode 100644
index 00000000..7f7cf9b6
--- /dev/null
+++ b/appl/cmd/mash/history.b
@@ -0,0 +1,206 @@
+implement Mashbuiltin;
+
+#
+# "history" builtin, defines:
+#
+
+include "mash.m";
+include "mashparse.m";
+
+mashlib: Mashlib;
+chanfill: ChanFill;
+
+Env: import mashlib;
+sys, bufio: import mashlib;
+
+Iobuf: import bufio;
+
+Hcmd: adt
+{
+ seek: int;
+ text: array of byte;
+};
+
+Reader: adt
+{
+ fid: int;
+ offset: int;
+ hint: int;
+ next: cyclic ref Reader;
+};
+
+history: array of ref Hcmd;
+lhist: int;
+nhist: int;
+seek: int;
+readers: ref Reader;
+eof := array[0] of byte;
+
+#
+# Interface to catch the use as a command.
+#
+init(nil: ref Draw->Context, args: list of string)
+{
+ raise "fail: " + hd args + " not loaded";
+}
+
+#
+# Used by whatis.
+#
+name(): string
+{
+ return "history";
+}
+
+#
+# Install commands.
+#
+mashinit(nil: list of string, lib: Mashlib, nil: Mashbuiltin, e: ref Env)
+{
+ mashlib = lib;
+ if (mashlib->histchan != nil)
+ return;
+ mashlib->startserve = 1;
+ nhist = 0;
+ lhist = 256;
+ history = array[lhist] of ref Hcmd;
+ seek = 0;
+ (f, c) := e.servefile(mashlib->HISTF);
+ spawn servehist(f, c);
+ (f, c) = e.servefile(mashlib->MASHF);
+ spawn servemash(f, c);
+}
+
+mashcmd(nil: ref Env, nil: list of string)
+{
+}
+
+addhist(b: array of byte)
+{
+ if (nhist == lhist) {
+ n := 3 * nhist / 4;
+ part := history[:n];
+ part[:] = history[nhist - n:];
+ nhist = n;
+ }
+ history[nhist] = ref Hcmd(seek, b);
+ nhist++;
+ seek += len b;
+}
+
+getfid(fid: int, del: int): ref Reader
+{
+ prev: ref Reader;
+ for (r := readers; r != nil; r = r.next) {
+ if (r.fid == fid) {
+ if (del) {
+ if (prev == nil)
+ readers = r.next;
+ else
+ prev.next = r.next;
+ return nil;
+ }
+ return r;
+ }
+ prev = r;
+ }
+ o := 0;
+ if (nhist > 0)
+ o = history[0].seek;
+ return readers = ref Reader(fid, o, 0, readers);
+}
+
+readhist(off, count, fid: int): (array of byte, string)
+{
+ r := getfid(fid, 0);
+ off += r.offset;
+ if (nhist == 0 || off >= seek)
+ return (eof, nil);
+ i := r.hint;
+ if (i >= nhist)
+ i = nhist - 1;
+ s := history[i].seek;
+ if (off == s) {
+ r.hint = i + 1;
+ return (history[i].text, nil);
+ }
+ if (off > s) {
+ do {
+ if (++i == nhist)
+ break;
+ s = history[i].seek;
+ } while (off >= s);
+ i--;
+ } else {
+ do {
+ if (--i < 0)
+ return (eof, "data truncated");
+ s = history[i].seek;
+ } while (off < s);
+ }
+ r.hint = i + 1;
+ b := history[i].text;
+ if (off != s)
+ b = b[off - s:];
+ return (b, nil);
+}
+
+loadhist(data: array of byte, fid: int, wc: Sys->Rwrite, c: ref Sys->FileIO)
+{
+ in: ref Iobuf;
+ if (chanfill == nil)
+ chanfill = load ChanFill ChanFill->PATH;
+ if (chanfill != nil)
+ in = chanfill->init(data, fid, wc, c, mashlib->bufio);
+ if (in == nil) {
+ in = bufio->sopen(string data);
+ if (in == nil) {
+ wc <-= (0, mashlib->errstr());
+ return;
+ }
+ wc <-= (len data, nil);
+ }
+ while ((s := in.gets('\n')) != nil)
+ addhist(array of byte s);
+ in.close();
+}
+
+servehist(f: string, c: ref Sys->FileIO)
+{
+ mashlib->reap();
+ h := chan of array of byte;
+ mashlib->histchan = h;
+ for (;;) {
+ alt {
+ b := <-h =>
+ addhist(b);
+ (off, count, fid, rc) := <-c.read =>
+ if (rc == nil) {
+ getfid(fid, 1);
+ continue;
+ }
+ rc <-= readhist(off, count, fid);
+ (off, data, fid, wc) := <-c.write =>
+ if (wc != nil)
+ loadhist(data, fid, wc, c);
+ }
+ }
+}
+
+servemash(f: string, c: ref Sys->FileIO)
+{
+ mashlib->reap();
+ for (;;) {
+ alt {
+ (off, count, fid, rc) := <-c.read =>
+ if (rc != nil)
+ rc <-= (nil, "not supported");
+ (off, data, fid, wc) := <-c.write =>
+ if (wc != nil) {
+ wc <-= (len data, nil);
+ if (mashlib->servechan != nil && len data > 0)
+ mashlib->servechan <-= data;
+ }
+ }
+ }
+}
diff --git a/appl/cmd/mash/lex.b b/appl/cmd/mash/lex.b
new file mode 100644
index 00000000..c9c3789b
--- /dev/null
+++ b/appl/cmd/mash/lex.b
@@ -0,0 +1,547 @@
+#
+# Lexical analyzer.
+#
+
+lexdebug : con 0;
+
+#
+# Import tokens from parser.
+#
+Land,
+Lat,
+Lbackq,
+Lcaret,
+Lcase,
+Lcolon,
+Lcolonmatch,
+Lcons,
+Ldefeq,
+Lelse,
+Leof,
+Leq,
+Leqeq,
+Lerror,
+Lfn,
+Lfor,
+Lgreat,
+Lgreatgreat,
+Lhd,
+Lif,
+Lin,
+Llen,
+Lless,
+Llessgreat,
+Lmatch,
+Lmatched,
+Lnot,
+Lnoteq,
+Loffcurly,
+Loffparen,
+Loncurly,
+Lonparen,
+Lpipe,
+Lquote,
+Lrescue,
+Lsemi,
+Ltl,
+Lwhile,
+Lword
+ : import Mashparse;
+
+KWSIZE: con 31; # keyword hashtable size
+NCTYPE: con 128; # character class array size
+
+ALPHA,
+NUMERIC,
+ONE,
+WS,
+META
+ : con 1 << iota;
+
+keywords := array[] of
+{
+ ("case", Lcase),
+ ("else", Lelse),
+ ("fn", Lfn),
+ ("for", Lfor),
+ ("hd", Lhd),
+ ("if", Lif),
+ ("in", Lin),
+ ("len", Llen),
+ ("rescue", Lrescue),
+ ("tl", Ltl),
+ ("while", Lwhile)
+};
+
+ctype := array[NCTYPE] of
+{
+ 0 or ' ' or '\t' or '\n' or '\r' or '\v' => WS,
+ ':' or '#' or ';' or '&' or '|' or '^' or '$' or '=' or '@'
+ or '~' or '`'or '{' or '}' or '(' or ')' or '<' or '>' => ONE,
+ 'a' to 'z' or 'A' to 'Z' or '_' => ALPHA,
+ '0' to '9' => NUMERIC,
+ '*' or '[' or ']' or '?' => META,
+ * => 0
+};
+
+keytab: ref HashTable;
+
+#
+# Initialize hashtable.
+#
+initlex()
+{
+ keytab = hash->new(KWSIZE);
+ for (i := 0; i < len keywords; i++) {
+ (s, v) := keywords[i];
+ keytab.insert(s, HashVal(v, 0.0, nil));
+ }
+}
+
+#
+# Keyword value, or -1.
+#
+keyval(i: ref Item): int
+{
+ if (i.op != Iword)
+ return -1;
+ w := i.word;
+ if (w.flags & Wquoted)
+ return -1;
+ v := keytab.find(w.text);
+ if (v == nil)
+ return -1;
+ return v.i;
+}
+
+#
+# Attach a source file to an environment.
+#
+Env.fopen(e: self ref Env, fd: ref Sys->FD, s: string)
+{
+ in := bufio->fopen(fd, Bufio->OREAD);
+ if (in == nil)
+ e.error(sys->sprint("could not fopen %s: %r\n", s));
+ e.file = ref File(in, s, 1, 0);
+}
+
+#
+# Attach a source string to an environment.
+#
+Env.sopen(e: self ref Env, s: string)
+{
+ in := bufio->sopen(s);
+ if (in == nil)
+ e.error(sys->sprint("Bufio->sopen failed: %r\n"));
+ e.file = ref File(in, "<string>", 1, 0);
+}
+
+#
+# Close source file.
+#
+fclose(e: ref Env, c: int)
+{
+ if (c == Bufio->ERROR)
+ readerror(e, e.file);
+ e.file.in.close();
+ e.file = nil;
+}
+
+#
+# Character class routines.
+#
+
+isalpha(c: int): int
+{
+ return c >= NCTYPE || (c >= 0 && (ctype[c] & ALPHA) != 0);
+}
+
+isalnum(c: int): int
+{
+ return c >= NCTYPE || (c >= 0 && (ctype[c] & (ALPHA | NUMERIC)) != 0);
+}
+
+isdigit(c: int): int
+{
+ return c >= 0 && c < NCTYPE && (ctype[c] & NUMERIC) != 0;
+}
+
+isquote(c: int): int
+{
+ return c < NCTYPE && (c < 0 || (ctype[c] & (ONE | WS | META)) != 0);
+}
+
+isspace(c: int): int
+{
+ return c >= 0 && c < NCTYPE && (ctype[c] & WS) != 0;
+}
+
+isterm(c: int): int
+{
+ return c < NCTYPE && (c < 0 || (ctype[c] & (ONE | WS)) != 0);
+}
+
+#
+# Test for an identifier.
+#
+ident(s: string): int
+{
+ if (s == nil || !isalpha(s[0]))
+ return 0;
+ n := len s;
+ for (x := 1; x < n; x++) {
+ if (!isalnum(s[x]))
+ return 0;
+ }
+ return 1;
+}
+
+#
+# Quote text.
+#
+enquote(s: string): string
+{
+ r := "'";
+ j := 1;
+ n := len s;
+ for (i := 0; i < n; i++) {
+ c := s[i];
+ if (c == '\'' || c == '\\')
+ r[j++] = '\\';
+ r[j++] = c;
+ }
+ r[j] = '\'';
+ return r;
+}
+
+#
+# Quote text if needed.
+#
+quote(s: string): string
+{
+ n := len s;
+ for (i := 0; i < n; i++) {
+ if (isquote(s[i]))
+ return enquote(s);
+ }
+ return s;
+}
+
+#
+# Test for single word and identifier.
+#
+Item.sword(i: self ref Item, e: ref Env): ref Item
+{
+ if (i.op == Iword && ident(i.word.text))
+ return i;
+ e.report("malformed identifier: " + i.text());
+ return nil;
+}
+
+readerror(e: ref Env, f: ref File)
+{
+ sys->fprint(e.stderr, "error reading %s: %r\n", f.name);
+}
+
+where(e: ref Env): string
+{
+ if ((e.flags & EInter) || e.file == nil)
+ return nil;
+ return e.file.name + ":" + string e.file.line + ": ";
+}
+
+#
+# Suck input (on error).
+#
+Env.suck(e: self ref Env)
+{
+ if (e.file == nil)
+ return;
+ in := e.file.in;
+ while ((c := in.getc()) >= 0 && c != '\n')
+ ;
+}
+
+#
+# Lexical analyzer.
+#
+Env.lex(e: self ref Env, yylval: ref Mashparse->YYSTYPE): int
+{
+ i, r: ref Item;
+reader:
+ for (;;) {
+ if (e.file == nil)
+ return -1;
+ f := e.file;
+ in := f.in;
+ while (isspace(c := in.getc())) {
+ if (c == '\n')
+ f.line++;
+ }
+ if (c < 0) {
+ fclose(e, c);
+ return Leof;
+ }
+ case c {
+ ':' =>
+ if ((d := in.getc()) == ':')
+ return Lcons;
+ if (d == '=')
+ return Ldefeq;
+ if (d == '~')
+ return Lcolonmatch;
+ if (d >= 0)
+ in.ungetc();
+ return Lcolon;
+ '#' =>
+ for (;;) {
+ if ((c = in.getc()) < 0) {
+ fclose(e, c);
+ return Leof;
+ }
+ if (c == '\n') {
+ f.line++;
+ continue reader;
+ }
+ }
+ ';' =>
+ return Lsemi;
+ '&' =>
+ return Land;
+ '|' =>
+ return Lpipe;
+ '^' =>
+ return Lcaret;
+ '@' =>
+ return Lat;
+ '!' =>
+ if ((d := in.getc()) == '=')
+ return Lnoteq;
+ if (d >= 0)
+ in.ungetc();
+ return Lnot;
+ '~' =>
+ return Lmatch;
+ '=' =>
+ if ((d := in.getc()) == '>')
+ return Lmatched;
+ if (d == '=')
+ return Leqeq;
+ if (d >= 0)
+ in.ungetc();
+ return Leq;
+ '`' =>
+ return Lbackq;
+ '"' =>
+ return Lquote;
+ '{' =>
+ return Loncurly;
+ '}' =>
+ return Loffcurly;
+ '(' =>
+ return Lonparen;
+ ')' =>
+ return Loffparen;
+ '<' =>
+ if ((d := in.getc()) == '>')
+ return Llessgreat;
+ if (d >= 0)
+ in.ungetc();
+ return Lless;
+ '>' =>
+ if ((d := in.getc()) == '>')
+ return Lgreatgreat;
+ if (d >= 0)
+ in.ungetc();
+ return Lgreat;
+ '\\' =>
+ if ((d := in.getc()) == '\n') {
+ f.line++;
+ continue reader;
+ }
+ if (d >= 0)
+ in.ungetc();
+ }
+ # Loop over "carets for free".
+ for (;;) {
+ if (c == '$')
+ (i, c) = getdollar(f);
+ else
+ (i, c) = getword(e, f, c);
+ if (i == nil)
+ return Lerror;
+ if (isterm(c) && c != '$')
+ break;
+ if (r != nil)
+ r = ref Item(Iicaret, nil, r, i, nil, nil);
+ else
+ r = i;
+ }
+ if (c >= 0)
+ in.ungetc();
+ if (r != nil)
+ yylval.item = ref Item(Iicaret, nil, r, i, nil, nil);
+ else if ((c = keyval(i)) >= 0)
+ return c;
+ else
+ yylval.item = i;
+ return Lword;
+ }
+}
+
+#
+# Get $n or $word.
+#
+getdollar(f: ref File): (ref Item, int)
+{
+ s: string;
+ in := f.in;
+ l := f.line;
+ o := Idollar;
+ if (isdigit(c := in.getc())) {
+ s[0] = c;
+ n := 1;
+ while (isdigit(c = in.getc()))
+ s[n++] = c;
+ o = Imatch;
+ } else {
+ if (c == '"') {
+ o = Idollarq;
+ c = in.getc();
+ }
+ if (isalpha(c)) {
+ s[0] = c;
+ n := 1;
+ while (isalnum(c = in.getc()))
+ s[n++] = c;
+ } else {
+ if (o == Idollar)
+ s = "$";
+ else
+ s = "$\"";
+ o = Iword;
+ }
+ }
+ return (ref Item(o, ref Word(s, 0, Src(l, f.name)), nil, nil, nil, nil), c);
+}
+
+#
+# Get word with quoting.
+#
+getword(e: ref Env, f: ref File, c: int): (ref Item, int)
+{
+ s: string;
+ in := f.in;
+ l := f.line;
+ wf := 0;
+ n := 0;
+ if (c == '\'') {
+ wf = Wquoted;
+ collect:
+ while ((c = in.getc()) >= 0) {
+ case c {
+ '\'' =>
+ c = in.getc();
+ break collect;
+ '\\' =>
+ c = in.getc();
+ if (c != '\'' && c != '\\') {
+ if (c == '\n')
+ continue collect;
+ if (c >= 0)
+ in.ungetc();
+ c = '\\';
+ }
+ '\n' =>
+ f.line++;
+ e.report("newline in quoted word");
+ return (nil, 0);
+ }
+ s[n++] = c;
+ }
+ } else {
+ do {
+ case c {
+ '*' or '[' or '?' =>
+ wf |= Wexpand;
+ }
+ s[n++] = c;
+ } while (!isterm(c = in.getc()) && c != '\'');
+ }
+ if (lexdebug && s == "exit")
+ exit;
+ return (ref Item(Iword, ref Word(s, wf, Src(l, f.name)), nil, nil, nil, nil), c);
+}
+
+#
+# Get a line, mapping escape newline to space newline.
+#
+getline(in: ref Bufio->Iobuf): string
+{
+ if (inchan != nil) {
+ alt {
+ b := <-inchan =>
+ if (inchan == nil)
+ return nil;
+ s := string b;
+ n := len s;
+ if (n > 1) {
+ while (s[n - 2] == '\\' && s[n - 1] == '\n') {
+ s[n - 2] = ' ';
+ s[n - 1] = ' ';
+ prprompt(1);
+ b = <-inchan;
+ if (b == nil)
+ break;
+ s += string b;
+ n = len s;
+ }
+ }
+ return s;
+ b := <-servechan =>
+ s := string b;
+ sys->print("%s", s);
+ return s;
+ }
+ } else {
+ s := in.gets('\n');
+ if (s == nil)
+ return nil;
+ n := len s;
+ if (n > 1) {
+ while (s[n - 2] == '\\' && s[n - 1] == '\n') {
+ s[n - 2] = ' ';
+ s[n - 1] = ' ';
+ prprompt(1);
+ t := in.gets('\n');
+ if (t == nil)
+ break;
+ s += t;
+ n = len s;
+ }
+ }
+ return s;
+ }
+}
+
+#
+# Interactive shell loop.
+#
+Env.interactive(e: self ref Env, fd: ref Sys->FD)
+{
+ in := bufio->fopen(fd, Sys->OREAD);
+ if (in == nil)
+ e.error(sys->sprint("could not fopen stdin: %r\n"));
+ e.flags |= EInter;
+ for (;;) {
+ prprompt(0);
+ if (startserve)
+ e.serve();
+ if ((s := getline(in)) == nil)
+ exitmash();
+ e.sopen(s);
+ parse->parse(e);
+ if (histchan != nil)
+ histchan <-= array of byte s;
+ }
+}
diff --git a/appl/cmd/mash/make.b b/appl/cmd/mash/make.b
new file mode 100644
index 00000000..4d566c00
--- /dev/null
+++ b/appl/cmd/mash/make.b
@@ -0,0 +1,723 @@
+implement Mashbuiltin;
+
+#
+# "make" builtin, defines:
+#
+# depends - print dependencies
+# make - make-like command
+# match - print details of rule matches
+# rules - print rules
+#
+
+include "mash.m";
+include "mashparse.m";
+
+verbose: con 0; # debug output
+
+mashlib: Mashlib;
+
+Cmd, Env, Item, Stab: import mashlib;
+Depend, Rule, Target: import mashlib;
+sys, bufio, hash: import mashlib;
+
+Iobuf: import bufio;
+
+#
+# Interface to catch the use as a command.
+#
+init(nil: ref Draw->Context, args: list of string)
+{
+ raise "fail: " + hd args + " not loaded";
+}
+
+#
+# Used by whatis.
+#
+name(): string
+{
+ return "make";
+}
+
+#
+# Install commands.
+#
+mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env)
+{
+ mashlib = lib;
+ e.defbuiltin("depends", this);
+ e.defbuiltin("make", this);
+ e.defbuiltin("match", this);
+ e.defbuiltin("rules", this);
+}
+
+#
+# Execute a builtin.
+#
+mashcmd(e: ref Env, l: list of string)
+{
+ s := hd l;
+ l = tl l;
+ case s {
+ "depends" =>
+ out := e.outfile();
+ if (out == nil)
+ return;
+ if (l == nil)
+ alldeps(out);
+ else
+ depends(out, l);
+ out.close();
+ "make" =>
+ domake(e, l);
+ "match" =>
+ domatch(e, l);
+ "rules" =>
+ out := e.outfile();
+ if (out == nil)
+ return;
+ if (l == nil)
+ allrules(out);
+ else
+ rules(out, l);
+ out.close();
+ }
+}
+
+#
+# Node states.
+#
+SUnknown, SNoexist, SExist, SStale, SMade, SDir, SDirload
+ : con iota;
+
+#
+# Node flags.
+#
+# FMark - marked as in progress
+#
+FMark
+ : con 1 << iota;
+
+Node: adt
+{
+ name: string;
+ state: int;
+ flags: int;
+ mtime: int;
+};
+
+#
+# Step in implicit chain.
+#
+Step: type (ref Rule, array of string, ref Node);
+
+#
+# Implicit match.
+#
+Match: adt
+{
+ node: ref Node;
+ path: list of Step;
+};
+
+NSIZE: con 127; # node hash size
+DSIZE: con 32; # number of dir entries for read
+
+ntab: array of list of ref Node; # node hash table
+
+initnodes()
+{
+ ntab = array[NSIZE] of list of ref Node;
+}
+
+#
+# Find node for a pathname.
+#
+getnode(s: string): ref Node
+{
+ h := hash->fun1(s, NSIZE);
+ for (l := ntab[h]; l != nil; l = tl l) {
+ n := hd l;
+ if (n.name == s)
+ return n;
+ }
+ r := ref Node(s, SUnknown, 0, 0);
+ ntab[h] = r :: ntab[h];
+ return r;
+}
+
+#
+# Make a pathname from a dir and an entry.
+#
+mkpath(d, s: string): string
+{
+ if (d == ".")
+ return s;
+ else if (d == "/")
+ return "/" + s;
+ else
+ return d + "/" + s;
+}
+
+#
+# Load a directory.
+#
+loaddir(s: string)
+{
+ if (verbose)
+ sys->print("loaddir %s\n", s);
+ fd := sys->open(s, Sys->OREAD);
+ if (fd == nil)
+ return;
+ for (;;) {
+ (c, dbuf) := sys->dirread(fd);
+ if(c <= 0)
+ break;
+ for (i := 0; i < c; i++) {
+ n := getnode(mkpath(s, dbuf[i].name));
+ if (dbuf[i].mode & Sys->DMDIR)
+ n.state = SDir;
+ else
+ n.state = SExist;
+ n.mtime = dbuf[i].mtime;
+ }
+ }
+}
+
+#
+# Load a file. Get its node, maybe stat it or loaddir.
+#
+loadfile(s: string): ref Node
+{
+ n := getnode(s);
+ if (n.state == SUnknown) {
+ if (verbose)
+ sys->print("stat %s\n", s);
+ (ok, d) := sys->stat(s);
+ if (ok >= 0) {
+ n.mtime = d.mtime;
+ if (d.mode & Sys->DMDIR) {
+ loaddir(s);
+ n.state = SDirload;
+ } else
+ n.state = SExist;
+ } else
+ n.state = SNoexist;
+ } else if (n.state == SDir) {
+ loaddir(s);
+ n.state = SDirload;
+ }
+ return n;
+}
+
+#
+# Get the node for a file and load the directories in its path.
+#
+getfile(s: string): ref Node
+{
+ d: string;
+ n := len s;
+ while (n >= 2 && s[0:2] == "./") {
+ n -= 2;
+ s = s[2:];
+ }
+ if (n > 0 && s[0] == '/') {
+ d = "/";
+ s = s[1:];
+ } else
+ d = ".";
+ (nil, l) := sys->tokenize(s, "/");
+ for (;;) {
+ w := loadfile(d);
+ if (l == nil)
+ return w;
+ s = hd l;
+ l = tl l;
+ d = mkpath(d, s);
+ }
+}
+
+#
+# If a dependency rule makes more than one target propogate SMade.
+#
+propagate(l: list of string)
+{
+ if (tl l == nil)
+ return ;
+ while (l != nil) {
+ s := hd l;
+ if (verbose)
+ sys->print("propogate to %s\n", s);
+ getfile(s).state = SMade;
+ l = tl l;
+ }
+}
+
+#
+# Try to make a node, or mark it as stale.
+# Return -1 on (reported) error, 0 on fail, 1 on success.
+#
+explicit(e: ref Env, t: ref Target, n: ref Node): int
+{
+ d: ref Depend;
+ for (l := t.depends; l != nil ; l = tl l) {
+ if ((hd l).op != Cnop) {
+ if (d != nil) {
+ e.report(sys->sprint("make: too many rules for %s", t.target));
+ return -1;
+ }
+ d = hd l;
+ }
+ }
+ for (l = t.depends; l != nil ; l = tl l) {
+ for (u := (hd l).depends; u != nil; u = tl u) {
+ s := hd u;
+ m := getfile(s);
+ x := make(e, m, s);
+ if (x < 0) {
+ sys->print("don't know how to make %s\n", s);
+ return x;
+ }
+ if (m.state == SMade || m.mtime > n.mtime) {
+ if (verbose)
+ sys->print("%s makes %s stale\n", s, t.target);
+ n.state = SStale;
+ }
+ }
+ }
+ if (d != nil) {
+ if (n.state == SNoexist || n.state == SStale) {
+ if (verbose)
+ sys->print("build %s with explicit rule\n", t.target);
+ e = e.copy();
+ e.flags |= mashlib->EEcho | Mashlib->ERaise;
+ e.flags &= ~mashlib->EInter;
+ d.cmd.xeq(e);
+ propagate(d.targets);
+ n.state = SMade;
+ } else if (verbose)
+ sys->print("%s up to date\n", t.target);
+ return 1;
+ }
+ return 0;
+}
+
+#
+# Report multiple implicit chains of equal length.
+#
+multimatch(e: ref Env, n: ref Node, l: list of Match)
+{
+ e.report(sys->sprint("%d rules match for %s", len l, n.name));
+ f := e.stderr;
+ while (l != nil) {
+ m := hd l;
+ sys->fprint(f, "%s", m.node.name);
+ for (p := m.path; p != nil; p = tl p) {
+ (nil, nil, t) := hd p;
+ sys->fprint(f, " -> %s", t.name);
+ }
+ sys->fprint(f, "\n");
+ l = tl l;
+ }
+}
+
+cycle(e: ref Env, n: ref Node)
+{
+ e.report(sys->sprint("make: cycle in dependencies for target %s", n.name));
+}
+
+#
+# Mark the nodes in an implicit chain.
+#
+markchain(e: ref Env, l: list of Step): int
+{
+ while (tl l != nil) {
+ (nil, nil, n) := hd l;
+ if (n.flags & FMark) {
+ cycle(e, n);
+ return 0;
+ }
+ n.flags |= FMark;
+ l = tl l;
+ }
+ return 1;
+}
+
+#
+# Unmark the nodes in an implicit chain.
+#
+unmarkchain(l: list of Step): int
+{
+ while (tl l != nil) {
+ (nil, nil, n) := hd l;
+ n.flags &= ~FMark;
+ l = tl l;
+ }
+ return 1;
+}
+
+#
+# Execute an implicit rule chain.
+#
+xeqmatch(e: ref Env, b, n: ref Node, l: list of Step): int
+{
+ if (!markchain(e, l))
+ return -1;
+ if (verbose)
+ sys->print("making %s for implicit rule chain\n", n.name);
+ e.args = nil;
+ x := make(e, n, n.name);
+ if (x < 0) {
+ sys->print("don't know how to make %s\n", n.name);
+ return x;
+ }
+ if (n.state == SMade || n.mtime > b.mtime || b.state == SStale) {
+ e = e.copy();
+ e.flags |= mashlib->EEcho | Mashlib->ERaise;
+ e.flags &= ~mashlib->EInter;
+ for (;;) {
+ (r, a, t) := hd l;
+ if (verbose)
+ sys->print("making %s with implicit rule\n", t.name);
+ e.args = a;
+ r.cmd.xeq(e);
+ t.state = SMade;
+ l = tl l;
+ if (l == nil)
+ break;
+ t.flags &= ~FMark;
+ }
+ } else
+ unmarkchain(l);
+ return 1;
+}
+
+#
+# Find the shortest implicit rule chain.
+#
+implicit(e: ref Env, base: ref Node): int
+{
+ win, lose: list of Match;
+ l: list of ref Rule;
+ cand := Match(base, nil) :: nil;
+ do {
+ # cand - list of candidate chains
+ # lose - list of extended chains that lose
+ # win - list of extended chains that win
+ lose = nil;
+ match:
+ # for each candidate
+ for (c := cand; c != nil; c = tl c) {
+ (b, x) := hd c;
+ s := b.name;
+ # find rules that match end of chain
+ m := mashlib->rulematch(s);
+ l = nil;
+ # exclude rules already in the chain
+ exclude:
+ for (n := m; n != nil; n = tl n) {
+ r := hd n;
+ for (y := x; y != nil; y = tl y) {
+ (u, nil, nil) := hd y;
+ if (u == r)
+ continue exclude;
+ }
+ l = r :: l;
+ }
+ if (l == nil)
+ continue match;
+ (nil, t) := sys->tokenize(s, "/");
+ # for each new rule that matched
+ for (n = l; n != nil; n = tl n) {
+ r := hd n;
+ a := r.matches(t);
+ if (a == nil) {
+ e.report("rule match cock up");
+ return -1;
+ }
+ a[0] = s;
+ e.args = a;
+ # eval rhs
+ (v, nil, nil) := r.rhs.ieval2(e);
+ if (v == nil)
+ continue;
+ y := (r, a, b) :: x;
+ z := getfile(v);
+ # winner or loser
+ if (z.state != SNoexist || Target.find(v) != nil)
+ win = (z, y) :: win;
+ else
+ lose = (z, y) :: lose;
+ }
+ }
+ # winner should be unique
+ if (win != nil) {
+ if (tl win != nil) {
+ multimatch(e, base, win);
+ return -1;
+ } else {
+ (a, p) := hd win;
+ return xeqmatch(e, base, a, p);
+ }
+ }
+ # losers are candidates in next round
+ cand = lose;
+ } while (cand != nil);
+ return 0;
+}
+
+#
+# Make a node (recursive).
+# Return -1 on (reported) error, 0 on fail, 1 on success.
+#
+make(e: ref Env, n: ref Node, s: string): int
+{
+ if (n == nil)
+ n = getfile(s);
+ if (verbose)
+ sys->print("making %s\n", n.name);
+ if (n.state == SMade)
+ return 1;
+ if (n.flags & FMark) {
+ cycle(e, n);
+ return -1;
+ }
+ n.flags |= FMark;
+ t := Target.find(s);
+ if (t != nil) {
+ x := explicit(e, t, n);
+ if (x != 0) {
+ n.flags &= ~FMark;
+ return x;
+ }
+ }
+ x := implicit(e, n);
+ n.flags &= ~FMark;
+ if (x != 0)
+ return x;
+ if (n.state == SExist)
+ return 0;
+ return -1;
+}
+
+makelevel: int = 0; # count recursion
+
+#
+# Make driver routine. Maybe initialize and handle exceptions.
+#
+domake(e: ref Env, l: list of string)
+{
+ if ((e.flags & mashlib->ETop) == 0) {
+ e.report("make not at top level");
+ return;
+ }
+ inited := 0;
+ if (makelevel > 0)
+ inited = 1;
+ makelevel++;
+ if (l == nil)
+ l = "default" :: nil;
+ while (l != nil) {
+ s := hd l;
+ l = tl l;
+ if (s[0] == '-') {
+ case s {
+ "-clear" =>
+ mashlib->initdep();
+ * =>
+ e.report("make: unknown option: " + s);
+ }
+ } else {
+ if (!inited) {
+ initnodes();
+ inited = 1;
+ }
+ {
+ if (make(e, nil, s) < 0) {
+ sys->print("don't know how to make %s\n", s);
+ raise "fail: make error";
+ }
+ }exception x{
+ mashlib->FAILPAT =>
+ makelevel--;
+ raise x;
+ }
+ }
+ }
+ makelevel--;
+}
+
+#
+# Print dependency/rule command.
+#
+prcmd(out: ref Iobuf, op: int, c: ref Cmd)
+{
+ if (op == Clistgroup)
+ out.putc(':');
+ if (c != nil) {
+ out.puts("{ ");
+ out.puts(c.text());
+ out.puts(" }");
+ } else
+ out.puts("{}");
+}
+
+#
+# Print details of rule matches.
+#
+domatch(e: ref Env, l: list of string)
+{
+ out := e.outfile();
+ if (out == nil)
+ return;
+ e = e.copy();
+ while (l != nil) {
+ s := hd l;
+ out.puts(sys->sprint("%s:\n", s));
+ m := mashlib->rulematch(s);
+ (nil, t) := sys->tokenize(s, "/");
+ while (m != nil) {
+ r := hd m;
+ out.puts(sys->sprint("\tlhs %s\n", r.lhs.text));
+ a := r.matches(t);
+ if (a != nil) {
+ a[0] = s;
+ n := len a;
+ for (i := 0; i < n; i++)
+ out.puts(sys->sprint("\t$%d '%s'\n", i, a[i]));
+ e.args = a;
+ (v, w, nil) := r.rhs.ieval2(e);
+ if (v != nil)
+ out.puts(sys->sprint("\trhs '%s'\n", v));
+ else
+ out.puts(sys->sprint("\trhs list %d\n", len w));
+ if (r.cmd != nil) {
+ out.putc('\t');
+ prcmd(out, r.op, r.cmd);
+ out.puts(";\n");
+ }
+ } else
+ out.puts("\tcock up\n");
+ m = tl m;
+ }
+ l = tl l;
+ }
+ out.close();
+}
+
+#
+# Print word list.
+#
+prwords(out: ref Iobuf, l: list of string, pre: int)
+{
+ while (l != nil) {
+ if (pre)
+ out.putc(' ');
+ out.puts(mashlib->quote(hd l));
+ if (!pre)
+ out.putc(' ');
+ l = tl l;
+ }
+}
+
+#
+# Print dependency.
+#
+prdep(out: ref Iobuf, d: ref Depend)
+{
+ prwords(out, d.targets, 0);
+ out.putc(':');
+ prwords(out, d.depends, 1);
+ if (d.op != Cnop) {
+ out.putc(' ');
+ prcmd(out, d.op, d.cmd);
+ }
+ out.puts(";\n");
+}
+
+#
+# Print all dependencies, avoiding duplicates.
+#
+alldep(out: ref Iobuf, d: ref Depend, pass: int)
+{
+ case pass {
+ 0 =>
+ d.mark = 0;
+ 1 =>
+ if (!d.mark) {
+ prdep(out, d);
+ d.mark = 1;
+ }
+ }
+}
+
+#
+# Print all dependencies.
+#
+alldeps(out: ref Iobuf)
+{
+ a := mashlib->dephash;
+ n := len a;
+ for (p := 0; p < 2; p++)
+ for (i := 0; i < n; i++)
+ for (l := a[i]; l != nil; l = tl l)
+ for (d := (hd l).depends; d != nil; d = tl d)
+ alldep(out, hd d, p);
+}
+
+#
+# Print dependencies.
+#
+depends(out: ref Iobuf, l: list of string)
+{
+ while (l != nil) {
+ s := hd l;
+ out.puts(s);
+ out.puts(":\n");
+ t := Target.find(s);
+ if (t != nil) {
+ for (d := t.depends; d != nil; d = tl d)
+ prdep(out, hd d);
+ }
+ l = tl l;
+ }
+}
+
+#
+# Print rule.
+#
+prrule(out: ref Iobuf, r: ref Rule)
+{
+ out.puts(r.lhs.text);
+ out.puts(" :~ ");
+ out.puts(r.rhs.text());
+ out.putc(' ');
+ prcmd(out, r.op, r.cmd);
+ out.puts(";\n");
+}
+
+#
+# Print all rules.
+#
+allrules(out: ref Iobuf)
+{
+ for (l := mashlib->rules; l != nil; l = tl l)
+ prrule(out, hd l);
+}
+
+#
+# Print matching rules.
+#
+rules(out: ref Iobuf, l: list of string)
+{
+ while (l != nil) {
+ s := hd l;
+ out.puts(s);
+ out.puts(":\n");
+ r := mashlib->rulematch(s);
+ while (r != nil) {
+ prrule(out, hd r);
+ r = tl r;
+ }
+ l = tl l;
+ }
+}
diff --git a/appl/cmd/mash/mash.b b/appl/cmd/mash/mash.b
new file mode 100644
index 00000000..4e2f2ded
--- /dev/null
+++ b/appl/cmd/mash/mash.b
@@ -0,0 +1,154 @@
+implement Mash;
+
+#
+# mash - Inferno make/shell
+#
+# Bruce Ellis - 1Q 98
+#
+
+include "mash.m";
+include "mashparse.m";
+
+#
+# mash consists of three modules plus library modules and loadable builtins.
+#
+# This module, Mash, loads the other two (Mashparse and Mashlib), loads
+# the builtin "builtins", initializes things and calls the parser.
+#
+# It has two entry points. One is the traditional init() function and the other,
+# tkinit, is an interface to WmMash that allows the "tk" builtin to cooperate
+# with the command window.
+#
+
+Mash: module
+{
+ tkinit: fn(ctxt: ref Draw->Context, top: ref Tk->Toplevel, args: list of string);
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+Iobuf: import Bufio;
+
+sys: Sys;
+lib: Mashlib;
+parse: Mashparse;
+
+Env, Stab: import lib;
+
+cmd: string;
+
+#
+# Check for /dev/console.
+#
+isconsole(fd: ref Sys->FD): int
+{
+ (ok1, d1) := sys->fstat(fd);
+ (ok2, d2) := sys->stat(lib->CONSOLE);
+ if (ok1 < 0 || ok2 < 0)
+ return 0;
+ return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path;
+}
+
+usage(e: ref Env)
+{
+ sys->fprint(e.stderr, "usage: mash [-denx] [-c command] [src [args]]\n");
+ lib->exits("usage");
+}
+
+flags(e: ref Env, l: list of string): list of string
+{
+ while (l != nil && len hd l && (s := hd l)[0] == '-') {
+ l = tl l;
+ if (s == "--")
+ break;
+ n := len s;
+ for (i := 1; i < n; i++) {
+ case s[i] {
+ 'c' =>
+ if (++i < n) {
+ if (l != nil)
+ usage(e);
+ cmd = s[i:];
+ } else {
+ if (len l != 1)
+ usage(e);
+ cmd = hd l;
+ }
+ return nil;
+ 'd' =>
+ e.flags |= lib->EDumping;
+ 'e' =>
+ e.flags |= lib->ERaise;
+ 'n' =>
+ e.flags |= lib->ENoxeq;
+ 'x' =>
+ e.flags |= lib->EEcho;
+ * =>
+ usage(e);
+ }
+ }
+ }
+ return l;
+}
+
+tkinit(ctxt: ref Draw->Context, top: ref Tk->Toplevel, args: list of string)
+{
+ fd: ref Sys->FD;
+ sys = load Sys Sys->PATH;
+ stderr := sys->fildes(2);
+ lib = load Mashlib Mashlib->PATH;
+ if (lib == nil) {
+ sys->fprint(stderr, "could not load %s: %r\n", Mashlib->PATH);
+ exit;
+ }
+ parse = load Mashparse Mashparse->PATH;
+ if (parse == nil) {
+ sys->fprint(stderr, "could not load %s: %r\n", Mashparse->PATH);
+ exit;
+ }
+ e := Env.new();
+ e.stderr = stderr;
+ stderr = nil;
+ lib->initmash(ctxt, top, sys, e, lib, parse);
+ parse->init(lib);
+ boot := args == nil;
+ if (!boot)
+ args = flags(e, tl args);
+ e.doload(lib->LIB + lib->BUILTINS);
+ lib->prompt = "mash% ";
+ lib->contin = "\t";
+ if (cmd == nil && args == nil && !boot) {
+ e.global.assign(lib->MASHINIT, "true" :: nil);
+ fd = sys->open(lib->PROFILE, Sys->OREAD);
+ if (fd != nil) {
+ e.fopen(fd, lib->PROFILE);
+ parse->parse(e);
+ fd = nil;
+ }
+ }
+ e.global.assign(lib->MASHINIT, nil);
+ if (cmd == nil) {
+ if (args != nil) {
+ s := hd args;
+ args = tl args;
+ fd = sys->open(s, Sys->OREAD);
+ if (fd == nil)
+ e.couldnot("open", s);
+ e.fopen(fd, s);
+ e.global.assign(lib->ARGS, args);
+ }
+ if (fd == nil) {
+ fd = sys->fildes(0);
+ if (isconsole(fd))
+ e.interactive(fd);
+ e.fopen(fd, "<stdin>");
+ fd = nil;
+ }
+ } else
+ e.sopen(cmd);
+ parse->parse(e);
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ tkinit(ctxt, nil, args);
+}
diff --git a/appl/cmd/mash/mash.m b/appl/cmd/mash/mash.m
new file mode 100644
index 00000000..ae16fee6
--- /dev/null
+++ b/appl/cmd/mash/mash.m
@@ -0,0 +1,372 @@
+include "sys.m";
+include "bufio.m";
+include "draw.m";
+include "hash.m";
+include "filepat.m";
+include "regex.m";
+include "sh.m";
+include "string.m";
+include "tk.m";
+
+#
+# mash - Inferno make/shell
+#
+# Bruce Ellis - 1Q 98
+#
+
+ Rin,
+ Rout,
+ Rappend,
+ Rinout,
+ Rcount
+ : con iota; # Redirections
+
+ Icaret,
+ Iicaret,
+ Idollar,
+ Idollarq,
+ Imatch,
+ Iword,
+ Iexpr,
+ Ibackq,
+ Iquote,
+ Iinpipe,
+ Ioutpipe,
+ Iredir
+ : con iota; # Items
+
+ Csimple,
+ Cseq,
+ Cfor,
+ Cif,
+ Celse,
+ Cwhile,
+ Ccase,
+ Ccases,
+ Cmatched,
+ Cdefeq,
+ Ceq,
+ Cfn,
+ Crescue,
+ Casync,
+ Cgroup,
+ Clistgroup,
+ Csubgroup,
+ Cnop,
+ Cword,
+ Clist,
+ Ccaret,
+ Chd,
+ Clen,
+ Cnot,
+ Ctl,
+ Ccons,
+ Ceqeq,
+ Cnoteq,
+ Cmatch,
+ Cpipe,
+ Cdepend,
+ Crule,
+ Cprivate
+ : con iota; # Commands
+
+ Svalue,
+ Sfunc,
+ Sbuiltin
+ : con iota; # Symbol types
+
+Mashlib: module
+{
+ PATH: con "/dis/lib/mashlib.dis";
+
+ File: adt
+ {
+ in: ref Bufio->Iobuf;
+ name: string;
+ line: int;
+ eof: int;
+ };
+
+ Src: adt
+ {
+ line: int;
+ file: string;
+ };
+
+ Wquoted,
+ Wexpand
+ : con 1 << iota;
+
+ Word: adt
+ {
+ text: string;
+ flags: int;
+ where: Src;
+
+ word: fn(w: self ref Word, d: string): string;
+ };
+
+ Item: adt
+ {
+ op: int;
+ word: ref Word;
+ left, right: ref Item;
+ cmd: ref Cmd;
+ redir: ref Redir;
+
+ item1: fn(op: int, l: ref Item): ref Item;
+ item2: fn(op: int, l, r: ref Item): ref Item;
+ itemc: fn(op: int, c: ref Cmd): ref Item;
+ iteml: fn(l: list of string): ref Item;
+ itemr: fn(op: int, i: ref Item): ref Item;
+ itemw: fn(s: string): ref Item;
+
+ caret: fn(i: self ref Item, e: ref Env): (string, list of string, int);
+ ieval: fn(i: self ref Item, e: ref Env): (string, list of string, int);
+ ieval1: fn(i: self ref Item, e: ref Env): ref Item;
+ ieval2: fn(i: self ref Item, e: ref Env): (string, list of string, int);
+ reval: fn(i: self ref Item, e: ref Env): (int, string);
+ sword: fn(i: self ref Item, e: ref Env): ref Item;
+ text: fn(i: self ref Item): string;
+ };
+
+ Redir: adt
+ {
+ op: int;
+ word: ref Item;
+ };
+
+ Cmd: adt
+ {
+ op: int;
+ words: cyclic list of ref Item;
+ left, right: cyclic ref Cmd;
+ item: cyclic ref Item;
+ redirs: cyclic list of ref Redir;
+ value: list of string;
+ error: int;
+
+ cmd1: fn(op: int, l: ref Cmd): ref Cmd;
+ cmd2: fn(op: int, l, r: ref Cmd): ref Cmd;
+ cmd1i: fn(op: int, l: ref Cmd, i: ref Item): ref Cmd;
+ cmd1w: fn(op: int, l: ref Cmd, w: list of ref Item): ref Cmd;
+ cmde: fn(c: self ref Cmd, op: int, l, r: ref Cmd): ref Cmd;
+ cmdiw: fn(op: int, i: ref Item, w: list of ref Item): ref Cmd;
+
+ assign: fn(c: self ref Cmd, e: ref Env, def: int);
+ checkpipe: fn(c: self ref Cmd, e: ref Env, f: int): int;
+ cmdio: fn(c: self ref Cmd, e: ref Env, i: ref Item);
+ depend: fn(c: self ref Cmd, e: ref Env);
+ eeval: fn(c: self ref Cmd, e: ref Env): (string, list of string);
+ eeval1: fn(c: self ref Cmd, e: ref Env): ref Cmd;
+ eeval2: fn(c: self ref Cmd, e: ref Env): (string, list of string, int);
+ evaleq: fn(c: self ref Cmd, e: ref Env): int;
+ evalmatch: fn(c: self ref Cmd, e: ref Env): int;
+ mkcmd: fn(c: self ref Cmd, e: ref Env, async: int): ref Cmd;
+ quote: fn(c: self ref Cmd, e: ref Env, back: int): ref Item;
+ rotcases: fn(c: self ref Cmd): ref Cmd;
+ rule: fn(c: self ref Cmd, e: ref Env);
+ serve: fn(c: self ref Cmd, e: ref Env, write: int): ref Item;
+ simple: fn(c: self ref Cmd, e: ref Env, wait: int);
+ text: fn(c: self ref Cmd): string;
+ truth: fn(c: self ref Cmd, e: ref Env): int;
+ xeq: fn(c: self ref Cmd, e: ref Env);
+ xeqit: fn(c: self ref Cmd, e: ref Env, wait: int);
+ };
+
+ Depend: adt
+ {
+ targets: list of string;
+ depends: list of string;
+ op: int;
+ cmd: ref Cmd;
+ mark: int;
+ };
+
+ Target: adt
+ {
+ target: string;
+ depends: list of ref Depend;
+
+ find: fn(s: string): ref Target;
+ };
+
+ Lhs: adt
+ {
+ text: string;
+ elems: list of string;
+ count: int;
+ };
+
+ Rule: adt
+ {
+ lhs: ref Lhs;
+ rhs: ref Item;
+ op: int;
+ cmd: ref Cmd;
+
+ match: fn(r: self ref Rule, a, n: int, t: list of string): int;
+ matches: fn(r: self ref Rule, t: list of string): array of string;
+ };
+
+ SHASH: con 31; # Symbol table hash size
+ SMASK: con 16r7FFFFFFF; # Mask for SHASH bits
+
+ Symb: adt
+ {
+ name: string;
+ value: list of string;
+ func: ref Cmd;
+ builtin: Mashbuiltin;
+ tag: int;
+ };
+
+ Stab: adt
+ {
+ tab: array of list of ref Symb;
+ wmask: int;
+ copy: int;
+
+ new: fn(): ref Stab;
+ clone: fn(t: self ref Stab): ref Stab;
+ all: fn(t: self ref Stab): list of ref Symb;
+ assign: fn(t: self ref Stab, s: string, v: list of string);
+ defbuiltin: fn(t: self ref Stab, s: string, b: Mashbuiltin);
+ define: fn(t: self ref Stab, s: string, f: ref Cmd);
+ find: fn(t: self ref Stab, s: string): ref Symb;
+ func: fn(t: self ref Stab, s: string): ref Cmd;
+ update: fn(t: self ref Stab, s: string, tag: int, v: list of string, f: ref Cmd, b: Mashbuiltin): ref Symb;
+ };
+
+ ETop, EInter, EEcho, ERaise, EDumping, ENoxeq:
+ con 1 << iota;
+
+ Env: adt
+ {
+ global: ref Stab;
+ local: ref Stab;
+ flags: int;
+ in, out: ref Sys->FD;
+ stderr: ref Sys->FD;
+ wait: ref Sys->FD;
+ file: ref File;
+ args: array of string;
+ level: int;
+
+ new: fn(): ref Env;
+ clone: fn(e: self ref Env): ref Env;
+ copy: fn(e: self ref Env): ref Env;
+
+ interactive: fn(e: self ref Env, fd: ref Sys->FD);
+
+ arg: fn(e: self ref Env, s: string): string;
+ builtin: fn(e: self ref Env, s: string): Mashbuiltin;
+ defbuiltin: fn(e: self ref Env, s: string, b: Mashbuiltin);
+ define: fn(e: self ref Env, s: string, f: ref Cmd);
+ dollar: fn(e: self ref Env, s: string): ref Symb;
+ func: fn(e: self ref Env, s: string): ref Cmd;
+ let: fn(e: self ref Env, s: string, v: list of string);
+ set: fn(e: self ref Env, s: string, v: list of string);
+
+ couldnot: fn(e: self ref Env, what, who: string);
+ diag: fn(e: self ref Env, s: string): string;
+ error: fn(e: self ref Env, s: string);
+ report: fn(e: self ref Env, s: string);
+ sopen: fn(e: self ref Env, s: string);
+ suck: fn(e: self ref Env);
+ undefined: fn(e: self ref Env, s: string);
+ usage: fn(e: self ref Env, s: string);
+
+ devnull: fn(e: self ref Env): ref Sys->FD;
+ fopen: fn(e: self ref Env, fd: ref Sys->FD, s: string);
+ outfile: fn(e: self ref Env): ref Bufio->Iobuf;
+ output: fn(e: self ref Env, s: string);
+ pipe: fn(e: self ref Env): array of ref Sys->FD;
+ runit: fn(e: self ref Env, s: list of string, in, out: ref Sys->FD, wait: int);
+ serve: fn(e: self ref Env);
+ servefd: fn(e: self ref Env, fd: ref Sys->FD, write: int): string;
+ servefile: fn(e: self ref Env, n: string): (string, ref Sys->FileIO);
+
+ doload: fn(e: self ref Env, s: string);
+ lex: fn(e: self ref Env, y: ref Mashparse->YYSTYPE): int;
+ mklist: fn(e: self ref Env, l: list of ref Item): list of ref Item;
+ mksimple: fn(e: self ref Env, l: list of ref Item): ref Cmd;
+ };
+
+ initmash: fn(ctxt: ref Draw->Context, top: ref Tk->Toplevel, s: Sys, e: ref Env, l: Mashlib, p: Mashparse);
+ nonexistent: fn(s: string): int;
+
+ errstr: fn(): string;
+ exits: fn(s: string);
+ ident: fn(s: string): int;
+ initdep: fn();
+ prepareio: fn(in, out: ref sys->FD): (int, ref Sys->FD);
+ prprompt: fn(n: int);
+ quote: fn(s: string): string;
+ reap: fn();
+ revitems: fn(l: list of ref Item): list of ref Item;
+ revstrs: fn(l: list of string): list of string;
+ rulematch: fn(s: string): list of ref Rule;
+
+ ARGS: con "args";
+ BUILTINS: con "builtins.dis";
+ CHAN: con "/chan";
+ CONSOLE: con "/dev/cons";
+ DEVNULL: con "/dev/null";
+ EEXISTS: con "file exists";
+ EPIPE: con "write on closed pipe";
+ EXIT: con "exit";
+ FAILPAT: con "fail:*";
+ FAIL: con "fail:";
+ FAILLEN: con len FAIL;
+ HISTF: con "history";
+ LIB: con "/dis/lib/mash/";
+ MASHF: con "mash";
+ MASHINIT: con "mashinit";
+ PROFILE: con "/lib/mashinit";
+ TRUE: con "true";
+ MAXELEV: con 256;
+
+ sys: Sys;
+ bufio: Bufio;
+ filepat: Filepat;
+ hash: Hash;
+ regex: Regex;
+ str: String;
+ tk: Tk;
+
+ gctxt: ref Draw->Context;
+ gtop: ref Tk->Toplevel;
+
+ prompt: string;
+ contin: string;
+
+ empty: list of string;
+
+ PIDEXIT: con 0;
+
+ histchan: chan of array of byte;
+ inchan: chan of array of byte;
+ pidchan: chan of int;
+ servechan: chan of array of byte;
+ startserve: int;
+
+ rules: list of ref Rule;
+ dephash: array of list of ref Target;
+
+ parse: Mashparse;
+};
+
+#
+# Interface to loadable builtin modules. mashinit is called when a module
+# is loaded. mashcmd is called for a builtin as defined by Env.defbuiltin().
+# init() is in the interface to catch the use of builtin modules as commands.
+# name() is used by whatis.
+#
+Mashbuiltin: module
+{
+ mashinit: fn(l: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Mashlib->Env);
+ mashcmd: fn(e: ref Mashlib->Env, l: list of string);
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+ name: fn(): string;
+};
diff --git a/appl/cmd/mash/mash.y b/appl/cmd/mash/mash.y
new file mode 100644
index 00000000..2417ef51
--- /dev/null
+++ b/appl/cmd/mash/mash.y
@@ -0,0 +1,269 @@
+%{
+include "mash.m";
+
+#
+# mash parser. Thread safe.
+#
+%}
+
+%module Mashparse
+{
+ PATH: con "/dis/lib/mashparse.dis";
+
+ init: fn(l: Mashlib);
+ parse: fn(e: ref Mashlib->Env);
+
+ YYSTYPE: adt
+ {
+ cmd: ref Mashlib->Cmd;
+ item: ref Mashlib->Item;
+ items: list of ref Mashlib->Item;
+ flag: int;
+ };
+
+ YYETYPE: type ref Mashlib->Env;
+}
+
+%{
+ lib: Mashlib;
+
+ Cmd, Item, Stab, Env: import lib;
+%}
+
+%left Lcase Lfor Lif Lwhile Loffparen # low prec
+%left Lelse
+%left Lpipe
+%left Leqeq Lmatch Lnoteq
+%right Lcons
+%left Lcaret
+%left Lnot Lhd Ltl Llen
+%type <flag> term
+%type <item> item wgen witem word redir sword
+%type <items> asimple list
+%type <cmd> case cases cmd cmda cmds cmdt complex
+%type <cmd> epilog expr cbrace cobrace obrace simple
+%token <item> Lword
+%token Lbackq Lcolon Lcolonmatch Ldefeq Leq Lmatched Lquote
+%token Loncurly Lonparen Loffcurly Loffparen Lat
+%token Lgreat Lgreatgreat Lless Llessgreat
+%token Lfn Lin Lrescue
+%token Land Leof Lsemi
+%token Lerror
+
+%%
+
+script : tcmds
+ ;
+
+tcmds : # empty
+ | tcmds xeq
+ ;
+
+xeq : cmda
+ { $1.xeq(e.yyenv); }
+ | Leof
+ | error
+ ;
+
+cmdt : # empty
+ { $$ = nil; }
+ | cmdt cmda
+ { $$ = Cmd.cmd2(Cseq, $1, $2); }
+ ;
+
+cmda : cmd term
+ { $$ = $1.mkcmd(e.yyenv, $2); }
+ ;
+
+cmds : cmdt
+ | cmdt cmd
+ { $$ = Cmd.cmd2(Cseq, $1, $2.mkcmd(e.yyenv, 0)); }
+ ;
+
+cmd : simple
+ | complex
+ | cmd Lpipe cmd
+ { $$ = Cmd.cmd2(Cpipe, $1, $3); }
+ ;
+
+simple : asimple
+ { $$ = e.yyenv.mksimple($1); }
+ | asimple Lcolon list cobrace
+ {
+ $4.words = e.yyenv.mklist($3);
+ $$ = Cmd.cmd1w(Cdepend, $4, e.yyenv.mklist($1));
+ }
+ ;
+
+complex : Loncurly cmds Loffcurly epilog
+ { $$ = $4.cmde(Cgroup, $2, nil); }
+ | Lat Loncurly cmds Loffcurly epilog
+ { $$ = $5.cmde(Csubgroup, $3, nil); }
+ | Lfor Lonparen sword Lin list Loffparen cmd
+ { $$ = Cmd.cmd1i(Cfor, $7, $3); $$.words = lib->revitems($5); }
+ | Lif Lonparen expr Loffparen cmd
+ { $$ = Cmd.cmd2(Cif, $3, $5); }
+ | Lif Lonparen expr Loffparen cmd Lelse cmd
+ { $$ = Cmd.cmd2(Cif, $3, Cmd.cmd2(Celse, $5, $7)); }
+ | Lwhile Lonparen expr Loffparen cmd
+ { $$ = Cmd.cmd2(Cwhile, $3, $5); }
+ | Lcase expr Loncurly cases Loffcurly
+ { $$ = Cmd.cmd2(Ccase, $2, $4.rotcases()); }
+ | sword Leq list
+ { $$ = Cmd.cmdiw(Ceq, $1, $3); }
+ | sword Ldefeq list
+ { $$ = Cmd.cmdiw(Cdefeq, $1, $3); }
+ | Lfn word obrace
+ { $$ = Cmd.cmd1i(Cfn, $3, $2); }
+ | Lrescue word obrace
+ { $$ = Cmd.cmd1i(Crescue, $3, $2); }
+ | word Lcolonmatch word cbrace
+ {
+ $4.item = $3;
+ $$ = Cmd.cmd1i(Crule, $4, $1);
+ }
+ ;
+
+cbrace : Lcolon Loncurly cmds Loffcurly
+ { $$ = Cmd.cmd1(Clistgroup, $3); }
+ | Loncurly cmds Loffcurly
+ { $$ = Cmd.cmd1(Cgroup, $2); }
+ ;
+
+cobrace : # empty
+ { $$ = Cmd.cmd1(Cnop, nil); }
+ | cbrace
+ ;
+
+obrace : # empty
+ { $$ = nil; }
+ | Loncurly cmds Loffcurly
+ { $$ = $2; }
+ ;
+
+cases : # empty
+ { $$ = nil; }
+ | cases case
+ { $$ = Cmd.cmd2(Ccases, $1, $2); }
+ ;
+
+case : expr Lmatched cmda
+ { $$ = Cmd.cmd2(Cmatched, $1, $3); }
+ ;
+
+asimple : word
+ { $$ = $1 :: nil; }
+ | asimple item
+ { $$ = $2 :: $1; }
+ ;
+
+item : witem
+ | redir
+ ;
+
+witem : word
+ | wgen
+ ;
+
+wgen : Lbackq Loncurly cmds Loffcurly
+ { $$ = Item.itemc(Ibackq, $3); }
+ | Lquote Loncurly cmds Loffcurly
+ { $$ = Item.itemc(Iquote, $3); }
+ | Lless Loncurly cmds Loffcurly
+ { $$ = Item.itemc(Iinpipe, $3); }
+ | Lgreat Loncurly cmds Loffcurly
+ { $$ = Item.itemc(Ioutpipe, $3); }
+ ;
+
+word : Lword
+ | word Lcaret word
+ { $$ = Item.item2(Icaret, $1, $3); }
+ | Lonparen expr Loffparen
+ { $$ = Item.itemc(Iexpr, $2); }
+ ;
+
+sword : Lword
+ { $$ = $1.sword(e.yyenv); }
+ ;
+
+list : # empty
+ { $$ = nil; }
+ | list witem
+ { $$ = $2 :: $1; }
+ ;
+
+epilog : # empty
+ { $$ = ref Cmd; $$.error = 0; }
+ | epilog redir
+ { $$ = $1; $1.cmdio(e.yyenv, $2); }
+ ;
+
+redir : Lless word
+ { $$ = Item.itemr(Rin, $2); }
+ | Lgreat word
+ { $$ = Item.itemr(Rout, $2); }
+ | Lgreatgreat word
+ { $$ = Item.itemr(Rappend, $2); }
+ | Llessgreat word
+ { $$ = Item.itemr(Rinout, $2); }
+ ;
+
+term : Lsemi
+ { $$ = 0; }
+ | Leof
+ { $$ = 0; }
+ | Land
+ { $$ = 1; }
+ ;
+
+expr : Lword
+ { $$ = Cmd.cmd1i(Cword, nil, $1); }
+ | wgen
+ { $$ = Cmd.cmd1i(Cword, nil, $1); }
+ | Lonparen expr Loffparen
+ { $$ = $2; }
+ | expr Lcaret expr
+ { $$ = Cmd.cmd2(Ccaret, $1, $3); }
+ | Lhd expr
+ { $$ = Cmd.cmd1(Chd, $2); }
+ | Ltl expr
+ { $$ = Cmd.cmd1(Ctl, $2); }
+ | Llen expr
+ { $$ = Cmd.cmd1(Clen, $2); }
+ | Lnot expr
+ { $$ = Cmd.cmd1(Cnot, $2); }
+ | expr Lcons expr
+ { $$ = Cmd.cmd2(Ccons, $1, $3); }
+ | expr Leqeq expr
+ { $$ = Cmd.cmd2(Ceqeq, $1, $3); }
+ | expr Lnoteq expr
+ { $$ = Cmd.cmd2(Cnoteq, $1, $3); }
+ | expr Lmatch expr
+ { $$ = Cmd.cmd2(Cmatch, $1, $3); }
+ ;
+%%
+
+init(l: Mashlib)
+{
+ lib = l;
+}
+
+parse(e: ref Env)
+{
+ y := ref YYENV;
+ y.yyenv = e;
+ y.yysys = lib->sys;
+ y.yystderr = e.stderr;
+ yyeparse(y);
+}
+
+yyerror(e: ref YYENV, s: string)
+{
+ e.yyenv.report(s);
+ e.yyenv.suck();
+}
+
+yyelex(e: ref YYENV): int
+{
+ return e.yyenv.lex(e.yylval);
+}
diff --git a/appl/cmd/mash/mashfile b/appl/cmd/mash/mashfile
new file mode 100644
index 00000000..0357c3dc
--- /dev/null
+++ b/appl/cmd/mash/mashfile
@@ -0,0 +1,36 @@
+make -clear;
+lflags = -wg;
+
+fn lc {
+ limbo $lflags $args;
+};
+
+libsrc = depends.b dump.b exec.b expr.b lex.b misc.b serve.b symb.b xeq.b;
+bus = builtins.dis tk.dis make.dis history.dis;
+core = mash.dis mashlib.dis mashparse.dis;
+
+bulib = /dis/lib/mash;
+bulibs = $bulib/$bus;
+
+mashparse.b mashparse.m : mash.y
+{
+ eyacc -vd mash.y;
+ mv y.tab.m mashparse.m;
+ mv y.tab.b mashparse.b;
+};
+
+*.dis :~ $1.b { lc $1.b };
+$bulib/*.dis :~ $1.dis { cp $1.dis $bulib };
+/dis/*.dis :~ $1.dis { cp $1.dis /dis };
+/dis/lib/*.dis :~ $1.dis { cp $1.dis /dis/lib };
+
+$core $bus : mash.m mashparse.m;
+mashlib.dis : $libsrc;
+
+insbu : $bulibs {};
+insdis : /dis/mash.dis /dis/lib/mashlib.dis /dis/lib/mashparse.dis {};
+
+all : eyacc.dis mash.dis mashlib.dis mashparse.dis $bus {};
+install : insbu insdis {};
+
+clean : { rm mashparse.b mashparse.m *.dis };
diff --git a/appl/cmd/mash/mashlib.b b/appl/cmd/mash/mashlib.b
new file mode 100644
index 00000000..c7ac7a29
--- /dev/null
+++ b/appl/cmd/mash/mashlib.b
@@ -0,0 +1,60 @@
+implement Mashlib;
+
+#
+# Mashlib - All of the real work except for the parsing.
+#
+
+include "mash.m";
+include "mashparse.m";
+
+Iobuf: import bufio;
+HashTable, HashVal: import hash;
+
+include "depends.b";
+include "dump.b";
+include "exec.b";
+include "expr.b";
+include "lex.b";
+include "misc.b";
+include "serve.b";
+include "symb.b";
+include "xeq.b";
+
+lib: Mashlib;
+
+initmash(ctxt: ref Draw->Context, top: ref Tk->Toplevel, s: Sys, e: ref Env, l: Mashlib, p: Mashparse)
+{
+ gctxt = ctxt;
+ gtop = top;
+ sys = s;
+ lib = l;
+ parse = p;
+ if (top != nil) {
+ tk = load Tk Tk->PATH;
+ if (tk == nil)
+ e.couldnot("load", Tk->PATH);
+ }
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ e.couldnot("load", Bufio->PATH);
+ hash = load Hash Hash->PATH;
+ if (hash == nil)
+ e.couldnot("load", Hash->PATH);
+ str = load String String->PATH;
+ if (str == nil)
+ e.couldnot("load", String->PATH);
+ initlex();
+ empty = "no" :: "value" :: nil;
+ startserve = 0;
+}
+
+nonexistent(e: string): int
+{
+ errs := array[] of {"does not exist", "directory entry not found"};
+ for (i := 0; i < len errs; i++){
+ j := len errs[i];
+ if (j <= len e && e[len e-j:] == errs[i])
+ return 1;
+ }
+ return 0;
+}
diff --git a/appl/cmd/mash/mashparse.b b/appl/cmd/mash/mashparse.b
new file mode 100644
index 00000000..b154f12c
--- /dev/null
+++ b/appl/cmd/mash/mashparse.b
@@ -0,0 +1,662 @@
+implement Mashparse;
+
+#line 2 "mash.y"
+include "mash.m";
+
+#
+# mash parser. Thread safe.
+#
+Mashparse: module {
+
+ PATH: con "/dis/lib/mashparse.dis";
+
+ init: fn(l: Mashlib);
+ parse: fn(e: ref Mashlib->Env);
+
+ YYSTYPE: adt
+ {
+ cmd: ref Mashlib->Cmd;
+ item: ref Mashlib->Item;
+ items: list of ref Mashlib->Item;
+ flag: int;
+ };
+
+ YYETYPE: type ref Mashlib->Env;
+Lcase: con 57346;
+Lfor: con 57347;
+Lif: con 57348;
+Lwhile: con 57349;
+Loffparen: con 57350;
+Lelse: con 57351;
+Lpipe: con 57352;
+Leqeq: con 57353;
+Lmatch: con 57354;
+Lnoteq: con 57355;
+Lcons: con 57356;
+Lcaret: con 57357;
+Lnot: con 57358;
+Lhd: con 57359;
+Ltl: con 57360;
+Llen: con 57361;
+Lword: con 57362;
+Lbackq: con 57363;
+Lcolon: con 57364;
+Lcolonmatch: con 57365;
+Ldefeq: con 57366;
+Leq: con 57367;
+Lmatched: con 57368;
+Lquote: con 57369;
+Loncurly: con 57370;
+Lonparen: con 57371;
+Loffcurly: con 57372;
+Lat: con 57373;
+Lgreat: con 57374;
+Lgreatgreat: con 57375;
+Lless: con 57376;
+Llessgreat: con 57377;
+Lfn: con 57378;
+Lin: con 57379;
+Lrescue: con 57380;
+Land: con 57381;
+Leof: con 57382;
+Lsemi: con 57383;
+Lerror: con 57384;
+
+};
+
+#line 28 "mash.y"
+ lib: Mashlib;
+
+ Cmd, Item, Stab, Env: import lib;
+YYEOFCODE: con 1;
+YYERRCODE: con 2;
+YYMAXDEPTH: con 150;
+
+#line 244 "mash.y"
+
+
+init(l: Mashlib)
+{
+ lib = l;
+}
+
+parse(e: ref Env)
+{
+ y := ref YYENV;
+ y.yyenv = e;
+ y.yysys = lib->sys;
+ y.yystderr = e.stderr;
+ yyeparse(y);
+}
+
+yyerror(e: ref YYENV, s: string)
+{
+ e.yyenv.report(s);
+ e.yyenv.suck();
+}
+
+yyelex(e: ref YYENV): int
+{
+ return e.yyenv.lex(e.yylval);
+}
+yyexca := array[] of {-1, 1,
+ 1, -1,
+ -2, 0,
+-1, 2,
+ 1, 1,
+ -2, 0,
+-1, 21,
+ 24, 51,
+ 25, 51,
+ -2, 48,
+};
+YYNPROD: con 75;
+YYPRIVATE: con 57344;
+yytoknames: array of string;
+yystates: array of string;
+yydebug: con 0;
+YYLAST: con 249;
+yyact := array[] of {
+ 7, 20, 4, 49, 41, 47, 110, 65, 103, 95,
+ 17, 24, 32, 112, 33, 146, 142, 38, 39, 28,
+ 59, 60, 140, 129, 40, 64, 22, 46, 63, 35,
+ 36, 34, 37, 128, 127, 38, 67, 69, 70, 71,
+ 27, 26, 25, 76, 22, 75, 126, 111, 77, 74,
+ 45, 80, 81, 38, 44, 78, 88, 89, 90, 91,
+ 92, 68, 22, 98, 99, 93, 94, 32, 124, 33,
+ 97, 106, 62, 107, 38, 39, 104, 108, 109, 104,
+ 68, 40, 105, 22, 66, 105, 56, 143, 55, 116,
+ 117, 118, 119, 120, 73, 32, 32, 33, 33, 38,
+ 39, 122, 132, 36, 131, 37, 40, 123, 22, 72,
+ 125, 56, 43, 55, 135, 136, 58, 57, 133, 62,
+ 134, 139, 38, 6, 62, 16, 13, 14, 15, 141,
+ 66, 22, 96, 67, 69, 62, 32, 79, 33, 84,
+ 83, 21, 24, 61, 147, 148, 144, 24, 149, 11,
+ 22, 3, 12, 16, 13, 14, 15, 18, 2, 19,
+ 1, 5, 85, 87, 86, 84, 83, 8, 101, 21,
+ 54, 51, 52, 53, 48, 39, 9, 11, 22, 82,
+ 12, 40, 42, 50, 137, 18, 56, 19, 55, 54,
+ 51, 52, 53, 48, 39, 115, 138, 38, 39, 130,
+ 40, 10, 50, 29, 40, 56, 22, 55, 102, 56,
+ 31, 55, 85, 87, 86, 84, 83, 121, 23, 30,
+ 85, 87, 86, 84, 83, 114, 0, 145, 85, 87,
+ 86, 84, 83, 113, 0, 0, 85, 87, 86, 84,
+ 83, 100, 0, 0, 85, 87, 86, 84, 83,
+};
+yypact := array[] of {
+-1000,-1000, 121,-1000,-1000,-1000,-1000, 1,-1000,-1000,
+ -3,-1000, 84, 25, 21, -2, 173, 92, 15, 15,
+ 120,-1000, 173,-1000, 149,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000, 109,-1000, 102, 33, 15, 15,-1000, 81,
+ 66, 19, 149,-1000, 117, 173, 173, 151,-1000,-1000,
+ 173, 173, 173, 173, 173, 56, 52,-1000,-1000, 104,
+ 104, 15, 15, 233,-1000, 54,-1000, 109,-1000, 109,
+ 109, 109,-1000,-1000,-1000,-1000, 1, 17, -24,-1000,
+ 225, 217,-1000, 173, 173, 173, 173, 173, 209,-1000,
+-1000,-1000,-1000, 177, 177,-1000,-1000,-1000, 57,-1000,
+-1000,-1000,-1000,-1000, 40,-1000, 16, 4, 3, -7,
+ 70,-1000,-1000, 149, 149, 154,-1000, 125, 125, 125,
+ 125,-1000, -8,-1000,-1000, -14,-1000,-1000,-1000,-1000,
+-1000, 15, 15, 70, 79, 137, 132,-1000,-1000, 201,
+-1000, -15,-1000, 149, 149, 149,-1000, 132, 132,-1000,
+};
+yypgo := array[] of {
+ 0, 218, 203, 3, 208, 1, 199, 10, 201, 7,
+ 196, 195, 0, 2, 4, 182, 176, 6, 5, 8,
+ 168, 9, 167, 160, 158, 151,
+};
+yyr1 := array[] of {
+ 0, 23, 24, 24, 25, 25, 25, 15, 15, 13,
+ 14, 14, 12, 12, 12, 22, 22, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 19,
+ 19, 20, 20, 21, 21, 11, 11, 10, 8, 8,
+ 2, 2, 4, 4, 3, 3, 3, 3, 5, 5,
+ 5, 7, 9, 9, 17, 17, 6, 6, 6, 6,
+ 1, 1, 1, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18,
+};
+yyr2 := array[] of {
+ 0, 1, 0, 2, 1, 1, 1, 0, 2, 2,
+ 1, 2, 1, 1, 3, 1, 4, 4, 5, 7,
+ 5, 7, 5, 5, 3, 3, 3, 3, 4, 4,
+ 3, 0, 1, 0, 3, 0, 2, 3, 1, 2,
+ 1, 1, 1, 1, 4, 4, 4, 4, 1, 3,
+ 3, 1, 0, 2, 0, 2, 2, 2, 2, 2,
+ 1, 1, 1, 1, 1, 3, 3, 2, 2, 2,
+ 2, 3, 3, 3, 3,
+};
+yychk := array[] of {
+-1000, -23, -24, -25, -13, 40, 2, -12, -22, -16,
+ -8, 28, 31, 5, 6, 7, 4, -7, 36, 38,
+ -5, 20, 29, -1, 10, 41, 40, 39, 22, -2,
+ -4, -6, -5, -3, 34, 32, 33, 35, 20, 21,
+ 27, -14, -15, 28, 29, 29, 29, -18, 20, -3,
+ 29, 17, 18, 19, 16, 34, 32, 25, 24, -5,
+ -5, 23, 15, -18, -12, -9, 28, -5, 28, -5,
+ -5, -5, 28, 28, 30, -13, -12, -14, -7, 20,
+ -18, -18, 28, 15, 14, 11, 13, 12, -18, -18,
+ -18, -18, -18, -9, -9, -21, 28, -21, -5, -5,
+ 8, -20, -4, -19, 22, 28, -14, -14, -14, -14,
+ -17, 30, 37, 8, 8, -11, -18, -18, -18, -18,
+ -18, 8, -14, -19, 28, -14, 30, 30, 30, 30,
+ -6, 34, 32, -17, -9, -12, -12, 30, -10, -18,
+ 30, -14, 30, 8, 9, 26, 30, -12, -12, -13,
+};
+yydef := array[] of {
+ 2, -2, -2, 3, 4, 5, 6, 0, 12, 13,
+ 15, 7, 0, 0, 0, 0, 0, 0, 0, 0,
+ 38, -2, 0, 9, 0, 60, 61, 62, 52, 39,
+ 40, 41, 42, 43, 0, 0, 0, 0, 48, 0,
+ 0, 0, 10, 7, 0, 0, 0, 0, 63, 64,
+ 0, 0, 0, 0, 0, 0, 0, 52, 52, 33,
+ 33, 0, 0, 0, 14, 31, 7, 56, 7, 57,
+ 58, 59, 7, 7, 54, 8, 11, 0, 0, 51,
+ 0, 0, 35, 0, 0, 0, 0, 0, 0, 67,
+ 68, 69, 70, 24, 25, 26, 7, 27, 0, 49,
+ 50, 16, 53, 32, 0, 7, 0, 0, 0, 0,
+ 17, 54, 52, 0, 0, 0, 66, 71, 72, 73,
+ 74, 65, 0, 28, 7, 0, 46, 47, 44, 45,
+ 55, 0, 0, 18, 0, 20, 22, 23, 36, 0,
+ 34, 0, 30, 0, 0, 0, 29, 19, 21, 37,
+};
+yytok1 := array[] of {
+ 1,
+};
+yytok2 := array[] of {
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
+ 42,
+};
+yytok3 := array[] of {
+ 0
+};
+
+YYFLAG: con -1000;
+
+# parser for yacc output
+YYENV: adt
+{
+ yylval: ref YYSTYPE; # lexical value
+ yyval: YYSTYPE; # goto value
+ yyenv: YYETYPE; # useer environment
+ yynerrs: int; # number of errors
+ yyerrflag: int; # error recovery flag
+ yysys: Sys;
+ yystderr: ref Sys->FD;
+};
+
+yytokname(yyc: int): string
+{
+ if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil)
+ return yytoknames[yyc-1];
+ return "<"+string yyc+">";
+}
+
+yystatname(yys: int): string
+{
+ if(yys >= 0 && yys < len yystates && yystates[yys] != nil)
+ return yystates[yys];
+ return "<"+string yys+">\n";
+}
+
+yylex1(e: ref YYENV): int
+{
+ c, yychar : int;
+ yychar = yyelex(e);
+ if(yychar <= 0)
+ c = yytok1[0];
+ else if(yychar < len yytok1)
+ c = yytok1[yychar];
+ else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2)
+ c = yytok2[yychar-YYPRIVATE];
+ else{
+ n := len yytok3;
+ c = 0;
+ for(i := 0; i < n; i+=2) {
+ if(yytok3[i+0] == yychar) {
+ c = yytok3[i+1];
+ break;
+ }
+ }
+ if(c == 0)
+ c = yytok2[1]; # unknown char
+ }
+ if(yydebug >= 3)
+ e.yysys->fprint(e.yystderr, "lex %.4ux %s\n", yychar, yytokname(c));
+ return c;
+}
+
+YYS: adt
+{
+ yyv: YYSTYPE;
+ yys: int;
+};
+
+yyparse(): int
+{
+ return yyeparse(nil);
+}
+
+yyeparse(e: ref YYENV): int
+{
+ if(e == nil)
+ e = ref YYENV;
+ if(e.yylval == nil)
+ e.yylval = ref YYSTYPE;
+ if(e.yysys == nil) {
+ e.yysys = load Sys "$Sys";
+ e.yystderr = e.yysys->fildes(2);
+ }
+
+ yys := array[YYMAXDEPTH] of YYS;
+
+ yystate := 0;
+ yychar := -1;
+ e.yynerrs = 0;
+ e.yyerrflag = 0;
+ yyp := -1;
+ yyn := 0;
+
+yystack:
+ for(;;){
+ # put a state and value onto the stack
+ if(yydebug >= 4)
+ e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+ yyp++;
+ if(yyp >= YYMAXDEPTH) {
+ yyerror(e, "yacc stack overflow");
+ yyn = 1;
+ break yystack;
+ }
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = e.yyval;
+
+ for(;;){
+ yyn = yypact[yystate];
+ if(yyn > YYFLAG) { # simple state
+ if(yychar < 0)
+ yychar = yylex1(e);
+ yyn += yychar;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yyn = yyact[yyn];
+ if(yychk[yyn] == yychar) { # valid shift
+ yychar = -1;
+ yyp++;
+ if(yyp >= YYMAXDEPTH) {
+ yyerror(e, "yacc stack overflow");
+ yyn = 1;
+ break yystack;
+ }
+ yystate = yyn;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = *e.yylval;
+ if(e.yyerrflag > 0)
+ e.yyerrflag--;
+ if(yydebug >= 4)
+ e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+ continue;
+ }
+ }
+ }
+
+ # default state action
+ yyn = yydef[yystate];
+ if(yyn == -2) {
+ if(yychar < 0)
+ yychar = yylex1(e);
+
+ # look through exception table
+ for(yyxi:=0;; yyxi+=2)
+ if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate)
+ break;
+ for(yyxi += 2;; yyxi += 2) {
+ yyn = yyexca[yyxi];
+ if(yyn < 0 || yyn == yychar)
+ break;
+ }
+ yyn = yyexca[yyxi+1];
+ if(yyn < 0){
+ yyn = 0;
+ break yystack;
+ }
+ }
+
+ if(yyn != 0)
+ break;
+
+ # error ... attempt to resume parsing
+ if(e.yyerrflag == 0) { # brand new error
+ yyerror(e, "syntax error");
+ e.yynerrs++;
+ if(yydebug >= 1) {
+ e.yysys->fprint(e.yystderr, "%s", yystatname(yystate));
+ e.yysys->fprint(e.yystderr, "saw %s\n", yytokname(yychar));
+ }
+ }
+
+ if(e.yyerrflag != 3) { # incompletely recovered error ... try again
+ e.yyerrflag = 3;
+
+ # find a state where "error" is a legal shift action
+ while(yyp >= 0) {
+ yyn = yypact[yys[yyp].yys] + YYERRCODE;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yystate = yyact[yyn]; # simulate a shift of "error"
+ if(yychk[yystate] == YYERRCODE) {
+ yychar = -1;
+ continue yystack;
+ }
+ }
+
+ # the current yyp has no shift on "error", pop stack
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "error recovery pops state %d, uncovers %d\n",
+ yys[yyp].yys, yys[yyp-1].yys );
+ yyp--;
+ }
+ # there is no state on the stack with an error shift ... abort
+ yyn = 1;
+ break yystack;
+ }
+
+ # no shift yet; clobber input char
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "error recovery discards %s\n", yytokname(yychar));
+ if(yychar == YYEOFCODE) {
+ yyn = 1;
+ break yystack;
+ }
+ yychar = -1;
+ # try again in the same state
+ }
+
+ # reduction by production yyn
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+ yypt := yyp;
+ yyp -= yyr2[yyn];
+# yyval = yys[yyp+1].yyv;
+ yym := yyn;
+
+ # consult goto table to find next state
+ yyn = yyr1[yyn];
+ yyg := yypgo[yyn];
+ yyj := yyg + yys[yyp].yys + 1;
+
+ if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+ yystate = yyact[yyg];
+ case yym {
+
+4=>
+#line 63 "mash.y"
+{ yys[yypt-0].yyv.cmd.xeq(e.yyenv); }
+7=>
+#line 69 "mash.y"
+{ e.yyval.cmd = nil; }
+8=>
+#line 71 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cseq, yys[yypt-1].yyv.cmd, yys[yypt-0].yyv.cmd); }
+9=>
+#line 75 "mash.y"
+{ e.yyval.cmd = yys[yypt-1].yyv.cmd.mkcmd(e.yyenv, yys[yypt-0].yyv.flag); }
+10=>
+e.yyval.cmd = yys[yyp+1].yyv.cmd;
+11=>
+#line 80 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cseq, yys[yypt-1].yyv.cmd, yys[yypt-0].yyv.cmd.mkcmd(e.yyenv, 0)); }
+12=>
+e.yyval.cmd = yys[yyp+1].yyv.cmd;
+13=>
+e.yyval.cmd = yys[yyp+1].yyv.cmd;
+14=>
+#line 86 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cpipe, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+15=>
+#line 90 "mash.y"
+{ e.yyval.cmd = e.yyenv.mksimple(yys[yypt-0].yyv.items); }
+16=>
+#line 92 "mash.y"
+{
+ yys[yypt-0].yyv.cmd.words = e.yyenv.mklist(yys[yypt-1].yyv.items);
+ e.yyval.cmd = Cmd.cmd1w(Cdepend, yys[yypt-0].yyv.cmd, e.yyenv.mklist(yys[yypt-3].yyv.items));
+ }
+17=>
+#line 99 "mash.y"
+{ e.yyval.cmd = yys[yypt-0].yyv.cmd.cmde(Cgroup, yys[yypt-2].yyv.cmd, nil); }
+18=>
+#line 101 "mash.y"
+{ e.yyval.cmd = yys[yypt-0].yyv.cmd.cmde(Csubgroup, yys[yypt-2].yyv.cmd, nil); }
+19=>
+#line 103 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1i(Cfor, yys[yypt-0].yyv.cmd, yys[yypt-4].yyv.item); e.yyval.cmd.words = lib->revitems(yys[yypt-2].yyv.items); }
+20=>
+#line 105 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cif, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+21=>
+#line 107 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cif, yys[yypt-4].yyv.cmd, Cmd.cmd2(Celse, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd)); }
+22=>
+#line 109 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cwhile, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+23=>
+#line 111 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Ccase, yys[yypt-3].yyv.cmd, yys[yypt-1].yyv.cmd.rotcases()); }
+24=>
+#line 113 "mash.y"
+{ e.yyval.cmd = Cmd.cmdiw(Ceq, yys[yypt-2].yyv.item, yys[yypt-0].yyv.items); }
+25=>
+#line 115 "mash.y"
+{ e.yyval.cmd = Cmd.cmdiw(Cdefeq, yys[yypt-2].yyv.item, yys[yypt-0].yyv.items); }
+26=>
+#line 117 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1i(Cfn, yys[yypt-0].yyv.cmd, yys[yypt-1].yyv.item); }
+27=>
+#line 119 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1i(Crescue, yys[yypt-0].yyv.cmd, yys[yypt-1].yyv.item); }
+28=>
+#line 121 "mash.y"
+{
+ yys[yypt-0].yyv.cmd.item = yys[yypt-1].yyv.item;
+ e.yyval.cmd = Cmd.cmd1i(Crule, yys[yypt-0].yyv.cmd, yys[yypt-3].yyv.item);
+ }
+29=>
+#line 128 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Clistgroup, yys[yypt-1].yyv.cmd); }
+30=>
+#line 130 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Cgroup, yys[yypt-1].yyv.cmd); }
+31=>
+#line 134 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Cnop, nil); }
+32=>
+e.yyval.cmd = yys[yyp+1].yyv.cmd;
+33=>
+#line 139 "mash.y"
+{ e.yyval.cmd = nil; }
+34=>
+#line 141 "mash.y"
+{ e.yyval.cmd = yys[yypt-1].yyv.cmd; }
+35=>
+#line 145 "mash.y"
+{ e.yyval.cmd = nil; }
+36=>
+#line 147 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Ccases, yys[yypt-1].yyv.cmd, yys[yypt-0].yyv.cmd); }
+37=>
+#line 151 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cmatched, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+38=>
+#line 155 "mash.y"
+{ e.yyval.items = yys[yypt-0].yyv.item :: nil; }
+39=>
+#line 157 "mash.y"
+{ e.yyval.items = yys[yypt-0].yyv.item :: yys[yypt-1].yyv.items; }
+40=>
+e.yyval.item = yys[yyp+1].yyv.item;
+41=>
+e.yyval.item = yys[yyp+1].yyv.item;
+42=>
+e.yyval.item = yys[yyp+1].yyv.item;
+43=>
+e.yyval.item = yys[yyp+1].yyv.item;
+44=>
+#line 169 "mash.y"
+{ e.yyval.item = Item.itemc(Ibackq, yys[yypt-1].yyv.cmd); }
+45=>
+#line 171 "mash.y"
+{ e.yyval.item = Item.itemc(Iquote, yys[yypt-1].yyv.cmd); }
+46=>
+#line 173 "mash.y"
+{ e.yyval.item = Item.itemc(Iinpipe, yys[yypt-1].yyv.cmd); }
+47=>
+#line 175 "mash.y"
+{ e.yyval.item = Item.itemc(Ioutpipe, yys[yypt-1].yyv.cmd); }
+48=>
+e.yyval.item = yys[yyp+1].yyv.item;
+49=>
+#line 180 "mash.y"
+{ e.yyval.item = Item.item2(Icaret, yys[yypt-2].yyv.item, yys[yypt-0].yyv.item); }
+50=>
+#line 182 "mash.y"
+{ e.yyval.item = Item.itemc(Iexpr, yys[yypt-1].yyv.cmd); }
+51=>
+#line 186 "mash.y"
+{ e.yyval.item = yys[yypt-0].yyv.item.sword(e.yyenv); }
+52=>
+#line 190 "mash.y"
+{ e.yyval.items = nil; }
+53=>
+#line 192 "mash.y"
+{ e.yyval.items = yys[yypt-0].yyv.item :: yys[yypt-1].yyv.items; }
+54=>
+#line 196 "mash.y"
+{ e.yyval.cmd = ref Cmd; e.yyval.cmd.error = 0; }
+55=>
+#line 198 "mash.y"
+{ e.yyval.cmd = yys[yypt-1].yyv.cmd; yys[yypt-1].yyv.cmd.cmdio(e.yyenv, yys[yypt-0].yyv.item); }
+56=>
+#line 202 "mash.y"
+{ e.yyval.item = Item.itemr(Rin, yys[yypt-0].yyv.item); }
+57=>
+#line 204 "mash.y"
+{ e.yyval.item = Item.itemr(Rout, yys[yypt-0].yyv.item); }
+58=>
+#line 206 "mash.y"
+{ e.yyval.item = Item.itemr(Rappend, yys[yypt-0].yyv.item); }
+59=>
+#line 208 "mash.y"
+{ e.yyval.item = Item.itemr(Rinout, yys[yypt-0].yyv.item); }
+60=>
+#line 212 "mash.y"
+{ e.yyval.flag = 0; }
+61=>
+#line 214 "mash.y"
+{ e.yyval.flag = 0; }
+62=>
+#line 216 "mash.y"
+{ e.yyval.flag = 1; }
+63=>
+#line 220 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1i(Cword, nil, yys[yypt-0].yyv.item); }
+64=>
+#line 222 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1i(Cword, nil, yys[yypt-0].yyv.item); }
+65=>
+#line 224 "mash.y"
+{ e.yyval.cmd = yys[yypt-1].yyv.cmd; }
+66=>
+#line 226 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Ccaret, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+67=>
+#line 228 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Chd, yys[yypt-0].yyv.cmd); }
+68=>
+#line 230 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Ctl, yys[yypt-0].yyv.cmd); }
+69=>
+#line 232 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Clen, yys[yypt-0].yyv.cmd); }
+70=>
+#line 234 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Cnot, yys[yypt-0].yyv.cmd); }
+71=>
+#line 236 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Ccons, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+72=>
+#line 238 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Ceqeq, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+73=>
+#line 240 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cnoteq, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+74=>
+#line 242 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cmatch, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+ }
+ }
+
+ return yyn;
+}
diff --git a/appl/cmd/mash/mashparse.m b/appl/cmd/mash/mashparse.m
new file mode 100644
index 00000000..157c2f54
--- /dev/null
+++ b/appl/cmd/mash/mashparse.m
@@ -0,0 +1,56 @@
+Mashparse: module {
+
+ PATH: con "/dis/lib/mashparse.dis";
+
+ init: fn(l: Mashlib);
+ parse: fn(e: ref Mashlib->Env);
+
+ YYSTYPE: adt
+ {
+ cmd: ref Mashlib->Cmd;
+ item: ref Mashlib->Item;
+ items: list of ref Mashlib->Item;
+ flag: int;
+ };
+
+ YYETYPE: type ref Mashlib->Env;
+Lcase: con 57346;
+Lfor: con 57347;
+Lif: con 57348;
+Lwhile: con 57349;
+Loffparen: con 57350;
+Lelse: con 57351;
+Lpipe: con 57352;
+Leqeq: con 57353;
+Lmatch: con 57354;
+Lnoteq: con 57355;
+Lcons: con 57356;
+Lcaret: con 57357;
+Lnot: con 57358;
+Lhd: con 57359;
+Ltl: con 57360;
+Llen: con 57361;
+Lword: con 57362;
+Lbackq: con 57363;
+Lcolon: con 57364;
+Lcolonmatch: con 57365;
+Ldefeq: con 57366;
+Leq: con 57367;
+Lmatched: con 57368;
+Lquote: con 57369;
+Loncurly: con 57370;
+Lonparen: con 57371;
+Loffcurly: con 57372;
+Lat: con 57373;
+Lgreat: con 57374;
+Lgreatgreat: con 57375;
+Lless: con 57376;
+Llessgreat: con 57377;
+Lfn: con 57378;
+Lin: con 57379;
+Lrescue: con 57380;
+Land: con 57381;
+Leof: con 57382;
+Lsemi: con 57383;
+Lerror: con 57384;
+};
diff --git a/appl/cmd/mash/misc.b b/appl/cmd/mash/misc.b
new file mode 100644
index 00000000..749f8be2
--- /dev/null
+++ b/appl/cmd/mash/misc.b
@@ -0,0 +1,313 @@
+#
+# Miscellaneous routines.
+#
+
+Cmd.cmd1(op: int, l: ref Cmd): ref Cmd
+{
+ return ref Cmd(op, nil, l, nil, nil, nil, nil, 0);
+}
+
+Cmd.cmd2(op: int, l, r: ref Cmd): ref Cmd
+{
+ return ref Cmd(op, nil, l, r, nil, nil, nil, 0);
+}
+
+Cmd.cmd1i(op: int, l: ref Cmd, i: ref Item): ref Cmd
+{
+ return ref Cmd(op, nil, l, nil, i, nil, nil, 0);
+}
+
+Cmd.cmd1w(op: int, l: ref Cmd, w: list of ref Item): ref Cmd
+{
+ return ref Cmd(op, w, l, nil, nil, nil, nil, 0);
+}
+
+Cmd.cmde(c: self ref Cmd, op: int, l, r: ref Cmd): ref Cmd
+{
+ c.op = op;
+ c.left = l;
+ c.right = r;
+ return c;
+}
+
+Cmd.cmdiw(op: int, i: ref Item, w: list of ref Item): ref Cmd
+{
+ return ref Cmd(op, revitems(w), nil, nil, i, nil, nil, 0);
+}
+
+Pin, Pout: con 1 << iota;
+
+rdmap := array[] of
+{
+ Rin => Pin,
+ Rout or Rappend => Pout,
+ Rinout => Pin | Pout,
+};
+
+rdsymbs := array[] of
+{
+ Rin => "<",
+ Rout => ">",
+ Rappend => ">>",
+ Rinout => "<>",
+};
+
+ionames := array[] of
+{
+ Pin => "input",
+ Pout => "ouput",
+ Pin | Pout => "input/output",
+};
+
+#
+# Check a pipeline for ambiguities.
+#
+Cmd.checkpipe(c: self ref Cmd, e: ref Env, f: int): int
+{
+ if (c.error)
+ return 0;
+ if (c.op == Cpipe) {
+ if (!c.left.checkpipe(e, f | Pout))
+ return 0;
+ if (!c.right.checkpipe(e, f | Pin))
+ return 0;
+ }
+ if (f) {
+ t := 0;
+ for (l := c.redirs; l != nil; l = tl l)
+ t |= rdmap[(hd l).op];
+ f &= t;
+ if (f) {
+ e.report(sys->sprint("%s redirection conflicts with pipe", ionames[f]));
+ return 0;
+ }
+ }
+ return 1;
+}
+
+#
+# Update a command with another redirection.
+#
+Cmd.cmdio(c: self ref Cmd, e: ref Env, i: ref Item)
+{
+ f := 0;
+ for (l := c.redirs; l != nil; l = tl l)
+ f |= rdmap[(hd l).op];
+ r := i.redir;
+ f &= rdmap[r.op];
+ if (f != 0) {
+ e.report(sys->sprint("repeat %s redirection", ionames[f]));
+ c.error = 1;
+ }
+ c.redirs = r :: c.redirs;
+}
+
+#
+# Make a basic command.
+#
+Cmd.mkcmd(c: self ref Cmd, e: ref Env, async: int): ref Cmd
+{
+ if (!c.checkpipe(e, 0))
+ return nil;
+ if (async)
+ return ref Cmd(Casync, nil, c, nil, nil, nil, nil, 0);
+ else
+ return c;
+}
+
+#
+# Rotate parse tree of cases.
+#
+Cmd.rotcases(c: self ref Cmd): ref Cmd
+{
+ l := c;
+ c = nil;
+ while (l != nil) {
+ t := l.right;
+ l.right = c;
+ c = l;
+ l = l.left;
+ c.left = t;
+ }
+ return c;
+}
+
+Item.item1(op: int, l: ref Item): ref Item
+{
+ return ref Item(op, nil, l, nil, nil, nil);
+}
+
+Item.item2(op: int, l, r: ref Item): ref Item
+{
+ return ref Item(op, nil, l, r, nil, nil);
+}
+
+Item.itemc(op: int, c: ref Cmd): ref Item
+{
+ return ref Item(op, nil, nil, nil, c, nil);
+}
+
+#
+# Make an item from a list of strings.
+#
+Item.iteml(l: list of string): ref Item
+{
+ if (l != nil && tl l == nil)
+ return Item.itemw(hd l);
+ r: list of string;
+ while (l != nil) {
+ r = (hd l) :: r;
+ l = tl l;
+ }
+ c := ref Cmd;
+ c.op = Clist;
+ c.value = revstrs(r);
+ return Item.itemc(Iexpr, c);
+}
+
+Item.itemr(op: int, i: ref Item): ref Item
+{
+ return ref Item(Iredir, nil, nil, nil, nil, ref Redir(op, i));
+}
+
+qword: Word = (nil, Wquoted, (0, nil));
+
+Item.itemw(s: string): ref Item
+{
+ w := ref qword;
+ w.text = s;
+ return ref Item(Iword, w, nil, nil, nil, nil);
+}
+
+revitems(l: list of ref Item): list of ref Item
+{
+ r: list of ref Item;
+ while (l != nil) {
+ r = (hd l) :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+revstrs(l: list of string): list of string
+{
+ r: list of string;
+ while (l != nil) {
+ r = (hd l) :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+prepend(l: list of string, r: list of string): list of string
+{
+ while (r != nil) {
+ l = (hd r) :: l;
+ r = tl r;
+ }
+ return l;
+}
+
+concat(l: list of string): string
+{
+ s := hd l;
+ for (;;) {
+ l = tl l;
+ if (l == nil)
+ return s;
+ s += " ";
+ s += hd l;
+ }
+}
+
+#
+# Make an item list, no redirections allowed.
+#
+Env.mklist(e: self ref Env, l: list of ref Item): list of ref Item
+{
+ r: list of ref Item;
+ while (l != nil) {
+ i := hd l;
+ if (i.op == Iredir)
+ e.report("redirection in list");
+ else
+ r = i :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+#
+# Make a simple command.
+#
+Env.mksimple(e: self ref Env, l: list of ref Item): ref Cmd
+{
+ r: list of ref Item;
+ c := ref Cmd;
+ c.op = Csimple;
+ c.error = 0;
+ while (l != nil) {
+ i := hd l;
+ if (i.op == Iredir)
+ c.cmdio(e, i);
+ else
+ r = i :: r;
+ l = tl l;
+ }
+ c.words = r;
+ return c;
+}
+
+Env.diag(e: self ref Env, s: string): string
+{
+ return where(e) + s;
+}
+
+Env.usage(e: self ref Env, s: string)
+{
+ e.report("usage: " + s);
+}
+
+Env.report(e: self ref Env, s: string)
+{
+ sys->fprint(e.stderr, "%s\n", e.diag(s));
+ if (e.flags & ERaise)
+ exits("error");
+}
+
+Env.error(e: self ref Env, s: string)
+{
+ e.report(s);
+ cleanup();
+}
+
+panic(s: string)
+{
+ raise "panic: " + s;
+}
+
+prprompt(n: int)
+{
+ case n {
+ 0 =>
+ sys->print("%s", prompt);
+ 1 =>
+ sys->print("%s", contin);
+ }
+}
+
+Env.couldnot(e: self ref Env, what, who: string)
+{
+ sys->fprint(e.stderr, "could not %s %s: %r\n", what, who);
+ exits("system error");
+}
+
+cleanup()
+{
+ exit;
+}
+
+exits(s: string)
+{
+ raise "fail: mash " + s;
+}
diff --git a/appl/cmd/mash/mkfile b/appl/cmd/mash/mkfile
new file mode 100644
index 00000000..942f7b38
--- /dev/null
+++ b/appl/cmd/mash/mkfile
@@ -0,0 +1,78 @@
+<../../../mkconfig
+
+TARG= mash.dis\
+ mashlib.dis\
+ mashparse.dis\
+ builtins.dis\
+ history.dis\
+ make.dis\
+
+INS= $ROOT/dis/mash.dis\
+ $ROOT/dis/lib/mashlib.dis\
+ $ROOT/dis/lib/mashparse.dis\
+ $ROOT/dis/lib/mash/builtins.dis\
+ $ROOT/dis/lib/mash/history.dis\
+ $ROOT/dis/lib/mash/make.dis\
+
+MODULES=\
+ mash.m\
+ mashparse.m\
+
+SYSMODULES=\
+ bufio.m\
+ draw.m\
+ filepat.m\
+ hash.m\
+ regex.m\
+ sh.m\
+ string.m\
+ sys.m\
+
+LIBSRC=\
+ depends.b\
+ dump.b\
+ exec.b\
+ expr.b\
+ lex.b\
+ misc.b\
+ serve.b\
+ symb.b\
+ xeq.b\
+
+all:V: $TARG
+
+install:V: $INS
+
+nuke:V: clean
+ rm -f $INS
+
+clean:V:
+ rm -f *.dis *.sbl
+
+uninstall:V:
+ rm -f $INS
+
+MODDIR=$ROOT/module
+SYS_MODULE=${SYSMODULES:%=$MODDIR/%}
+LIMBOFLAGS=-I$MODDIR
+
+$ROOT/dis/mash.dis: mash.dis
+ rm -f $ROOT/dis/mash.dis && cp mash.dis $ROOT/dis/mash.dis
+
+$ROOT/dis/lib/mashlib.dis: mashlib.dis
+ rm -f $ROOT/dis/mashlib.dis && cp mashlib.dis $ROOT/dis/lib/mashlib.dis
+
+$ROOT/dis/lib/mashparse.dis: mashparse.dis
+ rm -f $ROOT/dis/mashparse.dis && cp mashparse.dis $ROOT/dis/lib/mashparse.dis
+
+$ROOT/dis/lib/mash/%.dis: %.dis
+ rm -f $ROOT/dis/$stem.dis && cp $stem.dis $ROOT/dis/lib/mash/$stem.dis
+
+%.dis: $MODULES $SYS_MODULE
+mashlib.dis: $LIBSRC
+
+%.dis: %.b
+ limbo $LIMBOFLAGS -gw $stem.b
+
+%.s: %.b
+ limbo $LIMBOFLAGS -w -G -S $stem.b
diff --git a/appl/cmd/mash/serve.b b/appl/cmd/mash/serve.b
new file mode 100644
index 00000000..e293a8f4
--- /dev/null
+++ b/appl/cmd/mash/serve.b
@@ -0,0 +1,154 @@
+#
+# This should be called by spawned (persistent) threads.
+# It arranges for them to be killed at the end of the day.
+#
+reap()
+{
+ if (pidchan == nil) {
+ pidchan = chan of int;
+ spawn zombie();
+ }
+ pidchan <-= sys->pctl(0, nil);
+}
+
+#
+# This thread records spawned threads and kills them.
+#
+zombie()
+{
+ pids := array[10] of int;
+ pidx := 0;
+ for (;;) {
+ pid := <- pidchan;
+ if (pid == PIDEXIT) {
+ for (i := 0; i < pidx; i++)
+ kill(pids[i]);
+ exit;
+ }
+ if (pidx == len pids) {
+ n := pidx * 3 / 2;
+ a := array[n] of int;
+ a[:] = pids;
+ pids = a;
+ }
+ pids[pidx++] = pid;
+ }
+}
+
+#
+# Kill a thread.
+#
+kill(pid: int)
+{
+ fd := sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
+ if (fd != nil)
+ sys->fprint(fd, "kill");
+}
+
+#
+# Exit top level, killing spawned threads.
+#
+exitmash()
+{
+ if (pidchan != nil)
+ pidchan <-= PIDEXIT;
+ exit;
+}
+
+#
+# Slice a buffer if needed.
+#
+restrict(buff: array of byte, count: int): array of byte
+{
+ if (count < len buff)
+ return buff[:count];
+ else
+ return buff;
+}
+
+#
+# Serve mash console reads. Favours other programs
+# ahead of the input loop.
+#
+serve_read(c: ref Sys->FileIO, sync: chan of int)
+{
+ s: string;
+ in := sys->fildes(0);
+ sys->pctl(Sys->NEWFD, in.fd :: nil);
+ sync <-= 0;
+ reap();
+ buff := array[Sys->ATOMICIO] of byte;
+outer: for (;;) {
+ n := sys->read(in, buff, len buff);
+ if (n < 0) {
+ n = 0;
+ s = errstr();
+ } else
+ s = nil;
+ b := buff[:n];
+ alt {
+ (off, count, fid, rc) := <-c.read =>
+ if (rc == nil)
+ break;
+ rc <-= (restrict(b, count), s);
+ continue outer;
+ * =>
+ ;
+ }
+ inner: for (;;) {
+ alt {
+ (off, count, fid, rc) := <-c.read =>
+ if (rc == nil)
+ continue inner;
+ rc <-= (restrict(b, count), s);
+ inchan <-= b =>
+ ;
+ }
+ break;
+ }
+ }
+}
+
+#
+# Serve mash console writes.
+#
+serve_write(c: ref Sys->FileIO, sync: chan of int)
+{
+ out := sys->fildes(1);
+ sys->pctl(Sys->NEWFD, out.fd :: nil);
+ sync <-= 0;
+ reap();
+ for (;;) {
+ (off, data, fid, wc) := <-c.write;
+ if (wc == nil)
+ continue;
+ if (sys->write(out, data, len data) < 0)
+ wc <-= (0, errstr());
+ else
+ wc <-= (len data, nil);
+ }
+}
+
+#
+# Begin serving the mash console.
+#
+Env.serve(e: self ref Env)
+{
+ if (servechan != nil)
+ return;
+ (s, c) := e.servefile(nil);
+ inchan = chan of array of byte;
+ servechan = chan of array of byte;
+ sync := chan of int;
+ spawn serve_read(c, sync);
+ spawn serve_write(c, sync);
+ <-sync;
+ <-sync;
+ if (sys->bind(s, CONSOLE, Sys->MREPL) < 0)
+ e.couldnot("bind", CONSOLE);
+ sys->pctl(Sys->NEWFD, nil);
+ e.in = sys->open(CONSOLE, sys->OREAD | sys->ORCLOSE);
+ e.out = sys->open(CONSOLE, sys->OWRITE);
+ e.stderr = sys->open(CONSOLE, sys->OWRITE);
+ e.wait = nil;
+}
diff --git a/appl/cmd/mash/symb.b b/appl/cmd/mash/symb.b
new file mode 100644
index 00000000..8d317b37
--- /dev/null
+++ b/appl/cmd/mash/symb.b
@@ -0,0 +1,265 @@
+#
+# Symbol table routines. A symbol table becomes copy-on-write
+# when it is cloned. The first modification will copy the hash table.
+# Every list is then copied on first modification.
+#
+
+#
+# Copy a hash list.
+#
+cpsymbs(l: list of ref Symb): list of ref Symb
+{
+ r: list of ref Symb;
+ while (l != nil) {
+ r = (ref *hd l) :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+#
+# New symbol table.
+#
+Stab.new(): ref Stab
+{
+ return ref Stab(array[SHASH] of list of ref Symb, 0, 0);
+}
+
+#
+# Clone a symbol table. Copy Stab and mark contents copy-on-write.
+#
+Stab.clone(t: self ref Stab): ref Stab
+{
+ t.copy = 1;
+ t.wmask = SMASK;
+ return ref *t;
+}
+
+#
+# Update symbol table entry, or add new entry.
+#
+Stab.update(t: self ref Stab, s: string, tag: int, v: list of string, f: ref Cmd, b: Mashbuiltin): ref Symb
+{
+ if (t.copy) {
+ a := array[SHASH] of list of ref Symb;
+ a[:] = t.tab[:];
+ t.tab = a;
+ t.copy = 0;
+ }
+ x := hash->fun1(s, SHASH);
+ l := t.tab[x];
+ if (t.wmask & (1 << x)) {
+ l = cpsymbs(l);
+ t.tab[x] = l;
+ t.wmask &= ~(1 << x);
+ }
+ r := l;
+ while (r != nil) {
+ h := hd r;
+ if (h.name == s) {
+ case tag {
+ Svalue =>
+ h.value = v;
+ Sfunc =>
+ h.func = f;
+ Sbuiltin =>
+ h.builtin = b;
+ }
+ return h;
+ }
+ r = tl r;
+ }
+ n := ref Symb(s, v, f, b, 0);
+ t.tab[x] = n :: l;
+ return n;
+}
+
+#
+# Make a list of a symbol table's contents.
+#
+Stab.all(t: self ref Stab): list of ref Symb
+{
+ r: list of ref Symb;
+ for (i := 0; i < SHASH; i++) {
+ for (l := t.tab[i]; l != nil; l = tl l)
+ r = (ref *hd l) :: r;
+ }
+ return r;
+}
+
+#
+# Assign a list of strings to a variable. The distinguished value
+# "empty" is used to distinguish nil value from undefined.
+#
+Stab.assign(t: self ref Stab, s: string, v: list of string)
+{
+ if (v == nil)
+ v = empty;
+ t.update(s, Svalue, v, nil, nil);
+}
+
+#
+# Define a builtin.
+#
+Stab.defbuiltin(t: self ref Stab, s: string, b: Mashbuiltin)
+{
+ t.update(s, Sbuiltin, nil, nil, b);
+}
+
+#
+# Define a function.
+#
+Stab.define(t: self ref Stab, s: string, f: ref Cmd)
+{
+ t.update(s, Sfunc, nil, f, nil);
+}
+
+#
+# Symbol table lookup.
+#
+Stab.find(t: self ref Stab, s: string): ref Symb
+{
+ l := t.tab[hash->fun1(s, SHASH)];
+ while (l != nil) {
+ h := hd l;
+ if (h.name == s)
+ return h;
+ l = tl l;
+ }
+ return nil;
+}
+
+#
+# Function lookup.
+#
+Stab.func(t: self ref Stab, s: string): ref Cmd
+{
+ v := t.find(s);
+ if (v == nil)
+ return nil;
+ return v.func;
+}
+
+#
+# New environment.
+#
+Env.new(): ref Env
+{
+ return ref Env(Stab.new(), nil, ETop, nil, nil, nil, nil, nil, nil, 0);
+}
+
+#
+# Clone environment. No longer top-level or interactive.
+#
+Env.clone(e: self ref Env): ref Env
+{
+ e = e.copy();
+ e.flags &= ~(ETop | EInter);
+ e.global = e.global.clone();
+ if (e.local != nil)
+ e.local = e.local.clone();
+ return e;
+}
+
+#
+# Copy environment.
+#
+Env.copy(e: self ref Env): ref Env
+{
+ return ref *e;
+}
+
+#
+# Fetch $n argument.
+#
+Env.arg(e: self ref Env, s: string): string
+{
+ n := int s;
+ if (e.args == nil || n >= len e.args)
+ return "$" + s;
+ else
+ return e.args[n];
+}
+
+#
+# Lookup builtin.
+#
+Env.builtin(e: self ref Env, s: string): Mashbuiltin
+{
+ v := e.global.find(s);
+ if (v == nil)
+ return nil;
+ return v.builtin;
+}
+
+#
+# Define a builtin.
+#
+Env.defbuiltin(e: self ref Env, s: string, b: Mashbuiltin)
+{
+ e.global.defbuiltin(s, b);
+}
+
+#
+# Define a function.
+#
+Env.define(e: self ref Env, s: string, f: ref Cmd)
+{
+ e.global.define(s, f);
+}
+
+#
+# Value of a shell variable (check locals then globals).
+#
+Env.dollar(e: self ref Env, s: string): ref Symb
+{
+ if (e.local != nil) {
+ l := e.local.find(s);
+ if (l != nil && l.value != nil)
+ return l;
+ }
+ g := e.global.find(s);
+ if (g != nil && g.value != nil)
+ return g;
+ return nil;
+}
+
+#
+# Lookup a function.
+#
+Env.func(e: self ref Env, s: string): ref Cmd
+{
+ v := e.global.find(s);
+ if (v == nil)
+ return nil;
+ return v.func;
+}
+
+#
+# Local assignment.
+#
+Env.let(e: self ref Env, s: string, v: list of string)
+{
+ if (e.local == nil)
+ e.local = Stab.new();
+ e.local.assign(s, v);
+}
+
+#
+# Assignment. Update local or define global.
+#
+Env.set(e: self ref Env, s: string, v: list of string)
+{
+ if (e.local != nil && e.local.find(s) != nil)
+ e.local.assign(s, v);
+ else
+ e.global.assign(s, v);
+}
+
+#
+# Report undefined.
+#
+Env.undefined(e: self ref Env, s: string)
+{
+ e.report(s + ": undefined");
+}
diff --git a/appl/cmd/mash/tk.b b/appl/cmd/mash/tk.b
new file mode 100644
index 00000000..8b0f4f1a
--- /dev/null
+++ b/appl/cmd/mash/tk.b
@@ -0,0 +1,603 @@
+implement Mashbuiltin;
+
+#
+# "tk" builtin.
+#
+# tk clear - clears the text frame
+# tk def button name value
+# tk def ibutton name value image
+# tk def menu name
+# tk def item menu name value
+# tk dialog title mesg default label ...
+# tk dump - print commands to reconstruct toolbar
+# tk dump name ...
+# tk env - update tk execution env
+# tk file title dir pattern ...
+# tk geom
+# tk layout name ...
+# tk notice message
+# tk sel - print selection
+# tk sget - print snarf
+# tk sput string - put snarf
+# tk string mesg - get string
+# tk taskbar string
+# tk text - print window text
+#
+
+include "mash.m";
+include "mashparse.m";
+include "wmlib.m";
+include "dialog.m";
+include "selectfile.m";
+
+mashlib: Mashlib;
+wmlib: Wmlib;
+dialog: Dialog;
+selectfile: Selectfile;
+
+Env, Stab, Symb: import mashlib;
+sys, bufio, tk: import mashlib;
+gtop, gctxt, ident: import mashlib;
+
+Iobuf: import bufio;
+
+tkitems: ref Stab;
+tklayout: list of string;
+tkenv: ref Env;
+tkserving: int = 0;
+
+Cbutton, Cibutton, Cmenu: con Cprivate + iota;
+
+Cmark: con 3;
+BUTT: con ".b.";
+
+#
+# Interface to catch the use as a command.
+#
+init(nil: ref Draw->Context, args: list of string)
+{
+ raise "fail: " + hd args + " not loaded";
+}
+
+#
+# Used by whatis.
+#
+name(): string
+{
+ return "tk";
+}
+
+#
+# Install command and initialize state.
+#
+mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env)
+{
+ mashlib = lib;
+ if (gctxt == nil) {
+ e.report("tk: no graphics context");
+ return;
+ }
+ if (gtop == nil) {
+ e.report("tk: not run from wmsh");
+ return;
+ }
+ wmlib = load Wmlib Wmlib->PATH;
+ if (wmlib == nil) {
+ e.report(sys->sprint("tk: could not load %s: %r", Wmlib->PATH));
+ return;
+ }
+ dialog = load Dialog Dialog->PATH;
+ if (dialog == nil) {
+ e.report(sys->sprint("tk: could not load %s: %r", Dialog->PATH));
+ return;
+ }
+ selectfile = load Selectfile Selectfile->PATH;
+ if (selectfile == nil) {
+ e.report(sys->sprint("tk: could not load %s: %r", Selectfile->PATH));
+ return;
+ }
+ wmlib->init();
+ dialog->init();
+ selectfile->init();
+ e.defbuiltin("tk", this);
+ tkitems = Stab.new();
+}
+
+#
+# Execute the "tk" builtin.
+#
+mashcmd(e: ref Env, l: list of string)
+{
+ # must lock
+ l = tl l;
+ if (l == nil)
+ return;
+ s := hd l;
+ l = tl l;
+ case s {
+ "clear" =>
+ if (l != nil) {
+ e.usage("tk clear");
+ return;
+ }
+ clear(e);
+ "def" =>
+ define(e, l);
+ "dialog" =>
+ if (len l < 4) {
+ e.usage("tk dialog title mesg default label ...");
+ return;
+ }
+ dodialog(e, l);
+ "dump" =>
+ dump(e, l);
+ "env" =>
+ if (l != nil) {
+ e.usage("tk env");
+ return;
+ }
+ tkenv = e.clone();
+ tkenv.flags |= mashlib->ETop;
+ "file" =>
+ if (len l < 3) {
+ e.usage("tk file title dir pattern ...");
+ return;
+ }
+ dofile(e, hd l, hd tl l, tl tl l);
+ "geom" =>
+ if (l != nil) {
+ e.usage("tk geom");
+ return;
+ }
+ e.output(wmlib->geom(gtop));
+ "layout" =>
+ layout(e, l);
+ "notice" =>
+ if (len l != 1) {
+ e.usage("tk notice message");
+ return;
+ }
+ notice(hd l);
+ "sel" =>
+ if (l != nil) {
+ e.usage("tk sel");
+ return;
+ }
+ sel(e);
+ "sget" =>
+ if (l != nil) {
+ e.usage("tk sget");
+ return;
+ }
+ e.output(wmlib->snarfget());
+ "sput" =>
+ if (len l != 1) {
+ e.usage("tk sput string");
+ return;
+ }
+ wmlib->snarfput(hd l);
+ "string" =>
+ if (len l != 1) {
+ e.usage("tk string mesg");
+ return;
+ }
+ e.output(dialog->getstring(gctxt, gtop.image, hd l));
+ focus(e);
+ "taskbar" =>
+ if (len l != 1) {
+ e.usage("tk taskbar string");
+ return;
+ }
+ e.output(wmlib->taskbar(gtop, hd l));
+ "text" =>
+ if (l != nil) {
+ e.usage("tk text");
+ return;
+ }
+ text(e);
+ * =>
+ e.report(sys->sprint("tk: unknown command: %s", s));
+ }
+}
+
+#
+# Execute tk command and check for error.
+#
+tkcmd(e: ref Env, s: string): string
+{
+ if (e != nil && (e.flags & mashlib->EDumping))
+ sys->fprint(e.stderr, "+ %s\n", s);
+ r := tk->cmd(gtop, s);
+ if (r != nil && r[0] == '!' && e != nil)
+ sys->fprint(e.stderr, "tk: %s\n\tcommand was %s\n", r[1:], s);
+ return r;
+}
+
+focus(e: ref Env)
+{
+ tkcmd(e, "focus .ft.t");
+}
+
+#
+# Serve loop.
+#
+tkserve(mash: chan of string)
+{
+ mashlib->reap();
+ for (;;) {
+ cmd := <-mash;
+ if (mashlib->servechan != nil && len cmd > 1) {
+ cmd[len cmd - 1] = '\n';
+ mashlib->servechan <-= array of byte cmd[1:];
+ }
+ }
+}
+
+notname(e: ref Env, s: string)
+{
+ e.report(sys->sprint("tk: %s: malformed name", s));
+}
+
+#
+# Define a button, menu or item.
+#
+define(e: ref Env, l: list of string)
+{
+ if (l == nil) {
+ e.usage("tk def definition");
+ return;
+ }
+ s := hd l;
+ l = tl l;
+ case s {
+ "button" =>
+ if (len l != 2) {
+ e.usage("tk def button name value");
+ return;
+ }
+ s = hd l;
+ if (!ident(s)) {
+ notname(e, s);
+ return;
+ }
+ i := tkitems.update(s, Svalue, tl l, nil, nil);
+ i.tag = Cbutton;
+ "ibutton" =>
+ if (len l != 3) {
+ e.usage("tk def ibutton name value path");
+ return;
+ }
+ s = hd l;
+ if (!ident(s)) {
+ notname(e, s);
+ return;
+ }
+ i := tkitems.update(s, Svalue, tl l, nil, nil);
+ i.tag = Cibutton;
+ "menu" =>
+ if (len l != 1) {
+ e.usage("tk def menu name");
+ return;
+ }
+ s = hd l;
+ if (!ident(s)) {
+ notname(e, s);
+ return;
+ }
+ i := tkitems.update(s, Svalue, nil, nil, nil);
+ i.tag = Cmenu;
+ "item" =>
+ if (len l != 3) {
+ e.usage("tk def item menu name value");
+ return;
+ }
+ s = hd l;
+ i := tkitems.find(s);
+ if (i == nil || i.tag != Cmenu) {
+ e.report(s + ": not a menu");
+ return;
+ }
+ l = tl l;
+ i.value = updateitem(i.value, hd l, hd tl l);
+ * =>
+ e.report("tk: " + s + ": unknown command");
+ }
+}
+
+#
+# Update a menu item.
+#
+updateitem(l: list of string, c, v: string): list of string
+{
+ r: list of string;
+ while (l != nil) {
+ w := hd l;
+ l = tl l;
+ d := hd l;
+ l = tl l;
+ if (d == c) {
+ r = c :: v :: r;
+ c = nil;
+ } else
+ r = d :: w :: r;
+ }
+ if (c != nil)
+ r = c :: v :: r;
+ return mashlib->revstrs(r);
+}
+
+items(e: ref Env, l: list of string): list of ref Symb
+{
+ r: list of ref Symb;
+ while (l != nil) {
+ i := tkitems.find(hd l);
+ if (i == nil) {
+ e.report(hd l + ": not an item");
+ return nil;
+ }
+ r = i :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+deleteall(e: ref Env, l: list of string)
+{
+ while (l != nil) {
+ tkcmd(e, "destroy " + BUTT + hd l);
+ l = tl l;
+ }
+}
+
+sendcmd(c: string): string
+{
+ return tk->quote("send mash " + tk->quote(c));
+}
+
+addbutton(e: ref Env, w, t, c: string)
+{
+ tkcmd(e, sys->sprint("button %s%s -%s %s -command %s", BUTT, t, w, t, sendcmd(c)));
+}
+
+addimage(e: ref Env, t, f: string)
+{
+ r := tkcmd(nil, sys->sprint("image create bitmap %s -file %s.bit -maskfile %s.mask", t, f, f));
+ if (r != nil && r[0] == '!')
+ tkcmd(e, sys->sprint("image create bitmap %s -file %s.bit", t, f));
+}
+
+additem(e: ref Env, s: ref Symb)
+{
+ case s.tag {
+ Cbutton =>
+ addbutton(e, "text", s.name, hd s.value);
+ Cibutton =>
+ addimage(e, s.name, hd tl s.value);
+ addbutton(e, "image", s.name, hd s.value);
+ Cmenu =>
+ t := s.name;
+ tkcmd(e, sys->sprint("menubutton %s%s -text %s -menu %s%s.menu -underline -1", BUTT, t, t, BUTT,t));
+ t += ".menu";
+ tkcmd(e, "menu " + BUTT + t);
+ t = BUTT + t;
+ l := s.value;
+ while (l != nil) {
+ v := sendcmd(hd l);
+ l = tl l;
+ c := tk->quote(hd l);
+ l = tl l;
+ tkcmd(e, sys->sprint("%s add command -label %s -command %s", t, c, v));
+ }
+ }
+}
+
+pack(e: ref Env, l: list of string)
+{
+ s := "pack";
+ while (l != nil) {
+ s += sys->sprint(" %s%s", BUTT, hd l);
+ l = tl l;
+ }
+ s += " -side left";
+ tkcmd(e, s);
+}
+
+propagate(e: ref Env)
+{
+ tkcmd(e, "pack propagate . 0");
+ tkcmd(e, "update");
+}
+
+unmark(r: list of ref Symb)
+{
+ while (r != nil) {
+ s := hd r;
+ case s.tag {
+ Cbutton + Cmark or Cibutton + Cmark or Cmenu + Cmark =>
+ s.tag -= Cmark;
+ }
+ r = tl r;
+ }
+}
+
+#
+# Check that the layout tags are unique.
+#
+unique(e: ref Env, r: list of ref Symb): int
+{
+ u := 1;
+loop:
+ for (l := r; l != nil; l = tl l) {
+ s := hd l;
+ case s.tag {
+ Cbutton + Cmark or Cibutton + Cmark or Cmenu + Cmark =>
+ e.report(sys->sprint("layout: tag %s repeated", s.name));
+ u = 0;
+ break loop;
+ Cbutton or Cibutton or Cmenu =>
+ s.tag += Cmark;
+ }
+ }
+ unmark(r);
+ return u;
+}
+
+#
+# Update the button bar layout and the environment.
+# Maybe spawn the server.
+#
+layout(e: ref Env, l: list of string)
+{
+ r := items(e, l);
+ if (r == nil && l != nil)
+ return;
+ if (!unique(e, r))
+ return;
+ if (tklayout != nil)
+ deleteall(e, tklayout);
+ n := len r;
+ a := array[n] of ref Symb;
+ while (--n >= 0) {
+ a[n] = hd r;
+ r = tl r;
+ }
+ n = len a;
+ for (i := 0; i < n; i++)
+ additem(e, a[i]);
+ pack(e, l);
+ propagate(e);
+ tklayout = l;
+ tkenv = e.clone();
+ tkenv.flags |= mashlib->ETop;
+ if (!tkserving) {
+ tkserving = 1;
+ mash := chan of string;
+ tk->namechan(gtop, mash, "mash");
+ spawn tkserve(mash);
+ mashlib->startserve = 1;
+ }
+}
+
+dumpbutton(out: ref Iobuf, w: string, s: ref Symb)
+{
+ out.puts(sys->sprint("tk def %s %s %s", w, s.name, mashlib->quote(hd s.value)));
+ if (s.tag == Cibutton)
+ out.puts(sys->sprint(" %s", mashlib->quote(hd tl s.value)));
+ out.puts(";\n");
+}
+
+#
+# Print commands to reconstruct toolbar.
+#
+dump(e: ref Env, l: list of string)
+{
+ r: list of ref Symb;
+ if (l != nil)
+ r = items(e, l);
+ else
+ r = tkitems.all();
+ out := e.outfile();
+ if (out == nil)
+ return;
+ while (r != nil) {
+ s := hd r;
+ case s.tag {
+ Cbutton =>
+ dumpbutton(out, "button", s);
+ Cibutton =>
+ dumpbutton(out, "ibutton", s);
+ Cmenu =>
+ t := s.name;
+ out.puts(sys->sprint("tk def menu %s;\n", t));
+ i := s.value;
+ while (i != nil) {
+ v := hd i;
+ i = tl i;
+ c := hd i;
+ i = tl i;
+ out.puts(sys->sprint("tk def item %s %s %s;\n", t, c, mashlib->quote(v)));
+ }
+ }
+ r = tl r;
+ }
+ if (l == nil) {
+ out.puts("tk layout");
+ for (l = tklayout; l != nil; l = tl l) {
+ out.putc(' ');
+ out.puts(hd l);
+ }
+ out.puts(";\n");
+ }
+ out.close();
+}
+
+clear(e: ref Env)
+{
+ tkcmd(e, ".ft.t delete 1.0 end; update");
+}
+
+dofile(e: ref Env, title, dir: string, pats: list of string)
+{
+ e.output(selectfile->filename(gctxt, gtop.image, title, pats, dir));
+}
+
+sel(e: ref Env)
+{
+ sel := tkcmd(e, ".ft.t tag ranges sel");
+ if (sel != nil) {
+ s := tkcmd(e, ".ft.t dump " + sel);
+ e.output(s);
+ }
+}
+
+text(e: ref Env)
+{
+ sel := tkcmd(e, ".ft.t tag ranges sel");
+ if (sel != nil)
+ tkcmd(e, ".ft.t tag remove sel " + sel);
+ s := tkcmd(e, ".ft.t dump 1.0 end");
+ if (sel != nil)
+ tkcmd(e, ".ft.t tag add sel " + sel);
+ e.output(s);
+}
+
+notice0 := array[] of
+{
+ "frame .f -borderwidth 2 -relief groove -padx 3 -pady 3",
+ "frame .f.f",
+ "label .f.f.l -bitmap error -foreground red",
+};
+
+notice1 := array[] of
+{
+ "button .f.b -text { OK } -command {send cmd done}",
+ "pack .f.f.l .f.f.m -side left -expand 1 -padx 10 -pady 10",
+ "pack .f.f .f.b -padx 10 -pady 10",
+ "pack .f",
+ "update; cursor -default",
+};
+
+notice(mesg: string)
+{
+ x := int tk->cmd(gtop, ". cget -x");
+ y := int tk->cmd(gtop, ". cget -y");
+ where := sys->sprint("-x %d -y %d", x + 30, y + 30);
+ t := tk->toplevel(gctxt.screen, where + " -borderwidth 2 -relief raised");
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+ wmlib->tkcmds(t, notice0);
+ tk->cmd(t, "label .f.f.m -text '" + mesg);
+ wmlib->tkcmds(t, notice1);
+ <- cmd;
+}
+
+dodialog(e: ref Env, l: list of string)
+{
+ title := hd l;
+ l = tl l;
+ msg := hd l;
+ l = tl l;
+ x := dialog->prompt(gctxt, gtop.image, nil, title, msg, int hd l, tl l);
+ e.output(string x);
+ focus(e);
+}
diff --git a/appl/cmd/mash/xeq.b b/appl/cmd/mash/xeq.b
new file mode 100644
index 00000000..fd2f1e6f
--- /dev/null
+++ b/appl/cmd/mash/xeq.b
@@ -0,0 +1,543 @@
+#
+# Command execution.
+#
+
+#
+# Entry from parser.
+#
+Cmd.xeq(c: self ref Cmd, e: ref Env)
+{
+ if (e.flags & EDumping) {
+ s := c.text();
+ f := e.outfile();
+ f.puts(s);
+ if (s != nil && s[len s - 1] != '&')
+ f.putc(';');
+ f.putc('\n');
+ f.close();
+ f = nil;
+ }
+ if ((e.flags & ENoxeq) == 0)
+ c.xeqit(e, 1);
+}
+
+#
+# Execute a command. Tail recursion.
+#
+Cmd.xeqit(c: self ref Cmd, e: ref Env, wait: int)
+{
+tail: for (;;) {
+ if (c == nil)
+ return;
+ case c.op {
+ Csimple =>
+ c.simple(e, wait);
+ Casync =>
+ e = e.clone();
+ e.in = e.devnull();
+ e.wait = nil;
+ spawn c.left.xeqit(e, 1);
+ Cgroup =>
+ if (c.redirs != nil) {
+ (ok, in, out) := mkredirs(e, c.redirs);
+ if (!ok)
+ return;
+ e = e.copy();
+ e.in = in;
+ e.out = out;
+ c.left.xeqit(e, 1);
+ } else {
+ c = c.left;
+ continue tail;
+ }
+ Csubgroup =>
+ e = e.clone();
+ if (c.redirs != nil) {
+ (ok, in, out) := mkredirs(e, c.redirs);
+ if (!ok)
+ return;
+ e.in = in;
+ e.out = out;
+ }
+ c = c.left;
+ continue tail;
+ Cseq =>
+ c.left.xeqit(e, 1);
+ c = c.right;
+ continue tail;
+ Cpipe =>
+ do {
+ fds := e.pipe();
+ if (fds == nil)
+ return;
+ n := e.clone();
+ n.out = fds[0];
+ c.left.xeqit(n, 0);
+ n = nil;
+ e = e.clone();
+ e.in = fds[1];
+ fds = nil;
+ c = c.right;
+ } while (c.op == Cpipe);
+ continue tail;
+ Cif =>
+ t := c.left.truth(e);
+ if (c.right.op == Celse) {
+ if (t)
+ c.right.left.xeqit(e, wait);
+ else
+ c.right.right.xeqit(e, wait);
+ } else if (t)
+ c.right.xeqit(e, wait);
+ Celse =>
+ panic("unexpected else");
+ Cwhile =>
+ while (c.left.truth(e))
+ c.right.xeqit(e, wait);
+ Cfor =>
+ (ok, l) := evalw(c.words, e);
+ if (!ok)
+ return;
+ s := c.item.word.text;
+ c = c.left;
+ while (l != nil) {
+ e.let(s, (hd l) :: nil);
+ c.xeqit(e, 1);
+ l = tl l;
+ }
+ Ccase =>
+ (s1, l1) := c.left.eeval(e);
+ r := c.right;
+ while (r != nil) {
+ l := r.left;
+ (s2, l2) := l.left.eeval(e);
+ if (match2(e, s1, l1, s2, l2)) {
+ c = l.right;
+ continue tail;
+ }
+ r = r.right;
+ }
+ Ceq =>
+ c.assign(e, 0);
+ Cdefeq =>
+ c.assign(e, 1);
+ Cfn =>
+ (s, nil, nil) := c.item.ieval(e);
+ if (!ident(s)) {
+ e.report("bad function name");
+ return;
+ }
+ e.define(s, c.left);
+ Crescue =>
+ e.report("rescue not implemented");
+ Cdepend =>
+ c.depend(e);
+ Crule =>
+ c.rule(e);
+ * =>
+ sys->print("number %d\n", c.op);
+ } return; } # tail recursion
+}
+
+#
+# Execute quote or backquote generator. Return generated item.
+#
+Cmd.quote(c: self ref Cmd, e: ref Env, back: int): ref Item
+{
+ e = e.copy();
+ fds := e.pipe();
+ if (fds == nil)
+ return nil;
+ e.out = fds[0];
+ in := bufio->fopen(fds[1], Bufio->OREAD);
+ if (in == nil)
+ e.couldnot("fopen", "pipe");
+ c.xeqit(e, 0);
+ fds = nil;
+ e = nil;
+ if (back) {
+ l: list of string;
+ while ((s := in.gets('\n')) != nil) {
+ (nil, r) := sys->tokenize(s, " \t\r\n");
+ l = prepend(l, r);
+ }
+ return Item.iteml(revstrs(l));
+ } else {
+ s := in.gets('\n');
+ if (s != nil && s[len s - 1] == '\n')
+ s = s[:len s - 1];
+ return Item.itemw(s);
+ }
+}
+
+#
+# Execute serve generator.
+#
+Cmd.serve(c: self ref Cmd, e: ref Env, write: int): ref Item
+{
+ e = e.clone();
+ fds := e.pipe();
+ if (fds == nil)
+ return nil;
+ if (write)
+ e.in = fds[0];
+ else
+ e.out = fds[0];
+ s := e.servefd(fds[1], write);
+ if (s == nil)
+ return nil;
+ c.xeqit(e, 0);
+ return Item.itemw(s);
+}
+
+#
+# Expression evaluation, first pass.
+# Parse tree is copied and word items are evaluated.
+# nil return for error is propagated.
+#
+Cmd.eeval1(c: self ref Cmd, e: ref Env): ref Cmd
+{
+ case c.op {
+ Cword =>
+ l := c.item.ieval1(e);
+ if (l == nil)
+ return nil;
+ return Cmd.cmd1i(Cword, nil, l);
+ Chd or Ctl or Clen or Cnot =>
+ l := c.left.eeval1(e);
+ if (l == nil)
+ return nil;
+ return Cmd.cmd1(c.op, l);
+ Ccaret or Ccons or Ceqeq or Cnoteq or Cmatch =>
+ l := c.left.eeval1(e);
+ r := c.right.eeval1(e);
+ if (l == nil || r == nil)
+ return nil;
+ return Cmd.cmd2(c.op, l, r);
+ }
+ panic("expr1: bad op");
+ return nil;
+}
+
+#
+# Expression evaluation, second pass.
+# Returns a tuple (singleton, list, expand flag).
+#
+Cmd.eeval2(c: self ref Cmd, e: ref Env): (string, list of string, int)
+{
+ case c.op {
+ Cword =>
+ return c.item.ieval2(e);
+ Clist =>
+ return (nil, c.value, 0);
+ Ccaret =>
+ (s1, l1, x1) := c.left.eeval2(e);
+ (s2, l2, x2) := c.right.eeval2(e);
+ return caret(s1, l1, x1, s2, l2, x2);
+ Chd =>
+ (s, l, x) := c.left.eeval2(e);
+ if (s != nil)
+ return (s, nil, x);
+ if (l != nil)
+ return (hd l, nil, 0);
+ Ctl =>
+ (s, l, nil) := c.left.eeval2(e);
+ if (s != nil)
+ break;
+ if (l != nil)
+ return (nil, tl l, 0);
+ Clen =>
+ (s, l, nil) := c.left.eeval2(e);
+ if (s != nil)
+ return ("1", nil, 0);
+ return (string len l, nil, 0);
+ Cnot =>
+ (s, l, nil) := c.left.eeval2(e);
+ if (s == nil && l == nil)
+ return (TRUE, nil, 0);
+ Ccons =>
+ (s1, l1, nil) := c.left.eeval2(e);
+ (s2, l2, nil) := c.right.eeval2(e);
+ if (s1 != nil) {
+ if (s2 != nil)
+ return (nil, s1 :: s2 :: nil, 0);
+ if (l2 != nil)
+ return (nil, s1 :: l2, 0);
+ return (s1, nil, 0);
+ } else if (l1 != nil) {
+ if (s2 != nil)
+ return (nil, prepend(s2 :: nil, revstrs(l1)), 0);
+ if (l2 != nil)
+ return (nil, prepend(l2, revstrs(l1)), 0);
+ return (nil, l1, 0);
+ } else
+ return (s2, l2, 0);
+ Ceqeq =>
+ if (c.evaleq(e))
+ return (TRUE, nil, 0);
+ Cnoteq =>
+ if (!c.evaleq(e))
+ return (TRUE, nil, 0);
+ Cmatch =>
+ if (c.evalmatch(e))
+ return (TRUE, nil, 0);
+ * =>
+ panic("expr2: bad op");
+ }
+ return (nil, nil, 0);
+}
+
+#
+# Evaluate expression. 1st pass, 2nd pass, maybe glob.
+#
+Cmd.eeval(c: self ref Cmd, e: ref Env): (string, list of string)
+{
+ c = c.eeval1(e);
+ if (c == nil)
+ return (nil, nil);
+ (s, l, x) := c.eeval2(e);
+ if (x && s != nil)
+ (s, l) = glob(e, s);
+ return (s, l);
+}
+
+#
+# Assignment - let or set.
+#
+Cmd.assign(c: self ref Cmd, e: ref Env, def: int)
+{
+ i := c.item;
+ if (i == nil)
+ return;
+ (ok, v) := evalw(c.words, e);
+ if (!ok)
+ return;
+ s := c.item.word.text;
+ if (def)
+ e.let(s, v);
+ else
+ e.set(s, v);
+}
+
+#
+# Evaluate command and test for non-empty.
+#
+Cmd.truth(c: self ref Cmd, e: ref Env): int
+{
+ (s, l) := c.eeval(e);
+ return s != nil || l != nil;
+}
+
+#
+# Evaluate word.
+#
+evalw(l: list of ref Item, e: ref Env): (int, list of string)
+{
+ if (l == nil)
+ return (1, nil);
+ w := pass1(e, l);
+ if (w == nil)
+ return (0, nil);
+ return (1, pass2(e, w));
+}
+
+#
+# Evaluate list of items, pass 1 - reverses.
+#
+pass1(e: ref Env, l: list of ref Item): list of ref Item
+{
+ r: list of ref Item;
+ while (l != nil) {
+ i := (hd l).ieval1(e);
+ if (i == nil)
+ return nil;
+ r = i :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+#
+# Evaluate list of items, pass 2 with globbing - reverses (restores order).
+#
+pass2(e: ref Env, l: list of ref Item): list of string
+{
+ r: list of string;
+ while (l != nil) {
+ (s, t, x) := (hd l).ieval2(e);
+ if (x && s != nil)
+ (s, t) = glob(e, s);
+ if (s != nil)
+ r = s :: r;
+ else if (t != nil)
+ r = prepend(r, revstrs(t));
+ l = tl l;
+ }
+ return r;
+}
+
+#
+# Simple command. Maybe a function.
+#
+Cmd.simple(c: self ref Cmd, e: ref Env, wait: int)
+{
+ w := pass1(e, c.words);
+ if (w == nil)
+ return;
+ s := pass2(e, w);
+ if (s == nil)
+ return;
+ if (e.flags & EEcho)
+ echo(e, s);
+ (ok, in, out) := mkredirs(e, c.redirs);
+ if (ok)
+ e.runit(s, in, out, wait);
+}
+
+#
+# Cmd name and arglist. Maybe a function.
+#
+Env.runit(e: self ref Env, s: list of string, in, out: ref Sys->FD, wait: int)
+{
+ d := e.func(hd s);
+ if (d != nil) {
+ if (e.level >= MAXELEV) {
+ e.report(hd s + ": function nesting too deep");
+ return;
+ }
+ e = e.copy();
+ e.level++;
+ e.in = in;
+ e.out = out;
+ e.local = Stab.new();
+ e.local.assign(ARGS, tl s);
+ d.xeqit(e, wait);
+ } else
+ exec(s, e, in, out, wait);
+}
+
+#
+# Item evaluation, first pass. Copy parse tree. Expand variables.
+# Call first pass of expression evaluation. Execute generators.
+#
+Item.ieval1(i: self ref Item, e: ref Env): ref Item
+{
+ if (i == nil)
+ return nil;
+ case i.op {
+ Icaret or Iicaret =>
+ l := i.left.ieval1(e);
+ r := i.right.ieval1(e);
+ if (l == nil || r == nil)
+ return nil;
+ return Item.item2(i.op, l, r);
+ Idollar or Idollarq=>
+ s := e.dollar(i.word.text);
+ if (s == nil) {
+ e.undefined(i.word.text);
+ return nil;
+ }
+ if (s.value == empty)
+ return Item.itemw(nil);
+ if (i.op == Idollar)
+ return Item.iteml(s.value);
+ else
+ return Item.itemw(concat(s.value));
+ Iword or Imatch =>
+ return i;
+ Iexpr =>
+ l := i.cmd.eeval1(e);
+ if (l == nil)
+ return nil;
+ return Item.itemc(Iexpr, l);
+ Ibackq =>
+ return i.cmd.quote(e, 1);
+ Iquote =>
+ return i.cmd.quote(e, 0);
+ Iinpipe =>
+ return i.cmd.serve(e, 0);
+ Ioutpipe =>
+ return i.cmd.serve(e, 1);
+ }
+ panic("ieval1: bad op");
+ return nil;
+}
+
+#
+# Item evaluation, second pass. Outer level carets. Expand matches.
+# Call second pass of expression evaluation.
+#
+Item.ieval2(i: self ref Item, e: ref Env): (string, list of string, int)
+{
+ case i.op {
+ Icaret or Iicaret =>
+ return i.caret(e);
+ Imatch =>
+ return (e.arg(i.word.text), nil, 0);
+ Idollar or Idollarq =>
+ panic("ieval2: unexpected $");
+ Iword =>
+ return (i.word.text, nil, i.word.flags & Wexpand);
+ Iexpr =>
+ return i.cmd.eeval2(e);
+ Ibackq or Iinpipe or Ioutpipe =>
+ panic("ieval2: unexpected generator");
+ }
+ panic("ieval2: bad op");
+ return (nil, nil, 0);
+}
+
+#
+# Item evaluation.
+#
+Item.ieval(i: self ref Item, e: ref Env): (string, list of string, int)
+{
+ i = i.ieval1(e);
+ if (i == nil)
+ return (nil, nil, 0);
+ return i.ieval2(e);
+}
+
+#
+# Redirection item evaluation.
+#
+Item.reval(i: self ref Item, e: ref Env): (int, string)
+{
+ (s, l, nil) := i.ieval(e);
+ if (s == nil) {
+ if (l == nil)
+ e.report("null redirect");
+ else
+ e.report("list for redirect");
+ return (0, nil);
+ }
+ return (1, s);
+}
+
+#
+# Make redirection names.
+#
+mkrdnames(e: ref Env, l: list of ref Redir): (int, array of string)
+{
+ f := array[Rcount] of string;
+ while (l != nil) {
+ r := hd l;
+ (ok, s) := r.word.reval(e);
+ if (!ok)
+ return (0, nil);
+ f[r.op] = s;
+ l = tl l;
+ }
+ return (1, f);
+}
+
+#
+# Perform redirections.
+#
+mkredirs(e: ref Env, l: list of ref Redir): (int, ref Sys->FD, ref Sys->FD)
+{
+ (ok, f) := mkrdnames(e, l);
+ if (!ok)
+ return (0, nil, nil);
+ return redirect(e, f, e.in, e.out);
+}
diff --git a/appl/cmd/mathcalc.b b/appl/cmd/mathcalc.b
new file mode 100644
index 00000000..4f4f475f
--- /dev/null
+++ b/appl/cmd/mathcalc.b
@@ -0,0 +1,79 @@
+implement MathCalc;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "tk.m";
+ tk: Tk;
+
+include "bufio.m";
+ bufmod : Bufio;
+Iobuf : import bufmod;
+
+include "../lib/tcl.m";
+
+include "tcllib.m";
+
+
+MathCalc : module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+CALCPATH: con "/dis/lib/tcl_calc.dis";
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ cal := load TclLib CALCPATH;
+ if (cal==nil){
+ sys->print("mathcalc: can't load %s: %r\n", CALCPATH);
+ exit;
+ }
+ bufmod = load Bufio Bufio->PATH;
+ if (bufmod==nil){
+ sys->print("bufmod load %r\n");
+ exit;
+ }
+ iob := bufmod->fopen(sys->fildes(0),bufmod->OREAD);
+ if (iob==nil){
+ sys->print("mathcalc: cannot open stdin for reading: %r\n");
+ return;
+ }
+ input : string;
+ new_inp := "calc%";
+ sys->print("%s ", new_inp);
+ while((input=iob.gets('\n'))!=nil){
+ input=input[0:len input -1];
+ if (input=="quit")
+ exit;
+ arr:=array[] of {input};
+ (i,msg):=cal->exec(nil,arr);
+ if (msg!=nil)
+ sys->print("%s\n",msg);
+ sys->print("%s ", new_inp);
+ }
+
+}
+
+
+# expr0 : expr1
+# | expr0 '+' expr0
+# | expr0 '-' expr0
+# ;
+#
+# expr1 : expr2
+# | expr1 '*' expr1
+# | expr1 '/' expr1
+# ;
+#
+# expr2 : '-' expr2
+# | '+' expr2
+# | expr3
+# ;
+#
+# expr3 : INT
+# | REAL
+# ;
diff --git a/appl/cmd/mc.b b/appl/cmd/mc.b
new file mode 100644
index 00000000..265d548e
--- /dev/null
+++ b/appl/cmd/mc.b
@@ -0,0 +1,2547 @@
+implement Calculator;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+ arg: Arg;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "math.m";
+ maths: Math;
+include "rand.m";
+ rand: Rand;
+include "daytime.m";
+ daytime: Daytime;
+
+Calculator: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ arg = load Arg Arg->PATH;
+ bufio = load Bufio Bufio->PATH;
+ maths = load Math Math->PATH;
+ rand = load Rand Rand->PATH;
+ daytime = load Daytime Daytime->PATH;
+
+ maths->FPcontrol(0, Math->INVAL|Math->ZDIV|Math->OVFL|Math->UNFL|Math->INEX);
+
+ rand->init(daytime->now());
+ rand->init(rand->rand(Big)^rand->rand(Big));
+ daytime = nil;
+
+ arg->init(args);
+ while((c := arg->opt()) != 0){
+ case(c){
+ 'b' =>
+ bits = 1;
+ 'd' =>
+ debug = 1;
+ 's' =>
+ strict = 1;
+ }
+ }
+ gargs = args = arg->argv();
+ if(args == nil){
+ stdin = 1;
+ bin = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ }
+ else if(tl args == nil)
+ bin = bufio->open(hd args, Sys->OREAD);
+
+ syms = array[Hash] of ref Sym;
+
+ pushscope();
+ for(i := 0; keyw[i].t0 != nil; i++)
+ enter(keyw[i].t0, keyw[i].t1);
+ for(i = 0; conw[i].t0 != nil; i++)
+ adddec(conw[i].t0, Ocon, conw[i].t1, 0);
+ for(i = 0; varw[i].t0 != nil; i++)
+ adddec(varw[i].t0, Ovar, varw[i].t1, 0);
+ for(i = 0; funw[i].t0 != nil; i++)
+ adddec(funw[i].t0, Olfun, real funw[i].t1, funw[i].t2);
+
+ deg = lookup(Deg).dec;
+ pbase = lookup(Base).dec;
+ errdec = ref Dec;
+
+ pushscope();
+ for(;;){
+ e: ref Node;
+
+ {
+ t := lex();
+ if(t == Oeof)
+ break;
+ unlex(t);
+ ls := lexes;
+ e = stat(1);
+ ckstat(e, Onothing, 0);
+ if(ls == lexes){
+ t = lex();
+ error(nil, sys->sprint("syntax error near %s", opstring(t)));
+ unlex(t);
+ }
+ consume(Onl);
+ }
+ exception ex{
+ Eeof =>
+ e = nil;
+ err("premature eof");
+ skip();
+ "*" =>
+ e = nil;
+ err(ex);
+ skip();
+ }
+ if(0 && debug)
+ prtree(e, 0);
+ if(e != nil && e.op != Ofn){
+ (k, v) := (Onothing, 0.0);
+ {
+ (k, v) = estat(e);
+ }
+ exception ex{
+ "*" =>
+ e = nil;
+ err(ex);
+ }
+ if(pexp(e))
+ printnum(v, "\n");
+ if(k == Oexit)
+ exit;
+ }
+ }
+ popscope();
+ popscope();
+}
+
+bits: int;
+debug: int;
+strict: int;
+
+None: con -2;
+Eof: con -1;
+Eeof: con "eof";
+
+Hash: con 16;
+Big: con 1<<30;
+Maxint: con 16r7FFFFFFF;
+Nan: con Math->NaN;
+Infinity: con Math->Infinity;
+Pi: con Math->Pi;
+Eps: con 1E-10;
+Bigeps: con 1E-2;
+Ln2: con 0.6931471805599453;
+Ln10: con 2.302585092994046;
+Euler: con 2.71828182845904523536;
+Gamma: con 0.57721566490153286060;
+Phi: con 1.61803398874989484820;
+
+Oeof,
+Ostring, Onum, Oident, Ocon, Ovar, Ofun, Olfun,
+Oadd, Osub, Omul, Odiv, Omod, Oidiv, Oexp, Oand, Oor, Oxor, Olsh, Orsh,
+Oadde, Osube, Omule, Odive, Omode, Oidive, Oexpe, Oande, Oore, Oxore, Olshe, Orshe,
+Oeq, One, Ogt, Olt, Oge, Ole,
+Oinc, Opreinc, Opostinc, Odec, Opredec, Opostdec,
+Oandand, Ooror,
+Oexc, Onot, Ofact, Ocom,
+Oas, Odas,
+Oplus, Ominus, Oinv,
+Ocomma, Oscomma, Oquest, Ocolon,
+Onand, Onor, Oimp, Oimpby, Oiff,
+Olbr, Orbr, Olcbr, Orcbr, Oscolon, Onl,
+Onothing,
+Oprint, Oread,
+Oif, Oelse, Ofor, Owhile, Odo, Obreak, Ocont, Oexit, Oret, Ofn, Oinclude,
+Osigma, Opi, Ocfrac, Oderiv, Ointeg, Osolve,
+Olog, Olog10, Olog2, Ologb, Oexpf, Opow, Osqrt, Ocbrt, Ofloor, Oceil, Omin, Omax, Oabs, Ogamma, Osign, Oint, Ofrac, Oround, Oerf, Oatan2, Osin, Ocos, Otan, Oasin, Oacos, Oatan, Osinh, Ocosh, Otanh, Oasinh, Oacosh, Oatanh, Orand,
+Olast: con iota;
+
+Binary: con (1<<8);
+Preunary: con (1<<9);
+Postunary: con (1<<10);
+Assoc: con (1<<11);
+Rassoc: con (1<<12);
+Prec: con Binary-1;
+
+opss := array[Olast] of
+{
+ "eof",
+ "string",
+ "number",
+ "identifier",
+ "constant",
+ "variable",
+ "function",
+ "library function",
+ "+",
+ "-",
+ "*",
+ "/",
+ "%",
+ "//",
+ "&",
+ "|",
+ "^",
+ "<<",
+ ">>",
+ "+=",
+ "-=",
+ "*=",
+ "/=",
+ "%=",
+ "//=",
+ "&=",
+ "|=",
+ "^=",
+ "<<=",
+ ">>=",
+ "==",
+ "!=",
+ ">",
+ "<",
+ ">=",
+ "<=",
+ "++",
+ "++",
+ "++",
+ "--",
+ "--",
+ "--",
+ "**",
+ "&&",
+ "||",
+ "!",
+ "!",
+ "!",
+ "~",
+ "=",
+ ":=",
+ "+",
+ "-",
+ "1/",
+ ",",
+ ",",
+ "?",
+ ":",
+ "↑",
+ "↓",
+ "->",
+ "<-",
+ "<->",
+ "(",
+ ")",
+ "{",
+ "}",
+ ";",
+ "\n",
+ "",
+};
+
+ops := array[Olast] of
+{
+ Oeof => 0,
+ Ostring => 17,
+ Onum => 17,
+ Oident => 17,
+ Ocon => 17,
+ Ovar => 17,
+ Ofun => 17,
+ Olfun => 17,
+ Oadd => 12|Binary|Assoc|Preunary,
+ Osub => 12|Binary|Preunary,
+ Omul => 13|Binary|Assoc,
+ Odiv => 13|Binary,
+ Omod => 13|Binary,
+ Oidiv => 13|Binary,
+ Oexp => 14|Binary|Rassoc,
+ Oand => 8|Binary|Assoc,
+ Oor => 6|Binary|Assoc,
+ Oxor => 7|Binary|Assoc,
+ Olsh => 11|Binary,
+ Orsh => 11|Binary,
+ Oadde => 2|Binary|Rassoc,
+ Osube => 2|Binary|Rassoc,
+ Omule => 2|Binary|Rassoc,
+ Odive => 2|Binary|Rassoc,
+ Omode => 2|Binary|Rassoc,
+ Oidive => 2|Binary|Rassoc,
+ Oexpe => 2|Binary|Rassoc,
+ Oande => 2|Binary|Rassoc,
+ Oore => 2|Binary|Rassoc,
+ Oxore => 2|Binary|Rassoc,
+ Olshe => 2|Binary|Rassoc,
+ Orshe => 2|Binary|Rassoc,
+ Oeq => 9|Binary,
+ One => 9|Binary,
+ Ogt => 10|Binary,
+ Olt => 10|Binary,
+ Oge => 10|Binary,
+ Ole => 10|Binary,
+ Oinc => 15|Rassoc|Preunary|Postunary,
+ Opreinc => 15|Rassoc|Preunary,
+ Opostinc => 15|Rassoc|Postunary,
+ Odec => 15|Rassoc|Preunary|Postunary,
+ Opredec => 15|Rassoc|Preunary,
+ Opostdec => 15|Rassoc|Postunary,
+ Oandand => 5|Binary|Assoc,
+ Ooror => 4|Binary|Assoc,
+ Oexc => 15|Rassoc|Preunary|Postunary,
+ Onot => 15|Rassoc|Preunary,
+ Ofact => 15|Rassoc|Postunary,
+ Ocom => 15|Rassoc|Preunary,
+ Oas => 2|Binary|Rassoc,
+ Odas => 2|Binary|Rassoc,
+ Oplus => 15|Rassoc|Preunary,
+ Ominus => 15|Rassoc|Preunary,
+ Oinv => 15|Rassoc|Postunary,
+ Ocomma => 1|Binary|Assoc,
+ Oscomma => 1|Binary|Assoc,
+ Oquest => 3|Binary|Rassoc,
+ Ocolon => 3|Binary|Rassoc,
+ Onand => 8|Binary,
+ Onor => 6|Binary,
+ Oimp => 9|Binary,
+ Oimpby => 9|Binary,
+ Oiff => 10|Binary|Assoc,
+ Olbr => 16,
+ Orbr => 16,
+ Onothing => 0,
+};
+
+Deg: con "degrees";
+Base: con "printbase";
+Limit: con "solvelimit";
+Step: con "solvestep";
+
+keyw := array[] of
+{
+ ("include", Oinclude),
+ ("if", Oif),
+ ("else", Oelse),
+ ("for", Ofor),
+ ("while", Owhile),
+ ("do", Odo),
+ ("break", Obreak),
+ ("continue", Ocont),
+ ("exit", Oexit),
+ ("return", Oret),
+ ("print", Oprint),
+ ("read", Oread),
+ ("fn", Ofn),
+ ("", 0),
+};
+
+conw := array[] of
+{
+ ("π", Pi),
+ ("Pi", Pi),
+ ("e", Euler),
+ ("γ", Gamma),
+ ("Gamma", Gamma),
+ ("φ", Phi),
+ ("Phi", Phi),
+ ("∞", Infinity),
+ ("Infinity", Infinity),
+ ("NaN", Nan),
+ ("Nan", Nan),
+ ("nan", Nan),
+ ("", 0.0),
+};
+
+varw := array[] of
+{
+ (Deg, 0.0),
+ (Base, 10.0),
+ (Limit, 100.0),
+ (Step, 1.0),
+ ("", 0.0),
+};
+
+funw := array[] of
+{
+ ("log", Olog, 1),
+ ("ln", Olog, 1),
+ ("log10", Olog10, 1),
+ ("log2", Olog2, 1),
+ ("logb", Ologb, 2),
+ ("exp", Oexpf, 1),
+ ("pow", Opow, 2),
+ ("sqrt", Osqrt, 1),
+ ("cbrt", Ocbrt, 1),
+ ("floor", Ofloor, 1),
+ ("ceiling", Oceil, 1),
+ ("min", Omin, 2),
+ ("max", Omax, 2),
+ ("abs", Oabs, 1),
+ ("Γ", Ogamma, 1),
+ ("gamma", Ogamma, 1),
+ ("sign", Osign, 1),
+ ("int", Oint, 1),
+ ("frac", Ofrac, 1),
+ ("round", Oround, 1),
+ ("erf", Oerf, 1),
+ ("atan2", Oatan2, 2),
+ ("sin", Osin, 1),
+ ("cos", Ocos, 1),
+ ("tan", Otan, 1),
+ ("asin", Oasin, 1),
+ ("acos", Oacos, 1),
+ ("atan", Oatan, 1),
+ ("sinh", Osinh, 1),
+ ("cosh", Ocosh, 1),
+ ("tanh", Otanh, 1),
+ ("asinh", Oasinh, 1),
+ ("acosh", Oacosh, 1),
+ ("atanh", Oatanh, 1),
+ ("rand", Orand, 0),
+ ("Σ", Osigma, 3),
+ ("sigma", Osigma, 3),
+ ("Π", Opi, 3),
+ ("pi", Opi, 3),
+ ("cfrac", Ocfrac, 3),
+ ("Δ", Oderiv, 2),
+ ("differential", Oderiv, 2),
+ ("∫", Ointeg, 3),
+ ("integral", Ointeg, 3),
+ ("solve", Osolve, 1),
+ ("", 0, 0),
+};
+
+stdin: int;
+bin: ref Iobuf;
+lineno: int = 1;
+file: string;
+iostack: list of (int, int, int, string, ref Iobuf);
+geof: int;
+garg: string;
+gargs: list of string;
+bufc: int = None;
+buft: int = Olast;
+lexes: int;
+lexval: real;
+lexstr: string;
+lexsym: ref Sym;
+syms: array of ref Sym;
+deg: ref Dec;
+pbase: ref Dec;
+errdec: ref Dec;
+inloop: int;
+infn: int;
+
+Node: adt
+{
+ op: int;
+ left: cyclic ref Node;
+ right: cyclic ref Node;
+ val: real;
+ str: string;
+ dec: cyclic ref Dec;
+ src: int;
+};
+
+Dec: adt
+{
+ kind: int;
+ scope: int;
+ sym: cyclic ref Sym;
+ val: real;
+ na: int;
+ code: cyclic ref Node;
+ old: cyclic ref Dec;
+ next: cyclic ref Dec;
+};
+
+Sym: adt
+{
+ name: string;
+ kind: int;
+ dec: cyclic ref Dec;
+ next: cyclic ref Sym;
+};
+
+opstring(t: int): string
+{
+ s := opss[t];
+ if(s != nil)
+ return s;
+ for(i := 0; keyw[i].t0 != nil; i++)
+ if(t == keyw[i].t1)
+ return keyw[i].t0;
+ for(i = 0; funw[i].t0 != nil; i++)
+ if(t == funw[i].t1)
+ return funw[i].t0;
+ return s;
+}
+
+err(s: string)
+{
+ sys->print("error: %s\n", s);
+}
+
+error(n: ref Node, s: string)
+{
+ if(n != nil)
+ lno := n.src;
+ else
+ lno = lineno;
+ s = sys->sprint("line %d: %s", lno, s);
+ if(file != nil)
+ s = sys->sprint("file %s: %s", file, s);
+ raise s;
+}
+
+fatal(s: string)
+{
+ sys->print("fatal: %s\n", s);
+ exit;
+}
+
+stack(s: string, f: ref Iobuf)
+{
+ iostack = (bufc, buft, lineno, file, bin) :: iostack;
+ bufc = None;
+ buft = Olast;
+ lineno = 1;
+ file = s;
+ bin = f;
+}
+
+unstack()
+{
+ (bufc, buft, lineno, file, bin) = hd iostack;
+ iostack = tl iostack;
+}
+
+doinclude(s: string)
+{
+ f := bufio->open(s, Sys->OREAD);
+ if(f == nil)
+ error(nil, sys->sprint("cannot open %s", s));
+ stack(s, f);
+}
+
+getc(): int
+{
+ if((c := bufc) != None)
+ bufc = None;
+ else if(bin != nil)
+ c = bin.getc();
+ else{
+ if(garg == nil){
+ if(gargs == nil){
+ if(geof == 0){
+ geof = 1;
+ c = '\n';
+ }
+ else
+ c = Eof;
+ }
+ else{
+ garg = hd gargs;
+ gargs = tl gargs;
+ c = ' ';
+ }
+ }
+ else{
+ c = garg[0];
+ garg = garg[1: ];
+ }
+ }
+ if(c == Eof && iostack != nil){
+ unstack();
+ return getc();
+ }
+ return c;
+}
+
+ungetc(c: int)
+{
+ bufc = c;
+}
+
+slash(c: int): int
+{
+ if(c != '\\')
+ return c;
+ nc := getc();
+ case(nc){
+ 'b' => return '\b';
+ 'f' => return '\f';
+ 'n' => return '\n';
+ 'r' => return '\r';
+ 't' => return '\t';
+ }
+ return nc;
+}
+
+lexstring(): int
+{
+ sp := "";
+ while((c := getc()) != '"'){
+ if(c == Eof)
+ raise Eeof;
+ sp[len sp] = slash(c);
+ }
+ lexstr = sp;
+ return Ostring;
+}
+
+lexchar(): int
+{
+ while((c := getc()) != '\''){
+ if(c == Eof)
+ raise Eeof;
+ lexval = real slash(c);
+ }
+ return Onum;
+}
+
+basev(c: int, base: int): int
+{
+ if(c >= 'a' && c <= 'z')
+ c += 10-'a';
+ else if(c >= 'A' && c <= 'Z')
+ c += 10-'A';
+ else if(c >= '0' && c <= '9')
+ c -= '0';
+ else
+ return -1;
+ if(c >= base)
+ error(nil, "bad digit");
+ return c;
+}
+
+lexe(base: int): int
+{
+ neg := 0;
+ v := big 0;
+ c := getc();
+ if(c == '-')
+ neg = 1;
+ else
+ ungetc(c);
+ for(;;){
+ c = getc();
+ cc := basev(c, base);
+ if(cc < 0){
+ ungetc(c);
+ break;
+ }
+ v = big base*v+big cc;
+ }
+ if(neg)
+ v = -v;
+ return int v;
+}
+
+lexnum(): int
+{
+ base := 10;
+ exp := 0;
+ r := f := e := 0;
+ v := big 0;
+ c := getc();
+ if(c == '0'){
+ base = 8;
+ c = getc();
+ if(c == '.'){
+ base = 10;
+ ungetc(c);
+ }
+ else if(c == 'x' || c == 'X')
+ base = 16;
+ else
+ ungetc(c);
+ }
+ else
+ ungetc(c);
+ for(;;){
+ c = getc();
+ if(!r && (c == 'r' || c == 'R')){
+ if(f || e)
+ error(nil, "bad base");
+ r = 1;
+ base = int v;
+ if(base < 2 || base > 36)
+ error(nil, "bad base");
+ v = big 0;
+ continue;
+ }
+ if(c == '.'){
+ if(f || e)
+ error(nil, "bad real");
+ f = 1;
+ continue;
+ }
+ if(base == 10 && (c == 'e' || c == 'E')){
+ if(e)
+ error(nil, "bad E part");
+ e = 1;
+ exp = lexe(base);
+ continue;
+ }
+ cc := basev(c, base);
+ if(cc < 0){
+ ungetc(c);
+ break;
+ }
+ v = big base*v+big cc;
+ if(f)
+ f++;
+ }
+ lexval = real v;
+ if(f)
+ lexval /= real base**(f-1);
+ if(exp){
+ if(exp > 0)
+ lexval *= real base**exp;
+ else
+ lexval *= maths->pow(real base, real exp);
+ }
+ return Onum;
+}
+
+lexid(): int
+{
+ sp := "";
+ for(;;){
+ c := getc();
+ if(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c >= 'α' && c <= 'ω' || c >= 'Α' && c <= 'Ω' || c == '_')
+ sp[len sp] = c;
+ else{
+ ungetc(c);
+ break;
+ }
+ }
+ lexsym = enter(sp, Oident);
+ return lexsym.kind;
+}
+
+follow(c: int, c1: int, c2: int): int
+{
+ nc := getc();
+ if(nc == c)
+ return c1;
+ ungetc(nc);
+ return c2;
+}
+
+skip()
+{
+ if((t := buft) != Olast){
+ lex();
+ if(t == Onl)
+ return;
+ }
+ for(;;){
+ c := getc();
+ if(c == Eof){
+ ungetc(c);
+ return;
+ }
+ if(c == '\n'){
+ lineno++;
+ return;
+ }
+ }
+}
+
+lex(): int
+{
+ lexes++;
+ if((t := buft) != Olast){
+ buft = Olast;
+ if(t == Onl)
+ lineno++;
+ return t;
+ }
+ for(;;){
+ case(c := getc()){
+ Eof =>
+ return Oeof;
+ '#' =>
+ while((c = getc()) != '\n'){
+ if(c == Eof)
+ raise Eeof;
+ }
+ lineno++;
+ '\n' =>
+ lineno++;
+ return Onl;
+ ' ' or
+ '\t' or
+ '\r' or
+ '\v' =>
+ ;
+ '"' =>
+ return lexstring();
+ '\'' =>
+ return lexchar();
+ '0' to '9' =>
+ ungetc(c);
+ return lexnum();
+ 'a' to 'z' or
+ 'A' to 'Z' or
+ 'α' to 'ω' or
+ 'Α' to 'Ω' or
+ '_' =>
+ ungetc(c);
+ return lexid();
+ '+' =>
+ c = getc();
+ if(c == '=')
+ return Oadde;
+ ungetc(c);
+ return follow('+', Oinc, Oadd);
+ '-' =>
+ c = getc();
+ if(c == '=')
+ return Osube;
+ if(c == '>')
+ return Oimp;
+ ungetc(c);
+ return follow('-', Odec, Osub);
+ '*' =>
+ c = getc();
+ if(c == '=')
+ return Omule;
+ if(c == '*')
+ return follow('=', Oexpe, Oexp);
+ ungetc(c);
+ return Omul;
+ '/' =>
+ c = getc();
+ if(c == '=')
+ return Odive;
+ if(c == '/')
+ return follow('=', Oidive, Oidiv);
+ ungetc(c);
+ return Odiv;
+ '%' =>
+ return follow('=', Omode, Omod);
+ '&' =>
+ c = getc();
+ if(c == '=')
+ return Oande;
+ ungetc(c);
+ return follow('&', Oandand, Oand);
+ '|' =>
+ c = getc();
+ if(c == '=')
+ return Oore;
+ ungetc(c);
+ return follow('|', Ooror, Oor);
+ '^' =>
+ return follow('=', Oxore, Oxor);
+ '=' =>
+ return follow('=', Oeq, Oas);
+ '!' =>
+ return follow('=', One, Oexc);
+ '>' =>
+ c = getc();
+ if(c == '=')
+ return Oge;
+ if(c == '>')
+ return follow('=', Orshe, Orsh);
+ ungetc(c);
+ return Ogt;
+ '<' =>
+ c = getc();
+ if(c == '=')
+ return Ole;
+ if(c == '<')
+ return follow('=', Olshe, Olsh);
+ if(c == '-')
+ return follow('>', Oiff, Oimpby);
+ ungetc(c);
+ return Olt;
+ '(' =>
+ return Olbr;
+ ')' =>
+ return Orbr;
+ '{' =>
+ return Olcbr;
+ '}' =>
+ return Orcbr;
+ '~' =>
+ return Ocom;
+ '.' =>
+ ungetc(c);
+ return lexnum();
+ ',' =>
+ return Ocomma;
+ '?' =>
+ return Oquest;
+ ':' =>
+ return follow('=', Odas, Ocolon);
+ ';' =>
+ return Oscolon;
+ '↑' =>
+ return Onand;
+ '↓' =>
+ return Onor;
+ '∞' =>
+ lexval = Infinity;
+ return Onum;
+ * =>
+ error(nil, sys->sprint("bad character %c", c));
+ }
+ }
+}
+
+unlex(t: int)
+{
+ lexes--;
+ buft = t;
+ if(t == Onl)
+ lineno--;
+}
+
+mustbe(t: int)
+{
+ nt := lex();
+ if(nt != t)
+ error(nil, sys->sprint("expected %s not %s", opstring(t), opstring(nt)));
+}
+
+consume(t: int)
+{
+ nt := lex();
+ if(nt != t)
+ unlex(nt);
+}
+
+elex(): int
+{
+ t := lex();
+ if(binary(t))
+ return t;
+ if(hexp(t)){
+ unlex(t);
+ return Oscomma;
+ }
+ return t;
+}
+
+hexp(o: int): int
+{
+ return preunary(o) || o == Olbr || atom(o);
+}
+
+atom(o: int): int
+{
+ return o >= Ostring && o <= Olfun;
+}
+
+asop(o: int): int
+{
+ return o == Oas || o == Odas || o >= Oadde && o <= Orshe || o >= Oinc && o <= Opostdec;
+}
+
+preunary(o: int): int
+{
+ return ops[o]&Preunary;
+}
+
+postunary(o: int): int
+{
+ return ops[o]&Postunary;
+}
+
+binary(o: int): int
+{
+ return ops[o]&Binary;
+}
+
+prec(o: int): int
+{
+ return ops[o]&Prec;
+}
+
+assoc(o: int): int
+{
+ return ops[o]&Assoc;
+}
+
+rassoc(o: int): int
+{
+ return ops[o]&Rassoc;
+}
+
+preop(o: int): int
+{
+ case(o){
+ Oadd => return Oplus;
+ Osub => return Ominus;
+ Oinc => return Opreinc;
+ Odec => return Opredec;
+ Oexc => return Onot;
+ }
+ return o;
+}
+
+postop(o: int): int
+{
+ case(o){
+ Oinc => return Opostinc;
+ Odec => return Opostdec;
+ Oexc => return Ofact;
+ }
+ return o;
+}
+
+prtree(p: ref Node, in: int)
+{
+ if(p == nil)
+ return;
+ for(i := 0; i < in; i++)
+ sys->print(" ");
+ sys->print("%s ", opstring(p.op));
+ case(p.op){
+ Ostring =>
+ sys->print("%s", p.str);
+ Onum =>
+ sys->print("%g", p.val);
+ Ocon or
+ Ovar =>
+ sys->print("%s(%g)", p.dec.sym.name, p.dec.val);
+ Ofun or
+ Olfun =>
+ sys->print("%s", p.dec.sym.name);
+ }
+ sys->print("\n");
+ # sys->print(" - %d\n", p.src);
+ prtree(p.left, in+1);
+ prtree(p.right, in+1);
+}
+
+tree(o: int, l: ref Node, r: ref Node): ref Node
+{
+ p := ref Node;
+ p.op = o;
+ p.left = l;
+ p.right = r;
+ p.src = lineno;
+ if(asop(o)){
+ if(o >= Oadde && o <= Orshe){
+ p = tree(Oas, l, p);
+ p.right.op += Oadd-Oadde;
+ }
+ }
+ return p;
+}
+
+itree(n: int): ref Node
+{
+ return vtree(real n);
+}
+
+vtree(v: real): ref Node
+{
+ n := tree(Onum, nil, nil);
+ n.val = v;
+ return n;
+}
+
+ltree(s: string, a: ref Node): ref Node
+{
+ n := tree(Olfun, a, nil);
+ n.dec = lookup(s).dec;
+ return n;
+}
+
+ptree(n: ref Node, p: real): ref Node
+{
+ if(isinteger(p)){
+ i := int p;
+ if(i == 0)
+ return itree(1);
+ if(i == 1)
+ return n;
+ if(i == -1)
+ return tree(Oinv, n, nil);
+ if(i < 0)
+ return tree(Oinv, tree(Oexp, n, itree(-i)), nil);
+ }
+ return tree(Oexp, n, vtree(p));
+}
+
+iscon(n: ref Node): int
+{
+ return n.op == Onum || n.op == Ocon;
+}
+
+iszero(n: ref Node): int
+{
+ return iscon(n) && eval(n) == 0.0;
+}
+
+isone(n: ref Node): int
+{
+ return iscon(n) && eval(n) == 1.0;
+}
+
+isnan(n: ref Node): int
+{
+ return iscon(n) && maths->isnan(eval(n));
+}
+
+isinf(n: ref Node): int
+{
+ return iscon(n) && (v := eval(n)) == Infinity || v == -Infinity;
+}
+
+stat(scope: int): ref Node
+{
+ e1, e2, e3, e4: ref Node;
+
+ consume(Onl);
+ t := lex();
+ case(t){
+ Olcbr =>
+ if(scope)
+ pushscope();
+ for(;;){
+ e2 = stat(1);
+ if(e1 == nil)
+ e1 = e2;
+ else
+ e1 = tree(Ocomma, e1, e2);
+ consume(Onl);
+ t = lex();
+ if(t == Oeof)
+ raise Eeof;
+ if(t == Orcbr)
+ break;
+ unlex(t);
+ }
+ if(scope)
+ popscope();
+ return e1;
+ Oprint or
+ Oread or
+ Oret =>
+ if(t == Oret && !infn)
+ error(nil, "return not in fn");
+ e1= tree(t, expr(0, 1), nil);
+ consume(Oscolon);
+ if(t == Oread)
+ allvar(e1.left);
+ return e1;
+ Oif =>
+ # mustbe(Olbr);
+ e1 = expr(0, 1);
+ # mustbe(Orbr);
+ e2 = stat(1);
+ e3 = nil;
+ consume(Onl);
+ t = lex();
+ if(t == Oelse)
+ e3 = stat(1);
+ else
+ unlex(t);
+ return tree(Oif, e1, tree(Ocomma, e2, e3));
+ Ofor =>
+ inloop++;
+ mustbe(Olbr);
+ e1 = expr(0, 1);
+ mustbe(Oscolon);
+ e2 = expr(0, 1);
+ mustbe(Oscolon);
+ e3 = expr(0, 1);
+ mustbe(Orbr);
+ e4 = stat(1);
+ inloop--;
+ return tree(Ocomma, e1, tree(Ofor, e2, tree(Ocomma, e4, e3)));
+ Owhile =>
+ inloop++;
+ # mustbe(Olbr);
+ e1 = expr(0, 1);
+ # mustbe(Orbr);
+ e2 = stat(1);
+ inloop--;
+ return tree(Ofor, e1, tree(Ocomma, e2, nil));
+ Odo =>
+ inloop++;
+ e1 = stat(1);
+ consume(Onl);
+ mustbe(Owhile);
+ # mustbe(Olbr);
+ e2 = expr(0, 1);
+ # mustbe(Orbr);
+ consume(Oscolon);
+ inloop--;
+ return tree(Odo, e1, e2);
+ Obreak or
+ Ocont or
+ Oexit =>
+ if((t == Obreak || t == Ocont) && !inloop)
+ error(nil, "break/continue not in loop");
+ consume(Oscolon);
+ return tree(t, nil, nil);
+ Ofn =>
+ if(infn)
+ error(nil, "nested functions not allowed");
+ infn++;
+ mustbe(Oident);
+ s := lexsym;
+ d := mkdec(s, Ofun, 1);
+ d.code = tree(Ofn, nil, nil);
+ pushscope();
+ (d.na, d.code.left) = args(0);
+ allvar(d.code.left);
+ pushparams(d.code.left);
+ d.code.right = stat(0);
+ popscope();
+ infn--;
+ return d.code;
+ Oinclude =>
+ e1 = expr(0, 0);
+ if(e1.op != Ostring)
+ error(nil, "bad include file");
+ consume(Oscolon);
+ doinclude(e1.str);
+ return nil;
+ * =>
+ unlex(t);
+ e1 = expr(0, 1);
+ consume(Oscolon);
+ if(debug)
+ prnode(e1);
+ return e1;
+ }
+ return nil;
+}
+
+ckstat(n: ref Node, parop: int, pr: int)
+{
+ if(n == nil)
+ return;
+ pr |= n.op == Oprint;
+ ckstat(n.left, n.op, pr);
+ ckstat(n.right, n.op, pr);
+ case(n.op){
+ Ostring =>
+ if(!pr || parop != Oprint && parop != Ocomma)
+ error(n, "illegal string operation");
+ }
+}
+
+pexp(e: ref Node): int
+{
+ if(e == nil)
+ return 0;
+ if(e.op == Ocomma)
+ return pexp(e.right);
+ return e.op >= Ostring && e.op <= Oiff && !asop(e.op);
+}
+
+expr(p: int, zok: int): ref Node
+{
+ n := exp(p, zok);
+ ckexp(n, Onothing);
+ return n;
+}
+
+exp(p: int, zok: int): ref Node
+{
+ l := prim(zok);
+ if(l == nil)
+ return nil;
+ while(binary(t := elex()) && (o := prec(t)) >= p){
+ if(rassoc(t))
+ r := exp(o, 0);
+ else
+ r = exp(o+1, 0);
+ if(t == Oscomma)
+ t = Ocomma;
+ l = tree(t, l, r);
+ }
+ if(t != Oscomma)
+ unlex(t);
+ return l;
+}
+
+prim(zok: int): ref Node
+{
+ p: ref Node;
+ na: int;
+
+ t := lex();
+ if(preunary(t)){
+ t = preop(t);
+ return tree(t, exp(prec(t), 0), nil);
+ }
+ case(t){
+ Olbr =>
+ p = exp(0, zok);
+ mustbe(Orbr);
+ Ostring =>
+ p = tree(t, nil, nil);
+ p.str = lexstr;
+ Onum =>
+ p = tree(t, nil ,nil);
+ p.val = lexval;
+ Oident =>
+ s := lexsym;
+ d := s.dec;
+ if(d == nil)
+ d = mkdec(s, Ovar, 0);
+ case(t = d.kind){
+ Ocon or
+ Ovar =>
+ p = tree(t, nil, nil);
+ p.dec = d;
+ Ofun or
+ Olfun =>
+ p = tree(t, nil, nil);
+ p.dec = d;
+ (na, p.left) = args(prec(t));
+ if(!(t == Olfun && d.val == real Osolve && na == 2))
+ if(na != d.na)
+ error(p, "wrong number of arguments");
+ if(t == Olfun){
+ case(int d.val){
+ Osigma or
+ Opi or
+ Ocfrac or
+ Ointeg =>
+ if((op := p.left.left.left.op) != Oas && op != Odas)
+ error(p.left, "expression not an assignment");
+ Oderiv =>
+ if((op := p.left.left.op) != Oas && op != Odas)
+ error(p.left, "expression not an assignment");
+ }
+ }
+ }
+ * =>
+ unlex(t);
+ if(!zok)
+ error(nil, "missing expression");
+ return nil;
+ }
+ while(postunary(t = lex())){
+ t = postop(t);
+ p = tree(t, p, nil);
+ }
+ unlex(t);
+ return p;
+}
+
+ckexp(n: ref Node, parop: int)
+{
+ if(n == nil)
+ return;
+ o := n.op;
+ l := n.left;
+ r := n.right;
+ if(asop(o))
+ var(l);
+ case(o){
+ Ovar =>
+ s := n.dec.sym;
+ d := s.dec;
+ if(d == nil){
+ if(strict)
+ error(n, sys->sprint("%s undefined", s.name));
+ d = mkdec(s, Ovar, 1);
+ }
+ n.dec = d;
+ Odas =>
+ ckexp(r, o);
+ l.dec = mkdec(l.dec.sym, Ovar, 1);
+ * =>
+ ckexp(l, o);
+ ckexp(r, o);
+ if(o == Oquest && r.op != Ocolon)
+ error(n, "bad '?' operator");
+ if(o == Ocolon && parop != Oquest)
+ error(n, "bad ':' operator");
+ }
+}
+
+commas(n: ref Node): int
+{
+ if(n == nil || n.op == Ofun || n.op == Olfun)
+ return 0;
+ c := commas(n.left)+commas(n.right);
+ if(n.op == Ocomma)
+ c++;
+ return c;
+}
+
+allvar(n: ref Node)
+{
+ if(n == nil)
+ return;
+ if(n.op == Ocomma){
+ allvar(n.left);
+ allvar(n.right);
+ return;
+ }
+ var(n);
+}
+
+args(p: int): (int, ref Node)
+{
+ if(!p)
+ mustbe(Olbr);
+ a := exp(p, 1);
+ if(!p)
+ mustbe(Orbr);
+ na := 0;
+ if(a != nil)
+ na = commas(a)+1;
+ return (na, a);
+}
+
+hash(s: string): int
+{
+ l := len s;
+ h := 4104;
+ for(i := 0; i < l; i++)
+ h = 1729*h ^ s[i];
+ if(h < 0)
+ h = -h;
+ return h&(Hash-1);
+}
+
+enter(sp: string, k: int): ref Sym
+{
+ for(s := syms[hash(sp)]; s != nil; s = s.next){
+ if(sp == s.name)
+ return s;
+ }
+ s = ref Sym;
+ s.name = sp;
+ s.kind = k;
+ h := hash(sp);
+ s.next = syms[h];
+ syms[h] = s;
+ return s;
+}
+
+lookup(sp: string): ref Sym
+{
+ return enter(sp, Oident);
+}
+
+mkdec(s: ref Sym, k: int, dec: int): ref Dec
+{
+ d := ref Dec;
+ d.kind = k;
+ d.val = 0.0;
+ d.na = 0;
+ d.sym = s;
+ d.scope = 0;
+ if(dec)
+ pushdec(d);
+ return d;
+}
+
+adddec(sp: string, k: int, v: real, n: int): ref Dec
+{
+ d := mkdec(enter(sp, Oident), k, 1);
+ d.val = v;
+ d.na = n;
+ return d;
+}
+
+scope: int;
+curscope: ref Dec;
+scopes: list of ref Dec;
+
+pushscope()
+{
+ scope++;
+ scopes = curscope :: scopes;
+ curscope = nil;
+}
+
+popscope()
+{
+ popdecs();
+ curscope = hd scopes;
+ scopes = tl scopes;
+ scope--;
+}
+
+pushparams(n: ref Node)
+{
+ if(n == nil)
+ return;
+ if(n.op == Ocomma){
+ pushparams(n.left);
+ pushparams(n.right);
+ return;
+ }
+ n.dec = mkdec(n.dec.sym, Ovar, 1);
+}
+
+pushdec(d: ref Dec)
+{
+ if(0 && debug)
+ sys->print("dec %s scope %d\n", d.sym.name, scope);
+ d.scope = scope;
+ s := d.sym;
+ if(s.dec != nil && s.dec.scope == scope)
+ error(nil, sys->sprint("redeclaration of %s", s.name));
+ d.old = s.dec;
+ s.dec = d;
+ d.next = curscope;
+ curscope = d;
+}
+
+popdecs()
+{
+ nd: ref Dec;
+ for(d := curscope; d != nil; d = nd){
+ d.sym.dec = d.old;
+ d.old = nil;
+ nd = d.next;
+ d.next = nil;
+ }
+ curscope = nil;
+}
+
+estat(n: ref Node): (int, real)
+{
+ k: int;
+ v: real;
+
+ if(n == nil)
+ return (Onothing, 0.0);
+ l := n.left;
+ r := n.right;
+ case(n.op){
+ Ocomma =>
+ (k, v) = estat(l);
+ if(k == Oexit || k == Oret || k == Obreak || k == Ocont)
+ return (k, v);
+ return estat(r);
+ Oprint =>
+ v = print(l);
+ return (Onothing, v);
+ Oread =>
+ v = read(l);
+ return (Onothing, v);
+ Obreak or
+ Ocont or
+ Oexit =>
+ return (n.op, 0.0);
+ Oret =>
+ return (Oret, eval(l));
+ Oif =>
+ v = eval(l);
+ if(int v)
+ return estat(r.left);
+ else if(r.right != nil)
+ return estat(r.right);
+ else
+ return (Onothing, v);
+ Ofor =>
+ for(;;){
+ v = eval(l);
+ if(!int v)
+ break;
+ (k, v) = estat(r.left);
+ if(k == Oexit || k == Oret)
+ return (k, v);
+ if(k == Obreak)
+ break;
+ if(r.right != nil)
+ v = eval(r.right);
+ }
+ return (Onothing, v);
+ Odo =>
+ for(;;){
+ (k, v) = estat(l);
+ if(k == Oexit || k == Oret)
+ return (k, v);
+ if(k == Obreak)
+ break;
+ v = eval(r);
+ if(!int v)
+ break;
+ }
+ return (Onothing, v);
+ * =>
+ return (Onothing, eval(n));
+ }
+ return (Onothing, 0.0);
+}
+
+eval(e: ref Node): real
+{
+ lv, rv: real;
+
+ if(e == nil)
+ return 1.0;
+ o := e.op;
+ l := e.left;
+ r := e.right;
+ if(o != Ofun && o != Olfun)
+ lv = eval(l);
+ if(o != Oandand && o != Ooror && o != Oquest)
+ rv = eval(r);
+ case(o){
+ Ostring =>
+ return 0.0;
+ Onum =>
+ return e.val;
+ Ocon or
+ Ovar =>
+ return e.dec.val;
+ Ofun =>
+ return call(e.dec, l);
+ Olfun =>
+ return libfun(int e.dec.val, l);
+ Oadd =>
+ return lv+rv;
+ Osub =>
+ return lv-rv;
+ Omul =>
+ return lv*rv;
+ Odiv =>
+ return lv/rv;
+ Omod =>
+ return real (big lv%big rv);
+ Oidiv =>
+ return real (big lv/big rv);
+ Oand =>
+ return real (big lv&big rv);
+ Oor =>
+ return real (big lv|big rv);
+ Oxor =>
+ return real (big lv^big rv);
+ Olsh =>
+ return real (big lv<<int rv);
+ Orsh =>
+ return real (big lv>>int rv);
+ Oeq =>
+ return real (lv == rv);
+ One =>
+ return real (lv != rv);
+ Ogt =>
+ return real (lv > rv);
+ Olt =>
+ return real (lv < rv);
+ Oge =>
+ return real (lv >= rv);
+ Ole =>
+ return real (lv <= rv);
+ Opreinc =>
+ l.dec.val += 1.0;
+ return l.dec.val;
+ Opostinc =>
+ l.dec.val += 1.0;
+ return l.dec.val-1.0;
+ Opredec =>
+ l.dec.val -= 1.0;
+ return l.dec.val;
+ Opostdec =>
+ l.dec.val -= 1.0;
+ return l.dec.val+1.0;
+ Oexp =>
+ if(isinteger(rv) && rv >= 0.0)
+ return lv**int rv;
+ return maths->pow(lv, rv);
+ Oandand =>
+ if(!int lv)
+ return lv;
+ return eval(r);
+ Ooror =>
+ if(int lv)
+ return lv;
+ return eval(r);
+ Onot =>
+ return real !int lv;
+ Ofact =>
+ if(isinteger(lv) && lv >= 0.0){
+ n := int lv;
+ lv = 1.0;
+ for(i := 2; i <= n; i++)
+ lv *= real i;
+ return lv;
+ }
+ return gamma(lv+1.0);
+ Ocom =>
+ return real ~big lv;
+ Oas or
+ Odas =>
+ l.dec.val = rv;
+ return rv;
+ Oplus =>
+ return lv;
+ Ominus =>
+ return -lv;
+ Oinv =>
+ return 1.0/lv;
+ Ocomma =>
+ return rv;
+ Oquest =>
+ if(int lv)
+ return eval(r.left);
+ else
+ return eval(r.right);
+ Onand =>
+ return real !(int lv&int rv);
+ Onor =>
+ return real !(int lv|int rv);
+ Oimp =>
+ return real (!int lv|int rv);
+ Oimpby =>
+ return real (int lv|!int rv);
+ Oiff =>
+ return real !(int lv^int rv);
+ * =>
+ fatal(sys->sprint("case %s in eval", opstring(o)));
+ }
+ return 0.0;
+}
+
+var(e: ref Node)
+{
+ if(e == nil || e.op != Ovar || e.dec.kind != Ovar)
+ error(e, "expected a variable");
+}
+
+libfun(o: int, a: ref Node): real
+{
+ a1, a2: real;
+
+ case(o){
+ Osolve =>
+ return solve(a);
+ Osigma or
+ Opi or
+ Ocfrac =>
+ return series(o, a);
+ Oderiv =>
+ return differential(a);
+ Ointeg =>
+ return integral(a);
+ }
+ v := 0.0;
+ if(a != nil && a.op == Ocomma){
+ a1 = eval(a.left);
+ a2 = eval(a.right);
+ }
+ else
+ a1 = eval(a);
+ case(o){
+ Olog =>
+ v = maths->log(a1);
+ Olog10 =>
+ v = maths->log10(a1);
+ Olog2 =>
+ v = maths->log(a1)/maths->log(2.0);
+ Ologb =>
+ v = maths->log(a1)/maths->log(a2);
+ Oexpf =>
+ v = maths->exp(a1);
+ Opow =>
+ v = maths->pow(a1, a2);
+ Osqrt =>
+ v = maths->sqrt(a1);
+ Ocbrt =>
+ v = maths->cbrt(a1);
+ Ofloor =>
+ v = maths->floor(a1);
+ Oceil =>
+ v = maths->ceil(a1);
+ Omin =>
+ v = maths->fmin(a1, a2);
+ Omax =>
+ v = maths->fmax(a1, a2);
+ Oabs =>
+ v = maths->fabs(a1);
+ Ogamma =>
+ v = gamma(a1);
+ Osign =>
+ if(a1 > 0.0)
+ v = 1.0;
+ else if(a1 < 0.0)
+ v = -1.0;
+ else
+ v = 0.0;
+ Oint =>
+ (vi, nil) := maths->modf(a1);
+ v = real vi;
+ Ofrac =>
+ (nil, v) = maths->modf(a1);
+ Oround =>
+ v = maths->rint(a1);
+ Oerf =>
+ v = maths->erf(a1);
+ Osin =>
+ v = maths->sin(D2R(a1));
+ Ocos =>
+ v = maths->cos(D2R(a1));
+ Otan =>
+ v = maths->tan(D2R(a1));
+ Oasin =>
+ v = R2D(maths->asin(a1));
+ Oacos =>
+ v = R2D(maths->acos(a1));
+ Oatan =>
+ v = R2D(maths->atan(a1));
+ Oatan2 =>
+ v = R2D(maths->atan2(a1, a2));
+ Osinh =>
+ v = maths->sinh(a1);
+ Ocosh =>
+ v = maths->cosh(a1);
+ Otanh =>
+ v = maths->tanh(a1);
+ Oasinh =>
+ v = maths->asinh(a1);
+ Oacosh =>
+ v = maths->acosh(a1);
+ Oatanh =>
+ v = maths->atanh(a1);
+ Orand =>
+ v = real rand->rand(Big)/real Big;
+ * =>
+ fatal(sys->sprint("case %s in libfun", opstring(o)));
+ }
+ return v;
+}
+
+series(o: int, a: ref Node): real
+{
+ p0, p1, q0, q1: real;
+
+ l := a.left;
+ r := a.right;
+ if(o == Osigma)
+ v := 0.0;
+ else if(o == Opi)
+ v = 1.0;
+ else{
+ p0 = q1 = 0.0;
+ p1 = q0 = 1.0;
+ v = Infinity;
+ }
+ i := l.left.left.dec;
+ ov := i.val;
+ i.val = eval(l.left.right);
+ eq := 0;
+ for(;;){
+ rv := eval(l.right);
+ if(i.val > rv)
+ break;
+ lv := v;
+ ev := eval(r);
+ if(o == Osigma)
+ v += ev;
+ else if(o == Opi)
+ v *= ev;
+ else{
+ t := ev*p1+p0;
+ p0 = p1;
+ p1 = t;
+ t = ev*q1+q0;
+ q0 = q1;
+ q1 = t;
+ v = p1/q1;
+ }
+ if(v == lv && rv == Infinity){
+ eq++;
+ if(eq > 100)
+ break;
+ }
+ else
+ eq = 0;
+ i.val += 1.0;
+ }
+ i.val = ov;
+ return v;
+}
+
+pushe(a: ref Node, l: list of real): list of real
+{
+ if(a == nil)
+ return l;
+ if(a.op == Ocomma){
+ l = pushe(a.left, l);
+ return pushe(a.right, l);
+ }
+ l = eval(a) :: l;
+ return l;
+}
+
+pusha(f: ref Node, l: list of real, nl: list of real): (list of real, list of real)
+{
+ if(f == nil)
+ return (l, nl);
+ if(f.op == Ocomma){
+ (l, nl) = pusha(f.left, l, nl);
+ return pusha(f.right, l, nl);
+ }
+ l = f.dec.val :: l;
+ f.dec.val = hd nl;
+ return (l, tl nl);
+}
+
+pop(f: ref Node, l: list of real): list of real
+{
+ if(f == nil)
+ return l;
+ if(f.op == Ocomma){
+ l = pop(f.left, l);
+ return pop(f.right, l);
+ }
+ f.dec.val = hd l;
+ return tl l;
+}
+
+rev(l: list of real): list of real
+{
+ nl: list of real;
+
+ for( ; l != nil; l = tl l)
+ nl = hd l :: nl;
+ return nl;
+}
+
+call(d: ref Dec, a: ref Node): real
+{
+ l: list of real;
+
+ nl := rev(pushe(a, nil));
+ (l, nil) = pusha(d.code.left, nil, nl);
+ l = rev(l);
+ (k, v) := estat(d.code.right);
+ l = pop(d.code.left, l);
+ if(k == Oexit)
+ exit;
+ return v;
+}
+
+print(n: ref Node): real
+{
+ if(n == nil)
+ return 0.0;
+ if(n.op == Ocomma){
+ print(n.left);
+ return print(n.right);
+ }
+ if(n.op == Ostring){
+ sys->print("%s", n.str);
+ return 0.0;
+ }
+ v := eval(n);
+ printnum(v, "");
+ return v;
+}
+
+read(n: ref Node): real
+{
+ bio: ref Iobuf;
+
+ if(n == nil)
+ return 0.0;
+ if(n.op == Ocomma){
+ read(n.left);
+ return read(n.right);
+ }
+ sys->print("%s ? ", n.dec.sym.name);
+ if(!stdin){
+ bio = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ stack(nil, bio);
+ }
+ lexnum();
+ consume(Onl);
+ n.dec.val = lexval;
+ if(!stdin && bin == bio)
+ unstack();
+ return n.dec.val;
+}
+
+isint(v: real): int
+{
+ return v >= -real Maxint && v <= real Maxint;
+}
+
+isinteger(v: real): int
+{
+ return v == real int v && isint(v);
+}
+
+split(v: real): (int, real)
+{
+ # v >= 0.0
+ n := int v;
+ if(real n > v)
+ n--;
+ return (n, v-real n);
+}
+
+n2c(n: int): int
+{
+ if(n < 10)
+ return n+'0';
+ return n-10+'a';
+}
+
+gamma(v: real): real
+{
+ (s, lg) := maths->lgamma(v);
+ return real s*maths->exp(lg);
+}
+
+D2R(a: real): real
+{
+ if(deg.val != 0.0)
+ a *= Pi/180.0;
+ return a;
+}
+
+R2D(a: real): real
+{
+ if(deg.val != 0.0)
+ a /= Pi/180.0;
+ return a;
+}
+
+side(n: ref Node): int
+{
+ if(n == nil)
+ return 0;
+ if(asop(n.op) || n.op == Ofun)
+ return 1;
+ return side(n.left) || side(n.right);
+}
+
+sametree(n1: ref Node, n2: ref Node): int
+{
+ if(n1 == n2)
+ return 1;
+ if(n1 == nil || n2 == nil)
+ return 0;
+ if(n1.op != n2.op)
+ return 0;
+ case(n1.op){
+ Ostring =>
+ return n1.str == n2.str;
+ Onum =>
+ return n1.val == n2.val;
+ Ocon or
+ Ovar =>
+ return n1.dec == n2.dec;
+ Ofun or
+ Olfun =>
+ return n1.dec == n2.dec && sametree(n1.left, n2.left);
+ * =>
+ return sametree(n1.left, n2.left) && sametree(n1.right, n2.right);
+ }
+ return 0;
+}
+
+simplify(n: ref Node): ref Node
+{
+ if(n == nil)
+ return nil;
+ op := n.op;
+ l := n.left = simplify(n.left);
+ r := n.right = simplify(n.right);
+ if(l != nil && iscon(l) && (r == nil || iscon(r))){
+ if(isnan(l))
+ return l;
+ if(r != nil && isnan(r))
+ return r;
+ return vtree(eval(n));
+ }
+ case(op){
+ Onum or
+ Ocon or
+ Ovar or
+ Olfun or
+ Ocomma =>
+ return n;
+ Oplus =>
+ return l;
+ Ominus =>
+ if(l.op == Ominus)
+ return l.left;
+ Oinv =>
+ if(l.op == Oinv)
+ return l.left;
+ Oadd =>
+ if(iszero(l))
+ return r;
+ if(iszero(r))
+ return l;
+ if(sametree(l, r))
+ return tree(Omul, itree(2), l);
+ Osub =>
+ if(iszero(l))
+ return simplify(tree(Ominus, r, nil));
+ if(iszero(r))
+ return l;
+ if(sametree(l, r))
+ return itree(0);
+ Omul =>
+ if(iszero(l))
+ return l;
+ if(iszero(r))
+ return r;
+ if(isone(l))
+ return r;
+ if(isone(r))
+ return l;
+ if(sametree(l, r))
+ return tree(Oexp, l, itree(2));
+ Odiv =>
+ if(iszero(l))
+ return l;
+ if(iszero(r))
+ return vtree(Infinity);
+ if(isone(l))
+ return ptree(r, -1.0);
+ if(isone(r))
+ return l;
+ if(sametree(l, r))
+ return itree(1);
+ Oexp =>
+ if(iszero(l))
+ return l;
+ if(iszero(r))
+ return itree(1);
+ if(isone(l))
+ return l;
+ if(isone(r))
+ return l;
+ * =>
+ fatal(sys->sprint("case %s in simplify", opstring(op)));
+ }
+ return n;
+}
+
+deriv(n: ref Node, d: ref Dec): ref Node
+{
+ if(n == nil)
+ return nil;
+ op := n.op;
+ l := n.left;
+ r := n.right;
+ case(op){
+ Onum or
+ Ocon =>
+ n = itree(0);
+ Ovar =>
+ if(d == n.dec)
+ n = itree(1);
+ else
+ n = itree(0);
+ Olfun =>
+ case(int n.dec.val){
+ Olog =>
+ n = ptree(l, -1.0);
+ Olog10 =>
+ n = ptree(tree(Omul, l, vtree(Ln10)), -1.0);
+ Olog2 =>
+ n = ptree(tree(Omul, l, vtree(Ln2)), -1.0);
+ Oexpf =>
+ n = n;
+ Opow =>
+ return deriv(tree(Oexp, l.left, l.right), d);
+ Osqrt =>
+ return deriv(tree(Oexp, l, vtree(0.5)), d);
+ Ocbrt =>
+ return deriv(tree(Oexp, l, vtree(1.0/3.0)), d);
+ Osin =>
+ n = ltree("cos", l);
+ Ocos =>
+ n = tree(Ominus, ltree("sin", l), nil);
+ Otan =>
+ n = ptree(ltree("cos", l), -2.0);
+ Oasin =>
+ n = ptree(tree(Osub, itree(1), ptree(l, 2.0)), -0.5);
+ Oacos =>
+ n = tree(Ominus, ptree(tree(Osub, itree(1), ptree(l, 2.0)), -0.5), nil);
+ Oatan =>
+ n = ptree(tree(Oadd, itree(1), ptree(l, 2.0)), -1.0);
+ Osinh =>
+ n = ltree("cosh", l);
+ Ocosh =>
+ n = ltree("sinh", l);
+ Otanh =>
+ n = ptree(ltree("cosh", l), -2.0);
+ Oasinh =>
+ n = ptree(tree(Oadd, itree(1), ptree(l, 2.0)), -0.5);
+ Oacosh =>
+ n = ptree(tree(Osub, ptree(l, 2.0), itree(1)), -0.5);
+ Oatanh =>
+ n = ptree(tree(Osub, itree(1), ptree(l, 2.0)), -1.0);
+ * =>
+ return vtree(Nan);
+ }
+ return tree(Omul, n, deriv(l, d));
+ Oplus or
+ Ominus =>
+ n = tree(op, deriv(l, d), nil);
+ Oinv =>
+ n = tree(Omul, tree(Ominus, ptree(l, -2.0), nil), deriv(l, d));
+ Oadd or
+ Osub or
+ Ocomma =>
+ n = tree(op, deriv(l, d), deriv(r, d));
+ Omul =>
+ n = tree(Oadd, tree(Omul, deriv(l, d), r), tree(Omul, l, deriv(r, d)));
+ Odiv =>
+ n = tree(Osub, tree(Omul, deriv(l, d), r), tree(Omul, l, deriv(r, d)));
+ n = tree(Odiv, n, ptree(r, 2.0));
+ Oexp =>
+ nn := tree(Oadd, tree(Omul, deriv(l, d), tree(Odiv, r, l)), tree(Omul, ltree("log", l), deriv(r, d)));
+ n = tree(Omul, n, nn);
+ * =>
+ n = vtree(Nan);
+ }
+ return n;
+}
+
+derivative(n: ref Node, d: ref Dec): ref Node
+{
+ n = simplify(deriv(n, d));
+ if(isnan(n))
+ error(n, "no derivative");
+ if(debug)
+ prnode(n);
+ return n;
+}
+
+newton(f: ref Node, e: ref Node, d: ref Dec, v1: real, v2: real): (int, real)
+{
+ v := (v1+v2)/2.0;
+ lv := 0.0;
+ its := 0;
+ for(;;){
+ lv = v;
+ d.val = v;
+ v = eval(e);
+ # if(v < v1 || v > v2)
+ # return (0, 0.0);
+ if(maths->isnan(v))
+ return (0, 0.0);
+ if(its > 100 || fabs(v-lv) < Eps)
+ break;
+ its++;
+ }
+ if(fabs(v-lv) > Bigeps || fabs(eval(f)) > Bigeps)
+ return (0, 0.0);
+ return (1, v);
+}
+
+solve(n: ref Node): real
+{
+ d: ref Dec;
+
+ if(n == nil)
+ return Nan;
+ if(n.op == Ocomma){ # solve(..., var)
+ var(n.right);
+ d = n.right.dec;
+ n = n.left;
+ if(!varmem(n, d))
+ error(n, "variable not in equation");
+ }
+ else{
+ d = findvar(n, nil);
+ if(d == nil)
+ error(n, "variable missing");
+ if(d == errdec)
+ error(n, "one variable only required");
+ }
+ if(n.op == Oeq)
+ n.op = Osub;
+ dn := derivative(n, d);
+ var := tree(Ovar, nil, nil);
+ var.dec = d;
+ nr := tree(Osub, var, tree(Odiv, n, dn));
+ ov := d.val;
+ lim := lookup(Limit).dec.val;
+ step := lookup(Step).dec.val;
+ rval := Infinity;
+ d.val = -lim-step;
+ v1 := 0.0;
+ v2 := eval(n);
+ for(v := -lim; v <= lim; v += step){
+ d.val = v;
+ v1 = v2;
+ v2 = eval(n);
+ if(maths->isnan(v2)) # v == nan, v <= nan, v >= nan all give 1
+ continue;
+ if(fabs(v2) < Eps){
+ if(v >= -lim && v <= lim && v != rval){
+ printnum(v, " ");
+ rval = v;
+ }
+ }
+ else if(v1*v2 <= 0.0){
+ (f, rv) := newton(n, nr, var.dec, v-step, v);
+ if(f && rv >= -lim && rv <= lim && rv != rval){
+ printnum(rv, " ");
+ rval = rv;
+ }
+ }
+ }
+ d.val = ov;
+ if(rval == Infinity)
+ error(n, "no roots found");
+ else
+ sys->print("\n");
+ return rval;
+}
+
+differential(n: ref Node): real
+{
+ x := n.left.left.dec;
+ ov := x.val;
+ v := evalx(derivative(n.right, x), x, eval(n.left.right));
+ x.val = ov;
+ return v;
+}
+
+integral(n: ref Node): real
+{
+ l := n.left;
+ r := n.right;
+ x := l.left.left.dec;
+ ov := x.val;
+ a := eval(l.left.right);
+ b := eval(l.right);
+ h := b-a;
+ end := evalx(r, x, a) + evalx(r, x, b);
+ odd := even := 0.0;
+ oldarea := 0.0;
+ area := h*end/2.0;
+ for(i := 1; i < 1<<16; i <<= 1){
+ even += odd;
+ odd = 0.0;
+ xv := a+h/2.0;
+ for(j := 0; j < i; j++){
+ odd += evalx(r, x, xv);
+ xv += h;
+ }
+ h /= 2.0;
+ oldarea = area;
+ area = h*(end+4.0*odd+2.0*even)/3.0;
+ if(maths->isnan(area))
+ error(n, "integral not found");
+ if(fabs(area-oldarea) < Eps)
+ break;
+ }
+ if(fabs(area-oldarea) > Bigeps)
+ error(n, "integral not found");
+ x.val = ov;
+ return area;
+}
+
+evalx(n: ref Node, d: ref Dec, v: real): real
+{
+ d.val = v;
+ return eval(n);
+}
+
+findvar(n: ref Node, d: ref Dec): ref Dec
+{
+ if(n == nil)
+ return d;
+ d = findvar(n.left, d);
+ d = findvar(n.right, d);
+ if(n.op == Ovar){
+ if(d == nil)
+ d = n.dec;
+ if(n.dec != d)
+ d = errdec;
+ }
+ return d;
+}
+
+varmem(n: ref Node, d: ref Dec): int
+{
+ if(n == nil)
+ return 0;
+ if(n.op == Ovar)
+ return d == n.dec;
+ return varmem(n.left, d) || varmem(n.right, d);
+}
+
+fabs(r: real): real
+{
+ if(r < 0.0)
+ return -r;
+ return r;
+}
+
+cvt(v: real, base: int): string
+{
+ if(base == 10)
+ return sys->sprint("%g", v);
+ neg := 0;
+ if(v < 0.0){
+ neg = 1;
+ v = -v;
+ }
+ if(!isint(v)){
+ n := 0;
+ lg := maths->log(v)/maths->log(real base);
+ if(lg < 0.0){
+ (n, nil) = split(-lg);
+ v *= real base**n;
+ n = -n;
+ }
+ else{
+ (n, nil) = split(lg);
+ v /= real base**n;
+ }
+ s := cvt(v, base) + "E" + string n;
+ if(neg)
+ s = "-" + s;
+ return s;
+ }
+ (n, f) := split(v);
+ s := "";
+ do{
+ r := n%base;
+ n /= base;
+ s[len s] = n2c(r);
+ }while(n != 0);
+ ls := len s;
+ for(i := 0; i < ls/2; i++){
+ t := s[i];
+ s[i] = s[ls-1-i];
+ s[ls-1-i] = t;
+ }
+ if(f != 0.0){
+ s[len s] = '.';
+ for(i = 0; i < 16 && f != 0.0; i++){
+ f *= real base;
+ (n, f) = split(f);
+ s[len s] = n2c(n);
+ }
+ }
+ s = string base + "r" + s;
+ if(neg)
+ s = "-" + s;
+ return s;
+}
+
+printnum(v: real, s: string)
+{
+ base := int pbase.val;
+ if(!isinteger(pbase.val) || base < 2 || base > 36)
+ base = 10;
+ sys->print("%s%s", cvt(v, base), s);
+ if(bits){
+ r := array[1] of real;
+ b := array[8] of byte;
+ r[0] = v;
+ maths->export_real(b, r);
+ for(i := 0; i < 8; i++)
+ sys->print("%2.2x ", int b[i]);
+ sys->print("\n");
+ }
+}
+
+Left, Right, Pre, Post: con 1<<iota;
+
+lspace := array[] of { 0, 0, 2, 3, 4, 5, 0, 0, 0, 9, 10, 0, 0, 0, 0, 0, 0, 0 };
+rspace := array[] of { 0, 1, 2, 3, 4, 5, 0, 0, 0, 9, 10, 0, 0, 0, 0, 0, 0, 0 };
+
+preced(op1: int, op2: int, s: int): int
+{
+ br := 0;
+ p1 := prec(op1);
+ p2 := prec(op2);
+ if(p1 > p2)
+ br = 1;
+ else if(p1 == p2){
+ if(op1 == op2){
+ if(rassoc(op1))
+ br = s == Left;
+ else
+ br = s == Right && !assoc(op1);
+ }
+ else{
+ if(rassoc(op1))
+ br = s == Left;
+ else
+ br = s == Right && op1 != Oadd;
+ if(postunary(op1) && preunary(op2))
+ br = 1;
+ }
+ }
+ return br;
+}
+
+prnode(n: ref Node)
+{
+ pnode(n, Onothing, Pre);
+ sys->print("\n");
+}
+
+pnode(n: ref Node, opp: int, s: int)
+{
+ if(n == nil)
+ return;
+ op := n.op;
+ if(br := preced(opp, op, s))
+ sys->print("(");
+ if(op == Oas && n.right.op >= Oadd && n.right.op <= Orsh && n.left == n.right.left){
+ pnode(n.left, op, Left);
+ sys->print(" %s ", opstring(n.right.op+Oadde-Oadd));
+ pnode(n.right.right, op, Right);
+ }
+ else if(binary(op)){
+ p := prec(op);
+ pnode(n.left, op, Left);
+ if(lspace[p])
+ sys->print(" ");
+ sys->print("%s", opstring(op));
+ if(rspace[p])
+ sys->print(" ");
+ pnode(n.right, op, Right);
+ }
+ else if(op == Oinv){ # cannot print postunary -1
+ sys->print("%s", opstring(op));
+ pnode(n.left, Odiv, Right);
+ }
+ else if(preunary(op)){
+ sys->print("%s", opstring(op));
+ pnode(n.left, op, Pre);
+ }
+ else if(postunary(op)){
+ pnode(n.left, op, Post);
+ sys->print("%s", opstring(op));
+ }
+ else{
+ case(op){
+ Ostring =>
+ sys->print("%s", n.str);
+ Onum =>
+ sys->print("%g", n.val);
+ Ocon or
+ Ovar =>
+ sys->print("%s", n.dec.sym.name);
+ Ofun or
+ Olfun =>
+ sys->print("%s(", n.dec.sym.name);
+ pnode(n.left, Onothing, Pre);
+ sys->print(")");
+ * =>
+ fatal(sys->sprint("bad op %s in pnode()", opstring(op)));
+ }
+ }
+ if(br)
+ sys->print(")");
+}
diff --git a/appl/cmd/md5sum.b b/appl/cmd/md5sum.b
new file mode 100644
index 00000000..399ba354
--- /dev/null
+++ b/appl/cmd/md5sum.b
@@ -0,0 +1,65 @@
+implement MD5sum;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+
+MD5sum: module
+{
+ init: fn(nil : ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ kr = load Keyring Keyring->PATH;
+ a := tl argv;
+ err := 0;
+ if(a != nil){
+ for( ; a != nil; a = tl a) {
+ s := hd a;
+ fd := sys->open(s, Sys->OREAD);
+ if (fd == nil) {
+ sys->fprint(stderr, "md5sum: cannot open %s: %r\n", s);
+ err = 1;
+ } else
+ err |= md5sum(fd, s);
+ }
+ } else
+ err |= md5sum(sys->fildes(0), "");
+ if(err)
+ raise "fail:error";
+}
+
+md5sum(fd: ref Sys->FD, file: string): int
+{
+ err := 0;
+ buf := array[Sys->ATOMICIO] of byte;
+ state: ref Keyring->DigestState = nil;
+ nbytes := big 0;
+ while((nr := sys->read(fd, buf, len buf)) > 0){
+ state = kr->md5(buf, nr, nil, state);
+ nbytes += big nr;
+ }
+ if(nr < 0) {
+ sys->fprint(stderr, "md5sum: error reading %s: %r\n", file);
+ err = 1;
+ }
+ digest := array[Keyring->MD5dlen] of byte;
+ kr->md5(buf, 0, digest, state);
+ sum := "";
+ for(i:=0; i<len digest; i++)
+ sum += sys->sprint("%2.2ux", int digest[i]);
+ if(file != nil)
+ sys->print("%s\t%s\n", sum, file);
+ else
+ sys->print("%s\n", sum);
+ return err;
+}
diff --git a/appl/cmd/mdb.b b/appl/cmd/mdb.b
new file mode 100644
index 00000000..71938af5
--- /dev/null
+++ b/appl/cmd/mdb.b
@@ -0,0 +1,335 @@
+implement Mdb;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+ print, sprint: import sys;
+
+include "draw.m";
+include "string.m";
+ str: String;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Mdb: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+mfd: ref Sys->FD;
+dot := 0;
+lastaddr := 0;
+count := 1;
+
+atoi(s: string): int
+{
+ b := 10;
+ if(s == nil)
+ return 0;
+ if(s[0] == '0') {
+ b = 8;
+ s = s[1:];
+ if(s == nil)
+ return 0;
+ if(s[0] == 'x' || s[0] == 'X') {
+ b = 16;
+ s = s[1:];
+ }
+ }
+ n: int;
+ (n, nil) = str->toint(s, b);
+ return n;
+}
+
+eatws(s: string): string
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] != ' ' && s[i] != '\t')
+ return s[i:];
+ return nil;
+}
+
+eatnum(s: string): string
+{
+ if(len s == 0)
+ return s;
+ while(gotnum(s) || gotalpha(s))
+ s = s[1:];
+ return s;
+}
+
+gotnum(s: string): int
+{
+ if(len s == 0)
+ return 0;
+ if(s[0] >= '0' && s[0] <= '9')
+ return 1;
+ else
+ return 0;
+}
+
+gotalpha(s: string): int
+{
+ if(len s == 0)
+ return 0;
+ if((s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z'))
+ return 1;
+ else
+ return 0;
+}
+
+getexpr(s: string): (string, int, int)
+{
+ ov: int;
+ v := 0;
+ op := '+';
+ for(;;) {
+ ov = v;
+ s = eatws(s);
+ if(s == nil)
+ return (nil, 0, 0);
+ if(s[0] == '.' || s[0] == '+' || s[0] == '^') {
+ v = dot;
+ s = s[1:];
+ } else if(s[0] == '"') {
+ v = lastaddr;
+ s = s[1:];
+ } else if(s[0] == '(') {
+ (s, v, nil) = getexpr(s[1:]);
+ s = s[1:];
+ } else if(gotnum(s)) {
+ v = atoi(s);
+ s = eatnum(s);
+ } else
+ return (s, 0, 0);
+ case op {
+ '+' => v = ov+v;
+ '-' => v = ov-v;
+ '*' => v = ov*v;
+ '%' => v = ov/v;
+ '&' => v = ov&v;
+ '|' => v = ov|v;
+ }
+ if(s == nil)
+ return (nil, v, 1);
+ case s[0] {
+ '+' or '-' or '*' or '%' or '&' or '|' =>
+ op = s[0]; s = s[1:];
+ * =>
+ return (eatws(s), v, 1);
+ }
+ }
+}
+
+lastcmd := "";
+
+docmd(s: string)
+{
+ ok: int;
+ n: int;
+ s = eatws(s);
+ (s, n, ok) = getexpr(s);
+ if(ok) {
+ dot = n;
+ lastaddr = n;
+ }
+ count = 1;
+ if(s != nil && s[0] == ',') {
+ (s, n, ok) = getexpr(s[1:]);
+ if(ok)
+ count = n;
+ }
+ if(s == nil && (s = lastcmd) == nil)
+ return;
+ lastcmd = s;
+ cmd := s[0];
+ case cmd {
+ '?' or '/' =>
+ case s[1] {
+ 'w' =>
+ writemem(2, s[2:]);
+ 'W' =>
+ writemem(4, s[2:]);
+ * =>
+ dumpmem(s[1:], cmd);
+ }
+ '=' =>
+ dumpmem(s[1:], cmd);
+ * =>
+ sys->fprint(stderr, "invalid cmd: %c\n", cmd);
+ }
+}
+
+octal(n: int, d: int): string
+{
+ s: string;
+ do {
+ s = string (n%8) + s;
+ n /= 8;
+ } while(d-- > 1);
+ return "0" + s;
+}
+
+printable(c: int): string
+{
+ case c {
+ 32 to 126 =>
+ return sprint("%c", c);
+ '\n' =>
+ return "\\n";
+ '\r' =>
+ return "\\r";
+ '\b' =>
+ return "\\b";
+ '\a' =>
+ return "\\a";
+ '\v' =>
+ return "\\v";
+ * =>
+ return sprint("\\x%2.2x", c);
+ }
+
+}
+
+dumpmem(s: string, t: int)
+{
+ n := 0;
+ c := count;
+ while(c-- > 0) for(p:=0; p<len s; p++) {
+ fmt := s[p];
+ case fmt {
+ 'b' or 'c' or 'C' =>
+ n = 1;
+ 'x' or 'd' or 'u' or 'o' =>
+ n = 2;
+ 'X' or 'D' or 'U' or 'O' =>
+ n = 4;
+ 's' or 'S' or 'r' or 'R' =>
+ print("'%c' format not yet supported\n", fmt);
+ continue;
+ 'n' =>
+ print("\n");
+ continue;
+ '+' =>
+ dot++;
+ continue;
+ '-' =>
+ dot--;
+ continue;
+ '^' =>
+ dot -= n;
+ continue;
+ * =>
+ print("unknown format '%c'\n", fmt);
+ continue;
+ }
+ b := array[n] of byte;
+ v: int;
+ if(t == '=')
+ v = dot;
+ else {
+ sys->seek(mfd, big dot, Sys->SEEKSTART);
+ sys->read(mfd, b, len b);
+ v = 0;
+ for(i := 0; i < n; i++)
+ v |= int b[i] << (8*i);
+ }
+ case fmt {
+ 'c' => print("%c", v);
+ 'C' => print("%s", printable(v));
+ 'b' => print("%#2.2ux ", v);
+ 'x' => print("%#4.4ux ", v);
+ 'X' => print("%#8.8ux ", v);
+ 'd' => print("%-4d ", v);
+ 'D' => print("%-8d ", v);
+ 'u' => print("%-4ud ", v);
+ 'U' => print("%-8ud ", v);
+ 'o' => print("%s ", octal(v, 6));
+ 'O' => print("%s ", octal(v, 11));
+ }
+ if(t != '=')
+ dot += n;
+ }
+ print("\n");
+}
+
+writemem(n: int, s: string)
+{
+ v: int;
+ ok: int;
+ s = eatws(s);
+ sys->seek(mfd, big dot, Sys->SEEKSTART);
+ for(;;) {
+ (s, v, ok) = getexpr(s);
+ if(!ok)
+ return;
+ b := array[n] of byte;
+ for(i := 0; i < n; i++)
+ b[i] = byte (v >> (8*i));
+ if (sys->write(mfd, b, len b) != len b)
+ sys->fprint(stderr, "mdb: write error: %r\n");
+ }
+}
+
+usage()
+{
+ sys->fprint(stderr, "usage: mdb [-w] file [command]\n");
+ raise "fail:usage";
+}
+
+writeable := 0;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ str = load String String->PATH;
+ if (str == nil) {
+ sys->fprint(stderr, "mdb: cannot load %s: %r\n", String->PATH);
+ raise "fail:bad module";
+ }
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) {
+ sys->fprint(stderr, "mdb: cannot load %s: %r\n", Bufio->PATH);
+ raise "fail:bad module";
+ }
+
+ if (len argv < 2)
+ usage();
+ if (argv != nil)
+ argv = tl argv;
+ if (argv != nil && len hd argv && (hd argv)[0] == '-') {
+ if (hd argv != "-w")
+ usage();
+ writeable = 1;
+ argv = tl argv;
+ }
+ if (argv == nil)
+ usage();
+ fname := hd argv;
+ argv = tl argv;
+ cmd := "";
+ if(argv != nil)
+ cmd = hd argv;
+
+ oflags := Sys->OREAD;
+ if (writeable)
+ oflags = Sys->ORDWR;
+ mfd = sys->open(fname, oflags);
+ if(mfd == nil) {
+ sys->fprint(stderr, "mdb: cannot open %s: %r\n", fname);
+ raise "fail:cannot open";
+ }
+
+ if(cmd != nil)
+ docmd(cmd);
+ else {
+ stdin := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ while ((s := stdin.gets('\n')) != nil) {
+ if (s[len s -1] == '\n')
+ s = s[0:len s - 1];
+ docmd(s);
+ }
+ }
+}
diff --git a/appl/cmd/memfs.b b/appl/cmd/memfs.b
new file mode 100644
index 00000000..e18388c5
--- /dev/null
+++ b/appl/cmd/memfs.b
@@ -0,0 +1,648 @@
+implement MemFS;
+
+include "sys.m";
+ sys: Sys;
+ OTRUNC, ORCLOSE, OREAD, OWRITE: import Sys;
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+include "styxlib.m";
+ styxlib: Styxlib;
+ Styxserver: import styxlib;
+include "draw.m";
+include "arg.m";
+
+MemFS: module {
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+
+blksz : con 512;
+Efull : con "filesystem full";
+
+Memfile : adt {
+ name : string;
+ owner : string;
+ qid : Sys->Qid;
+ perm : int;
+ atime : int;
+ mtime : int;
+ nopen : int;
+ data : array of array of byte; # allocated in blks, no holes
+ length : int;
+ parent : cyclic ref Memfile; # Dir entry linkage
+ kids : cyclic ref Memfile;
+ prev : cyclic ref Memfile;
+ next : cyclic ref Memfile;
+ hashnext : cyclic ref Memfile; # Qid hash linkage
+};
+
+Qidhash : adt {
+ buckets : array of ref Memfile;
+ nextqid : int;
+ new : fn () : ref Qidhash;
+ add : fn (h : self ref Qidhash, mf : ref Memfile);
+ remove : fn (h : self ref Qidhash, mf : ref Memfile);
+ lookup : fn (h : self ref Qidhash, qid : Sys->Qid) : ref Memfile;
+};
+
+timefd: ref Sys->FD;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ styx = checkload(load Styx Styx->PATH, Styx->PATH);
+ styxlib = checkload(load Styxlib Styxlib->PATH, Styxlib->PATH);
+ arg := checkload(load Arg Arg->PATH, Arg->PATH);
+
+ amode := Sys->MREPL;
+ maxsz := 16r7fffffff;
+ srv := 0;
+ mntpt := "/tmp";
+
+ arg->init(argv);
+ arg->setusage("memfs [-s] [-rab] [-m size] [mountpoint]");
+ while((opt := arg->opt()) != 0) {
+ case opt{
+ 's' =>
+ srv = 1;
+ 'r' =>
+ amode = Sys->MREPL;
+ 'a' =>
+ amode = Sys->MAFTER;
+ 'b' =>
+ amode = Sys->MBEFORE;
+ 'm' =>
+ maxsz = int arg->earg();
+ * =>
+ arg->usage();
+ }
+ }
+ argv = arg->argv();
+ arg = nil;
+ if (argv != nil)
+ mntpt = hd argv;
+
+ srvfd: ref Sys->FD;
+ mntfd: ref Sys->FD;
+ if (srv)
+ srvfd = sys->fildes(0);
+ else {
+ p := array [2] of ref Sys->FD;
+ if (sys->pipe(p) == -1)
+ error(sys->sprint("cannot create pipe: %r"));
+ mntfd = p[0];
+ srvfd = p[1];
+ }
+ styx->init();
+ styxlib->init(styx);
+ timefd = sys->open("/dev/time", sys->OREAD);
+
+ (tc, styxsrv) := Styxserver.new(srvfd);
+ if (srv)
+ memfs(maxsz, tc, styxsrv, nil);
+ else {
+ sync := chan of int;
+ spawn memfs(maxsz, tc, styxsrv, sync);
+ <-sync;
+ if (sys->mount(mntfd, nil, mntpt, amode | Sys->MCREATE, nil) == -1)
+ error(sys->sprint("failed to mount onto %s: %r", mntpt));
+ }
+}
+
+checkload[T](x: T, p: string): T
+{
+ if(x == nil)
+ error(sys->sprint("cannot load %s: %r", p));
+ return x;
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+error(e: string)
+{
+ sys->fprint(stderr(), "memfs: %s\n", e);
+ raise "fail:error";
+}
+
+freeblks: int;
+
+memfs(maxsz : int, tc : chan of ref Tmsg, srv : ref Styxserver, sync: chan of int)
+{
+ sys->pctl(Sys->NEWNS, nil);
+ if (sync != nil)
+ sync <-= 1;
+ freeblks = (maxsz / blksz);
+ qhash := Qidhash.new();
+
+ # init root
+ root := newmf(qhash, nil, "memfs", srv.uname, 8r755 | Sys->DMDIR);
+ root.parent = root;
+
+ while((tmsg := <-tc) != nil) {
+# sys->print("%s\n", tmsg.text());
+ Msg:
+ pick tm := tmsg {
+ Readerror =>
+ break;
+ Version =>
+ srv.devversion(tm);
+ Auth =>
+ srv.devauth(tm);
+ Flush =>
+ srv.reply(ref Rmsg.Flush(tm.tag));
+ Walk =>
+ (err, c, mf) := fidtomf(srv, qhash, tm.fid);
+ if (err != "") {
+ srv.reply(ref Rmsg.Error(tm.tag, err));
+ continue;
+ }
+ nc: ref styxlib->Chan;
+ if (tm.newfid != tm.fid) {
+ nc = srv.clone(c, tm.newfid);
+ if (nc == nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, "fid in use"));
+ continue;
+ }
+ c = nc;
+ }
+ qids: array of Sys->Qid;
+ if (len tm.names > 0) {
+ oqid := c.qid;
+ opath := c.path;
+ qids = array[len tm.names] of Sys->Qid;
+ wmf := mf;
+ for (i := 0; i < len tm.names; i++) {
+ wmf = dirlookup(wmf, tm.names[i]);
+ if (wmf == nil) {
+ if (nc == nil) {
+ c.qid = oqid;
+ c.path = opath;
+ } else
+ srv.chanfree(nc);
+ if (i == 0)
+ srv.reply(ref Rmsg.Error(tm.tag, Styxlib->Enotfound));
+ else
+ srv.reply(ref Rmsg.Walk(tm.tag, qids[0:i]));
+ break Msg;
+ }
+ c.qid = wmf.qid;
+ qids[i] = wmf.qid;
+ }
+ }
+ srv.reply(ref Rmsg.Walk(tm.tag, qids));
+ Open =>
+ (err, c, mf) := fidtomf(srv, qhash, tm.fid);
+ if (err == "" && c.open)
+ err = Styxlib->Eopen;
+ if (err == "" && !modeok(tm.mode, mf.perm, c.uname, mf.owner))
+ err = Styxlib->Eperm;
+ if (err == "" && (mf.perm & Sys->DMDIR) && (tm.mode & (OTRUNC|OWRITE|ORCLOSE)))
+ err = Styxlib->Eperm;
+ if (err == "" && (tm.mode & ORCLOSE)) {
+ p := mf.parent;
+ if (p == nil || !modeok(OWRITE, p.perm, c.uname, p.owner))
+ err = Styxlib->Eperm;
+ }
+
+ if (err != "") {
+ srv.reply(ref Rmsg.Error(tm.tag, err));
+ continue;
+ }
+
+ c.open = 1;
+ c.mode = tm.mode;
+ c.qid.vers = mf.qid.vers;
+ mf.nopen++;
+ if (tm.mode & OTRUNC) {
+ # OTRUNC cannot be set for a directory
+ # always at least one blk so don't need to check fs limit
+ freeblks += (len mf.data);
+ mf.data = nil;
+ freeblks--;
+ mf.data = array[1] of {* => array [blksz] of byte};
+ mf.length = 0;
+ mf.mtime = now();
+ }
+ srv.reply(ref Rmsg.Open(tm.tag, mf.qid, Styx->MAXFDATA));
+ Create =>
+ (err, c, parent) := fidtomf(srv, qhash, tm.fid);
+ if (err == "" && c.open)
+ err = Styxlib->Eopen;
+ if (err == "" && !(parent.qid.qtype & Sys->QTDIR))
+ err = Styxlib->Enotdir;
+ if (err == "" && !modeok(OWRITE, parent.perm, c.uname, parent.owner))
+ err = Styxlib->Eperm;
+ if (err == "" && (tm.perm & Sys->DMDIR) && (tm.mode & (OTRUNC|OWRITE|ORCLOSE)))
+ err = Styxlib->Eperm;
+ if (err == "" && dirlookup(parent, tm.name) != nil)
+ err = Styxlib->Eexists;
+
+ if (err != "") {
+ srv.reply(ref Rmsg.Error(tm.tag, err));
+ continue;
+ }
+
+ isdir := tm.perm & Sys->DMDIR;
+ if (!isdir && freeblks <= 0) {
+ srv.reply(ref Rmsg.Error(tm.tag, Efull));
+ continue;
+ }
+
+ # modify perms as per Styx specification...
+ perm : int;
+ if (isdir)
+ perm = (tm.perm&~8r777) | (parent.perm&tm.perm&8r777);
+ else
+ perm = (tm.perm&(~8r777|8r111)) | (parent.perm&tm.perm& 8r666);
+
+ nmf := newmf(qhash, parent, tm.name, c.uname, perm);
+ if (!isdir) {
+ freeblks--;
+ nmf.data = array[1] of {* => array [blksz] of byte};
+ }
+
+ # link in the new MemFile
+ nmf.next = parent.kids;
+ if (parent.kids != nil)
+ parent.kids.prev = nmf;
+ parent.kids = nmf;
+
+ c.open = 1;
+ c.mode = tm.mode;
+ c.qid = nmf.qid;
+ nmf.nopen = 1;
+ srv.reply(ref Rmsg.Create(tm.tag, nmf.qid, Styx->MAXFDATA));
+ Read =>
+ (err, c, mf) := fidtomf(srv, qhash, tm.fid);
+ if (err == "" && !c.open)
+ err = Styxlib->Ebadfid;
+
+ if (err != "") {
+ srv.reply(ref Rmsg.Error(tm.tag, err));
+ continue;
+ }
+ data: array of byte = nil;
+ if (mf.perm & Sys->DMDIR)
+ data = dirdata(mf, int tm.offset, tm.count);
+ else
+ data = filedata(mf, int tm.offset, tm.count);
+ mf.atime = now();
+ srv.reply(ref Rmsg.Read(tm.tag, data));
+ Write =>
+ (err, c, mf) := fidtomf(srv, qhash, tm.fid);
+ if (c != nil && !c.open)
+ err = Styxlib->Ebadfid;
+ if (err == nil && (mf.perm & Sys->DMDIR))
+ err = Styxlib->Eperm;
+ if (err == nil)
+ err = writefile(mf, int tm.offset, tm.data);
+ if (err != nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, err));
+ continue;
+ }
+ srv.reply(ref Rmsg.Write(tm.tag, len tm.data));
+ Clunk =>
+ (err, c, mf) := fidtomf(srv, qhash, tm.fid);
+ if (c != nil)
+ srv.chanfree(c);
+ if (err != nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, err));
+ continue;
+ }
+ if (c.open) {
+ if (c.mode & ORCLOSE)
+ unlink(mf);
+ mf.nopen--;
+ freeblks += delfile(qhash, mf);
+ }
+ srv.reply(ref Rmsg.Clunk(tm.tag));
+ Stat =>
+ (err, c, mf) := fidtomf(srv, qhash, tm.fid);
+ if (err != nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, err));
+ continue;
+ }
+ srv.reply(ref Rmsg.Stat(tm.tag, fileinfo(mf)));
+ Remove =>
+ (err, c, mf) := fidtomf(srv, qhash, tm.fid);
+ if (err != nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, err));
+ continue;
+ }
+ srv.chanfree(c);
+ parent := mf.parent;
+ if (!modeok(OWRITE, parent.perm, c.uname, parent.owner))
+ err = Styxlib->Eperm;
+ if (err == "" && (mf.perm & Sys->DMDIR) && mf.kids != nil)
+ err = "directory not empty";
+ if (err == "" && mf == root)
+ err = "root directory";
+ if (err != nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, err));
+ continue;
+ }
+
+ unlink(mf);
+ if (c.open)
+ mf.nopen--;
+ freeblks += delfile(qhash, mf);
+ srv.reply(ref Rmsg.Remove(tm.tag));
+ Wstat =>
+ (err, c, mf) := fidtomf(srv, qhash, tm.fid);
+ stat := tm.stat;
+ perm := mf.perm & ~Sys->DMDIR;
+ if (err == nil && stat.name != mf.name) {
+ parent := mf.parent;
+ if (!modeok(OWRITE, parent.perm, c.uname, parent.owner))
+ err = Styxlib->Eperm;
+ else if (dirlookup(parent, stat.name) != nil)
+ err = Styxlib->Eexists;
+ }
+ if (err == nil && (stat.mode != mf.perm || stat.mtime != mf.mtime)) {
+ if (c.uname != mf.owner)
+ err = Styxlib->Eperm;
+ }
+ if (err != nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, err));
+ continue;
+ }
+ isdir := mf.perm & Sys->DMDIR;
+ if(stat.name != nil)
+ mf.name = stat.name;
+ if(stat.mode != ~0)
+ mf.perm = stat.mode | isdir;
+ if(stat.mtime != ~0)
+ mf.mtime = stat.mtime;
+ if(stat.uid != nil)
+ mf.owner = stat.uid;
+ t := now();
+ mf.atime = t;
+ mf.parent.mtime = t;
+ # not supporting group id at the moment
+ srv.reply(ref Rmsg.Wstat(tm.tag));
+ Attach =>
+ c := srv.newchan(tm.fid);
+ if (c == nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, Styxlib->Einuse));
+ continue;
+ }
+ c.uname = tm.uname;
+ c.qid = root.qid;
+ srv.reply(ref Rmsg.Attach(tm.tag, c.qid));
+ }
+ }
+}
+
+writefile(mf: ref Memfile, offset: int, data: array of byte): string
+{
+ if(mf.perm & Sys->DMAPPEND)
+ offset = mf.length;
+ startblk := offset/blksz;
+ nblks := ((len data + offset) - (startblk * blksz))/blksz;
+ lastblk := startblk + nblks;
+ need := lastblk + 1 - len mf.data;
+ if (need > 0) {
+ if (need > freeblks)
+ return Efull;
+ mf.data = (array [lastblk+1] of array of byte)[:] = mf.data;
+ freeblks -= need;
+ }
+ mf.length = max(mf.length, offset + len data);
+
+ # handle (possibly incomplete first block) separately
+ offset %= blksz;
+ end := min(blksz-offset, len data);
+ if (mf.data[startblk] == nil)
+ mf.data[startblk] = array [blksz] of byte;
+ mf.data[startblk++][offset:] = data[:end];
+
+ ix := blksz - offset;
+ while (ix < len data) {
+ if (mf.data[startblk] == nil)
+ mf.data[startblk] = array [blksz] of byte;
+ end = min(ix+blksz,len data);
+ mf.data[startblk++][:] = data[ix:end];
+ ix += blksz;
+ }
+ mf.mtime = now();
+ return nil;
+}
+
+filedata(mf: ref Memfile, offset, n: int): array of byte
+{
+ if (offset +n > mf.length)
+ n = mf.length - offset;
+ if (n == 0)
+ return nil;
+
+ data := array [n] of byte;
+ startblk := offset/blksz;
+ offset %= blksz;
+ rn := min(blksz - offset, n);
+ data[:] = mf.data[startblk++][offset:offset+rn];
+ ix := blksz - offset;
+ while (ix < n) {
+ rn = blksz;
+ if (ix+rn > n)
+ rn = n - ix;
+ data[ix:] = mf.data[startblk++][:rn];
+ ix += blksz;
+ }
+ return data;
+}
+
+QHSIZE: con 256;
+QHMASK: con QHSIZE-1;
+
+Qidhash.new() : ref Qidhash
+{
+ qh := ref Qidhash;
+ qh.buckets = array [QHSIZE] of ref Memfile;
+ qh.nextqid = 0;
+ return qh;
+}
+
+Qidhash.add(h : self ref Qidhash, mf : ref Memfile)
+{
+ path := h.nextqid++;
+ mf.qid = Sys->Qid(big path, 0, Sys->QTFILE);
+ bix := path & QHMASK;
+ mf.hashnext = h.buckets[bix];
+ h.buckets[bix] = mf;
+}
+
+Qidhash.remove(h : self ref Qidhash, mf : ref Memfile)
+{
+
+ bix := int mf.qid.path & QHMASK;
+ prev : ref Memfile;
+ for (cur := h.buckets[bix]; cur != nil; cur = cur.hashnext) {
+ if (cur == mf)
+ break;
+ prev = cur;
+ }
+ if (cur != nil) {
+ if (prev != nil)
+ prev.hashnext = cur.hashnext;
+ else
+ h.buckets[bix] = cur.hashnext;
+ cur.hashnext = nil;
+ }
+}
+
+Qidhash.lookup(h : self ref Qidhash, qid : Sys->Qid) : ref Memfile
+{
+ bix := int qid.path & QHMASK;
+ for (mf := h.buckets[bix]; mf != nil; mf = mf.hashnext)
+ if (mf.qid.path == qid.path)
+ break;
+ return mf;
+}
+
+newmf(qh : ref Qidhash, parent : ref Memfile, name, owner : string, perm : int) : ref Memfile
+{
+ # qid gets set by Qidhash.add()
+ t := now();
+ mf := ref Memfile (name, owner, Sys->Qid(big 0,0,Sys->QTFILE), perm, t, t, 0, nil, 0, parent, nil, nil, nil, nil);
+ qh.add(mf);
+ if(perm & Sys->DMDIR)
+ mf.qid.qtype = Sys->QTDIR;
+ return mf;
+}
+
+fidtomf(srv : ref Styxserver, qh : ref Qidhash, fid : int) : (string, ref Styxlib->Chan, ref Memfile)
+{
+ c := srv.fidtochan(fid);
+ if (c == nil)
+ return (Styxlib->Ebadfid, nil, nil);
+ mf := qh.lookup(c.qid);
+ if (mf == nil)
+ return (Styxlib->Enotfound, c, nil);
+ return (nil, c, mf);
+}
+
+unlink(mf : ref Memfile)
+{
+ parent := mf.parent;
+ if (parent == nil)
+ return;
+ if (mf.next != nil)
+ mf.next.prev = mf.prev;
+ if (mf.prev != nil)
+ mf.prev.next = mf.next;
+ else
+ mf.parent.kids = mf.next;
+ mf.parent = nil;
+ mf.prev = nil;
+ mf.next = nil;
+}
+
+delfile(qh : ref Qidhash, mf : ref Memfile) : int
+{
+ if (mf.nopen <= 0 && mf.parent == nil && mf.kids == nil
+ && mf.prev == nil && mf.next == nil) {
+ qh.remove(mf);
+ nblks := len mf.data;
+ mf.data = nil;
+ return nblks;
+ }
+ return 0;
+}
+
+dirlookup(dir : ref Memfile, name : string) : ref Memfile
+{
+ if (name == ".")
+ return dir;
+ if (name == "..")
+ return dir.parent;
+ for (mf := dir.kids; mf != nil; mf = mf.next) {
+ if (mf.name == name)
+ break;
+ }
+ return mf;
+}
+
+access := array[] of {8r400, 8r200, 8r600, 8r100};
+modeok(mode, perm : int, user, owner : string) : int
+{
+ if(mode >= (OTRUNC|ORCLOSE|OREAD|OWRITE))
+ return 0;
+
+ # not handling groups!
+ if (user != owner)
+ perm <<= 6;
+
+ if ((mode & OTRUNC) && !(perm & 8r200))
+ return 0;
+
+ a := access[mode &3];
+ if ((a & perm) != a)
+ return 0;
+ return 1;
+}
+
+dirdata(dir : ref Memfile, start, n : int) : array of byte
+{
+ data := array[Styx->MAXFDATA] of byte;
+ for (k := dir.kids; start > 0 && k != nil; k = k.next) {
+ a := styx->packdir(fileinfo(k));
+ start -= len a;
+ }
+ r := 0;
+ for (; r < n && k != nil; k = k.next) {
+ a := styx->packdir(fileinfo(k));
+ if(r+len a > n)
+ break;
+ data[r:] = a;
+ r += len a;
+ }
+ return data[0:r];
+}
+
+fileinfo(f : ref Memfile) : Sys->Dir
+{
+ dir := sys->zerodir;
+ dir.name = f.name;
+ dir.uid = f.owner;
+ dir.gid = "memfs";
+ dir.qid = f.qid;
+ dir.mode = f.perm;
+ dir.atime = f.atime;
+ dir.mtime = f.mtime;
+ dir.length = big f.length;
+ dir.dtype = 0;
+ dir.dev = 0;
+ return dir;
+}
+
+min(a, b : int) : int
+{
+ if (a < b)
+ return a;
+ return b;
+}
+
+max(a, b : int) : int
+{
+ if (a > b)
+ return a;
+ return b;
+}
+
+now(): int
+{
+ if (timefd == nil)
+ return 0;
+ buf := array[128] of byte;
+ sys->seek(timefd, big 0, 0);
+ n := sys->read(timefd, buf, len buf);
+ if(n < 0)
+ return 0;
+
+ t := (big string buf[0:n]) / big 1000000;
+ return int t;
+}
diff --git a/appl/cmd/metamorph.b b/appl/cmd/metamorph.b
new file mode 100644
index 00000000..9b693c85
--- /dev/null
+++ b/appl/cmd/metamorph.b
@@ -0,0 +1,94 @@
+implement metamorph;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+include "string.m";
+include "imagefile.m";
+
+sys: Sys;
+bufio: Bufio;
+str: String;
+draw: Draw;
+
+FD: import sys;
+Display: import draw;
+
+stderr: ref FD;
+
+metamorph: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ str = load String String->PATH;
+ if (bufio == nil) {
+ sys->fprint(stderr, "could not load %s: %r\n", Bufio->PATH);
+ exit;
+ }
+ draw = load Draw Draw->PATH;
+ if (draw == nil) {
+ sys->fprint(stderr, "could not load %s: %r\n", Draw->PATH);
+ exit;
+ }
+ ri := load RImagefile RImagefile->READGIFPATH;
+ if (ri == nil) {
+ sys->fprint(stderr, "could not load %s: %r\n", RImagefile->READGIFPATH);
+ exit;
+ }
+ ir := load Imageremap Imageremap->PATH;
+ if (ir == nil) {
+ sys->fprint(stderr, "could not load %s: %r\n", Imageremap->PATH);
+ exit;
+ }
+
+ if (len args < 2) {
+ sys->fprint(stderr, "Metamorph Usage:\n metamorph <# of slides>\n\n");
+ return;
+ }
+
+ infile :string;
+
+
+ (numslides, nil) := str->toint((hd (tl args)), 10);
+
+ for (count := 1;count <=numslides; count++) {
+
+ ri->init(bufio);
+
+ if ( count < 10 )
+ infile= sys->sprint("img00%d.GIF",count);
+ if (( count >= 10 ) && ( count < 100))
+ infile= sys->sprint("img0%d.GIF",count);
+ if (count >= 100)
+ infile= sys->sprint("img%d.GIF",count);
+
+ outfile := sys->sprint("img%d.bit",count);
+
+ inf := bufio->open(infile, Bufio->OREAD);
+ sys->print ("Reading %s\n",infile);
+ if (inf == nil) {
+ sys->fprint(stderr, "could not fopen(0): %r\n");
+ exit;
+ }
+ (gif, s) := ri->read(inf);
+ if (gif == nil) {
+ sys->fprint(stderr, "bad GIF: %s\n", s);
+ exit;
+ }
+ (im, e) := ir->remap(gif, ctxt.display, 1);
+ if (im == nil) {
+ sys->fprint(stderr, "bad remap: %s\n", e);
+ exit;
+ }
+ sys->print("Writing %s\n",outfile);
+ outf := sys->create(outfile, sys->OWRITE,438);
+ ctxt.display.writeimage(outf, im);
+ outf = nil;
+ }
+}
diff --git a/appl/cmd/mk/ar.m b/appl/cmd/mk/ar.m
new file mode 100644
index 00000000..dfa686ae
--- /dev/null
+++ b/appl/cmd/mk/ar.m
@@ -0,0 +1,26 @@
+#
+# initially generated by c2l
+#
+
+Ar: module
+{
+ PATH: con "ar.dis";
+
+ ARMAG: con "!<arch>\n";
+ SARMAG: con 8;
+ ARFMAG: con "`\n";
+ SARNAME: con 16;
+
+ ar_hdr: adt{
+ name: array of byte; # SARNAME
+ date: array of byte; # 12
+ uid: array of byte; # 6
+ gid: array of byte; # 6
+ mode: array of byte; # 8
+ size: array of byte; # 10
+ fmag: array of byte; # 2
+ };
+
+ SAR_HDR: con 60;
+
+};
diff --git a/appl/cmd/mk/mk.b b/appl/cmd/mk/mk.b
new file mode 100644
index 00000000..49a5c1a2
--- /dev/null
+++ b/appl/cmd/mk/mk.b
@@ -0,0 +1,4211 @@
+#
+# initially generated by c2l
+#
+
+implement Mk;
+
+include "draw.m";
+
+Mk: module
+{
+ init: fn(nil: ref Draw->Context, argl: list of string);
+};
+
+include "sys.m";
+ sys: Sys;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "libc0.m";
+ libc0: Libc0;
+include "math.m";
+ math: Math;
+include "regex.m";
+ regex: Regex;
+include "ar.m";
+ ARMAG, SARMAG, ARFMAG, SARNAME, ar_hdr, SAR_HDR: import Ar;
+include "daytime.m";
+ daytime: Daytime;
+include "sh.m";
+
+init(nil: ref Draw->Context, argl: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ libc0 = load Libc0 Libc0->PATH;
+ math = load Math Math->PATH;
+ regex = load Regex Regex->PATH;
+ daytime = load Daytime Daytime->PATH;
+ main(len argl, libc0->ls2aab(argl));
+}
+
+NAMELEN: con 28;
+ERRLEN: con 64;
+PNPROC, PNGROUP : con iota;
+
+# function pointer enum for symtraverse
+ECOPY, PRINT1: con iota;
+
+Bufblock: adt{
+ next: cyclic ref Bufblock;
+ start: array of byte;
+ end: int;
+ current: int;
+};
+
+Word: adt{
+ s: array of byte;
+ next: cyclic ref Word;
+};
+
+Envy: adt{
+ name: array of byte;
+ values: ref Word;
+};
+
+Resub: adt{
+ sp: array of byte;
+ ep: array of byte;
+};
+
+Rule: adt{
+ target: array of byte; # one target
+ tail: ref Word; # constituents of targets
+ recipe: array of byte; # do it !
+ attr: int; # attributes
+ line: int; # source line
+ file: array of byte; # source file
+ alltargets: ref Word; # all the targets
+ rule: int; # rule number
+ pat: Regex->Re; # reg exp goo
+ prog: array of byte; # to use in out of date
+ chain: cyclic ref Rule; # hashed per target
+ next: cyclic ref Rule;
+};
+
+# Rule.attr
+META, SEQ, UPD, QUIET, VIR, REGEXP, NOREC, DEL, NOVIRT: con 1<<iota;
+NREGEXP: con 10;
+
+Arc: adt{
+ flag: int;
+ n: cyclic ref Node;
+ r: ref Rule;
+ stem: array of byte;
+ prog: array of byte;
+ match: array of array of byte;
+ next: cyclic ref Arc;
+};
+
+# Arc.flag
+TOGO: con 1;
+
+Node: adt{
+ name: array of byte;
+ time: int;
+ flags: int;
+ prereqs: cyclic ref Arc;
+ next: cyclic ref Node; # list for a rule
+};
+
+# Node.flags
+VIRTUAL, CYCLE, READY, CANPRETEND, PRETENDING, NOTMADE, BEINGMADE, MADE, PROBABLE, VACUOUS, NORECIPE, DELETE, NOMINUSE: con 1<<iota;
+
+Job: adt{
+ r: ref Rule; # master rule for job
+ n: ref Node; # list of node targets
+ stem: array of byte;
+ match: array of array of byte;
+ p: ref Word; # prerequistes
+ np: ref Word; # new prerequistes
+ t: ref Word; # targets
+ at: ref Word; # all targets
+ nproc: int; # slot number
+ next: cyclic ref Job;
+};
+
+Symtab: adt{
+ space: int;
+ name: array of byte;
+ svalue: array of byte;
+ ivalue: int;
+ nvalue: ref Node;
+ rvalue: ref Rule;
+ wvalue: ref Word;
+ next: cyclic ref Symtab;
+};
+
+S_VAR # variable -> value
+, S_TARGET # target -> rule
+, S_TIME # file -> time
+, S_PID # pid -> products
+, S_NODE # target name -> node
+, S_AGG # aggregate -> time
+, S_BITCH # bitched about aggregate not there
+, S_NOEXPORT # var -> noexport
+, S_OVERRIDE # can't override
+, S_OUTOFDATE # n1\377n2 -> 2(outofdate) or 1(not outofdate)
+, S_MAKEFILE # target -> node
+, S_MAKEVAR # dumpable mk variable
+, S_EXPORTED # var -> current exported value
+, S_BULKED # we have bulked this dir
+, S_WESET # variable; we set in the mkfile
+# an internal mk variable (e.g., stem, target)
+, S_INTERNAL: con iota;
+NAMEBLOCK: con 1000;
+BIGBLOCK: con 20000;
+D_PARSE, D_GRAPH, D_EXEC: con 1<<iota;
+
+MKFILE: con "mkfile";
+
+version := array[] of { byte '@', byte '(', byte '#', byte ')', byte 'm', byte 'k', byte ' ', byte 'g', byte 'e', byte 'n', byte 'e', byte 'r', byte 'a', byte 'l', byte ' ', byte 'r', byte 'e', byte 'l', byte 'e', byte 'a', byte 's', byte 'e', byte ' ', byte '4', byte ' ', byte '(', byte 'p', byte 'l', byte 'a', byte 'n', byte ' ', byte '9', byte ')', byte '\0' };
+debug: int;
+rules, metarules: ref Rule;
+nflag: int = 0;
+tflag: int = 0;
+iflag: int = 0;
+kflag: int = 0;
+aflag: int = 0;
+uflag: int = 0;
+explain: array of byte = nil;
+target1: ref Word;
+nreps: int = 1;
+jobs: ref Job;
+bout: ref Iobuf;
+patrule: ref Rule;
+
+main(argc: int, argv: array of array of byte)
+{
+ w: ref Word;
+ s, temp: array of byte;
+ files := array[256] of array of byte;
+ f: array of array of byte = files;
+ ff: int;
+ sflag: int = 1;
+ i: int;
+ tfd: ref Sys->FD = sys->fildes(-1);
+ tb: ref Iobuf;
+ buf, whatif: ref Bufblock;
+
+ #
+ # * start with a copy of the current environment variables
+ # * instead of sharing them
+ #
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ buf = newbuf();
+ whatif = nil;
+ if(argc)
+ ;
+ for(argv = argv[1: ]; argv[0] != nil && argv[0][0] == byte '-'; argv = argv[1: ]){
+ bufcpy(buf, argv[0], libc0->strlen(argv[0]));
+ insert(buf, ' ');
+ case(int argv[0][1]){
+ 'a' =>
+ aflag = 1;
+ 'd' =>
+ if(int (s = argv[0][2: ])[0])
+ while(int s[0]){
+ case(int s[0]){
+ 'p' =>
+ debug |= D_PARSE;
+ 'g' =>
+ debug |= D_GRAPH;
+ 'e' =>
+ debug |= D_EXEC;
+ }
+ s = s[1: ];
+ }
+ else
+ debug = 16rffff;
+ 'e' =>
+ explain = argv[0][2: ];
+ 'f' =>
+ argv = argv[1: ];
+ if(argv[0] == nil)
+ badusage();
+ f[0] = argv[0];
+ f = f[1: ];
+ bufcpy(buf, argv[0], libc0->strlen(argv[0]));
+ insert(buf, ' ');
+ 'i' =>
+ iflag = 1;
+ 'k' =>
+ kflag = 1;
+ 'n' =>
+ nflag = 1;
+ 's' =>
+ sflag = 1;
+ 't' =>
+ tflag = 1;
+ 'u' =>
+ uflag = 1;
+ 'w' =>
+ if(whatif == nil)
+ whatif = newbuf();
+ else
+ insert(whatif, ' ');
+ if(int argv[0][2])
+ bufcpy(whatif, argv[0][2: ], libc0->strlen(argv[0][2: ]));
+ else{
+ argv = argv[1: ];
+ if(argv[0] == nil)
+ badusage();
+ bufcpy(whatif, argv[0][0: ], libc0->strlen(argv[0][0: ]));
+ }
+ * =>
+ badusage();
+ }
+ }
+ if(aflag)
+ iflag = 1;
+ usage();
+ syminit();
+ initenv();
+ initbind();
+ openwait();
+ usage();
+ #
+ # assignment args become null strings
+ #
+ temp = nil;
+ for(i = 0; argv[i] != nil; i++)
+ if(libc0->strchr(argv[i], '=') != nil){
+ bufcpy(buf, argv[i], libc0->strlen(argv[i]));
+ insert(buf, ' ');
+ if(tfd == nil){
+ temp = maketmp();
+ if(temp == nil){
+ perrors("temp file");
+ Exit();
+ }
+ sys->create(libc0->ab2s(temp), Sys->OWRITE, 8r600);
+ if((tfd = sys->open(libc0->ab2s(temp), 2)) == nil){
+ perror(temp);
+ Exit();
+ }
+ tb = bufio->fopen(tfd, Sys->OWRITE);
+ }
+ tb.puts(sys->sprint("%s\n", libc0->ab2s(argv[i])));
+ argv[i][0] = byte 0;
+ }
+ if(tfd != nil){
+ tb.flush();
+ sys->seek(tfd, big 0, 0);
+ parse(libc0->s2ab("command line args"), tfd, 1);
+ sys->remove(libc0->ab2s(temp));
+ }
+ if(buf.current != 0){
+ buf.current--;
+ insert(buf, 0);
+ }
+ symlookw(libc0->s2ab("MKFLAGS"), S_VAR, stow(buf.start));
+ buf.current = 0;
+ for(i = 0; argv[i] != nil; i++){
+ if(argv[i][0] == byte 0)
+ continue;
+ if(i)
+ insert(buf, ' ');
+ bufcpy(buf, argv[i], libc0->strlen(argv[i]));
+ }
+ insert(buf, 0);
+ symlookw(libc0->s2ab("MKARGS"), S_VAR, stow(buf.start));
+ freebuf(buf);
+ if(f == files){
+ if(access(libc0->s2ab(MKFILE), Sys->OREAD) == 0)
+ parse(libc0->s2ab(MKFILE), sys->open(MKFILE, 0), 0);
+ }
+ else
+ for(ff = 0; ff < len files && files[ff] != nil; ff++)
+ parse(files[ff], sys->open(libc0->ab2s(files[ff]), 0), 0);
+ if(debug&D_PARSE){
+ dumpw(libc0->s2ab("default targets"), target1);
+ dumpr(libc0->s2ab("rules"), rules);
+ dumpr(libc0->s2ab("metarules"), metarules);
+ dumpv(libc0->s2ab("variables"));
+ }
+ if(whatif != nil){
+ insert(whatif, 0);
+ timeinit(whatif.start);
+ freebuf(whatif);
+ }
+ execinit();
+ # skip assignment args
+ while(argv[0] != nil && argv[0][0] == byte 0)
+ argv = argv[1: ];
+ catchnotes();
+ if(argv[0] == nil){
+ if(target1 != nil)
+ for(w = target1; w != nil; w = w.next)
+ mk(w.s);
+ else{
+ sys->fprint(sys->fildes(2), "mk: nothing to mk\n");
+ Exit();
+ }
+ }
+ else{
+ if(sflag){
+ for(; argv[0] != nil; argv = argv[1: ])
+ if(int argv[0][0])
+ mk(argv[0]);
+ }
+ else{
+ head, tail, t: ref Word;
+
+ # fake a new rule with all the args as prereqs
+ tail = nil;
+ t = nil;
+ for(; argv[0] != nil; argv = argv[1: ])
+ if(int argv[0][0]){
+ if(tail == nil)
+ tail = t = newword(argv[0]);
+ else{
+ t.next = newword(argv[0]);
+ t = t.next;
+ }
+ }
+ if(tail.next == nil)
+ mk(tail.s);
+ else{
+ head = newword(libc0->s2ab("command line arguments"));
+ addrules(head, tail, libc0->strdup(libc0->s2ab("")), VIR, mkinline, nil);
+ mk(head.s);
+ }
+ }
+ }
+ if(uflag)
+ prusage();
+ bout.flush();
+ exit;
+}
+
+badusage()
+{
+ sys->fprint(sys->fildes(2), "Usage: mk [-f file] [-n] [-a] [-e] [-t] [-k] [-i] [-d[egp]] [targets ...]\n");
+ Exit();
+}
+
+assert(s: array of byte, n: int)
+{
+ if(!n){
+ sys->fprint(sys->fildes(2), "mk: Assertion ``%s'' failed.\n", libc0->ab2s(s));
+ Exit();
+ }
+}
+
+regerror(s: array of byte)
+{
+ if(patrule != nil)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: regular expression error; %s\n", libc0->ab2s(patrule.file), patrule.line, libc0->ab2s(s));
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: regular expression error; %s\n", libc0->ab2s(infile), mkinline, libc0->ab2s(s));
+ Exit();
+}
+
+perror(s: array of byte)
+{
+ perrors(libc0->ab2s(s));
+}
+
+perrors(s: string)
+{
+ sys->fprint(sys->fildes(2), "mk: %s: %r\n", s);
+}
+
+access(s: array of byte, mode: int): int
+{
+ fd := sys->open(libc0->ab2s(s), mode);
+ if (fd == nil)
+ return -1;
+ fd = nil;
+ return 0;
+}
+
+stob(buf: array of byte, s: string)
+{
+ b := libc0->s2ab(s);
+ libc0->strncpy(buf, b, len buf);
+}
+
+mktemp(t: array of byte)
+{
+ x := libc0->strchr(t, 'X');
+ if(x == nil)
+ return;
+ pid := libc0->s2ab(string sys->pctl(0, nil));
+ for(i := 'a'; i <= 'z'; i++){
+ x[0] = byte i;
+ x = x[1: ];
+ libc0->strncpy(x, pid, libc0->strlen(x));
+ (ok, nil) := sys->stat(libc0->ab2s(t));
+ if(ok >= 0)
+ continue;
+ }
+}
+
+postnote(t: int, pid: int, note: array of byte)
+{
+ if(pid == 0)
+ return;
+ fd := sys->open("#p/" + string pid + "/ctl", Sys->OWRITE);
+ if(fd == nil)
+ return;
+ s := libc0->ab2s(note);
+ if(t == PNGROUP)
+ s += "grp";
+ sys->fprint(fd, "%s", s);
+ fd = nil;
+}
+
+map(s: array of byte, n: int): int
+{
+ i := j := 0;
+ ls := libc0->strlen(s);
+ while(i < ls){
+ if(j == n)
+ return i;
+ (nil, l, nil) := sys->byte2char(s, i);
+ i += l;
+ j++;
+ }
+ return -1;
+}
+
+regadd(s: array of byte, m: array of (int, int), rm: array of Resub, n: int)
+{
+ k := len m;
+ for(i := 0; i < n; i++)
+ rm[i].sp = rm[i].ep= nil;
+ for(i = 0; i < k && i < n; i++){
+ (a, b) := m[i];
+ if(a >= 0 && b >= 0){
+ a = map(s, a);
+ b = map(s, b);
+ if(a >= 0 && b >= 0){
+ rm[i].sp = s[a: ];
+ rm[i].ep = s[b: ];
+ }
+ }
+ }
+}
+
+scopy(d: array of byte, j: int, m: array of Resub, k: int, n: int): int
+{
+ if(k >= n)
+ return 0;
+ sp := m[k].sp;
+ ep := m[k].ep;
+ if(sp == nil || ep == nil)
+ return 0;
+ c := ep[0];
+ ep[0] = byte 0;
+ libc0->strcpy(d[j: ], sp);
+ ep[0] = c;
+ return libc0->strlen(sp)-libc0->strlen(ep);
+}
+
+regsub(s: array of byte, d: array of byte, m: array of Resub, n: int)
+{
+ # libc0->strncpy(d, s, libc0->strlen(d));
+ ls := libc0->strlen(s);
+ j := 0;
+ for(i := 0; i < ls; i++){
+ case(int s[i]){
+ '\\' =>
+ if(i+1 < ls && s[i+1] >= byte '0' && s[i+1] <= byte '9'){
+ k := int s[++i]-'0';
+ j += scopy(d, j, m, k, n);
+ }
+ else
+ d[j++] = byte '\\';
+ '&' =>
+ j += scopy(d, j, m, 0, n);
+ * =>
+ d[j++] = s[i];
+ }
+ }
+ d[j] = byte 0;
+}
+
+wpid := -1;
+wfd : ref Sys->FD;
+wprocs := 0;
+
+openwait()
+{
+ pid := sys->pctl(0, nil);
+ w := sys->sprint("#p/%d/wait", pid);
+ fd := sys->open(w, Sys->OREAD);
+ if(fd == nil){
+ perrors("fd == nil in wait");
+ return;
+ }
+ wpid = pid;
+ wfd = fd;
+}
+
+addwait()
+{
+ if(wpid == sys->pctl(0, nil))
+ wprocs++;
+}
+
+wait(): (int, array of byte)
+{
+ n: int;
+
+ if(wpid != -1 && wpid != sys->pctl(0, nil)){
+ perrors(sys->sprint("wait: pid %d != pid %d", wpid, sys->pctl(0, nil)));
+ return (-1, nil);
+ }
+ if(wprocs == 0)
+ return (-1, nil);
+ buf := array[Sys->WAITLEN] of byte;
+ status := "";
+ for(;;){
+ if((n = sys->read(wfd, buf, len buf))<0)
+ perrors("bad read in wait");
+ status = string buf[0:n];
+ break;
+ }
+ s := "";
+ if(status[len status - 1] != ':')
+ s = status;
+ wprocs--;
+ return (int status, libc0->s2ab(s));
+}
+
+abort()
+{
+ exit;
+}
+
+execl(sh: string, name: string, a1: string, a2: string, a3: string, a4: string)
+{
+ # sys->print("execl %s : %s %s %s %s %s\n", sh, name, a1, a2, a3, a4);
+
+ c := load Command sh;
+ if(c == nil){
+ sys->fprint(sys->fildes(2), "x %s: %r\n", sh);
+ return;
+ }
+ argl: list of string;
+ if(a4 != nil)
+ argl = a4 :: argl;
+ if(a3 != nil)
+ argl = a3 :: argl;
+ if(a2 != nil)
+ argl = a2 :: argl;
+ if(a1 != nil)
+ argl = a1 :: argl;
+ # argl = "-x" :: argl;
+ argl = name :: argl;
+ # argl := list of { name, a1, a2, a3, a4 };
+ if(debug&D_EXEC)
+ sys->fprint(sys->fildes(1), "executing %s with args (%s, %s, %s, %s, %s)\n", sh, name, a1, a2, a3, a4);
+ c->init(nil, argl);
+}
+
+getuser(): string
+{
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil)
+ return "";
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+ return string buf[0: n];
+}
+
+initbind()
+{
+ f := sys->sprint("/usr/%s/lib/mkbinds", getuser());
+ b := bufio->open(f, Bufio->OREAD);
+ if(b == nil)
+ b = bufio->open("/appl/cmd/mk/mkbinds", Bufio->OREAD);
+ if(b == nil)
+ return;
+ while((s := b.gets('\n')) != nil){
+ m := len s;
+ if(s[m-1] == '\n')
+ s = s[0: m-1];
+ (n, l) := sys->tokenize(s, " \t");
+ if(n == 2)
+ sys->bind(hd l, hd tl l, Sys->MREPL);
+ }
+}
+
+#
+# mk
+#
+
+runerrs: int;
+
+mk(target: array of byte)
+{
+ node: ref Node;
+ did: int = 0;
+
+ nproc(); # it can be updated dynamically
+ nrep(); # it can be updated dynamically
+ runerrs = 0;
+ node = graph(target);
+ if(debug&D_GRAPH){
+ dumpn(libc0->s2ab("new target\n"), node);
+ bout.flush();
+ }
+ clrmade(node);
+ while(node.flags&NOTMADE){
+ if(work(node, nil, nil))
+ did = 1; # found something to do
+ else{
+ if(waitup(1, nil) > 0){
+ if(node.flags&(NOTMADE|BEINGMADE)){
+ assert(libc0->s2ab("must be run errors"), runerrs);
+ break; # nothing more waiting
+ }
+ }
+ }
+ }
+ if(node.flags&BEINGMADE)
+ waitup(-1, nil);
+ while(jobs != nil)
+ waitup(-2, nil);
+ assert(libc0->s2ab("target didn't get done"), runerrs || node.flags&MADE);
+ if(did == 0)
+ bout.puts(sys->sprint("mk: '%s' is up to date\n", libc0->ab2s(node.name)));
+}
+
+clrmade(n: ref Node)
+{
+ a: ref Arc;
+
+ n.flags &= ~(CANPRETEND|PRETENDING);
+ if(libc0->strchr(n.name, '(') == nil || n.time)
+ n.flags |= CANPRETEND;
+ n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|NOTMADE;
+ for(a = n.prereqs; a != nil; a = a.next)
+ if(a.n != nil)
+ clrmade(a.n);
+}
+
+unpretend(n: ref Node)
+{
+ n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|NOTMADE;
+ n.flags &= ~(CANPRETEND|PRETENDING);
+ n.time = 0;
+}
+
+work(node: ref Node, p: ref Node, parc: ref Arc): int
+{
+ a, ra: ref Arc;
+ weoutofdate, ready: int;
+ did: int = 0;
+
+ # print("work(%s) flags=0x%x time=%ld\n", node->name, node->flags, node->time);/*
+ if(node.flags&BEINGMADE)
+ return did;
+ if(node.flags&MADE && node.flags&PRETENDING && p != nil && outofdate(p, parc, 0)){
+ if(explain != nil)
+ sys->fprint(sys->fildes(1), "unpretending %s(%d) because %s is out of date(%d)\n", libc0->ab2s(node.name), node.time, libc0->ab2s(p.name), p.time);
+ unpretend(node);
+ }
+ #
+ # have a look if we are pretending in case
+ # someone has been unpretended out from underneath us
+ #
+ if(node.flags&MADE){
+ if(node.flags&PRETENDING){
+ node.time = 0;
+ }
+ else
+ return did;
+ }
+ # consider no prerequsite case
+ if(node.prereqs == nil){
+ if(node.time == 0){
+ sys->fprint(sys->fildes(2), "mk: don't know how to make '%s'\n", libc0->ab2s(node.name));
+ if(kflag){
+ node.flags |= BEINGMADE;
+ runerrs++;
+ }
+ else
+ Exit();
+ }
+ else
+ node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
+ return did;
+ }
+ #
+ # now see if we are out of date or what
+ #
+ ready = 1;
+ weoutofdate = aflag;
+ ra = nil;
+ for(a = node.prereqs; a != nil; a = a.next)
+ if(a.n != nil){
+ did = work(a.n, node, a) || did;
+ if(a.n.flags&(NOTMADE|BEINGMADE))
+ ready = 0;
+ if(outofdate(node, a, 0)){
+ weoutofdate = 1;
+ if(ra == nil || ra.n == nil || ra.n.time < a.n.time)
+ ra = a;
+ }
+ }
+ else{
+ if(node.time == 0){
+ if(ra == nil)
+ ra = a;
+ weoutofdate = 1;
+ }
+ }
+ if(ready == 0) # can't do anything now
+ return did;
+ if(weoutofdate == 0){
+ node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
+ return did;
+ }
+ #
+ # can we pretend to be made?
+ #
+ if(iflag == 0 && node.time == 0 && node.flags&(PRETENDING|CANPRETEND) && p != nil && ra.n != nil && !outofdate(p, ra, 0)){
+ node.flags &= ~CANPRETEND;
+ node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
+ if(explain != nil && (node.flags&PRETENDING) == 0)
+ sys->fprint(sys->fildes(1), "pretending %s has time %d\n", libc0->ab2s(node.name), node.time);
+ node.flags |= PRETENDING;
+ return did;
+ }
+ #
+ # node is out of date and we REALLY do have to do something.
+ # quickly rescan for pretenders
+ #
+ for(a = node.prereqs; a != nil; a = a.next)
+ if(a.n != nil && a.n.flags&PRETENDING){
+ if(explain != nil)
+ if(ra.n != nil)
+ bout.puts(sys->sprint("unpretending %s because of %s because of %s\n", libc0->ab2s(a.n.name), libc0->ab2s(node.name), libc0->ab2s(ra.n.name)));
+ else
+ bout.puts(sys->sprint("unpretending %s because of %s because of %s\n", libc0->ab2s(a.n.name), libc0->ab2s(node.name), "rule with no prerequisites"));
+ unpretend(a.n);
+ did = work(a.n, node, a) || did;
+ ready = 0;
+ }
+ if(ready == 0) # try later unless nothing has happened for -k's sake
+ return did || work(node, p, parc);
+ did = dorecipe(node) || did;
+ return did;
+}
+
+update(fake: int, node: ref Node)
+{
+ a: ref Arc;
+
+ if(fake)
+ node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|BEINGMADE;
+ else
+ node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
+ if((node.flags&VIRTUAL) == 0 && access(node.name, 0) == 0){
+ node.time = timeof(node.name, 1);
+ node.flags &= ~(CANPRETEND|PRETENDING);
+ for(a = node.prereqs; a != nil; a = a.next)
+ if(a.prog != nil)
+ outofdate(node, a, 1);
+ }
+ else{
+ node.time = 1;
+ for(a = node.prereqs; a != nil; a = a.next)
+ if(a.n != nil && outofdate(node, a, 1))
+ node.time = a.n.time;
+ }
+ # print("----node %s time=%ld flags=0x%x\n", node->name, node->time, node->flags);/*
+}
+
+pcmp(prog: array of byte, p: array of byte, q: array of byte): int
+{
+ buf := array[3*NAMEBLOCK] of byte;
+ pid: int;
+
+ bout.flush();
+ stob(buf, sys->sprint("%s '%s' '%s'\n", libc0->ab2s(prog), libc0->ab2s(p), libc0->ab2s(q)));
+ pid = pipecmd(buf, nil, nil);
+ apid := array[1] of int;
+ apid[0] = pid;
+ while(waitup(-3, apid) >= 0)
+ ;
+ pid = apid[0];
+ if(pid)
+ return 2;
+ else
+ return 1;
+}
+
+outofdate(node: ref Node, arc: ref Arc, eval: int): int
+{
+ buf := array[3*NAMEBLOCK] of byte;
+ str: array of byte;
+ sym: ref Symtab;
+ ret: int;
+
+ str = nil;
+ if(arc.prog != nil){
+ stob(buf, sys->sprint("%s%c%s", libc0->ab2s(node.name), 8r377, libc0->ab2s(arc.n.name)));
+ sym = symlooki(buf, S_OUTOFDATE, 0);
+ if(sym == nil || eval){
+ if(sym == nil)
+ str = libc0->strdup(buf);
+ ret = pcmp(arc.prog, node.name, arc.n.name);
+ if(sym != nil)
+ sym.ivalue = ret;
+ else
+ symlooki(str, S_OUTOFDATE, ret);
+ }
+ else
+ ret = int sym.ivalue;
+ return ret-1;
+ }
+ else if(libc0->strchr(arc.n.name, '(') != nil && arc.n.time == 0) # missing archive member
+ return 1;
+ else
+ return node.time < arc.n.time;
+}
+
+
+#
+# recipe
+#
+
+dorecipe(node: ref Node): int
+{
+ buf := array[BIGBLOCK] of byte;
+ n: ref Node;
+ r: ref Rule = nil;
+ a, aa: ref Arc;
+ head := ref Word;
+ ahead := ref Word;
+ lp := ref Word;
+ ln := ref Word;
+ w, ww, aw: ref Word;
+ s: ref Symtab;
+ did: int = 0;
+
+ aa = nil;
+ #
+ # pick up the rule
+ #
+ for(a = node.prereqs; a != nil; a = a.next)
+ if(int a.r.recipe[0])
+ r = (aa = a).r;
+ #
+ # no recipe? go to buggery!
+ #
+ if(r == nil){
+ if(!(node.flags&VIRTUAL) && !(node.flags&NORECIPE)){
+ sys->fprint(sys->fildes(2), "mk: no recipe to make '%s'\n", libc0->ab2s(node.name));
+ Exit();
+ }
+ if(libc0->strchr(node.name, '(') != nil && node.time == 0)
+ node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
+ else
+ update(0, node);
+ if(tflag){
+ if(!(node.flags&VIRTUAL))
+ touch(node.name);
+ else if(explain != nil)
+ bout.puts(sys->sprint("no touch of virtual '%s'\n", libc0->ab2s(node.name)));
+ }
+ return did;
+ }
+ #
+ # build the node list
+ #
+ node.next = nil;
+ head.next = nil;
+ ww = head;
+ ahead.next = nil;
+ aw = ahead;
+ if(r.attr&REGEXP){
+ ww.next = newword(node.name);
+ aw.next = newword(node.name);
+ }
+ else{
+ for(w = r.alltargets; w != nil; w = w.next){
+ if(r.attr&META)
+ subst(aa.stem, w.s, buf);
+ else
+ libc0->strcpy(buf, w.s);
+ aw.next = newword(buf);
+ aw = aw.next;
+ if((s = symlooki(buf, S_NODE, 0)) == nil)
+ continue; # not a node we are interested in
+ n = s.nvalue;
+ if(aflag == 0 && n.time){
+ for(a = n.prereqs; a != nil; a = a.next)
+ if(a.n != nil && outofdate(n, a, 0))
+ break;
+ if(a == nil)
+ continue;
+ }
+ ww.next = newword(buf);
+ ww = ww.next;
+ if(n == node)
+ continue;
+ n.next = node.next;
+ node.next = n;
+ }
+ }
+ for(n = node; n != nil; n = n.next)
+ if((n.flags&READY) == 0)
+ return did;
+ #
+ # gather the params for the job
+ #
+ lp.next = ln.next = nil;
+ for(n = node; n != nil; n = n.next){
+ for(a = n.prereqs; a != nil; a = a.next){
+ if(a.n != nil){
+ addw(lp, a.n.name);
+ if(outofdate(n, a, 0)){
+ addw(ln, a.n.name);
+ if(explain != nil)
+ sys->fprint(sys->fildes(1), "%s(%d) < %s(%d)\n", libc0->ab2s(n.name), n.time, libc0->ab2s(a.n.name), a.n.time);
+ }
+ }
+ else{
+ if(explain != nil)
+ sys->fprint(sys->fildes(1), "%s has no prerequisites\n", libc0->ab2s(n.name));
+ }
+ }
+ n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|BEINGMADE;
+ }
+ # print("lt=%s ln=%s lp=%s\n",wtos(head.next, ' '),wtos(ln.next, ' '),wtos(lp.next, ' '));/*
+ run(newjob(r, node, aa.stem, aa.match, lp.next, ln.next, head.next, ahead.next));
+ return 1;
+}
+
+addw(w: ref Word, s: array of byte)
+{
+ lw: ref Word;
+
+ for(lw = w; (w = w.next) != nil; lw = w){
+ if(libc0->strcmp(s, w.s) == 0)
+ return;
+ }
+ lw.next = newword(s);
+}
+
+#
+# rule
+#
+
+lr, lmr: ref Rule;
+nrules: int = 0;
+
+addrule(head: array of byte, tail: ref Word, body: array of byte, ahead: ref Word, attr: int, hline: int, prog: array of byte)
+{
+ r, rr: ref Rule;
+ sym: ref Symtab;
+ reuse: int;
+
+ r = nil;
+ reuse = 0;
+ if((sym = symlooki(head, S_TARGET, 0)) != nil){
+ for(r = sym.rvalue; r != nil; r = r.chain)
+ if(rcmp(r, head, tail) == 0){
+ reuse = 1;
+ break;
+ }
+ }
+ if(r == nil)
+ r = ref Rule;
+ r.target = head;
+ r.tail = tail;
+ r.recipe = body;
+ r.line = hline;
+ r.file = infile;
+ r.attr = attr;
+ r.alltargets = ahead;
+ r.prog = prog;
+ r.rule = nrules++;
+ if(!reuse){
+ rr = symlookr(head, S_TARGET, r).rvalue;
+ if(rr != r){
+ r.chain = rr.chain;
+ rr.chain = r;
+ }
+ else
+ r.chain = nil;
+ }
+ if(!reuse)
+ r.next = nil;
+ if(attr&REGEXP || charin(head, libc0->s2ab("%&")) != nil){
+ r.attr |= META;
+ if(reuse)
+ return;
+ if(attr&REGEXP){
+ patrule = r;
+ e := "";
+ (r.pat, e) = regex->compile(libc0->ab2s(head), 1);
+ if(e != nil)
+ perrors(sys->sprint("%s: %s", libc0->ab2s(head), e));
+ }
+ if(metarules == nil)
+ metarules = lmr = r;
+ else{
+ lmr.next = r;
+ lmr = r;
+ }
+ }
+ else{
+ if(reuse)
+ return;
+ r.pat = nil;
+ if(rules == nil)
+ rules = lr = r;
+ else{
+ lr.next = r;
+ lr = r;
+ }
+ }
+}
+
+dumpr(s: array of byte, r: ref Rule)
+{
+ bout.puts(sys->sprint("%s: start=%x\n", libc0->ab2s(s), r));
+ for(; r != nil; r = r.next){
+ bout.puts(sys->sprint("\tRule %x: %s[%d] attr=%x next=%x chain=%x alltarget='%s'", r, libc0->ab2s(r.file), r.line, r.attr, r.next, r.chain, wtostr(r.alltargets, ' ')));
+ if(r.prog != nil)
+ bout.puts(sys->sprint(" prog='%s'", libc0->ab2s(r.prog)));
+ bout.puts(sys->sprint("\n\ttarget=%s: %s\n", libc0->ab2s(r.target), wtostr(r.tail, ' ')));
+ bout.puts(sys->sprint("\trecipe@%x='%s'\n", r.recipe, libc0->ab2s(r.recipe)));
+ }
+}
+
+rcmp(r: ref Rule, target: array of byte, tail: ref Word): int
+{
+ w: ref Word;
+
+ if(libc0->strcmp(r.target, target))
+ return 1;
+ for(w = r.tail; w != nil && tail != nil; (w, tail) = (w.next, tail.next))
+ if(libc0->strcmp(w.s, tail.s))
+ return 1;
+ return w != nil || tail != nil;
+}
+
+rulecnt(): array of byte
+{
+ s: array of byte;
+
+ s = array[nrules] of byte;
+ for(i := 0; i < nrules; i++)
+ s[i] = byte 0;
+ return s;
+}
+
+#
+# graph
+#
+
+
+graph(target: array of byte): ref Node
+{
+ node: ref Node;
+ cnt: array of byte;
+
+ cnt = rulecnt();
+ node = applyrules(target, cnt);
+ cnt = nil;
+ cyclechk(node);
+ node.flags |= PROBABLE; # make sure it doesn't get deleted
+ vacuous(node);
+ ambiguous(node);
+ attribute(node);
+ return node;
+}
+
+applyrules(target: array of byte, cnt: array of byte): ref Node
+{
+ sym: ref Symtab;
+ node: ref Node;
+ r: ref Rule;
+ head := ref Arc;
+ a: ref Arc = head;
+ w: ref Word;
+ stem := array[NAMEBLOCK] of byte;
+ buf := array[NAMEBLOCK] of byte;
+ rmatch := array[NREGEXP] of Resub;
+
+ # print("applyrules(%lux='%s')\n", target, target);/*
+ sym = symlooki(target, S_NODE, 0);
+ if(sym != nil)
+ return sym.nvalue;
+ target = libc0->strdup(target);
+ node = newnode(target);
+ head.n = nil;
+ head.next = nil;
+ sym = symlooki(target, S_TARGET, 0);
+ for(i := 0; i < NREGEXP; i++)
+ rmatch[i].sp = rmatch[i].ep = nil;
+ if(sym != nil)
+ tmp_1 := sym.rvalue;
+ else
+ tmp_1 = nil;
+ for(r = tmp_1; r != nil; r = r.chain){
+ if(r.attr&META)
+ continue;
+ if(libc0->strcmp(target, r.target))
+ continue;
+ if((r.recipe == nil || !int r.recipe[0]) && (r.tail == nil || r.tail.s == nil || !int r.tail.s[0])) # no effect; ignore
+ continue;
+ if(int cnt[r.rule] >= nreps)
+ continue;
+ cnt[r.rule]++;
+ node.flags |= PROBABLE;
+ # if(r->attr&VIR)
+ # * node->flags |= VIRTUAL;
+ # * if(r->attr&NOREC)
+ # * node->flags |= NORECIPE;
+ # * if(r->attr&DEL)
+ # * node->flags |= DELETE;
+ #
+ if(r.tail == nil || r.tail.s == nil || !int r.tail.s[0]){
+ a.next = newarc(nil, r, libc0->s2ab(""), rmatch);
+ a = a.next;
+ }
+ else
+ for(w = r.tail; w != nil; w = w.next){
+ a.next = newarc(applyrules(w.s, cnt), r, libc0->s2ab(""), rmatch);
+ a = a.next;
+ }
+ cnt[r.rule]--;
+ head.n = node;
+ }
+ for(r = metarules; r != nil; r = r.next){
+ if((r.recipe == nil || !int r.recipe[0]) && (r.tail == nil || r.tail.s == nil || !int r.tail.s[0])) # no effect; ignore
+ continue;
+ if(r.attr&NOVIRT && a != head && a.r.attr&VIR)
+ continue;
+ if(r.attr&REGEXP){
+ stem[0] = byte 0;
+ patrule = r;
+ for(i = 0; i < NREGEXP; i++)
+ rmatch[i].sp = rmatch[i].ep = nil;
+ m := regex->execute(r.pat, libc0->ab2s(node.name));
+ if(m == nil)
+ continue;
+ regadd(node.name, m, rmatch, NREGEXP);
+ }
+ else{
+ if(!match(node.name, r.target, stem))
+ continue;
+ }
+ if(int cnt[r.rule] >= nreps)
+ continue;
+ cnt[r.rule]++;
+ # if(r->attr&VIR)
+ # * node->flags |= VIRTUAL;
+ # * if(r->attr&NOREC)
+ # * node->flags |= NORECIPE;
+ # * if(r->attr&DEL)
+ # * node->flags |= DELETE;
+ #
+ if(r.tail == nil || r.tail.s == nil || !int r.tail.s[0]){
+ a.next = newarc(nil, r, stem, rmatch);
+ a = a.next;
+ }
+ else
+ for(w = r.tail; w != nil; w = w.next){
+ if(r.attr&REGEXP)
+ regsub(w.s, buf, rmatch, NREGEXP);
+ else
+ subst(stem, w.s, buf);
+ a.next = newarc(applyrules(buf, cnt), r, stem, rmatch);
+ a = a.next;
+ }
+ cnt[r.rule]--;
+ }
+ a.next = node.prereqs;
+ node.prereqs = head.next;
+ return node;
+}
+
+togo(node: ref Node)
+{
+ la, a: ref Arc;
+
+ # delete them now
+ la = nil;
+ for(a = node.prereqs; a != nil; (la, a) = (a, a.next))
+ if(a.flag&TOGO){
+ if(a == node.prereqs)
+ node.prereqs = a.next;
+ else
+ (la.next, a) = (a.next, la);
+ }
+}
+
+vacuous(node: ref Node): int
+{
+ la, a: ref Arc;
+ vac: int = !(node.flags&PROBABLE);
+
+ if(node.flags&READY)
+ return node.flags&VACUOUS;
+ node.flags |= READY;
+ for(a = node.prereqs; a != nil; a = a.next)
+ if(a.n != nil && vacuous(a.n) && a.r.attr&META)
+ a.flag |= TOGO;
+ else
+ vac = 0;
+ # if a rule generated arcs that DON'T go; no others from that rule go
+ for(a = node.prereqs; a != nil; a = a.next)
+ if((a.flag&TOGO) == 0)
+ for(la = node.prereqs; la != nil; la = la.next)
+ if(la.flag&TOGO && la.r == a.r){
+ la.flag &= ~TOGO;
+ }
+ togo(node);
+ if(vac)
+ node.flags |= VACUOUS;
+ return vac;
+}
+
+newnode(name: array of byte): ref Node
+{
+ node: ref Node;
+
+ node = ref Node;
+ symlookn(name, S_NODE, node);
+ node.name = name;
+ node.time = timeof(name, 0);
+ node.prereqs = nil;
+ if(node.time)
+ node.flags = PROBABLE;
+ else
+ node.flags = 0;
+ node.next = nil;
+ return node;
+}
+
+dumpn(s: array of byte, n: ref Node)
+{
+ buf := array[1024] of byte;
+ a: ref Arc;
+
+ if(s[0] == byte ' ')
+ stob(buf, sys->sprint("%s ", libc0->ab2s(s)));
+ else
+ stob(buf, sys->sprint("%s ", ""));
+ bout.puts(sys->sprint("%s%s@%x: time=%d flags=0x%x next=%x\n", libc0->ab2s(s), libc0->ab2s(n.name), n, n.time, n.flags, n.next));
+ for(a = n.prereqs; a != nil; a = a.next)
+ dumpa(buf, a);
+}
+
+trace(s: array of byte, a: ref Arc)
+{
+ sys->fprint(sys->fildes(2), "\t%s", libc0->ab2s(s));
+ while(a != nil){
+ if(a.n != nil)
+ sys->fprint(sys->fildes(2), " <-(%s:%d)- %s", libc0->ab2s(a.r.file), a.r.line, libc0->ab2s(a.n.name));
+ else
+ sys->fprint(sys->fildes(2), " <-(%s:%d)- %s", libc0->ab2s(a.r.file), a.r.line, "");
+ if(a.n != nil){
+ for(a = a.n.prereqs; a != nil; a = a.next)
+ if(int a.r.recipe[0])
+ break;
+ }
+ else
+ a = nil;
+ }
+ sys->fprint(sys->fildes(2), "\n");
+}
+
+cyclechk(n: ref Node)
+{
+ a: ref Arc;
+
+ if(n.flags&CYCLE && n.prereqs != nil){
+ sys->fprint(sys->fildes(2), "mk: cycle in graph detected at target %s\n", libc0->ab2s(n.name));
+ Exit();
+ }
+ n.flags |= CYCLE;
+ for(a = n.prereqs; a != nil; a = a.next)
+ if(a.n != nil)
+ cyclechk(a.n);
+ n.flags &= ~CYCLE;
+}
+
+ambiguous(n: ref Node)
+{
+ a: ref Arc;
+ r: ref Rule = nil;
+ la: ref Arc;
+ bad: int = 0;
+
+ la = nil;
+ for(a = n.prereqs; a != nil; a = a.next){
+ if(a.n != nil)
+ ambiguous(a.n);
+ if(a.r.recipe[0] == byte 0)
+ continue;
+ if(r == nil)
+ (r, la) = (a.r, a);
+ else{
+ if(r.recipe != a.r.recipe){
+ if(r.attr&META && !(a.r.attr&META)){
+ la.flag |= TOGO;
+ (r, la) = (a.r, a);
+ }
+ else if(!(r.attr&META) && a.r.attr&META){
+ a.flag |= TOGO;
+ continue;
+ }
+ }
+ if(r.recipe != a.r.recipe){
+ if(bad == 0){
+ sys->fprint(sys->fildes(2), "mk: ambiguous recipes for %s:\n", libc0->ab2s(n.name));
+ bad = 1;
+ trace(n.name, la);
+ }
+ trace(n.name, a);
+ }
+ }
+ }
+ if(bad)
+ Exit();
+ togo(n);
+}
+
+attribute(n: ref Node)
+{
+ a: ref Arc;
+
+ for(a = n.prereqs; a != nil; a = a.next){
+ if(a.r.attr&VIR)
+ n.flags |= VIRTUAL;
+ if(a.r.attr&NOREC)
+ n.flags |= NORECIPE;
+ if(a.r.attr&DEL)
+ n.flags |= DELETE;
+ if(a.n != nil)
+ attribute(a.n);
+ }
+ if(n.flags&VIRTUAL)
+ n.time = 0;
+}
+
+#
+# arc
+#
+
+newarc(n: ref Node, r: ref Rule, stem: array of byte, match: array of Resub): ref Arc
+{
+ a: ref Arc;
+
+ a = ref Arc;
+ a.n = n;
+ a.r = r;
+ a.stem = libc0->strdup(stem);
+ a.match = array[NREGEXP] of array of byte;
+ rcopy(a.match, match, NREGEXP);
+ a.next = nil;
+ a.flag = 0;
+ a.prog = r.prog;
+ return a;
+}
+
+dumpa(s: array of byte, a: ref Arc)
+{
+ buf := array[1024] of byte;
+
+ bout.puts(sys->sprint("%sArc@%x: n=%x r=%x flag=0x%x stem='%s'", libc0->ab2s(s), a, a.n, a.r, a.flag, libc0->ab2s(a.stem)));
+ if(a.prog != nil)
+ bout.puts(sys->sprint(" prog='%s'", libc0->ab2s(a.prog)));
+ bout.puts("\n");
+ if(a.n != nil){
+ if(s[0] == byte ' ')
+ stob(buf, sys->sprint("%s ", libc0->ab2s(s)));
+ else
+ stob(buf, sys->sprint("%s ", ""));
+ dumpn(buf, a.n);
+ }
+}
+
+nrep()
+{
+ sym: ref Symtab;
+ w: ref Word;
+
+ sym = symlooki(libc0->s2ab("NREP"), S_VAR, 0);
+ if(sym != nil){
+ w = sym.wvalue;
+ if(w != nil && w.s != nil && int w.s[0])
+ nreps = int string w.s;
+ }
+ if(nreps < 1)
+ nreps = 1;
+ if(debug&D_GRAPH)
+ bout.puts(sys->sprint("nreps = %d\n", nreps));
+}
+
+#
+# job
+#
+
+newjob(r: ref Rule, nlist: ref Node, stem: array of byte, match: array of array of byte, pre: ref Word, npre: ref Word, tar: ref Word, atar: ref Word): ref Job
+{
+ j: ref Job;
+
+ j = ref Job;
+ j.r = r;
+ j.n = nlist;
+ j.stem = stem;
+ j.match = match;
+ j.p = pre;
+ j.np = npre;
+ j.t = tar;
+ j.at = atar;
+ j.nproc = -1;
+ j.next = nil;
+ return j;
+}
+
+dumpj(s: array of byte, j: ref Job, all: int)
+{
+ bout.puts(sys->sprint("%s\n", libc0->ab2s(s)));
+ while(j != nil){
+ bout.puts(sys->sprint("job@%x: r=%x n=%x stem='%s' nproc=%d\n", j, j.r, j.n, libc0->ab2s(j.stem), j.nproc));
+ bout.puts(sys->sprint("\ttarget='%s' alltarget='%s' prereq='%s' nprereq='%s'\n", wtostr(j.t, ' '), wtostr(j.at, ' '), wtostr(j.p, ' '), wtostr(j.np, ' ')));
+ if(all)
+ j = j.next;
+ else
+ j = nil;
+ }
+}
+
+#
+# run
+#
+
+Event: adt{
+ pid: int;
+ job: ref Job;
+};
+
+events: array of Event;
+nevents, nrunning, nproclimit: int;
+
+Process: adt{
+ pid: int;
+ status: int;
+ b: cyclic ref Process;
+ f: cyclic ref Process;
+};
+
+phead, pfree: ref Process;
+
+run(j: ref Job)
+{
+ jj: ref Job;
+
+ if(jobs != nil){
+ for(jj = jobs; jj.next != nil; jj = jj.next)
+ ;
+ jj.next = j;
+ }
+ else
+ jobs = j;
+ j.next = nil;
+ # this code also in waitup after parse redirect
+ if(nrunning < nproclimit)
+ sched();
+}
+
+sched()
+{
+ flags: array of byte;
+ j: ref Job;
+ buf: ref Bufblock;
+ slot: int;
+ n: ref Node;
+ e: array of Envy;
+
+ if(jobs == nil){
+ usage();
+ return;
+ }
+ j = jobs;
+ jobs = j.next;
+ if(debug&D_EXEC)
+ sys->fprint(sys->fildes(1), "firing up job for target %s\n", libc0->ab2s(wtos(j.t, ' ')));
+ slot = nextslot();
+ events[slot].job = j;
+ buf = newbuf();
+ e = buildenv(j, slot);
+ shprint(j.r.recipe, e, buf);
+ if(!tflag && (nflag || !(j.r.attr&QUIET)))
+ bout.write(buf.start, libc0->strlen(buf.start));
+ freebuf(buf);
+ if(nflag || tflag){
+ bout.flush();
+ for(n = j.n; n != nil; n = n.next){
+ if(tflag){
+ if(!(n.flags&VIRTUAL))
+ touch(n.name);
+ else if(explain != nil)
+ bout.puts(sys->sprint("no touch of virtual '%s'\n", libc0->ab2s(n.name)));
+ }
+ n.time = daytime->now();
+ n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
+ }
+ }
+ else{
+ if(debug&D_EXEC)
+ sys->fprint(sys->fildes(1), "recipe='%s'", libc0->ab2s(j.r.recipe)); #
+ bout.flush();
+ if(j.r.attr&NOMINUSE)
+ flags = nil;
+ else
+ flags = libc0->s2ab("-e");
+ events[slot].pid = execsh(flags, j.r.recipe, nil, e);
+ usage();
+ nrunning++;
+ if(debug&D_EXEC)
+ sys->fprint(sys->fildes(1), "pid for target %s = %d\n", libc0->ab2s(wtos(j.t, ' ')), events[slot].pid);
+ }
+}
+
+waitup(echildok: int, retstatus: array of int): int
+{
+ e: array of Envy;
+ pid, slot: int;
+ s: ref Symtab;
+ w: ref Word;
+ j: ref Job;
+ buf := array[ERRLEN] of byte;
+ bp: ref Bufblock;
+ uarg: int = 0;
+ done: int;
+ n: ref Node;
+ p: ref Process;
+ runerrs: int;
+
+ # first check against the proces slist
+ if(retstatus != nil)
+ for(p = phead; p != nil; p = p.f)
+ if(p.pid == retstatus[0]){
+ retstatus[0] = p.status;
+ pdelete(p);
+ return -1;
+ }
+ # rogue processes
+for(;;){
+ pid = waitfor(buf);
+ if(pid == -1){
+ if(echildok > 0)
+ return 1;
+ else{
+ sys->fprint(sys->fildes(2), "mk: (waitup %d) ", echildok);
+ perrors("mk wait");
+ Exit();
+ }
+ }
+ if(debug&D_EXEC)
+ sys->fprint(sys->fildes(1), "waitup got pid=%d, status='%s'\n", pid, libc0->ab2s(buf));
+ if(retstatus != nil && pid == retstatus[0]){
+ if(int buf[0])
+ retstatus[0] = 1;
+ else
+ retstatus[0] = 0;
+ return -1;
+ }
+ slot = pidslot(pid);
+ if(slot < 0){
+ if(debug&D_EXEC)
+ sys->fprint(sys->fildes(2), "mk: wait returned unexpected process %d\n", pid);
+ if(int buf[0])
+ pnew(pid, 1);
+ else
+ pnew(pid, 0);
+ continue;
+ }
+ break;
+}
+ j = events[slot].job;
+ usage();
+ nrunning--;
+ events[slot].pid = -1;
+ if(int buf[0]){
+ e = buildenv(j, slot);
+ bp = newbuf();
+ shprint(j.r.recipe, e, bp);
+ front(bp.start);
+ sys->fprint(sys->fildes(2), "mk: %s: exit status=%s", libc0->ab2s(bp.start), libc0->ab2s(buf));
+ freebuf(bp);
+ for((n, done) = (j.n, 0); n != nil; n = n.next)
+ if(n.flags&DELETE){
+ if(done++ == 0)
+ sys->fprint(sys->fildes(2), ", deleting");
+ sys->fprint(sys->fildes(2), " '%s'", libc0->ab2s(n.name));
+ delete(n.name);
+ }
+ sys->fprint(sys->fildes(2), "\n");
+ if(kflag){
+ runerrs++;
+ uarg = 1;
+ }
+ else{
+ jobs = nil;
+ Exit();
+ }
+ }
+ for(w = j.t; w != nil; w = w.next){
+ if((s = symlooki(w.s, S_NODE, 0)) == nil)
+ continue; # not interested in this node
+ update(uarg, s.nvalue);
+ }
+ if(nrunning < nproclimit)
+ sched();
+ return 0;
+}
+
+nproc()
+{
+ sym: ref Symtab;
+ w: ref Word;
+
+ if((sym = symlooki(libc0->s2ab("NPROC"), S_VAR, 0)) != nil){
+ w = sym.wvalue;
+ if(w != nil && w.s != nil && int w.s[0])
+ nproclimit = int string w.s;
+ }
+ if(1 || nproclimit < 1)
+ nproclimit = 1;
+ if(debug&D_EXEC)
+ sys->fprint(sys->fildes(1), "nprocs = %d\n", nproclimit);
+ if(nproclimit > nevents){
+ if(nevents){
+ olen := len events;
+ ne := array[nproclimit] of Event;
+ if(olen)
+ ne[0: ] = events[0: olen];
+ events = ne;
+ }
+ else
+ events = array[nproclimit] of Event;
+ while(nevents < nproclimit)
+ events[nevents++].pid = 0;
+ }
+}
+
+nextslot(): int
+{
+ i: int;
+
+ for(i = 0; i < nproclimit; i++)
+ if(events[i].pid <= 0)
+ return i;
+ assert(libc0->s2ab("out of slots!!"), 0);
+ return 0; # cyntax
+}
+
+pidslot(pid: int): int
+{
+ i: int;
+
+ for(i = 0; i < nevents; i++)
+ if(events[i].pid == pid)
+ return i;
+ if(debug&D_EXEC)
+ sys->fprint(sys->fildes(2), "mk: wait returned unexpected process %d\n", pid);
+ return -1;
+}
+
+pnew(pid: int, status: int)
+{
+ p: ref Process;
+
+ if(pfree != nil){
+ p = pfree;
+ pfree = p.f;
+ }
+ else
+ p = ref Process;
+ p.pid = pid;
+ p.status = status;
+ p.f = phead;
+ phead = p;
+ if(p.f != nil)
+ p.f.b = p;
+ p.b = nil;
+}
+
+pdelete(p: ref Process)
+{
+ if(p.f != nil)
+ p.f.b = p.b;
+ if(p.b != nil)
+ p.b.f = p.f;
+ else
+ phead = p.f;
+ p.f = pfree;
+ pfree = p;
+}
+
+killchildren(msg: array of byte)
+{
+ p: ref Process;
+
+ kflag = 1; # to make sure waitup doesn't exit
+ jobs = nil; # make sure no more get scheduled
+ for(p = phead; p != nil; p = p.f)
+ expunge(p.pid, msg);
+ while(waitup(1, nil) == 0)
+ ;
+ bout.puts(sys->sprint("mk: %s\n", libc0->ab2s(msg)));
+ Exit();
+}
+
+tslot := array[1000] of int;
+tick: int;
+
+usage()
+{
+ t: int;
+
+ t = daytime->now();
+ if(tick)
+ tslot[nrunning] += t-tick;
+ tick = t;
+}
+
+prusage()
+{
+ i: int;
+
+ usage();
+ for(i = 0; i <= nevents; i++)
+ sys->fprint(sys->fildes(1), "%d: %d\n", i, tslot[i]);
+}
+
+#
+# file
+#
+
+# table-driven version in bootes dump of 12/31/96
+timeof(name: array of byte, force: int): int
+{
+ if(libc0->strchr(name, '(') != nil)
+ return atimeof(force, name); # archive
+ if(force)
+ return mtime(name);
+ return filetime(name);
+}
+
+touch(name: array of byte)
+{
+ bout.puts(sys->sprint("touch(%s)\n", libc0->ab2s(name)));
+ if(nflag)
+ return;
+ if(libc0->strchr(name, '(') != nil)
+ atouch(name); # archive
+ else if(chgtime(name) < 0){
+ perror(name);
+ Exit();
+ }
+}
+
+delete(name: array of byte)
+{
+ if(libc0->strchr(name, '(') == nil){ # file
+ if(sys->remove(libc0->ab2s(name)) < 0)
+ perror(name);
+ }
+ else
+ sys->fprint(sys->fildes(2), "hoon off; mk can'tdelete archive members\n");
+}
+
+timeinit(s: array of byte)
+{
+ t: int;
+ cp: array of byte;
+ r: int;
+ c, n: int;
+
+ t = daytime->now();
+ while(int s[0]){
+ cp = s;
+ do{
+ (r, n, nil) = sys->byte2char(s, 0);
+ if(r == ' ' || r == ',' || r == '\n')
+ break;
+ s = s[n: ];
+ }while(int s[0]);
+ c = int s[0];
+ s[0] = byte 0;
+ symlooki(libc0->strdup(cp), S_TIME, t).ivalue = t;
+ if(c){
+ s[0] = byte c;
+ s = s[1: ];
+ }
+ while(int s[0]){
+ (r, n, nil) = sys->byte2char(s, 0);
+ if(r != ' ' && r != ',' && r != '\n')
+ break;
+ s = s[n: ];
+ }
+ }
+}
+
+
+#
+# parse
+#
+
+infile: array of byte;
+mkinline: int;
+
+parse(f: array of byte, fd: ref Sys->FD, varoverride: int)
+{
+ hline, v: int;
+ body: array of byte;
+ head, tail: ref Word;
+ attr, set, pid: int;
+ prog, p: array of byte;
+ newfd: ref Sys->FD;
+ in: ref Iobuf;
+ buf: ref Bufblock;
+
+ if(fd == nil){
+ perror(f);
+ Exit();
+ }
+ ipush();
+ infile = libc0->strdup(f);
+ mkinline = 1;
+ in = bufio->fopen(fd, Sys->OREAD);
+ buf = newbuf();
+ while(assline(in, buf)){
+ hline = mkinline;
+ (v, head, tail, attr, prog) = rhead(buf.start);
+ case(v){
+ '<' =>
+ p = wtos(tail, ' ');
+ if(p[0] == byte 0){
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "missing include file name\n");
+ Exit();
+ }
+ newfd = sys->open(libc0->ab2s(p), Sys->OREAD);
+ if(newfd == nil){
+ sys->fprint(sys->fildes(2), "warning: skipping missing include file: ");
+ perror(p);
+ }
+ else
+ parse(p, newfd, 0);
+ '|' =>
+ p = wtos(tail, ' ');
+ if(p[0] == byte 0){
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "missing include program name\n");
+ Exit();
+ }
+ execinit();
+ anewfd := array[1] of ref Sys->FD;
+ anewfd[0] = newfd;
+ pid = pipecmd(p, envy, anewfd);
+ newfd = anewfd[0];
+ if(newfd == nil){
+ sys->fprint(sys->fildes(2), "warning: skipping missing program file: ");
+ perror(p);
+ }
+ else
+ parse(p, newfd, 0);
+ apid := array[1] of int;
+ apid[0] = pid;
+ while(waitup(-3, apid) >= 0)
+ ;
+ pid = apid[0];
+ if(pid != 0){
+ sys->fprint(sys->fildes(2), "bad include program status\n");
+ Exit();
+ }
+ ':' =>
+ body = rbody(in);
+ addrules(head, tail, body, attr, hline, prog);
+ '=' =>
+ if(head.next != nil){
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "multiple vars on left side of assignment\n");
+ Exit();
+ }
+ if(symlooki(head.s, S_OVERRIDE, 0) != nil){
+ set = varoverride;
+ }
+ else{
+ set = 1;
+ if(varoverride)
+ symlooks(head.s, S_OVERRIDE, libc0->s2ab(""));
+ }
+ if(set){
+ #
+ # char *cp;
+ # dumpw("tail", tail);
+ # cp = wtos(tail, ' '); print("assign %s to %s\n", head->s, cp); free(cp);
+ #
+ setvar(head.s, tail);
+ symlooks(head.s, S_WESET, libc0->s2ab(""));
+ }
+ if(attr)
+ symlooks(head.s, S_NOEXPORT, libc0->s2ab(""));
+ * =>
+ if(hline >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), hline);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "expected one of :<=\n");
+ Exit();
+ }
+ }
+ fd = nil;
+ freebuf(buf);
+ ipop();
+}
+
+addrules(head: ref Word, tail: ref Word, body: array of byte, attr: int, hline: int, prog: array of byte)
+{
+ w: ref Word;
+
+ assert(libc0->s2ab("addrules args"), head != nil && body != nil);
+ # tuck away first non-meta rule as default target
+ if(target1 == nil && !(attr&REGEXP)){
+ for(w = head; w != nil; w = w.next)
+ if(charin(w.s, libc0->s2ab("%&")) != nil)
+ break;
+ if(w == nil)
+ target1 = wdup(head);
+ }
+ for(w = head; w != nil; w = w.next)
+ addrule(w.s, tail, body, head, attr, hline, prog);
+}
+
+rhead(line: array of byte): (int, ref Word, ref Word, int, array of byte)
+{
+ h, t: ref Word;
+ attr: int;
+ prog: array of byte;
+ p, pp: array of byte;
+ sep: int;
+ r: int;
+ n: int;
+ w: ref Word;
+
+ p = charin(line, libc0->s2ab(":=<"));
+ if(p == nil)
+ return ('?', nil, nil, 0, nil);
+ sep = int p[0];
+ p[0] = byte 0;
+ p = p[1: ];
+ if(sep == '<' && p[0] == byte '|'){
+ sep = '|';
+ p = p[1: ];
+ }
+ attr = 0;
+ prog = nil;
+ if(sep == '='){
+ pp = charin(p, termchars); # termchars is shell-dependent
+ if(pp != nil && pp[0] == byte '='){
+ while(p != pp){
+ (r, n, nil) = sys->byte2char(p, 0);
+ case(r){
+ * =>
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "unknown attribute '%c'\n", int p[0]);
+ Exit();
+ 'U' =>
+ attr = 1;
+ }
+ p = p[n: ];
+ }
+ p = p[1: ]; # skip trailing '='
+ }
+ }
+ if(sep == ':' && int p[0] && p[0] != byte ' ' && p[0] != byte '\t'){
+ while(int p[0]){
+ (r, n, nil) = sys->byte2char(p, 0);
+ if(r == ':')
+ break;
+ ea := p[n-1];
+ p = p[n: ];
+ case(r){
+ * =>
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "unknown attribute '%c'\n", int ea);
+ Exit();
+ 'D' =>
+ attr |= DEL;
+ 'E' =>
+ attr |= NOMINUSE;
+ 'n' =>
+ attr |= NOVIRT;
+ 'N' =>
+ attr |= NOREC;
+ 'P' =>
+ pp = libc0->strchr(p, ':');
+ if(pp == nil || pp[0] == byte 0){
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "missing trailing :\n");
+ Exit();
+ }
+ pp[0] = byte 0;
+ prog = libc0->strdup(p);
+ pp[0] = byte ':';
+ p = pp;
+ 'Q' =>
+ attr |= QUIET;
+ 'R' =>
+ attr |= REGEXP;
+ 'U' =>
+ attr |= UPD;
+ 'V' =>
+ attr |= VIR;
+ }
+ }
+ if(p[0] != byte ':'){
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "missing trailing :\n");
+ Exit();
+ }
+ p = p[1: ];
+ }
+ h = w = stow(line);
+ if(w.s[0] == byte 0 && sep != '<' && sep != '|'){
+ if(mkinline-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline-1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "no var on left side of assignment/rule\n");
+ Exit();
+ }
+ t = stow(p);
+ return (sep, h, t, attr, prog);
+}
+
+rbody(in: ref Iobuf): array of byte
+{
+ buf: ref Bufblock;
+ r, lastr: int;
+ p: array of byte;
+
+ lastr = '\n';
+ buf = newbuf();
+ for(;;){
+ r = in.getc();
+ if(r < 0)
+ break;
+ if(lastr == '\n'){
+ if(r == '#')
+ rinsert(buf, r);
+ else if(r != ' ' && r != '\t'){
+ in.ungetc();
+ break;
+ }
+ }
+ else
+ rinsert(buf, r);
+ lastr = r;
+ if(r == '\n')
+ mkinline++;
+ }
+ insert(buf, 0);
+ p = libc0->strdup(buf.start);
+ freebuf(buf);
+ return p;
+}
+
+input: adt{
+ file: array of byte;
+ line: int;
+ next: cyclic ref input;
+};
+
+inputs: ref input = nil;
+
+ipush()
+{
+ in, me: ref input;
+
+ me = ref input;
+ me.file = infile;
+ me.line = mkinline;
+ me.next = nil;
+ if(inputs == nil)
+ inputs = me;
+ else{
+ for(in = inputs; in.next != nil;)
+ in = in.next;
+ in.next = me;
+ }
+}
+
+ipop()
+{
+ in, me: ref input;
+
+ assert(libc0->s2ab("pop input list"), inputs != nil);
+ if(inputs.next == nil){
+ me = inputs;
+ inputs = nil;
+ }
+ else{
+ for(in = inputs; in.next.next != nil;)
+ in = in.next;
+ me = in.next;
+ in.next = nil;
+ }
+ infile = me.file;
+ mkinline = me.line;
+ me = nil;
+}
+
+#
+# lex
+#
+
+#
+# * Assemble a line skipping blank lines, comments, and eliding
+# * escaped newlines
+#
+assline(bp: ref Iobuf, buf: ref Bufblock): int
+{
+ c, lastc: int;
+
+ buf.current = 0;
+ while((c = nextrune(bp, 1)) >= 0){
+ case(c){
+ '\r' => # consumes CRs for Win95
+ continue;
+ '\n' =>
+ if(buf.current != 0){
+ insert(buf, 0);
+ return 1;
+ }
+ # skip empty lines
+ '\\' or '\'' or '"' =>
+ rinsert(buf, c);
+ if(escapetoken(bp, buf, 1, c) == 0)
+ Exit();
+ '`' =>
+ if(bquote(bp, buf) == 0)
+ Exit();
+ '#' =>
+ lastc = '#';
+ while((c = bp.getb()) != '\n'){
+ if(c < 0){
+ insert(buf, 0);
+ return buf.start[0] != byte 0;
+ }
+ if(c != '\r')
+ lastc = c;
+ }
+ mkinline++;
+ if(lastc == '\\')
+ break; # propagate escaped newlines??
+ if(buf.current != 0){
+ insert(buf, 0);
+ return 1;
+ }
+ * =>
+ rinsert(buf, c);
+ }
+ }
+ insert(buf, 0);
+ return buf.start[0] != byte 0;
+}
+
+#
+# * assemble a back-quoted shell command into a buffer
+#
+bquote(bp: ref Iobuf, buf: ref Bufblock): int
+{
+ c, line, term, start: int;
+
+ line = mkinline;
+ while((c = bp.getc()) == ' ' || c == '\t')
+ ;
+ if(c == '{'){
+ term = '}'; # rc style
+ while((c = bp.getc()) == ' ' || c == '\t')
+ ;
+ }
+ else
+ term = '`'; # sh style
+ start = buf.current;
+ for(; c > 0; c = nextrune(bp, 0)){
+ if(c == term){
+ insert(buf, '\n');
+ insert(buf, 0);
+ buf.current = start;
+ execinit();
+ execsh(nil, buf.start[buf.current: ], buf, envy);
+ return 1;
+ }
+ if(c == '\n')
+ break;
+ if(c == '\'' || c == '"' || c == '\\'){
+ insert(buf, c);
+ if(!escapetoken(bp, buf, 1, c))
+ return 0;
+ continue;
+ }
+ rinsert(buf, c);
+ }
+ if(line >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), line);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "missing closing %c after `\n", term);
+ return 0;
+}
+
+#
+# * get next character stripping escaped newlines
+# * the flag specifies whether escaped newlines are to be elided or
+# * replaced with a blank.
+#
+savec: int;
+
+nextrune(bp: ref Iobuf, elide: int): int
+{
+ c, c2: int;
+
+ if(savec){
+ c = savec;
+ savec = 0;
+ return c;
+ }
+ for(;;){
+ c = bp.getc();
+ if(c == '\\'){
+ c2 = bp.getc();
+ if(c2 == '\r'){
+ savec = c2;
+ c2 = bp.getc();
+ }
+ if(c2 == '\n'){
+ savec = 0;
+ mkinline++;
+ if(elide)
+ continue;
+ return ' ';
+ }
+ bp.ungetc();
+ }
+ if(c == '\n')
+ mkinline++;
+ return c;
+ }
+ return 0;
+}
+
+#
+# symtab
+#
+
+NHASH: con 4099;
+HASHMUL: con 79;
+
+hash := array[NHASH] of ref Symtab;
+
+syminit()
+{
+ s: ref Symtab;
+ ss, ns: ref Symtab;
+
+ for(i := 0; i < NHASH; i++){
+ s = hash[i];
+ for(ss = s; ss != nil; ss = ns){
+ ns = s.next;
+ ss = nil;
+ }
+ hash[i] = nil;
+ }
+}
+
+symval(sym: ref Symtab): int
+{
+ return sym.svalue != nil ||
+ sym.ivalue != 0 ||
+ sym.nvalue != nil ||
+ sym.rvalue != nil ||
+ sym.wvalue != nil;
+}
+
+symlooks(sym: array of byte, space: int, s: array of byte): ref Symtab
+{
+ return symlook(sym, space, s != nil, s, 0, nil, nil, nil);
+}
+
+symlooki(sym: array of byte, space: int, i: int): ref Symtab
+{
+ return symlook(sym, space, i != 0, nil, i, nil, nil, nil);
+}
+
+symlookn(sym: array of byte, space: int, n: ref Node): ref Symtab
+{
+ return symlook(sym, space, n != nil, nil, 0, n, nil, nil);
+}
+
+symlookr(sym: array of byte, space: int, r: ref Rule): ref Symtab
+{
+ return symlook(sym, space, r != nil, nil, 0, nil, r, nil);
+}
+
+symlookw(sym: array of byte, space: int, w: ref Word): ref Symtab
+{
+ return symlook(sym, space, w != nil, nil, 0, nil, nil, w);
+}
+
+symlook(sym: array of byte, space: int, install: int, sv: array of byte, iv: int, nv: ref Node, rv: ref Rule, wv: ref Word): ref Symtab
+{
+ h: int;
+ p: array of byte;
+ s: ref Symtab;
+
+ for((p, h) = (sym, space); int p[0]; ){
+ h *= HASHMUL;
+ h += int p[0];
+ p = p[1: ];
+ }
+ if(h < 0)
+ h = ~h;
+ h %= NHASH;
+ for(s = hash[h]; s != nil; s = s.next)
+ if(s.space == space && libc0->strcmp(s.name, sym) == 0)
+ return s;
+ if(install == 0)
+ return nil;
+ s = ref Symtab;
+ s.space = space;
+ s.name = sym;
+ s.svalue = sv;
+ s.ivalue = iv;
+ s.nvalue = nv;
+ s.rvalue = rv;
+ s.wvalue = wv;
+ s.next = hash[h];
+ hash[h] = s;
+ return s;
+}
+
+symdel(sym: array of byte, space: int)
+{
+ h: int;
+ p: array of byte;
+ s, ls: ref Symtab;
+
+ # multiple memory leaks
+ for((p, h) = (sym, space); int p[0]; ){
+ h *= HASHMUL;
+ h += int p[0];
+ p = p[1: ];
+ }
+ if(h < 0)
+ h = ~h;
+ h %= NHASH;
+ for((s, ls) = (hash[h], nil); s != nil; (ls, s) = (s, s.next))
+ if(s.space == space && libc0->strcmp(s.name, sym) == 0){
+ if(ls != nil)
+ ls.next = s.next;
+ else
+ hash[h] = s.next;
+ s = nil;
+ }
+}
+
+symtraverse(space: int, fnx: int)
+{
+ s: ref Symtab;
+ ss: ref Symtab;
+
+ for(i := 0; i < NHASH; i++){
+ s = hash[i];
+ for(ss = s; ss != nil; ss = ss.next)
+ if(ss.space == space){
+ if(fnx == ECOPY)
+ ecopy(ss);
+ else if(fnx == PRINT1)
+ print1(ss);
+ }
+ }
+}
+
+symstat()
+{
+ s: ref Symtab;
+ ss: ref Symtab;
+ n: int;
+ l := array[1000] of int;
+
+ for(i := 0; i < 1000; i++)
+ l[i] = 0;
+ for(i = 0; i < NHASH; i++){
+ s = hash[i];
+ for((ss, n) = (s, 0); ss != nil; ss = ss.next)
+ n++;
+ l[n]++;
+ }
+ for(n = 0; n < 1000; n++)
+ if(l[n])
+ bout.puts(sys->sprint("%d of length %d\n", l[n], n));
+}
+
+#
+# varsub
+#
+
+varsub(s: array of byte): (ref Word, array of byte)
+{
+ b: ref Bufblock;
+ w: ref Word;
+
+ if(s[0] == byte '{') # either ${name} or ${name: A%B==C%D}
+ return expandvar(s);
+ (b, s) = varname(s);
+ if(b == nil)
+ return (nil, s);
+ (w, s) = varmatch(b.start, s);
+ freebuf(b);
+ return (w, s);
+}
+
+#
+# * extract a variable name
+#
+varname(s: array of byte): (ref Bufblock, array of byte)
+{
+ b: ref Bufblock;
+ cp: array of byte;
+ r: int;
+ n: int;
+
+ b = newbuf();
+ cp = s;
+ for(;;){
+ (r, n, nil) = sys->byte2char(cp, 0);
+ if(!(r > ' ' && libc0->strchr(libc0->s2ab("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~"), r) == nil))
+ break;
+ rinsert(b, r);
+ cp = cp[n: ];
+ }
+ if(b.current == 0){
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "missing variable name <%s>\n", libc0->ab2s(s));
+ freebuf(b);
+ return (nil, s);
+ }
+ s = cp;
+ insert(b, 0);
+ return (b, s);
+}
+
+varmatch(name: array of byte, s: array of byte): (ref Word, array of byte)
+{
+ w: ref Word;
+ sym: ref Symtab;
+ cp: array of byte;
+
+ sym = symlooki(name, S_VAR, 0);
+ if(sym != nil){
+ # check for at least one non-NULL value
+ for(w = sym.wvalue; w != nil; w = w.next)
+ if(w.s != nil && int w.s[0])
+ return (wdup(w), s);
+ }
+ for(cp = s; cp[0] == byte ' ' || cp[0] == byte '\t'; cp = cp[1: ]) # skip trailing whitespace
+ ;
+ s = cp;
+ return (nil, s);
+}
+
+expandvar(s: array of byte): (ref Word, array of byte)
+{
+ w: ref Word;
+ buf: ref Bufblock;
+ sym: ref Symtab;
+ cp, begin, end: array of byte;
+
+ begin = s;
+ s = s[1: ]; # skip the '{'
+ (buf, s) = varname(s);
+ if(buf == nil)
+ return (nil, s);
+ cp = s;
+ if(cp[0] == byte '}'){ # ${name} variant
+ s[0]++; # skip the '}'
+ (w, s) = varmatch(buf.start, s);
+ freebuf(buf);
+ return (w, s);
+ }
+ if(cp[0] != byte ':'){
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "bad variable name <%s>\n", libc0->ab2s(buf.start));
+ freebuf(buf);
+ return (nil, s);
+ }
+ cp = cp[1: ];
+ end = charin(cp, libc0->s2ab("}"));
+ if(end == nil){
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "missing '}': %s\n", libc0->ab2s(begin));
+ Exit();
+ }
+ end[0] = byte 0;
+ s = end[1: ];
+ sym = symlooki(buf.start, S_VAR, 0);
+ if(sym == nil || !symval(sym))
+ w = newword(buf.start);
+ else
+ w = subsub(sym.wvalue, cp, end);
+ freebuf(buf);
+ return (w, s);
+}
+
+extractpat(s: array of byte, r: array of byte, term: array of byte, end: array of byte): (ref Word, array of byte)
+{
+ save: int;
+ cp: array of byte;
+ w: ref Word;
+
+ cp = charin(s, term);
+ if(cp != nil){
+ r = cp;
+ if(cp == s)
+ return (nil, r);
+ save = int cp[0];
+ cp[0] = byte 0;
+ w = stow(s);
+ cp[0] = byte save;
+ }
+ else{
+ r = end;
+ w = stow(s);
+ }
+ return (w, r);
+}
+
+subsub(v: ref Word, s: array of byte, end: array of byte): ref Word
+{
+ nmid, ok: int;
+ head, tail, w, h, a, b, c, d: ref Word;
+ buf: ref Bufblock;
+ cp, enda: array of byte;
+
+ (a, cp) = extractpat(s, cp, libc0->s2ab("=%&"), end);
+ b = c = d = nil;
+ if(cp[0] == byte '%' || cp[0] == byte '&')
+ (b, cp) = extractpat(cp[1: ], cp, libc0->s2ab("="), end);
+ if(cp[0] == byte '=')
+ (c, cp) = extractpat(cp[1: ], cp, libc0->s2ab("&%"), end);
+ if(cp[0] == byte '%' || cp[0] == byte '&')
+ d = stow(cp[1: ]);
+ else if(int cp[0])
+ d = stow(cp);
+ head = tail = nil;
+ buf = newbuf();
+ for(; v != nil; v = v.next){
+ h = w = nil;
+ (ok, nmid, enda) = submatch(v.s, a, b, nmid, enda);
+ if(ok){
+ # enda points to end of A match in source;
+ # * nmid = number of chars between end of A and start of B
+ #
+ if(c != nil){
+ h = w = wdup(c);
+ while(w.next != nil)
+ w = w.next;
+ }
+ if((cp[0] == byte '%' || cp[0] == byte '&') && nmid > 0){
+ if(w != nil){
+ bufcpy(buf, w.s, libc0->strlen(w.s));
+ bufcpy(buf, enda, nmid);
+ insert(buf, 0);
+ w.s = nil;
+ w.s = libc0->strdup(buf.start);
+ }
+ else{
+ bufcpy(buf, enda, nmid);
+ insert(buf, 0);
+ h = w = newword(buf.start);
+ }
+ buf.current = 0;
+ }
+ if(d != nil && int d.s[0]){
+ if(w != nil){
+ bufcpy(buf, w.s, libc0->strlen(w.s));
+ bufcpy(buf, d.s, libc0->strlen(d.s));
+ insert(buf, 0);
+ w.s = nil;
+ w.s = libc0->strdup(buf.start);
+ w.next = wdup(d.next);
+ while(w.next != nil)
+ w = w.next;
+ buf.current = 0;
+ }
+ else
+ h = w = wdup(d);
+ }
+ }
+ if(w == nil)
+ h = w = newword(v.s);
+ if(head == nil)
+ head = h;
+ else
+ tail.next = h;
+ tail = w;
+ }
+ freebuf(buf);
+ delword(a);
+ delword(b);
+ delword(c);
+ delword(d);
+ return head;
+}
+
+submatch(s: array of byte, a: ref Word, b: ref Word, nmid: int, enda: array of byte): (int, int, array of byte)
+{
+ w: ref Word;
+ n: int;
+ end: array of byte;
+
+ n = 0;
+ for(w = a; w != nil; w = w.next){
+ n = libc0->strlen(w.s);
+ if(libc0->strncmp(s, w.s, n) == 0)
+ break;
+ }
+ if(a != nil && w == nil) # a == NULL matches everything
+ return (0, nmid, enda);
+ enda = s[n: ]; # pointer to end a A part match
+ nmid = libc0->strlen(s)-n; # size of remainder of source
+ end = enda[nmid: ];
+ onmid := nmid;
+ for(w = b; w != nil; w = w.next){
+ n = libc0->strlen(w.s);
+ if(libc0->strcmp(w.s, enda[onmid-n: ]) == 0){ # end-n
+ nmid -= n;
+ break;
+ }
+ }
+ if(b != nil && w == nil) # b == NULL matches everything
+ return (0, nmid, enda);
+ return (1, nmid, enda);
+}
+
+#
+# var
+#
+
+setvar(name: array of byte, value: ref Word)
+{
+ # s := libc0->ab2s(name);
+ # if(s == "ROOT" || s == "OBJTYPE"){
+ # if(s[0] == 'R')
+ # v := "";
+ # else
+ # v = "386";
+ # value.s = libc0->strdup(libc0->s2ab(v));
+ # }
+
+ symlookw(name, S_VAR, value).wvalue = value;
+ symlooks(name, S_MAKEVAR, libc0->s2ab(""));
+}
+
+print1(s: ref Symtab)
+{
+ w: ref Word;
+
+ bout.puts(sys->sprint("\t%s=", libc0->ab2s(s.name)));
+ for(w = s.wvalue; w != nil; w = w.next)
+ bout.puts(sys->sprint("'%s'", libc0->ab2s(w.s)));
+ bout.puts(sys->sprint("\n"));
+}
+
+dumpv(s: array of byte)
+{
+ bout.puts(sys->sprint("%s:\n", libc0->ab2s(s)));
+ symtraverse(S_VAR, PRINT1);
+}
+
+shname(a: array of byte): array of byte
+{
+ r: int;
+ n: int;
+
+ while(int a[0]){
+ (r, n, nil) = sys->byte2char(a, 0);
+ if(!(r > ' ' && libc0->strchr(libc0->s2ab("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~"), r) == nil))
+ break;
+ a = a[n: ];
+ }
+ return a;
+}
+
+#
+# word
+#
+
+
+newword(s: array of byte): ref Word
+{
+ w: ref Word;
+
+ w = ref Word;
+ w.s = libc0->strdup(s);
+ w.next = nil;
+ return w;
+}
+
+stow(s: array of byte): ref Word
+{
+ head, w, new: ref Word;
+
+ w = head = nil;
+ while(int s[0]){
+ (new, s) = nextword(s);
+ if(new == nil)
+ break;
+ if(w != nil)
+ w.next = new;
+ else
+ head = w = new;
+ while(w.next != nil)
+ w = w.next;
+ }
+ if(head == nil)
+ head = newword(libc0->s2ab(""));
+ return head;
+}
+
+wtos(w: ref Word, sep: int): array of byte
+{
+ buf: ref Bufblock;
+ cp: array of byte;
+
+ buf = newbuf();
+ for(; w != nil; w = w.next){
+ for(cp = w.s; int cp[0]; cp = cp[1: ])
+ insert(buf, int cp[0]);
+ if(w.next != nil)
+ insert(buf, sep);
+ }
+ insert(buf, 0);
+ cp = libc0->strdup(buf.start);
+ freebuf(buf);
+ return cp;
+}
+
+wtostr(w: ref Word, sep: int): string
+{
+ return libc0->ab2s(wtos(w, sep));
+}
+
+wdup(w: ref Word): ref Word
+{
+ v, new, base: ref Word;
+
+ v = base = nil;
+ while(w != nil){
+ new = newword(w.s);
+ if(v != nil)
+ v.next = new;
+ else
+ base = new;
+ v = new;
+ w = w.next;
+ }
+ return base;
+}
+
+delword(w: ref Word)
+{
+ v: ref Word;
+
+ while((v = w) != nil){
+ w = w.next;
+ if(v.s != nil)
+ v.s = nil;
+ v = nil;
+ }
+}
+
+#
+# * break out a word from a string handling quotes, executions,
+# * and variable expansions.
+#
+nextword(s: array of byte): (ref Word, array of byte)
+{
+ b: ref Bufblock;
+ head, tail, w: ref Word;
+ r, n: int;
+ cp: array of byte;
+
+ cp = s;
+ b = newbuf();
+ head = tail = nil;
+ while(cp[0] == byte ' ' || cp[0] == byte '\t') # leading white space
+ cp = cp[1: ];
+ loop := 1;
+ while(loop && int cp[0]){
+ (r, n, nil) = sys->byte2char(cp, 0);
+ cp = cp[n: ];
+ case(r){
+ ' ' or '\t' or '\n' =>
+ loop = 0;
+ '\\' or '\'' or '"' =>
+ cp = expandquote(cp, r, b);
+ if(cp == nil){
+ sys->fprint(sys->fildes(2), "missing closing quote: %s\n", libc0->ab2s(s));
+ Exit();
+ }
+ '$' =>
+ (w, cp) = varsub(cp);
+ if(w == nil)
+ break;
+ if(b.current != 0){
+ bufcpy(b, w.s, libc0->strlen(w.s));
+ insert(b, 0);
+ w.s = nil;
+ w.s = libc0->strdup(b.start);
+ b.current = 0;
+ }
+ if(head != nil){
+ bufcpy(b, tail.s, libc0->strlen(tail.s));
+ bufcpy(b, w.s, libc0->strlen(w.s));
+ insert(b, 0);
+ tail.s = nil;
+ tail.s = libc0->strdup(b.start);
+ tail.next = w.next;
+ w.s = nil;
+ w = nil;
+ b.current = 0;
+ }
+ else
+ tail = head = w;
+ while(tail.next != nil)
+ tail = tail.next;
+ * =>
+ rinsert(b, r);
+ }
+ }
+ s = cp;
+ if(b.current != 0){
+ if(head != nil){
+ oc := b.current;
+ cp = b.start[b.current: ];
+ bufcpy(b, tail.s, libc0->strlen(tail.s));
+ bufcpy(b, b.start, oc);
+ insert(b, 0);
+ tail.s = nil;
+ tail.s = libc0->strdup(cp);
+ }
+ else{
+ insert(b, 0);
+ head = newword(b.start);
+ }
+ }
+ freebuf(b);
+ return (head, s);
+}
+
+dumpw(s: array of byte, w: ref Word)
+{
+ bout.puts(sys->sprint("%s", libc0->ab2s(s)));
+ for(; w != nil; w = w.next)
+ bout.puts(sys->sprint(" '%s'", libc0->ab2s(w.s)));
+ bout.putb(byte '\n');
+}
+
+#
+# match
+#
+
+match(name: array of byte, template: array of byte, stem: array of byte): int
+{
+ r: int;
+ n: int;
+
+ while(int name[0] && int template[0]){
+ (r, n, nil) = sys->byte2char(template, 0);
+ if(r == '%' || r == '&')
+ break;
+ while(n--)
+ if(name[0] != template[0])
+ return 0;
+ name = name[1: ];
+ template = template[1: ];
+ }
+ if(!(template[0] == byte '%' || template[0] == byte '&'))
+ return 0;
+ n = libc0->strlen(name)-libc0->strlen(template[1: ]);
+ if(n < 0 || libc0->strcmp(template[1: ], name[n: ]))
+ return 0;
+ libc0->strncpy(stem, name, n);
+ stem[n] = byte 0;
+ if(template[0] == byte '&')
+ return charin(stem, libc0->s2ab("./")) == nil;
+ return 1;
+}
+
+subst(stem: array of byte, template: array of byte, dest: array of byte)
+{
+ r: int;
+ s: array of byte;
+ n: int;
+
+ while(int template[0]){
+ (r, n, nil) = sys->byte2char(template, 0);
+ if(r == '%' || r == '&'){
+ template = template[n: ];
+ for(s = stem; int s[0]; s = s[1: ]){
+ dest[0] = s[0];
+ dest = dest[1: ];
+ }
+ }
+ else
+ while(n--){
+ dest[0] = template[0];
+ dest = dest[1: ];
+ template = template[1: ];
+ }
+ }
+ dest[0] = byte 0;
+}
+
+#
+# os
+#
+
+shell := "/dis/sh.dis";
+shellname := "sh";
+
+pcopy(a: array of ref Sys->FD): array of ref Sys->FD
+{
+ b := array[2] of ref Sys->FD;
+ b[0: ] = a[0: 2];
+ return b;
+}
+
+readenv()
+{
+ p: array of byte;
+ envf, f: ref Sys->FD;
+ e := array[20] of Sys->Dir;
+ nam := array[NAMELEN+5] of byte;
+ i, n, lenx: int;
+ w: ref Word;
+
+ sys->pctl(Sys->FORKENV, nil); # use copy of the current environment variables
+ envf = sys->open("/env", Sys->OREAD);
+ if(envf == nil)
+ return;
+ for(;;){
+ (n, e) = sys->dirread(envf);
+ if(n <= 0)
+ break;
+ for(i = 0; i < n; i++){
+ lenx = int e[i].length;
+ # don't import funny names, NULL values,
+ # * or internal mk variables
+ #
+ if(lenx <= 0 || shname(libc0->s2ab(e[i].name))[0] != byte '\0')
+ continue;
+ if(symlooki(libc0->s2ab(e[i].name), S_INTERNAL, 0) != nil)
+ continue;
+ stob(nam, sys->sprint("/env/%s", e[i].name));
+ f = sys->open(libc0->ab2s(nam), Sys->OREAD);
+ if(f == nil)
+ continue;
+ p = array[lenx+1] of byte;
+ if(sys->read(f, p, lenx) != lenx){
+ perror(nam);
+ f = nil;
+ continue;
+ }
+ f = nil;
+ if(p[lenx-1] == byte 0)
+ lenx--;
+ else
+ p[lenx] = byte 0;
+ w = encodenulls(p, lenx);
+ p = nil;
+ p = libc0->strdup(libc0->s2ab(e[i].name));
+ setvar(p, w);
+ symlooks(p, S_EXPORTED, libc0->s2ab("")).svalue = libc0->s2ab("");
+ }
+ }
+ envf = nil;
+}
+
+# break string of values into words at 01's or nulls
+encodenulls(s: array of byte, n: int): ref Word
+{
+ w, head: ref Word;
+ cp: array of byte;
+
+ head = w = nil;
+ while(n-- > 0){
+ for(cp = s; int cp[0] && cp[0] != byte '\u0001'; cp = cp[1: ])
+ n--;
+ cp[0] = byte 0;
+ if(w != nil){
+ w.next = newword(s);
+ w = w.next;
+ }
+ else
+ head = w = newword(s);
+ s = cp[1: ];
+ }
+ if(head == nil)
+ head = newword(libc0->s2ab(""));
+ return head;
+}
+
+# as well as 01's, change blanks to nulls, so that rc will
+# * treat the words as separate arguments
+#
+exportenv(e: array of Envy)
+{
+ f: ref Sys->FD;
+ n, hasvalue: int;
+ w: ref Word;
+ sy: ref Symtab;
+ nam := array[NAMELEN+5] of byte;
+
+ for(i := 0; e[i].name != nil; i++){
+ sy = symlooki(e[i].name, S_VAR, 0);
+ if(e[i].values == nil || e[i].values.s == nil || e[i].values.s[0] == byte 0)
+ hasvalue = 0;
+ else
+ hasvalue = 1;
+ if(sy == nil && !hasvalue) # non-existant null symbol
+ continue;
+ stob(nam, sys->sprint("/env/%s", libc0->ab2s(e[i].name)));
+ if(sy != nil && !hasvalue){ # Remove from environment
+ # we could remove it from the symbol table
+ # * too, but we're in the child copy, and it
+ # * would still remain in the parent's table.
+ #
+ sys->remove(libc0->ab2s(nam));
+ delword(e[i].values);
+ e[i].values = nil; # memory leak
+ continue;
+ }
+ f = sys->create(libc0->ab2s(nam), Sys->OWRITE, 8r666);
+ if(f == nil){
+ sys->fprint(sys->fildes(2), "can't create %s, f=%d\n", libc0->ab2s(nam), f.fd);
+ perror(nam);
+ continue;
+ }
+ for(w = e[i].values; w != nil; w = w.next){
+ n = libc0->strlen(w.s);
+ if(n){
+ if(sys->write(f, w.s, n) != n)
+ perror(nam);
+ if(w.next != nil && sys->write(f, libc0->s2ab(" "), 1) != 1)
+ perror(nam);
+ }
+ }
+ f = nil;
+ }
+}
+
+dirtime(dir: array of byte, path: array of byte)
+{
+ i: int;
+ fd: ref Sys->FD;
+ n: int;
+ t: int;
+ db := array[32] of Sys->Dir;
+ buf := array[4096] of byte;
+
+ fd = sys->open(libc0->ab2s(dir), Sys->OREAD);
+ if(fd != nil){
+ for(;;){
+ (n, db) = sys->dirread(fd);
+ if(n <= 0)
+ break;
+ for(i = 0; i < n; i++){
+ t = db[i].mtime;
+ if(t == 0) # zero mode file
+ continue;
+ stob(buf, sys->sprint("%s%s", libc0->ab2s(path), db[i].name));
+ if(symlooki(buf, S_TIME, 0) != nil)
+ continue;
+ symlooki(libc0->strdup(buf), S_TIME, t).ivalue = t;
+ }
+ }
+ fd = nil;
+ }
+}
+
+waitfor(msg: array of byte): int
+{
+ wm: array of byte;
+ pid: int;
+
+ (pid, wm) = wait();
+ if(pid > 0)
+ libc0->strncpy(msg, wm, ERRLEN);
+ return pid;
+}
+
+expunge(pid: int, msg: array of byte)
+{
+ postnote(PNPROC, pid, msg);
+}
+
+sub(cmd: array of byte, env: array of Envy): array of byte
+{
+ buf := newbuf();
+ shprint(cmd, env, buf);
+ return buf.start;
+}
+
+fork1(c1: chan of int, args: array of byte, cmd: array of byte, buf: ref Bufblock, e: array of Envy, in: array of ref Sys->FD, out: array of ref Sys->FD)
+{
+ pid: int;
+
+ c1<- = sys->pctl(Sys->FORKFD|Sys->FORKENV, nil);
+
+ {
+ if(buf != nil)
+ out[0] = nil;
+ if(sys->pipe(in) < 0){
+ perrors("pipe");
+ Exit();
+ }
+ c2 := chan of int;
+ spawn fork2(c2, cmd, pcopy(in), pcopy(out));
+ pid = <- c2;
+ addwait();
+ {
+ sys->dup(in[0].fd, 0);
+ if(buf != nil){
+ sys->dup(out[1].fd, 1);
+ out[1] = nil;
+ }
+ in[0] = nil;
+ in[1] = nil;
+ if(e != nil)
+ exportenv(e);
+ argss := libc0->ab2s(args);
+ sys->pctl(Sys->NEWFD, 0 :: 1 :: 2 :: nil);
+ if(shflags != nil)
+ execl(shell, shellname, shflags, argss, nil, nil);
+ else
+ execl(shell, shellname, argss, nil, nil, nil);
+ exit;
+ # perror(shell);
+ # exits("exec");
+ }
+ }
+}
+
+fork2(c2: chan of int, cmd: array of byte, in: array of ref Sys->FD, out: array of ref Sys->FD)
+{
+ n, p: int;
+
+ c2<- = sys->pctl(Sys->FORKFD, nil);
+
+ {
+ out[1] = nil;
+ in[0] = nil;
+ p = libc0->strlen(cmd);
+ c := 0;
+ while(c < p){ # cmd < p
+ if(debug&D_EXEC)
+ sys->fprint(sys->fildes(1), "writing '%s' to shell\n", libc0->ab2s(cmd[0: p-c]));
+ n = sys->write(in[1], cmd, p-c); # p-cmd
+ if(n < 0)
+ break;
+ cmd = cmd[n: ];
+ c += n;
+ }
+ in[1] = nil;
+ exit;
+ # exits(nil);
+ }
+}
+
+execsh(args: array of byte, cmd: array of byte, buf: ref Bufblock, e: array of Envy): int
+{
+ tot, n, pid: int;
+ in := array[2] of ref Sys->FD;
+ out := array[2] of ref Sys->FD;
+
+ cmd = sub(cmd, e);
+
+ if(buf != nil && sys->pipe(out) < 0){
+ perrors("pipe");
+ Exit();
+ }
+ c1 := chan of int;
+ spawn fork1(c1, args, cmd, buf, e, in, pcopy(out));
+ pid = <-c1;
+ addwait();
+ if(buf != nil){
+ out[1] = nil;
+ tot = 0;
+ for(;;){
+ if(buf.current >= buf.end)
+ growbuf(buf);
+ n = sys->read(out[0], buf.start[buf.current: ], buf.end-buf.current);
+ if(n <= 0)
+ break;
+ buf.current += n;
+ tot += n;
+ }
+ if(tot && buf.start[buf.current-1] == byte '\n')
+ buf.current--;
+ out[0] = nil;
+ }
+ return pid;
+}
+
+fork3(c3: chan of int, cmd: array of byte, e: array of Envy, fd: array of ref Sys->FD, pfd: array of ref Sys->FD)
+{
+ c3<- = sys->pctl(Sys->FORKFD|Sys->FORKENV, nil);
+
+ {
+ if(fd != nil){
+ pfd[0] = nil;
+ sys->dup(pfd[1].fd, 1);
+ pfd[1] = nil;
+ }
+ if(e != nil)
+ exportenv(e);
+ cmds := libc0->ab2s(cmd);
+ if(shflags != nil)
+ execl(shell, shellname, shflags, "-c", cmds, nil);
+ else
+ execl(shell, shellname, "-c", cmds, nil, nil);
+ exit;
+ # perror(shell);
+ # exits("exec");
+ }
+}
+
+pipecmd(cmd: array of byte, e: array of Envy, fd: array of ref Sys->FD): int
+{
+ pid: int;
+ pfd := array[2] of ref Sys->FD;
+
+ cmd = sub(cmd, e);
+
+ if(debug&D_EXEC)
+ sys->fprint(sys->fildes(1), "pipecmd='%s'", libc0->ab2s(cmd)); #
+ if(fd != nil && sys->pipe(pfd) < 0){
+ perrors("pipe");
+ Exit();
+ }
+ c3 := chan of int;
+ spawn fork3(c3, cmd, e, fd, pcopy(pfd));
+ pid = <- c3;
+ addwait();
+ if(fd != nil){
+ pfd[1] = nil;
+ fd[0] = pfd[0];
+ }
+ return pid;
+}
+
+Exit()
+{
+ while(wait().t0 >= 0)
+ ;
+ bout.flush();
+ exit;
+}
+
+nnote: int;
+
+notifyf(a: array of byte, msg: array of byte): int
+{
+ if(a != nil)
+ ;
+ if(++nnote > 100){ # until andrew fixes his program
+ sys->fprint(sys->fildes(2), "mk: too many notes\n");
+ # notify(nil);
+ abort();
+ }
+ if(libc0->strcmp(msg, libc0->s2ab("interrupt")) != 0 && libc0->strcmp(msg, libc0->s2ab("hangup")) != 0)
+ return 0;
+ killchildren(msg);
+ return -1;
+}
+
+catchnotes()
+{
+ # atnotify(notifyf, 1);
+}
+
+temp := array[] of { byte '/', byte 't', byte 'm', byte 'p', byte '/', byte 'm', byte 'k', byte 'a', byte 'r', byte 'g', byte 'X', byte 'X', byte 'X', byte 'X', byte 'X', byte 'X', byte '\0' };
+
+maketmp(): array of byte
+{
+ t := libc0->strdup(temp);
+ mktemp(t);
+ return t;
+}
+
+chgtime(name: array of byte): int
+{
+ (ok, nil) := sys->stat(libc0->ab2s(name));
+ if(ok >= 0){
+ sbuf := sys->nulldir;
+ sbuf.mtime = daytime->now();
+ return sys->wstat(libc0->ab2s(name), sbuf);
+ }
+ fd := sys->create(libc0->ab2s(name), Sys->OWRITE, 8r666);
+ if(fd == nil)
+ return -1;
+ fd = nil;
+ return 0;
+}
+
+rcopy(tox: array of array of byte, match: array of Resub, n: int)
+{
+ c: int;
+ p: array of byte;
+
+ i := 0;
+ tox[0] = match[0].sp; # stem0 matches complete target
+ for(i++; --n > 0; i++){
+ if(match[i].sp != nil && match[i].ep != nil){
+ p = match[i].ep;
+ c = int p[0];
+ p[0] = byte 0;
+ tox[i] = libc0->strdup(match[i].sp);
+ p[0] = byte c;
+ }
+ else
+ tox[i] = nil;
+ }
+}
+
+mkdirstat(name: array of byte): (int, Sys->Dir)
+{
+ return sys->stat(libc0->ab2s(name));
+}
+
+membername(s: array of byte, fd: ref Sys->FD, sz: int): array of byte
+{
+ if(fd == nil)
+ ;
+ if(sz)
+ ;
+ return s;
+}
+
+#
+# sh
+#
+
+termchars := array[] of { byte '\'', byte '=', byte ' ', byte '\t', byte '\0' }; # used in parse.c to isolate assignment attribute
+shflags := ""; # rc flag to force non-interactive mode - was -l
+IWS: int = '\u0001'; # inter-word separator in env - not used in plan 9
+
+#
+# * This file contains functions that depend on rc's syntax. Most
+# * of the routines extract strings observing rc's escape conventions
+#
+#
+# * skip a token in single quotes.
+#
+squote(cp: array of byte): array of byte
+{
+ r: int;
+ n, nn: int;
+
+ while(int cp[0]){
+ (r, n, nil) = sys->byte2char(cp, 0);
+ if(r == '\''){
+ (r, nn, nil) = sys->byte2char(cp[n: ], 0);
+ n += nn;
+ if(r != '\'')
+ return cp;
+ }
+ cp = cp[n: ];
+ }
+ if(-1 >= 0) # should never occur
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "missing closing '\n");
+ return nil;
+}
+
+#
+# * search a string for characters in a pattern set
+# * characters in quotes and variable generators are escaped
+#
+charin(cp: array of byte, pat: array of byte): array of byte
+{
+ r: int;
+ n, vargen: int;
+
+ vargen = 0;
+ while(int cp[0]){
+ (r, n, nil) = sys->byte2char(cp, 0);
+ case(r){
+ '\'' => # skip quoted string
+ cp = squote(cp[1: ]); # n must = 1
+ if(cp == nil)
+ return nil;
+ '$' =>
+ if((cp[1: ])[0] == byte '{')
+ vargen = 1;
+ '}' =>
+ if(vargen)
+ vargen = 0;
+ else if(libc0->strchr(pat, r) != nil)
+ return cp;
+ * =>
+ if(vargen == 0 && libc0->strchr(pat, r) != nil)
+ return cp;
+ }
+ cp = cp[n: ];
+ }
+ if(vargen){
+ if(-1 >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "missing closing } in pattern generator\n");
+ }
+ return nil;
+}
+
+#
+# * extract an escaped token. Possible escape chars are single-quote,
+# * double-quote,and backslash. Only the first is valid for rc. the
+# * others are just inserted into the receiving buffer.
+#
+expandquote(s: array of byte, r: int, b: ref Bufblock): array of byte
+{
+ n: int;
+
+ if(r != '\''){
+ rinsert(b, r);
+ return s;
+ }
+ while(int s[0]){
+ (r, n, nil) = sys->byte2char(s, 0);
+ s = s[n: ];
+ if(r == '\''){
+ if(s[0] == byte '\'')
+ s = s[1: ];
+ else
+ return s;
+ }
+ rinsert(b, r);
+ }
+ return nil;
+}
+
+#
+# * Input an escaped token. Possible escape chars are single-quote,
+# * double-quote and backslash. Only the first is a valid escape for
+# * rc; the others are just inserted into the receiving buffer.
+#
+escapetoken(bp: ref Iobuf, buf: ref Bufblock, preserve: int, esc: int): int
+{
+ c, line: int;
+
+ if(esc != '\'')
+ return 1;
+ line = mkinline;
+ while((c = nextrune(bp, 0)) > 0){
+ if(c == '\''){
+ if(preserve)
+ rinsert(buf, c);
+ c = bp.getc();
+ if(c < 0)
+ break;
+ if(c != '\''){
+ bp.ungetc();
+ return 1;
+ }
+ }
+ rinsert(buf, c);
+ }
+ if(line >= 0)
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), line);
+ else
+ sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
+ sys->fprint(sys->fildes(2), "missing closing %c\n", esc);
+ return 0;
+}
+
+#
+# * copy a single-quoted string; s points to char after opening quote
+#
+copysingle(s: array of byte, buf: ref Bufblock): array of byte
+{
+ r, n: int;
+
+ while(int s[0]){
+ (r, n, nil) = sys->byte2char(s, 0);
+ s = s[n: ];
+ rinsert(buf, r);
+ if(r == '\'')
+ break;
+ }
+ return s;
+}
+
+#
+# * check for quoted strings. backquotes are handled here; single quotes above.
+# * s points to char after opening quote, q.
+#
+copyq(s: array of byte, q: int, buf: ref Bufblock): array of byte
+{
+ n: int;
+
+ if(q == '\'') # copy quoted string
+ return copysingle(s, buf);
+ if(q != '`') # not quoted
+ return s;
+ while(int s[0]){ # copy backquoted string
+ (q, n, nil) = sys->byte2char(s, 0);
+ s = s[n: ];
+ rinsert(buf, q);
+ if(q == '}')
+ break;
+ if(q == '\'')
+ s = copysingle(s, buf); # copy quoted string
+ }
+ return s;
+}
+
+#
+# shprint
+#
+
+shprint(s: array of byte, env: array of Envy, buf: ref Bufblock)
+{
+ n: int;
+ r: int;
+
+ while(int s[0]){
+ (r, n, nil) = sys->byte2char(s, 0);
+ if(r == '$')
+ s = vexpand(s, env, buf);
+ else{
+ rinsert(buf, r);
+ s = s[n: ];
+ s = copyq(s, r, buf); # handle quoted strings
+ }
+ }
+ insert(buf, 0);
+}
+
+mygetenv(name: array of byte, env: array of Envy): array of byte
+{
+ if(env == nil)
+ return nil;
+ if(symlooki(name, S_WESET, 0) == nil && symlooki(name, S_INTERNAL, 0) == nil)
+ return nil;
+ # only resolve internal variables and variables we've set
+ for(e := 0; env[e].name != nil; e++){
+ if(libc0->strcmp(env[e].name, name) == 0)
+ return wtos(env[e].values, ' ');
+ }
+ return nil;
+}
+
+vexpand(w: array of byte, env: array of Envy, buf: ref Bufblock): array of byte
+{
+ s: array of byte;
+ carry: byte;
+ p, q: array of byte;
+
+ assert(libc0->s2ab("vexpand no $"), w[0] == byte '$');
+ p = w[1: ]; # skip dollar sign
+ if(p[0] == byte '{'){
+ p = p[1: ];
+ q = libc0->strchr(p, '}');
+ if(q == nil)
+ q = libc0->strchr(p, 0);
+ }
+ else
+ q = shname(p);
+ carry = q[0];
+ q[0] = byte 0;
+ s = mygetenv(p, env);
+ q[0] = carry;
+ if(carry == byte '}')
+ q = q[1: ];
+ if(s != nil){
+ bufcpy(buf, s, libc0->strlen(s));
+ s = nil;
+ }
+ else
+ # copy name intact
+ bufcpy(buf, w, libc0->strlen(w)-libc0->strlen(q)); # q-w
+ return q;
+}
+
+front(s: array of byte)
+{
+ t, q: array of byte;
+ i, j: int;
+ # flds := array[512] of array of byte;
+ fields: list of string;
+
+ q = libc0->strdup(s);
+ (i, fields) = sys->tokenize(libc0->ab2s(q), " \t\n");
+ flds := array[len fields] of array of byte;
+ for(j = 0; j < len flds; j++){
+ flds[j] = libc0->s2ab(hd fields);
+ fields = tl fields;
+ }
+ if(i > 5){
+ flds[4] = flds[i-1];
+ flds[3] = libc0->s2ab("...");
+ i = 5;
+ }
+ t = s;
+ for(j = 0; j < i; j++){
+ for(s = flds[j]; int s[0]; ){
+ t[0] = s[0];
+ s = s[1: ];
+ t = t[1: ];
+ }
+ t[0] = byte ' ';
+ t = t[1: ];
+ }
+ t[0] = byte 0;
+ q = nil;
+}
+
+#
+# env
+#
+
+ENVQUANTA: con 10;
+
+envy: array of Envy;
+nextv: int;
+myenv: array of array of byte;
+
+initenv()
+{
+ p: int;
+
+ myenv = array[19] of {
+ libc0->s2ab("target"),
+ libc0->s2ab("stem"),
+ libc0->s2ab("prereq"),
+ libc0->s2ab("pid"),
+ libc0->s2ab("nproc"),
+ libc0->s2ab("newprereq"),
+ libc0->s2ab("alltarget"),
+ libc0->s2ab("newmember"),
+ libc0->s2ab("stem0"), # must be in order from here
+ libc0->s2ab("stem1"),
+ libc0->s2ab("stem2"),
+ libc0->s2ab("stem3"),
+ libc0->s2ab("stem4"),
+ libc0->s2ab("stem5"),
+ libc0->s2ab("stem6"),
+ libc0->s2ab("stem7"),
+ libc0->s2ab("stem8"),
+ libc0->s2ab("stem9"),
+ array of byte nil,
+ };
+
+ for(p = 0; myenv[p] != nil; p++)
+ symlooks(myenv[p], S_INTERNAL, libc0->s2ab(""));
+ readenv(); # o.s. dependent
+}
+
+envsize: int;
+
+envinsert(name: array of byte, value: ref Word)
+{
+ if(nextv >= envsize){
+ envsize += ENVQUANTA;
+ es := len envy;
+ ne := array[envsize] of Envy;
+ if(es)
+ ne[0: ] = envy[0: es];
+ envy = ne;
+ }
+ envy[nextv].name = name;
+ envy[nextv++].values = value;
+}
+
+envupd(name: array of byte, value: ref Word)
+{
+ e: int;
+
+ for(e = 0; envy[e].name != nil; e++)
+ if(libc0->strcmp(name, envy[e].name) == 0){
+ delword(envy[e].values);
+ envy[e].values = value;
+ return;
+ }
+ envy[e].name = name;
+ envy[e].values = value;
+ envinsert(nil, nil);
+}
+
+ecopy(s: ref Symtab)
+{
+ p: int;
+
+ if(symlooki(s.name, S_NOEXPORT, 0) != nil)
+ return;
+ for(p = 0; myenv[p] != nil; p++)
+ if(libc0->strcmp(myenv[p], s.name) == 0)
+ return;
+ envinsert(s.name, s.wvalue);
+}
+
+execinit()
+{
+ p: int;
+
+ nextv = 0;
+ for(p = 0; myenv[p] != nil; p++)
+ envinsert(myenv[p], stow(libc0->s2ab("")));
+ symtraverse(S_VAR, ECOPY);
+ envinsert(nil, nil);
+}
+
+buildenv(j: ref Job, slot: int): array of Envy
+{
+ p: int;
+ cp, qp: array of byte;
+ w, v: ref Word;
+ l: ref Word;
+ i: int;
+ buf := array[256] of byte;
+
+ envupd(libc0->s2ab("target"), wdup(j.t));
+ if(j.r.attr&REGEXP)
+ envupd(libc0->s2ab("stem"), newword(libc0->s2ab("")));
+ else
+ envupd(libc0->s2ab("stem"), newword(j.stem));
+ envupd(libc0->s2ab("prereq"), wdup(j.p));
+ stob(buf, sys->sprint("%d", sys->pctl(0, nil)));
+ envupd(libc0->s2ab("pid"), newword(buf));
+ stob(buf, sys->sprint("%d", slot));
+ envupd(libc0->s2ab("nproc"), newword(buf));
+ envupd(libc0->s2ab("newprereq"), wdup(j.np));
+ envupd(libc0->s2ab("alltarget"), wdup(j.at));
+ l = ref Word;
+ l.next = v = w = wdup(j.np);
+ while(w != nil){
+ cp = libc0->strchr(w.s, '(');
+ if(cp != nil){
+ cp = cp[1: ];
+ qp = libc0->strchr(cp, ')');
+ if(qp != nil){
+ qp[0] = byte 0;
+ libc0->strcpy(w.s, cp);
+ l.next = w;
+ l = w;
+ w = w.next;
+ continue;
+ }
+ }
+ l.next = w.next;
+ w.s = nil;
+ w = nil;
+ w = l.next;
+ }
+ v = l.next;
+ envupd(libc0->s2ab("newmember"), v);
+ # update stem0 -> stem9
+ for(p = 0; myenv[p] != nil; p++)
+ if(libc0->strcmp(myenv[p], libc0->s2ab("stem0")) == 0)
+ break;
+ for(i = 0; myenv[p] != nil; i++){
+ if(j.r.attr&REGEXP && j.match[i] != nil)
+ envupd(myenv[p], newword(j.match[i]));
+ else
+ envupd(myenv[p], newword(libc0->s2ab("")));
+ p++;
+ }
+ return envy;
+}
+
+#
+# dir
+#
+
+bulkmtime(dir: array of byte)
+{
+ buf := array[4096] of byte;
+ ss, s: array of byte;
+ db: Sys->Dir;
+ ok: int;
+
+ if(dir != nil){
+ s = dir;
+ if(libc0->strcmp(dir, libc0->s2ab("/")) == 0)
+ libc0->strcpy(buf, dir);
+ else
+ stob(buf, sys->sprint("%s/", libc0->ab2s(dir)));
+ (ok, db) = mkdirstat(dir);
+ if(ok >= 0 && (db.qid.qtype&Sys->QTDIR) == 0){
+ # bugger off
+ sys->fprint(sys->fildes(2), "mk: %s is not a directory path=%ux\n", libc0->ab2s(dir), int db.qid.path);
+ Exit();
+ }
+ }
+ else{
+ s = libc0->s2ab(".");
+ buf[0] = byte 0;
+ }
+ if(symlooki(s, S_BULKED, 0) != nil)
+ return;
+ ss = libc0->strdup(s);
+ symlooks(ss, S_BULKED, ss);
+ dirtime(s, buf);
+}
+
+mtime(name: array of byte): int
+{
+ sbuf: Sys->Dir;
+ s, ss: array of byte;
+ carry: byte;
+ ok: int;
+
+ s = libc0->strrchr(name, '/');
+ if(s == name)
+ s = s[1: ];
+ if(s != nil){
+ ss = name;
+ carry = s[0];
+ s[0] = byte 0;
+ }
+ else{
+ ss = nil;
+ carry = byte 0;
+ }
+ bulkmtime(ss);
+ if(int carry)
+ s[0] = carry;
+ (ok, sbuf) = mkdirstat(name);
+ if(ok < 0)
+ return 0;
+ return sbuf.mtime;
+}
+
+filetime(name: array of byte): int
+{
+ sym: ref Symtab;
+
+ sym = symlooki(name, S_TIME, 0);
+ if(sym != nil)
+ return sym.ivalue; # uggh
+ return mtime(name);
+}
+
+#
+# archive
+#
+
+dolong: int;
+
+atimeof(force: int, name: array of byte): int
+{
+ sym: ref Symtab;
+ t: int;
+ archive, member: array of byte;
+ buf := array[512] of byte;
+
+ (archive, member) = split(name);
+ if(archive == nil)
+ Exit();
+ t = mtime(archive);
+ sym = symlooki(archive, S_AGG, 0);
+ if(sym != nil){
+ if(force || t > sym.ivalue){
+ atimes(archive);
+ sym.ivalue = t;
+ }
+ }
+ else{
+ atimes(archive);
+ # mark the aggegate as having been done
+ symlooks(libc0->strdup(archive), S_AGG, libc0->s2ab("")).ivalue = t;
+ }
+ # truncate long member name to sizeof of name field in archive header
+ if(dolong)
+ stob(buf, sys->sprint("%s(%s)", libc0->ab2s(archive), libc0->ab2s(member)));
+ else
+ stob(buf, sys->sprint("%s(%.*s)", libc0->ab2s(archive), SARNAME, libc0->ab2s(member)));
+ sym = symlooki(buf, S_TIME, 0);
+ if(sym != nil)
+ return sym.ivalue; # uggh
+ return 0;
+}
+
+atouch(name: array of byte)
+{
+ archive, member: array of byte;
+ fd: ref Sys->FD;
+ i: int;
+ # h: ar_hdr;
+ t: int;
+
+ (archive, member) = split(name);
+ if(archive == nil)
+ Exit();
+ fd = sys->open(libc0->ab2s(archive), Sys->ORDWR);
+ if(fd == nil){
+ fd = sys->create(libc0->ab2s(archive), Sys->OWRITE, 8r666);
+ if(fd == nil){
+ perror(archive);
+ Exit();
+ }
+ sys->write(fd, libc0->s2ab(ARMAG), SARMAG);
+ }
+ if(symlooki(name, S_TIME, 0) != nil){
+ # hoon off and change it in situ
+ sys->seek(fd, big SARMAG, 0);
+ buf := array[SAR_HDR] of byte;
+ while(sys->read(fd, buf, SAR_HDR) == SAR_HDR){
+ name = buf[0: SARNAME];
+ for(i = SARNAME-1; i > 0 && name[i] == byte ' '; i--)
+ ;
+ name[i+1] = byte 0;
+ if(libc0->strcmp(member, name) == 0){
+ t = SARNAME-SAR_HDR; # ughgghh
+ sys->seek(fd, big t, 1);
+ sys->fprint(fd, "%-12d", daytime->now());
+ break;
+ }
+ t = int string buf[48: 58];
+ if(t&8r1)
+ t++;
+ sys->seek(fd, big t, 1);
+ }
+ }
+ fd = nil;
+}
+
+atimes(ar: array of byte)
+{
+ # h: ar_hdr;
+ t: int;
+ fd: ref Sys->FD;
+ i: int;
+ buf := array[BIGBLOCK] of byte;
+ n: array of byte;
+ name := array[SARNAME+1] of byte;
+
+ fd = sys->open(libc0->ab2s(ar), Sys->OREAD);
+ if(fd == nil)
+ return;
+ if(sys->read(fd, buf, SARMAG) != SARMAG){
+ fd = nil;
+ return;
+ }
+ b := array[SAR_HDR] of byte;
+ while(sys->read(fd, b, SAR_HDR) == SAR_HDR){
+ t = int string b[16: 28];
+ if(t == 0) # as it sometimes happens; thanks ken
+ t = 1;
+ hname := b[0: SARNAME];
+ libc0->strncpy(name, hname, SARNAME);
+ for(i = SARNAME-1; i > 0 && name[i] == byte ' '; i--)
+ ;
+ if(name[i] == byte '/') # system V bug
+ i--;
+ name[i+1] = byte 0;
+ n = membername(name, fd, int string b[48: 58]);
+ if(n == nil){
+ dolong = 1;
+ continue;
+ }
+ stob(buf, sys->sprint("%s(%s)", libc0->ab2s(ar), libc0->ab2s(n)));
+ symlooki(libc0->strdup(buf), S_TIME, t).ivalue = t;
+ t = int string b[48: 58];
+ if(t&8r1)
+ t++;
+ sys->seek(fd, big t, 1);
+ }
+ fd = nil;
+}
+
+typex(file: array of byte): int
+{
+ fd: ref Sys->FD;
+ buf := array[SARMAG] of byte;
+
+ fd = sys->open(libc0->ab2s(file), Sys->OREAD);
+ if(fd == nil){
+ if(symlooki(file, S_BITCH, 0) == nil){
+ bout.puts(sys->sprint("%s doesn't exist: assuming it will be an archive\n", libc0->ab2s(file)));
+ symlooks(file, S_BITCH, file);
+ }
+ return 1;
+ }
+ if(sys->read(fd, buf, SARMAG) != SARMAG){
+ fd = nil;
+ return 0;
+ }
+ fd = nil;
+ return !libc0->strncmp(libc0->s2ab(ARMAG), buf, SARMAG);
+}
+
+split(name: array of byte): (array of byte, array of byte)
+{
+ member: array of byte;
+ p, q: array of byte;
+
+ p = libc0->strdup(name);
+ q = libc0->strchr(p, '(');
+ if(q != nil){
+ q[0] = byte 0;
+ q = q[1: ];
+ member = q;
+ q = libc0->strchr(q, ')');
+ if(q != nil)
+ q[0] = byte 0;
+ if(typex(p))
+ return (p, member);
+ p = nil;
+ sys->fprint(sys->fildes(2), "mk: '%s' is not an archive\n", libc0->ab2s(name));
+ }
+ return (nil, member);
+}
+
+#
+# bufblock
+#
+
+freelist: ref Bufblock;
+
+QUANTA: con 4096;
+
+newbuf(): ref Bufblock
+{
+ p: ref Bufblock;
+
+ if(freelist != nil){
+ p = freelist;
+ freelist = freelist.next;
+ }
+ else{
+ p = ref Bufblock;
+ p.start = array[QUANTA*1] of byte;
+ p.end = QUANTA;
+ }
+ p.current = 0;
+ p.start[0] = byte 0;
+ p.next = nil;
+ return p;
+}
+
+freebuf(p: ref Bufblock)
+{
+ p.next = freelist;
+ freelist = p;
+}
+
+growbuf(p: ref Bufblock)
+{
+ n: int;
+ f: ref Bufblock;
+ cp: array of byte;
+
+ n = p.end+QUANTA;
+ # search the free list for a big buffer
+ for(f = freelist; f != nil; f = f.next){
+ if(f.end >= n){
+ f.start[0: ] = p.start[0: p.end];
+ cp = f.start;
+ f.start = p.start;
+ p.start = cp;
+ cpi := f.end;
+ f.end = p.end;
+ p.end = cpi;
+ f.current = 0;
+ break;
+ }
+ }
+ if(f == nil){ # not found - grow it
+ nps := array[n] of byte;
+ for(i := 0; i < p.end; i++)
+ nps[i] = p.start[i];
+ p.start = nps;
+ p.end = n;
+ }
+ p.current = n-QUANTA;
+}
+
+bufcpy(buf: ref Bufblock, cp: array of byte, n: int)
+{
+ i := 0;
+ while(n--)
+ insert(buf, int cp[i++]);
+}
+
+insert(buf: ref Bufblock, c: int)
+{
+ if(buf.current >= buf.end)
+ growbuf(buf);
+ buf.start[buf.current++] = byte c;
+}
+
+rinsert(buf: ref Bufblock, r: int)
+{
+ n: int;
+
+ b := array[Sys->UTFmax] of byte;
+ n = sys->char2byte(r, b, 0);
+ if(buf.current+n > buf.end)
+ growbuf(buf);
+ buf.start[buf.current: ] = b[0: n];
+ buf.current += n;
+}
+
diff --git a/appl/cmd/mk/mkbinds b/appl/cmd/mk/mkbinds
new file mode 100644
index 00000000..a5df28dc
--- /dev/null
+++ b/appl/cmd/mk/mkbinds
@@ -0,0 +1,2 @@
+/appl/cmd/mk/mkconfig /mkconfig
+/appl/cmd/mk/mksubdirs /mkfiles/mksubdirs
diff --git a/appl/cmd/mk/mkconfig b/appl/cmd/mk/mkconfig
new file mode 100644
index 00000000..17d31a93
--- /dev/null
+++ b/appl/cmd/mk/mkconfig
@@ -0,0 +1,28 @@
+#
+# Set the following 4 variables. The host system is the system where
+# the software will be built; the target system is where it will run.
+# They are almost always the same.
+
+# On Nt systems, the ROOT path MUST be of the form `drive:/path'
+ROOT=
+
+#
+# Except for building kernels, SYSTARG must always be the same as SYSHOST
+#
+SYSHOST=Plan9 # build system OS type (Hp, Inferno, Irix, Linux, Nt, Plan9, Solaris)
+SYSTARG=$SYSHOST # target system OS type (Hp, Inferno, Irix, Linux, Nt, Plan9, Solaris)
+
+#
+# specify the architecture of the target system - Inferno imports it from the
+# environment; for other systems it is usually just hard-coded
+#
+#OBJTYPE=386 # target system object type (s800, mips, 386, arm, sparc)
+OBJTYPE=386
+
+#
+# no changes required beyond this point
+#
+OBJDIR=$SYSTARG/$OBJTYPE
+
+<$ROOT/mkfiles/mkhost-$SYSHOST # variables appropriate for host system
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE # variables used to build target object type
diff --git a/appl/cmd/mk/mkfile b/appl/cmd/mk/mkfile
new file mode 100644
index 00000000..51d20245
--- /dev/null
+++ b/appl/cmd/mk/mkfile
@@ -0,0 +1,19 @@
+<../../../mkconfig
+
+TARG= mk.dis\
+
+MODULES=\
+ ar.m\
+
+SYSMODULES= \
+ bufio.m\
+ draw.m\
+ math.m\
+ sys.m\
+ regex.m\
+ daytime.m\
+ libc0.m\
+
+DISBIN=$ROOT/dis
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/mk/mksubdirs b/appl/cmd/mk/mksubdirs
new file mode 100644
index 00000000..3fe01c81
--- /dev/null
+++ b/appl/cmd/mk/mksubdirs
@@ -0,0 +1,16 @@
+all:V: all-$SHELLTYPE
+install:V: install-$SHELLTYPE
+uninstall:V: uninstall-$SHELLTYPE
+nuke:V: nuke-$SHELLTYPE
+clean:V: clean-$SHELLTYPE
+
+%-rc %-nt %-sh:QV:
+ load std
+ for j in $DIRS {
+ if { ftest -d $j } {
+ echo 'cd' $j '; mk' $MKFLAGS $stem
+ cd $j; mk $MKFLAGS $stem; cd ..
+ } else {
+ ! { ftest -e $j }
+ }
+ }
diff --git a/appl/cmd/mkdir.b b/appl/cmd/mkdir.b
new file mode 100644
index 00000000..21e03ee7
--- /dev/null
+++ b/appl/cmd/mkdir.b
@@ -0,0 +1,75 @@
+implement Mkdir;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+
+stderr: ref Sys->FD;
+
+Mkdir: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ if(argv == nil || (argv = tl argv) == nil)
+ exit;
+ pflag := 0;
+ if(hd argv == "-p"){
+ pflag = 1;
+ argv = tl argv;
+ }
+ e := "";
+ for(; argv != nil; argv = tl argv){
+ dir := hd argv;
+ if(!pflag){
+ (ok, d) := sys->stat(dir);
+ if(ok < 0){
+ if(mkdir(dir) < 0)
+ e = "error";
+ }else{
+ sys->fprint(stderr, "mkdir: %s already exists\n", dir);
+ e = "error";
+ }
+ }else if(mkpath(dir) < 0)
+ e = "error";
+ }
+ if(e != nil)
+ raise "fail:"+e;
+}
+
+mkpath(dir: string): int
+{
+ (nil, flds) := sys->tokenize(dir, "/");
+ s := "";
+ if(dir != "" && dir[0] != '/')
+ s = ".";
+ for(; flds != nil; flds = tl flds){
+ s += "/"+hd flds;
+ (ok, d) := sys->stat(s);
+ if(ok < 0){
+ if(mkdir(s) < 0)
+ return -1;
+ }else if((d.mode & Sys->DMDIR) == 0){
+ sys->fprint(stderr, "mkdir: can't create %s: %s not a directory\n", dir, s);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+mkdir(dir: string): int
+{
+ f := sys->create(dir, Sys->OREAD, Sys->DMDIR + 8r777);
+ if(f == nil) {
+ sys->fprint(stderr, "mkdir: can't create %s: %r\n", dir);
+ return -1;
+ }
+ return 0;
+}
diff --git a/appl/cmd/mkfile b/appl/cmd/mkfile
new file mode 100644
index 00000000..2bbdb938
--- /dev/null
+++ b/appl/cmd/mkfile
@@ -0,0 +1,219 @@
+<../../mkconfig
+
+DIRS=\
+ auth\
+ auxi\
+ avr\
+ disk\
+ install\
+ ip\
+ lego\
+ limbo\
+ mash\
+ mk\
+ mpc\
+ ndb\
+ sh\
+ spki\
+ usb\
+
+TARG=\
+ 9660srv.dis\
+ 9export.dis\
+ 9srvfs.dis\
+ 9win.dis\
+ B.dis\
+ archfs.dis\
+ auplay.dis\
+ auhdr.dis\
+ basename.dis\
+ bind.dis\
+ # bit2gif.dis\
+ broke.dis\
+ bytes.dis\
+ cal.dis\
+ cat.dis\
+ cd.dis\
+ chgrp.dis\
+ chmod.dis\
+ cleanname.dis\
+ cmp.dis\
+ comm.dis\
+ cook.dis\
+ cprof.dis\
+ cp.dis\
+ cpu.dis\
+ crypt.dis\
+ date.dis\
+ dbfs.dis\
+ dd.dis\
+ dial.dis\
+ diff.dis\
+ disdep.dis\
+ disdump.dis\
+ dossrv.dis\
+ du.dis\
+ echo.dis\
+ ed.dis\
+ emuinit.dis\
+ env.dis\
+ export.dis\
+ fc.dis\
+ fcp.dis\
+ fmt.dis\
+ fone.dis\
+ fortune.dis\
+ freq.dis\
+ fs.dis\
+ ftest.dis\
+ ftpfs.dis\
+ getauthinfo.dis\
+ gettar.dis\
+ # gif2bit.dis\
+ grep.dis\
+ gunzip.dis\
+ gzip.dis\
+ idea.dis\
+ import.dis\
+ iostats.dis\
+ itest.dis\
+ itreplay.dis\
+ kill.dis\
+ listen.dis\
+ lockfs.dis\
+ logfile.dis\
+ look.dis\
+ lookman.dis\
+ lc.dis\
+ ls.dis\
+ lstar.dis\
+ man.dis\
+ man2txt.dis\
+ mathcalc.dis\
+ mc.dis\
+ md5sum.dis\
+ mdb.dis\
+ memfs.dis\
+ metamorph.dis\
+ mkdir.dis\
+ mntgen.dis\
+ mount.dis\
+ mouse.dis\
+ mprof.dis\
+ mv.dis\
+ netkey.dis\
+ netstat.dis\
+ newer.dis\
+ ns.dis\
+ nsbuild.dis\
+ os.dis\
+ p.dis\
+ pause.dis\
+ plumb.dis\
+ plumber.dis\
+ prof.dis\
+ ps.dis\
+ puttar.dis\
+ pwd.dis\
+ ramfile.dis\
+ randpass.dis\
+ raw2iaf.dis\
+ rawdbfs.dis\
+ rcmd.dis\
+ rdp.dis\
+ read.dis\
+ rioimport.dis\
+ rm.dis\
+ runas.dis\
+ sed.dis\
+ sendmail.dis\
+ sha1sum.dis\
+ shutdown.dis\
+ sleep.dis\
+ sort.dis\
+ src.dis\
+ stack.dis\
+ stackv.dis\
+ stream.dis\
+ strings.dis\
+ styxchat.dis\
+ styxmon.dis\
+ styxlisten.dis\
+ sum.dis\
+ tail.dis\
+ tarfs.dis\
+ tclsh.dis\
+ tcs.dis\
+ tee.dis\
+ telnet.dis\
+ test.dis\
+ time.dis\
+ timestamp.dis\
+ tkcmd.dis\
+ touch.dis\
+ touchcal.dis\
+ tokenize.dis\
+ tr.dis\
+ tsort.dis\
+ unicode.dis\
+ units.dis\
+ uniq.dis\
+ unmount.dis\
+ uudecode.dis\
+ uuencode.dis\
+ wav2iaf.dis\
+ wc.dis\
+ webgrab.dis\
+ wish.dis\
+ wmexport.dis\
+ wmimport.dis\
+ xargs.dis\
+ xd.dis\
+ xmount.dis\
+ yacc.dis\
+ zeros.dis\
+
+MODULES=\
+
+SYSMODULES=\
+ bufio.m\
+ bundle.m\
+ daytime.m\
+ draw.m\
+ env.m\
+ filepat.m\
+ filter.m\
+ fslib.m\
+ ir.m\
+ keyring.m\
+ man.m\
+ newns.m\
+ prefab.m\
+ readdir.m\
+ regex.m\
+ security.m\
+ sh.m\
+ srv.m\
+ string.m\
+ styx.m\
+ styxlib.m\
+ sys.m\
+ tk.m\
+ tkclient.m\
+ url.m\
+ webget.m\
+ workdir.m\
+
+DISBIN=$ROOT/dis
+
+<$ROOT/mkfiles/mkdis
+<$ROOT/mkfiles/mksubdirs
+
+auhdr.dis: auplay.dis
+ rm -f auhdr.dis && cp auplay.dis auhdr.dis
+
+dbfs.dis: $MODDIR/styxservers.m
+rawdbfs.dis: $MODDIR/styxservers.m
+import.dis: $MODDIR/encoding.m $MODDIR/factotum.m
+basename.dis: $MODDIR/names.m
+cleanname.dis: $MODDIR/names.m
diff --git a/appl/cmd/mntgen.b b/appl/cmd/mntgen.b
new file mode 100644
index 00000000..3f066425
--- /dev/null
+++ b/appl/cmd/mntgen.b
@@ -0,0 +1,188 @@
+implement Mntgen;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "styx.m";
+ styx: Styx;
+ Rmsg, Tmsg: import styx;
+include "styxservers.m";
+ styxservers: Styxservers;
+ Ebadfid, Enotfound, Eopen, Einuse: import Styxservers;
+ Styxserver, readbytes, Navigator, Fid: import styxservers;
+
+ nametree: Nametree;
+ Tree: import nametree;
+
+Mntgen: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+Qroot: con big 16rfffffff;
+
+badmodule(p: string)
+{
+ sys->fprint(sys->fildes(2), "cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+DEBUG: con 0;
+
+Entry: adt {
+ refcount: int;
+ path: big;
+};
+refcounts := array[10] of Entry;
+tree: ref Tree;
+nav: ref Navigator;
+
+uniq: int;
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ styx = load Styx Styx->PATH;
+ if (styx == nil)
+ badmodule(Styx->PATH);
+ styx->init();
+ styxservers = load Styxservers Styxservers->PATH;
+ if (styxservers == nil)
+ badmodule(Styxservers->PATH);
+ styxservers->init(styx);
+
+ nametree = load Nametree Nametree->PATH;
+ if (nametree == nil)
+ badmodule(Nametree->PATH);
+ nametree->init();
+
+ navop: chan of ref Styxservers->Navop;
+ (tree, navop) = nametree->start();
+ nav = Navigator.new(navop);
+ (tchan, srv) := Styxserver.new(sys->fildes(0), nav, Qroot);
+
+ tree.create(Qroot, dir(".", Sys->DMDIR | 8r555, Qroot));
+
+ for (;;) {
+ gm := <-tchan;
+ if (gm == nil) {
+ tree.quit();
+ exit;
+ }
+ e := handlemsg(gm, srv, tree);
+ if (e != nil)
+ srv.reply(ref Rmsg.Error(gm.tag, e));
+ }
+}
+
+walk1(c: ref Fid, name: string): string
+{
+ if (name == ".."){
+ if (c.path != Qroot)
+ decref(c.path);
+ c.walk(Sys->Qid(Qroot, 0, Sys->QTDIR));
+ } else if (c.path == Qroot) {
+ (d, err) := nav.walk(c.path, name);
+ if (d == nil)
+ d = addentry(name);
+ else
+ incref(d.qid.path);
+ c.walk(d.qid);
+ } else
+ return Enotfound;
+ return nil;
+}
+
+handlemsg(gm: ref Styx->Tmsg, srv: ref Styxserver, nil: ref Tree): string
+{
+ pick m := gm {
+ Walk =>
+ c := srv.getfid(m.fid);
+ if(c == nil)
+ return Ebadfid;
+ if(c.isopen)
+ return Eopen;
+ if(m.newfid != m.fid){
+ nc := srv.newfid(m.newfid);
+ if(nc == nil)
+ return Einuse;
+ c = c.clone(nc);
+ incref(c.path);
+ }
+ qids := array[len m.names] of Sys->Qid;
+ oldpath := c.path;
+ oldqtype := c.qtype;
+ incref(oldpath);
+ for (i := 0; i < len m.names; i++){
+ err := walk1(c, m.names[i]);
+ if (err != nil){
+ if(m.newfid != m.fid){
+ decref(c.path);
+ srv.delfid(c);
+ }
+ c.path = oldpath;
+ c.qtype = oldqtype;
+ if(i == 0)
+ return err;
+ srv.reply(ref Rmsg.Walk(m.tag, qids[0:i]));
+ return nil;
+ }
+ qids[i] = Sys->Qid(c.path, 0, c.qtype);
+ }
+ decref(oldpath);
+ srv.reply(ref Rmsg.Walk(m.tag, qids));
+ Clunk =>
+ c := srv.clunk(m);
+ if (c != nil && c.path != Qroot)
+ decref(c.path);
+ * =>
+ srv.default(gm);
+ }
+ return nil;
+}
+
+addentry(name: string): ref Sys->Dir
+{
+ for (i := 0; i < len refcounts; i++)
+ if (refcounts[i].refcount == 0)
+ break;
+ if (i == len refcounts) {
+ refcounts = (array[len refcounts * 2] of Entry)[0:] = refcounts;
+ for (j := i; j < len refcounts; j++)
+ refcounts[j].refcount = 0;
+ }
+ d := dir(name, Sys->DMDIR|8r555, big i | (big uniq++ << 32));
+ tree.create(Qroot, d);
+ refcounts[i] = (1, d.qid.path);
+ return ref d;
+}
+
+incref(q: big)
+{
+ id := int q;
+ if (id >= 0 && id < len refcounts){
+ refcounts[id].refcount++;
+ }
+}
+
+decref(q: big)
+{
+ id := int q;
+ if (id >= 0 && id < len refcounts){
+ if (--refcounts[id].refcount == 0)
+ tree.remove(refcounts[id].path);
+ }
+}
+
+Blankdir: Sys->Dir;
+dir(name: string, perm: int, qid: big): Sys->Dir
+{
+ d := Blankdir;
+ d.name = name;
+ d.uid = "me";
+ d.gid = "me";
+ d.qid.path = qid;
+ if (perm & Sys->DMDIR)
+ d.qid.qtype = Sys->QTDIR;
+ else
+ d.qid.qtype = Sys->QTFILE;
+ d.mode = perm;
+ return d;
+}
diff --git a/appl/cmd/mount.b b/appl/cmd/mount.b
new file mode 100644
index 00000000..9eb6e3a9
--- /dev/null
+++ b/appl/cmd/mount.b
@@ -0,0 +1,348 @@
+implement Mount;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "keyring.m";
+include "security.m";
+include "factotum.m";
+include "styxconv.m";
+include "styxpersist.m";
+include "arg.m";
+include "sh.m";
+
+Mount: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+verbose := 0;
+doauth := 1;
+do9 := 0;
+oldstyx := 0;
+persist := 0;
+showstyx := 0;
+quiet := 0;
+
+alg := "none";
+keyfile: string;
+spec: string;
+addr: string;
+
+fail(status, msg: string)
+{
+ sys->fprint(sys->fildes(2), "mount: %s\n", msg);
+ raise "fail:"+status;
+}
+
+nomod(mod: string)
+{
+ fail("load", sys->sprint("can't load %s: %r", mod));
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+
+ arg->init(args);
+ arg->setusage("mount [-a|-b] [-coA9] [-C cryptoalg] [-k keyfile] [-q] net!addr|file|{command} mountpoint [spec]");
+ flags := 0;
+ while((o := arg->opt()) != 0){
+ case o {
+ 'a' =>
+ flags |= Sys->MAFTER;
+ 'b' =>
+ flags |= Sys->MBEFORE;
+ 'c' =>
+ flags |= Sys->MCREATE;
+ 'C' =>
+ alg = arg->earg();
+ 'k' or
+ 'f' =>
+ keyfile = arg->earg();
+ 'A' =>
+ doauth = 0;
+ '9' =>
+ doauth = 0;
+ do9 = 1;
+ 'o' =>
+ oldstyx = 1;
+ 'v' =>
+ verbose = 1;
+ 'P' =>
+ persist = 1;
+ 'S' =>
+ showstyx = 1;
+ 'q' =>
+ quiet = 1;
+ * =>
+ arg->usage();
+ }
+ }
+ args = arg->argv();
+ if(len args != 2){
+ if(len args != 3)
+ arg->usage();
+ spec = hd tl tl args;
+ }
+ arg = nil;
+ addr = hd args;
+ mountpoint := hd tl args;
+
+ if(oldstyx && do9)
+ fail("usage", "cannot combine -o and -9 options");
+
+ fd := connect(ctxt, addr);
+ ok: int;
+ if(do9){
+ fd = styxlog(fd);
+ factotum := load Factotum Factotum->PATH;
+ if(factotum == nil)
+ nomod(Factotum->PATH);
+ factotum->init();
+ ok = factotum->mount(fd, mountpoint, flags, spec, nil).t0;
+ }else{
+ err: string;
+ if(!persist){
+ (fd, err) = authcvt(fd);
+ if(fd == nil)
+ fail("error", err);
+ }
+ fd = styxlog(fd);
+ ok = sys->mount(fd, nil, mountpoint, flags, spec);
+ }
+ if(ok < 0 && !quiet)
+ fail("mount failed", sys->sprint("mount failed: %r"));
+}
+
+connect(ctxt: ref Draw->Context, dest: string): ref Sys->FD
+{
+ if(dest != nil && dest[0] == '{' && dest[len dest - 1] == '}'){
+ if(persist)
+ fail("usage", "cannot persistently mount a command");
+ doauth = 0;
+ return popen(ctxt, dest :: nil);
+ }
+ (n, nil) := sys->tokenize(dest, "!");
+ if(n == 1){
+ fd := sys->open(dest, Sys->ORDWR);
+ if(fd != nil){
+ if(persist)
+ fail("usage", "cannot persistently mount a file");
+ return fd;
+ }
+ if(dest[0] == '/')
+ fail("open failed", sys->sprint("can't open %s: %r", dest));
+ }
+ svc := "styx";
+ if(do9)
+ svc = "9fs";
+ dest = netmkaddr(dest, "net", svc);
+ if(persist){
+ styxpersist := load Styxpersist Styxpersist->PATH;
+ if(styxpersist == nil)
+ fail("load", sys->sprint("cannot load %s: %r", Styxpersist->PATH));
+ sys->pipe(p := array[2] of ref Sys->FD);
+ (c, err) := styxpersist->init(p[0], do9, nil);
+ if(c == nil)
+ fail("error", "styxpersist: "+err);
+ spawn dialler(c, dest);
+ return p[1];
+ }
+ (ok, c) := sys->dial(dest, nil);
+ if(ok < 0)
+ fail("dial failed", sys->sprint("can't dial %s: %r", dest));
+ return c.dfd;
+}
+
+dialler(dialc: chan of chan of ref Sys->FD, dest: string)
+{
+ while((reply := <-dialc) != nil){
+ if(verbose)
+ sys->print("dialling %s\n", addr);
+ (ok, c) := sys->dial(dest, nil);
+ if(ok == -1){
+ reply <-= nil;
+ continue;
+ }
+ (fd, err) := authcvt(c.dfd);
+ if(fd == nil && verbose)
+ sys->print("%s\n", err);
+ # XXX could check that user at the other end is still the same.
+ reply <-= fd;
+ }
+}
+
+authcvt(fd: ref Sys->FD): (ref Sys->FD, string)
+{
+ err: string;
+ if(doauth){
+ (fd, err) = authenticate(keyfile, alg, fd, addr);
+ if(fd == nil)
+ return (nil, err);
+ if(verbose)
+ sys->print("remote username is %s\n", err);
+ }
+ if(oldstyx)
+ return cvstyx(fd);
+ return (fd, nil);
+}
+
+popen(ctxt: ref Draw->Context, argv: list of string): ref Sys->FD
+{
+ sh := load Sh Sh->PATH;
+ if(sh == nil)
+ nomod(Sh->PATH);
+ sync := chan of int;
+ fds := array[2] of ref Sys->FD;
+ sys->pipe(fds);
+ spawn runcmd(sh, ctxt, argv, fds[0], sync);
+ <-sync;
+ return fds[1];
+}
+
+runcmd(sh: Sh, ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD, sync: chan of int)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ sys->dup(stdin.fd, 0);
+ stdin = nil;
+ sync <-= 0;
+ sh->run(ctxt, argv);
+}
+
+cvstyx(fd: ref Sys->FD): (ref Sys->FD, string)
+{
+ styxconv := load Styxconv Styxconv->PATH;
+ if(styxconv == nil)
+ return (nil, sys->sprint("cannot load %s: %r", Styxconv->PATH));
+ styxconv->init();
+ p := array[2] of ref Sys->FD;
+ if(sys->pipe(p) < 0)
+ return (nil, sys->sprint("can't create pipe: %r"));
+ pidc := chan of int;
+ spawn styxconv->styxconv(p[1], fd, pidc);
+ p[1] = nil;
+ <-pidc;
+ return (p[0], nil);
+}
+
+authenticate(keyfile, alg: string, dfd: ref Sys->FD, addr: string): (ref Sys->FD, string)
+{
+ cert : string;
+
+ kr := load Keyring Keyring->PATH;
+ if(kr == nil)
+ return (nil, sys->sprint("cannot load %s: %r", Keyring->PATH));
+
+ kd := "/usr/" + user() + "/keyring/";
+ if(keyfile == nil) {
+ cert = kd + netmkaddr(addr, "tcp", "");
+ (ok, nil) := sys->stat(cert);
+ if (ok < 0)
+ cert = kd + "default";
+ }
+ else if(len keyfile > 0 && keyfile[0] != '/')
+ cert = kd + keyfile;
+ else
+ cert = keyfile;
+ ai := kr->readauthinfo(cert);
+ if(ai == nil)
+ return (nil, sys->sprint("cannot read %s: %r", cert));
+
+ auth := load Auth Auth->PATH;
+ if(auth == nil)
+ nomod(Auth->PATH);
+
+ err := auth->init();
+ if(err != nil)
+ return (nil, "cannot init auth: "+err);
+
+ fd: ref Sys->FD;
+ (fd, err) = auth->client(alg, ai, dfd);
+ if(fd == nil)
+ return (nil, "authentication failed: "+err);
+ return (fd, err);
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil)
+ return "";
+
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+
+ return string buf[0:n];
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, nil) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
+
+kill(pid: int)
+{
+ if ((fd := sys->open("#p/" + string pid + "/ctl", Sys->OWRITE)) != nil)
+ sys->fprint(fd, "kill");
+}
+
+include "styx.m";
+ styx: Styx;
+ Rmsg, Tmsg: import styx;
+
+styxlog(fd: ref Sys->FD): ref Sys->FD
+{
+ if(showstyx){
+ sys->pipe(p := array[2] of ref Sys->FD);
+ styx = load Styx Styx->PATH;
+ styx->init();
+ spawn tmsgreader(p[0], fd, p1 := chan[1] of int, p2 := chan[1] of int);
+ spawn rmsgreader(fd, p[0], p2, p1);
+ fd = p[1];
+ }
+ return fd;
+}
+
+tmsgreader(cfd, sfd: ref Sys->FD, p1, p2: chan of int)
+{
+ p1 <-= sys->pctl(0, nil);
+ m: ref Tmsg;
+ do{
+ m = Tmsg.read(cfd, 9000);
+ sys->print("%s\n", m.text());
+ d := m.pack();
+ if(sys->write(sfd, d, len d) != len d)
+ sys->print("tmsg write error: %r\n");
+ } while(m != nil && tagof(m) != tagof(Tmsg.Readerror));
+ kill(<-p2);
+}
+
+rmsgreader(sfd, cfd: ref Sys->FD, p1, p2: chan of int)
+{
+ p1 <-= sys->pctl(0, nil);
+ m: ref Rmsg;
+ do{
+ m = Rmsg.read(sfd, 9000);
+ sys->print("%s\n", m.text());
+ d := m.pack();
+ if(sys->write(cfd, d, len d) != len d)
+ sys->print("rmsg write error: %r\n");
+ } while(m != nil && tagof(m) != tagof(Tmsg.Readerror));
+ kill(<-p2);
+}
diff --git a/appl/cmd/mouse.b b/appl/cmd/mouse.b
new file mode 100644
index 00000000..9e21d61a
--- /dev/null
+++ b/appl/cmd/mouse.b
@@ -0,0 +1,394 @@
+implement mouse;
+# ported from plan 9's aux/mouse
+
+include "sys.m";
+ sys: Sys;
+ sprint, fprint, sleep: import sys;
+include "draw.m";
+
+stderr: ref Sys->FD;
+
+mouse: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Sleep500: con 500;
+Sleep1000: con 1000;
+Sleep2000: con 2000;
+TIMEOUT: con 5000;
+fail := "fail:";
+usage()
+{
+ fprint(stderr, "usage: mouse [type]\n");
+ raise fail+"usage";
+}
+
+write(fd: ref Sys->FD, buf: array of byte, n: int): int
+{
+ if (debug) {
+ sys->fprint(stderr, "write(%d) ", fd.fd);
+ for (i := 0; i < len buf; i++) {
+ sys->fprint(stderr, "'%c' ", int buf[i]);
+ }
+ sys->fprint(stderr, "\n");
+ }
+ return sys->write(fd, buf, n);
+}
+
+speeds := array[] of {"b1200", "b2400", "b4800", "b9600"};
+debug := 0;
+can9600 := 0;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+{
+ if (argv == nil)
+ usage();
+
+ argv = tl argv;
+ def := 0;
+ baud := 0;
+ while (argv != nil && len (arg := hd argv) > 1 && arg[0] == '-') {
+ case arg[1] {
+ 'D' =>
+ debug = 1;
+ * =>
+ usage();
+ }
+ argv = tl argv;
+ }
+ if (len argv > 1)
+ usage();
+
+ p: string;
+ if (argv == nil)
+ p = mouseprobe();
+ else
+ p = hd argv;
+ if (p != nil && !isnum(p)) {
+ mouseconfig(p);
+ return;
+ }
+ if (p == nil) {
+ serial("0");
+ serial("1");
+ fprint(stderr, "mouse: no mouse detected\n");
+ } else {
+ err := serial(p);
+ fprint(stderr, "mouse: %s\n", err);
+ }
+}
+exception{
+ # this could be taken out so the shell could
+ # get an indication that the command has failed.
+ "fail:*" =>
+ ;
+}
+}
+
+# probe for a serial mouse on port p;
+# return some an error string if not found.
+serial(p: string): string
+{
+ baud := 0;
+ f := sys->sprint("/dev/eia%sctl", p);
+ if ((ctl := sys->open(f, Sys->ORDWR)) == nil)
+ return sprint("can't open %s - %r\n", f);
+
+ f = sys->sprint("/dev/eia%s", p);
+ if ((data := sys->open(f, Sys->ORDWR)) == nil)
+ return sprint("can't open %s - %r\n", f);
+
+ if(debug) fprint(stderr, "ctl=%d, data=%d\n", ctl.fd, data.fd);
+
+ if(debug) fprint(stderr, "MorW()\n");
+ mtype := MorW(ctl, data);
+ if (mtype == 0) {
+ if(debug) return "no mouse detected";
+
+ if(debug) fprint(stderr, "C()\n");
+ mtype = C(ctl, data);
+ }
+ if (mtype == 0)
+ return "no mouse detected on port "+p;
+
+ if(debug)fprint(stderr, "done eia setup\n");
+ mt := "serial " + p;
+ case mtype {
+ * =>
+ return "unknown mouse type";
+ 'C' =>
+ if(debug) fprint(stderr, "Logitech 5 byte mouse\n");
+ Cbaud(ctl, data, baud);
+ 'W' =>
+ if(debug) fprint(stderr, "Type W mouse\n");
+ Wbaud(ctl, data, baud);
+ 'M' =>
+ if(debug) fprint(stderr, "Microsoft compatible mouse\n");
+ mt += " M";
+ }
+ mouseconfig(mt);
+ return nil;
+}
+
+mouseconfig(mt: string)
+{
+ if ((conf := sys->open("/dev/mousectl", Sys->OWRITE)) == nil) {
+ fprint(stderr, "mouse: can't open mousectl - %r\n");
+ raise fail+"open mousectl";
+ }
+ if(debug) fprint(stderr, "opened mousectl\n");
+ if (write(conf, array of byte mt, len array of byte mt) < 0) {
+ fprint(stderr, "mouse: error setting mouse type - %r\n");
+ raise fail+"write conf";
+ }
+ fprint(stderr, "mouse: configured as '%s'\n", mt);
+}
+
+isnum(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] < '0' || s[i] > '9')
+ return 0;
+ return 1;
+}
+
+mouseprobe(): string
+{
+ if ((probe := sys->open("/dev/mouseprobe", Sys->OREAD)) == nil) {
+ fprint(stderr, "mouse: can't open mouseprobe - %r\n");
+ return nil;
+ }
+ buf := array[64] of byte;
+ n := sys->read(probe, buf, len buf);
+ if (n <= 0)
+ return nil;
+ if (buf[n - 1] == byte '\n')
+ n--;
+ if(debug) fprint(stderr, "mouse probe detected mouse of type '%s'\n", string buf[0:n]);
+ return string buf[0:n];
+}
+
+readbyte(fd: ref Sys->FD): int
+{
+ buf := array[1] of byte;
+ (n, err) := timedread(fd, buf, 1, 200);
+ if (n < 0) {
+ if (err == nil)
+ return -1;
+ fprint(stderr, "mouse: readbyte failed - %s\n", err);
+ raise fail+"read failed";
+ }
+ return int buf[0];
+}
+
+slowread(fd: ref Sys->FD, buf: array of byte, nbytes: int, msg: string): int
+{
+ for (i := 0; i < nbytes; i++) {
+ if ((c := readbyte(fd)) == -1)
+ break;
+ buf[i] = byte c;
+ }
+ if(debug) dumpbuf(buf[0:i], msg);
+ return i;
+}
+
+dumpbuf(buf: array of byte, msg: string)
+{
+ sys->fprint(stderr, "%s", msg);
+ for (i := 0; i < len buf; i++)
+ sys->fprint(stderr, "#%ux ", int buf[i]);
+ sys->fprint(stderr, "\n");
+}
+
+toggleRTS(fd: ref Sys->FD)
+{
+ # reset the mouse (toggle RTS)
+ # must be >100mS
+ writes(fd, "d0");
+ sleep(10);
+ writes(fd, "r0");
+ sleep(Sleep500);
+ writes(fd, "d1");
+ sleep(10);
+ writes(fd, "r1");
+ sleep(Sleep500);
+}
+
+setupeia(fd: ref Sys->FD, baud, bits: string)
+{
+ # set the speed to 1200/2400/4800/9600 baud,
+ # 7/8-bit data, one stop bit and no parity
+
+ (abaud, abits) := (array of byte baud, array of byte bits);
+ if(debug)sys->fprint(stderr, "setupeia(%s,%s)\n", baud, bits);
+ write(fd, abaud, len abaud);
+ write(fd, abits, len abits);
+ writes(fd, "s1");
+ writes(fd, "pn");
+}
+
+# check for types M, M3 & W
+#
+# we talk to all these mice using 1200 baud
+
+MorW(ctl, data: ref Sys->FD): int
+{
+ # set up for type M, V or W
+ # flush any pending data
+
+ setupeia(ctl, "b1200", "l7");
+ toggleRTS(ctl);
+ if(debug)sys->fprint(stderr, "toggled RTS\n");
+
+ buf := array[256] of byte;
+ while (slowread(data, buf, len buf, "flush: ") > 0)
+ ;
+ if(debug) sys->fprint(stderr, "done slowread\n");
+ toggleRTS(ctl);
+
+ # see if there's any data from the mouse
+ # (type M, V and W mice)
+ c := slowread(data, buf, len buf, "check M: ");
+
+ # type M, V and W mice return "M" or "M3" after reset.
+ # check for type W by sending a 'Send Standard Configuration'
+ # command, "*?".
+ if (c > 0 && int buf[0] == 'M') {
+ writes(data, "*?");
+ c = slowread(data, buf, len buf, "check W: ");
+ # 4 bytes back indicates a type W mouse
+ if (c == 4) {
+ if (int buf[1] & (1<<4))
+ can9600 = 1;
+ setupeia(ctl, "b1200", "l8");
+ writes(data, "*U");
+ slowread(data, buf, len buf, "check W: ");
+ return 'W';
+ }
+ return 'M';
+ }
+ return 0;
+}
+
+# check for type C by seeing if it responds to the status
+# command "s". the mouse is at an unknown speed so we
+# have to check all possible speeds.
+C(ctl, data: ref Sys->FD): int
+{
+ buf := array[256] of byte;
+ for (s := speeds; len s > 0; s = s[1:]) {
+ if (debug) sys->print("%s\n", s[0]);
+ setupeia(ctl, s[0], "l8");
+ writes(data, "s");
+ c := slowread(data, buf, len buf, "check C: ");
+ if (c >= 1 && (int buf[0] & 16rbf) == 16r0f) {
+ sleep(100);
+ writes(data, "*n");
+ sleep(100);
+ setupeia(ctl, "b1200", "l8");
+ writes(data, "s");
+ c = slowread(data, buf, len buf, "recheck C: ");
+ if (c >= 1 && (int buf[0] & 16rbf) == 16r0f) {
+ writes(data, "U");
+ return 'C';
+ }
+ }
+ sleep(100);
+ }
+ return 0;
+}
+
+Cbaud(ctl, data: ref Sys->FD, baud: int)
+{
+ buf := array[2] of byte;
+ case baud {
+ 0 or 1200 =>
+ return;
+ 2400 =>
+ buf[1] = byte 'o';
+ 4800 =>
+ buf[1] = byte 'p';
+ 9600 =>
+ buf[1] = byte 'q';
+ * =>
+ fprint(stderr, "mouse: can't set baud rate, mouse at 1200\n");
+ return;
+ }
+ buf[0] = byte '*';
+ sleep(100);
+ write(data, buf, 2);
+ sleep(100);
+ write(data, buf, 2);
+ setupeia(ctl, sys->sprint("b%d", baud), "l8");
+}
+
+Wbaud(ctl, data: ref Sys->FD, baud: int)
+{
+ case baud {
+ 0 or 1200 =>
+ return;
+ * =>
+ if (baud == 9600 && can9600)
+ break;
+ fprint(stderr, "mouse: can't set baud rate, mouse at 1200\n");
+ return;
+ }
+ writes(data, "*q");
+ setupeia(ctl, "b9600", "l8");
+ slowread(data, array[32] of byte, 32, "setbaud: ");
+}
+
+readproc(fd: ref Sys->FD, buf: array of byte, n: int,
+ pidch: chan of int, ch: chan of (int, string))
+{
+ s: string;
+ pidch <-= sys->pctl(0, nil);
+ n = sys->read(fd, buf, n);
+ if (n < 0)
+ s = sys->sprint("read: %r");
+ ch <-= (n, s);
+}
+
+sleepproc(t: int, pidch: chan of int, ch: chan of (int, string))
+{
+ pidch <-= sys->pctl(0, nil);
+ sys->sleep(t);
+ ch <-= (-1, nil);
+}
+
+timedread(fd: ref Sys->FD, buf: array of byte, n: int, t: int): (int, string)
+{
+ pidch := chan of int;
+ retch := chan of (int, string);
+ spawn readproc(fd, buf, n, pidch, retch);
+ wpid := <-pidch;
+ spawn sleepproc(t, pidch, retch);
+ spid := <-pidch;
+
+ (nr, err) := <-retch;
+ if (nr == -1 && err == nil)
+ kill(wpid);
+ else
+ kill(spid);
+ return (nr, err);
+}
+
+kill(pid: int)
+{
+ if ((fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE)) == nil) {
+ fprint(stderr, "couldn't kill %d: %r\n", pid);
+ return;
+ }
+ sys->write(fd, array of byte "kill", 4);
+}
+
+writes(fd: ref Sys->FD, s: string): int
+{
+ a := array of byte s;
+ return write(fd, a, len a);
+}
+
diff --git a/appl/cmd/mpc/mkfile b/appl/cmd/mpc/mkfile
new file mode 100644
index 00000000..17d7ab37
--- /dev/null
+++ b/appl/cmd/mpc/mkfile
@@ -0,0 +1,14 @@
+<../../../mkconfig
+
+TARG=\
+ qconfig.dis\
+ qflash.dis\
+
+SYSMODULES=\
+ sys.m\
+ draw.m\
+ string.m\
+
+DISBIN=$ROOT/dis/mpc
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/mpc/qconfig.b b/appl/cmd/mpc/qconfig.b
new file mode 100644
index 00000000..dbff19b7
--- /dev/null
+++ b/appl/cmd/mpc/qconfig.b
@@ -0,0 +1,193 @@
+implement Configflash;
+
+#
+# this isn't a proper config program: it's currently just
+# enough to set important parameters such as ethernet address.
+# an extension is in the works.
+# --chf
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+Configflash: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+Region: adt {
+ base: int;
+ limit: int;
+};
+
+#
+# structure of allocation descriptor
+#
+Fcheck: con 0;
+Fbase: con 4;
+Flen: con 8;
+Ftag: con 11;
+Fsig: con 12;
+Fasize: con 3*4+3+1;
+
+Tdead: con byte 0;
+Tboot: con byte 16r01;
+Tconf: con byte 16r02;
+Tnone: con byte 16rFF;
+
+flashsig := array[] of {byte 16rF1, byte 16rA5, byte 16r5A, byte 16r1F};
+noval := array[] of {0 to 3 =>byte 16rFF}; #
+
+Ctag, Cscreen, Cconsole, Cbaud, Cether, Cea, Cend: con iota;
+config := array[] of {
+ Ctag => "#plan9.ini\n", # current flag for qboot, don't change
+ Cscreen => "vgasize=640x480x8\n",
+ Cconsole => "console=0 lcd\n",
+ Cbaud => "baud=9600\n",
+ Cether => "ether0=type=SCC port=2 ", # note missing \n
+ Cea => "ea=08003e400080\n",
+ Cend => "\0" # qboot currently requires it but shouldn't
+};
+
+Param: adt {
+ name: string;
+ index: int;
+};
+
+params := array[] of {
+ Param("vgasize", Cscreen),
+ Param("console", Cconsole),
+ Param("ea", Cea),
+ Param("baud", Cbaud)
+};
+
+# could come from file or #F/flash/flashctl
+FLASHSEG: con 256*1024;
+bootregion := Region(0, FLASHSEG);
+
+stderr: ref Sys->FD;
+prog := "qconfig";
+damaged := 0;
+debug := 0;
+
+usage()
+{
+ sys->fprint(stderr, "Usage: %s [-D] [-f flash] [-param value ...]\n", prog);
+ exit;
+}
+
+err(s: string)
+{
+ sys->fprint(stderr, "%s: %s", prog, s);
+ if(!damaged)
+ sys->fprint(stderr, "; flash not modified\n");
+ else
+ sys->fprint(stderr, "; flash might now be invalid\n");
+ exit;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ stderr = sys->fildes(2);
+ if(args != nil){
+ prog = hd args;
+ args = tl args;
+ }
+ str = load String String->PATH;
+ if(str == nil)
+ err(sys->sprint("can't load %s: %r", String->PATH));
+ flash := "#F/flash/flash";
+ offset := 0;
+ region := bootregion;
+
+ for(; args != nil && (hd args)[0] == '-'; args = tl args)
+ case a := hd args {
+ "-f" =>
+ (flash, args) = argf(tl args);
+ "-D" =>
+ debug = 1;
+ * =>
+ p := lookparam(params, a[1:]);
+ if(p.index < 0)
+ err(sys->sprint("unknown config parameter: %s", a));
+ v: string;
+ (v, args) = argf(tl args);
+ config[p.index] = a[1:]+"="+v+"\n"; # would be nice to check it
+ }
+ if(len args > 0)
+ usage();
+ out := sys->open(flash, Sys->ORDWR);
+ if(out == nil)
+ err(sys->sprint("can't open %s for read/write: %r", flash));
+ # TO DO: hunt for free space and add new entry
+ plonk(out, FLASHSEG-Fasize, mkdesc(0, 128*1024, Tboot));
+ c := flatten(config);
+ if(debug)
+ sys->print("%s", c);
+ bconf := array of byte c;
+ plonk(out, FLASHSEG-Fasize*2, mkdesc(128*1024, len bconf, Tconf));
+ plonk(out, 128*1024, bconf);
+}
+
+argf(args: list of string): (string, list of string)
+{
+ if(args == nil)
+ usage();
+ return (hd args, args);
+}
+
+lookparam(options: array of Param, s: string): Param
+{
+ for(i := 0; i < len options; i++)
+ if(options[i].name == s)
+ return options[i];
+ return Param(nil, -1);
+}
+
+flatten(a: array of string): string
+{
+ s := "";
+ for(i := 0; i < len a; i++)
+ s += a[i];
+ return s;
+}
+
+plonk(out: ref Sys->FD, where: int, val: array of byte)
+{
+ if(debug){
+ sys->print("write #%ux [%d]:", where, len val);
+ for(i:=0; i<len val; i++)
+ sys->print(" %.2ux", int val[i]);
+ sys->print("\n");
+ }
+ sys->seek(out, big where, 0);
+ if(sys->write(out, val, len val) != len val)
+ err(sys->sprint("bad flash write: %r"));
+}
+
+cvt(v: int): array of byte
+{
+ a := array[4] of byte;
+ a[0] = byte (v>>24);
+ a[1] = byte (v>>16);
+ a[2] = byte (v>>8);
+ a[3] = byte (v & 16rff);
+ return a;
+}
+
+mkdesc(base: int, length: int, tag: byte): array of byte
+{
+ a := array[Fasize] of byte;
+ a[Fcheck:] = noval;
+ a[Fbase:] = cvt(base);
+ a[Flen:] = cvt(length)[1:]; # it's three bytes
+ a[Ftag] = tag;
+ a[Fsig:] = flashsig;
+ return a;
+}
diff --git a/appl/cmd/mpc/qflash.b b/appl/cmd/mpc/qflash.b
new file mode 100644
index 00000000..13c77ce8
--- /dev/null
+++ b/appl/cmd/mpc/qflash.b
@@ -0,0 +1,188 @@
+implement Writeflash;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+Writeflash: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+Region: adt {
+ base: int;
+ limit: int;
+};
+
+# could come from file or #F/flash/flashctl
+FLASHSEG: con 256*1024;
+kernelregion := Region(FLASHSEG, FLASHSEG+2*FLASHSEG);
+bootregion := Region(0, FLASHSEG);
+
+stderr: ref Sys->FD;
+prog := "qflash";
+damaged := 0;
+
+usage()
+{
+ sys->fprint(stderr, "Usage: %s [-b] [-o offset] [-f flashdev] file\n", prog);
+ exit;
+}
+
+err(s: string)
+{
+ sys->fprint(stderr, "%s: %s", prog, s);
+ if(!damaged)
+ sys->fprint(stderr, "; flash not modified\n");
+ else
+ sys->fprint(stderr, "; flash might now be invalid\n");
+ exit;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ stderr = sys->fildes(2);
+ if(args != nil){
+ prog = hd args;
+ args = tl args;
+ }
+ str = load String String->PATH;
+ if(str == nil)
+ err(sys->sprint("can't load %s: %r", String->PATH));
+ region := kernelregion;
+ flash := "#F/flash/flash";
+ offset := 0;
+ save := 0;
+
+ for(; args != nil && (hd args)[0] == '-'; args = tl args)
+ case hd args {
+ "-b" =>
+ region = bootregion;
+ offset = 16r100 - 8*4; # size of exec header
+ save = 1;
+ "-h" =>
+ region.limit += FLASHSEG;
+ "-f" =>
+ if(tl args == nil)
+ usage();
+ flash = hd args;
+ args = tl args;
+ "-o" =>
+ if(tl args == nil)
+ usage();
+ args = tl args;
+ s := hd args;
+ v: int;
+ rs: string;
+ if(str->prefix("16r", s))
+ (v, rs) = str->toint(s[3:], 16);
+ else if(str->prefix("0x", s))
+ (v, rs) = str->toint(s[2:], 16);
+ else if(str->prefix("0", s))
+ (v, rs) = str->toint(s[1:], 8);
+ else
+ (v, rs) = str->toint(s, 10);
+ if(v < 0 || len rs != 0)
+ err(sys->sprint("bad offset: %s", s));
+ offset = v;
+ "-s" =>
+ save = 1;
+ * =>
+ usage();
+ }
+ if(args == nil)
+ usage();
+ fname := hd args;
+ fd := sys->open(fname, Sys->OREAD);
+ if(fd == nil)
+ err(sys->sprint("can't open %s: %r", fname));
+ (r, dir) := sys->fstat(fd);
+ if(r < 0)
+ err(sys->sprint("can't stat %s: %r", fname));
+ length := int dir.length;
+ avail := region.limit - (region.base+offset);
+ if(length > avail)
+ err(sys->sprint("%s contents %ud bytes, exceeds flash region %ud bytes", fname, length, avail));
+ # check fname's contents...
+ where := region.base+offset;
+ saved: list of (int, array of byte);
+ if(save){
+ saved = saveflash(flash, region.base, where) :: saved;
+ saved = saveflash(flash, where+length, region.limit) :: saved;
+ }
+ for(i := (region.base+offset)/FLASHSEG; i < region.limit/FLASHSEG; i++)
+ erase(flash, i);
+ out := sys->open(flash, Sys->OWRITE);
+ if(out == nil)
+ err(sys->sprint("can't open %s for writing: %r", flash));
+ if(sys->seek(out, big where, 0) != big where)
+ err(sys->sprint("can't seek to #%6.6ux on flash: %r", where));
+ if(length)
+ sys->print("writing %ud bytes to %s at #%6.6ux\n", length, flash, where);
+ buf := array[Sys->ATOMICIO] of byte;
+ total := 0;
+ while((n := sys->read(fd, buf, len buf)) > 0) {
+ if(total+n > avail)
+ err(sys->sprint("file %s too big for region of %ud bytes", fname, avail));
+ r = sys->write(out, buf, n);
+ damaged = 1;
+ if(r != n){
+ if(r < 0)
+ err(sys->sprint("error writing %s at byte %ud: %r", flash, total));
+ else
+ err(sys->sprint("short write on %s at byte %ud", flash, total));
+ }
+ total += n;
+ }
+ if(n < 0)
+ err(sys->sprint("error reading %s: %r", fname));
+ sys->print("wrote %ud bytes from %s to flash %s (#%6.6ux-#%6.6ux)\n", total, fname, flash, region.base, region.base+total);
+ for(l := saved; l != nil; l = tl l){
+ (addr, data) := hd l;
+ n = len data;
+ if(n == 0)
+ continue;
+ sys->print("restoring %ud bytes at #%6.6ux\n", n, addr);
+ if(sys->seek(out, big addr, 0) != big addr)
+ err(sys->sprint("can't seek to #%6.6ux on %s: %r", addr, flash));
+ r = sys->write(out, data, n);
+ if(r < 0)
+ err(sys->sprint("error writing %s: %r", flash));
+ else if(r != n)
+ err(sys->sprint("short write on %s at byte %ud/%ud", flash, r, n));
+ else
+ sys->print("restored %ud bytes at #%6.6ux\n", n, addr);
+ }
+}
+
+erase(flash: string, seg: int)
+{
+ ctl := sys->open(flash+"ctl", Sys->OWRITE);
+ if(ctl == nil)
+ err(sys->sprint("can't open %sctl: %r\n", flash));
+ if(sys->fprint(ctl, "erase %ud", seg*FLASHSEG) < 0)
+ err(sys->sprint("can't erase flash %s segment %d: %r\n", flash, seg));
+}
+
+saveflash(flash: string, base: int, limit: int): (int, array of byte)
+{
+ fd := sys->open(flash, Sys->OREAD);
+ if(fd == nil)
+ err(sys->sprint("can't open %s for reading: %r", flash));
+ nb := limit - base;
+ if(nb <= 0)
+ return (base, nil);
+ if(sys->seek(fd, big base, 0) != big base)
+ err(sys->sprint("can't seek to #%6.6ux to save flash contents: %r", base));
+ saved := array[nb] of byte;
+ if(sys->read(fd, saved, len saved) != len saved)
+ err(sys->sprint("can't read flash #%6.6ux to #%6.6ux: %r", base, limit));
+ sys->print("saved %ud bytes at #%6.6ux\n", len saved, base);
+ return (base, saved);
+}
diff --git a/appl/cmd/mprof.b b/appl/cmd/mprof.b
new file mode 100644
index 00000000..1722d50a
--- /dev/null
+++ b/appl/cmd/mprof.b
@@ -0,0 +1,260 @@
+implement Prof;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+ arg: Arg;
+include "profile.m";
+ profile: Profile;
+include "sh.m";
+
+stderr: ref Sys->FD;
+
+Prof: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+ init0: fn(nil: ref Draw->Context, argv: list of string): Profile->Prof;
+};
+
+ignored(s: string)
+{
+ sys->fprint(stderr, "mprof: warning: %s ignored\n", s);
+}
+
+exits(e: string)
+{
+ if(profile != nil)
+ profile->end();
+ raise "fail:" + e;
+}
+
+pfatal(s: string)
+{
+ sys->fprint(stderr, "mprof: %s: %s\n", s, profile->lasterror());
+ exits("error");
+}
+
+badmodule(p: string)
+{
+ sys->fprint(stderr, "mprof: cannot load %s: %r\n", p);
+ exits("bad module");
+}
+
+usage(s: string)
+{
+ sys->fprint(stderr, "mprof: %s\n", s);
+ sys->fprint(stderr, "usage: mprof [-bcMflnve] [-m modname]... [cmd arg ...]");
+ exits("usage");
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ init0(ctxt, argv);
+}
+
+init0(ctxt: ref Draw->Context, argv: list of string): Profile->Prof
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ badmodule(Arg->PATH);
+ arg->init(argv);
+ profile = load Profile Profile->PATH;
+ if(profile == nil)
+ badmodule(Profile->PATH);
+ if(profile->init() < 0)
+ pfatal("cannot initialize profile device");
+
+ v := 0;
+ begin := end := 0;
+ ep := 0;
+ wm := 0;
+ mem := 0;
+ exec, mods: list of string;
+ while((c := arg->opt()) != 0){
+ case c {
+ 'b' => begin = 1;
+ 'c' => end = 1;
+ 'M' => v |= profile->MODULE;
+ 'f' => v |= profile->FUNCTION;
+ 'l' => v |= profile->LINE;
+ 'n' => v |= profile->FULLHDR;
+ 'v' => v |= profile->VERBOSE;
+ 'm' =>
+ if((s := arg->arg()) == nil)
+ usage("missing module name");
+ mods = s :: mods;
+ 'e' =>
+ ep = 1;
+ 'g' =>
+ wm = 1;
+ '1' =>
+ mem |= Profile->MAIN;
+ '2' =>
+ mem |= Profile->HEAP;
+ '3' =>
+ mem |= Profile->IMAGE;
+ * =>
+ usage(sys->sprint("unknown option -%c", c));
+ }
+ }
+
+ exec = arg->argv();
+
+ if(begin && end)
+ ignored("-e option");
+ if((begin || end) && v != 0)
+ ignored("output format");
+ if(begin && exec != nil)
+ begin = 0;
+ if(begin == 0 && exec == nil){
+ if(mods != nil)
+ ignored("-m option");
+ mods = nil;
+ }
+ if(end){
+ if(mods != nil)
+ ignored("-m option");
+ if(ep || exec != nil)
+ ignored("command");
+ profile->end();
+ exit;
+ }
+
+ for( ; mods != nil; mods = tl mods)
+ profile->profile(hd mods);
+
+ if(begin){
+ if(profile->memstart(mem) < 0)
+ pfatal("cannot start profiling");
+ exit;
+ }
+ r := 0;
+ if(exec != nil){
+ if(ep)
+ profile->profile(disname(hd exec));
+ if(profile->memstart(mem) < 0)
+ pfatal("cannot start profiling");
+ # r = run(ctxt, hd exec, exec);
+ wfd := openwait(sys->pctl(0, nil));
+ ci := chan of int;
+ spawn execute(ctxt, hd exec, exec, ci);
+ epid := <- ci;
+ wait(wfd, epid);
+ }
+ if(profile->stop() < 0)
+ pfatal("cannot stop profiling");
+ if(exec == nil || r >= 0){
+ modl := profile->memstats();
+ if(modl.mods == nil)
+ pfatal("no profile information");
+ if(wm){
+ if(exec == nil){
+ if(profile->memstart(mem) < 0)
+ pfatal("cannot restart profiling");
+ }
+ else
+ profile->end();
+ return modl;
+ }
+ if(!(v&(profile->MODULE|profile->FUNCTION|profile->LINE)))
+ v |= profile->MODULE|profile->LINE;
+ if(profile->memshow(modl, v) < 0)
+ pfatal("cannot show profile");
+ if(exec == nil){
+ if(profile->memstart(mem) < 0)
+ pfatal("cannot restart profiling");
+ exit;
+ }
+ }
+ profile->end();
+ return (nil, 0, nil);
+}
+
+disname(cmd: string): string
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+ if(exists(file))
+ return file;
+ if(file[0]!='/' && file[0:2]!="./")
+ file = "/dis/"+file;
+ # if(exists(file))
+ # return file;
+ return file;
+}
+
+execute(ctxt: ref Draw->Context, cmd : string, argl : list of string, ci: chan of int)
+{
+ ci <-= sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: stderr.fd :: nil);
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+ c := load Command file;
+ if(c == nil) {
+ err := sys->sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ if(c == nil){
+ sys->fprint(stderr, "mprof: %s: %s\n", cmd, err);
+ return;
+ }
+ }
+ c->init(ctxt, argl);
+}
+
+# run(ctxt: ref Draw->Context, cmd : string, argl : list of string): int
+# {
+# file := cmd;
+# if(len file<4 || file[len file-4:]!=".dis")
+# file += ".dis";
+# c := load Command file;
+# if(c == nil) {
+# err := sys->sprint("%r");
+# if(file[0]!='/' && file[0:2]!="./"){
+# c = load Command "/dis/"+file;
+# if(c == nil)
+# err = sys->sprint("%r");
+# }
+# if(c == nil){
+# sys->fprint(stderr, "mprof: %s: %s\n", cmd, err);
+# return -1;
+# }
+# }
+# c->init(ctxt, argl);
+# return 0;
+# }
+
+openwait(pid : int) : ref Sys->FD
+{
+ w := sys->sprint("#p/%d/wait", pid);
+ fd := sys->open(w, Sys->OREAD);
+ if (fd == nil)
+ pfatal("fd == nil in wait");
+ return fd;
+}
+
+wait(wfd : ref Sys->FD, wpid : int)
+{
+ n : int;
+
+ buf := array[Sys->WAITLEN] of byte;
+ status := "";
+ for(;;) {
+ if ((n = sys->read(wfd, buf, len buf)) < 0)
+ pfatal("bad read in wait");
+ status = string buf[0:n];
+ if (int status == wpid)
+ break;
+ }
+}
+
+exists(f: string): int
+{
+ return sys->open(f, Sys->OREAD) != nil;
+}
diff --git a/appl/cmd/mv.b b/appl/cmd/mv.b
new file mode 100644
index 00000000..2ca8e671
--- /dev/null
+++ b/appl/cmd/mv.b
@@ -0,0 +1,184 @@
+implement Mv;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+
+include "draw.m";
+ draw: Draw;
+
+include "string.m";
+ str: String;
+
+
+Mv: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ str = load String String->PATH;
+ if(str == nil) {
+ sys->fprint(stderr, "mv: can't load %s: %r\n", String->PATH);
+ raise "fail:load";
+ }
+
+ dirto, dirfrom: Sys->Dir;
+ todir, toelem: string;
+ if(len argv<3) {
+ sys->fprint(stderr, "usage: mv fromfile tofile\n");
+ sys->fprint(stderr, " mv fromfile ... todir\n");
+ raise "fail:usage";
+ }
+ argv = tl argv;
+ arr := array[len argv] of string;
+ for (i:=0; argv!=nil;i++){
+ arr[i]= hd argv;
+ argv = tl argv;
+ }
+ (i,dirto)=sys->stat(arr[len arr-1]);
+ if(i >= 0 && (dirto.mode&Sys->DMDIR)){
+ (i,dirfrom)=sys->stat(arr[0]);
+ if(len arr == 2 && i >= 0 && (dirfrom.mode&Sys->DMDIR))
+ (todir,toelem)=split(arr[len arr-1]);
+ else{
+ todir = arr[len arr -1];
+ toelem = ""; # toelem will be fromelem
+ }
+ }else
+ (todir,toelem)=split(arr[len arr-1]);
+ if(len arr > 2 && toelem != nil) {
+ sys->fprint(stderr, "mv: %s not a directory\n", arr[len arr-1]);
+ raise "fail:error";
+ }
+ failed := 0;
+ for(i=0; i < len arr-1; i++)
+ if (mv(arr[i], todir, toelem) < 0)
+ failed++;
+ if(failed)
+ raise "fail:error";
+}
+
+mv(from,todir,toelem : string): int
+{
+ (i,dirb):=sys->stat(from);
+ if(i != 0) {
+ sys->fprint(stderr, "mv: can't stat %s: %r\n", from);
+ return -1;
+ }
+ (fromdir,fromelem):=split(from);
+ fromname:= fromdir+fromelem;
+ if(toelem == nil){
+ if (todir[len todir-1]!='/')
+ todir[len todir]='/';
+ toelem = fromelem;
+ }
+ i = len toelem;
+ if(i==0){
+ sys->fprint(stderr, "mv: null last name element moving %s\n", fromname);
+ return -1;
+ }
+ toname:=todir+toelem;
+ if(samefile(fromdir, todir)){
+ if(samefile(fromname, toname)){
+ sys->fprint(stderr, "mv: %s and %s are the same\n", fromname, toname);
+ return -1;
+ }
+ (j,dirt):=sys->stat(toname);
+ if( (j == 0) && (dirb.mode&Sys->DMDIR) ){
+ sys->fprint(stderr, "mv: can't rename a directory to an existing name\n");
+ return -1;
+ }
+ if(j == 0)
+ hardremove(toname);
+ dirt = sys->nulldir;
+ dirt.name=toelem;
+ if(sys->wstat(fromname,dirt) >= 0)
+ return 0;
+ if(dirb.mode&Sys->DMDIR){
+ sys->fprint(stderr, "mv: can't rename directory %s: %r\n", fromname);
+ return -1;
+ }
+ }
+ # Renaming won't work --- have to copy
+ if(dirb.mode&Sys->DMDIR){
+ sys->fprint(stderr, "mv: %s is a directory, not copied to %s\n", fromname, toname);
+ return -1;
+ }
+ fdf := sys->open(fromname, Sys->OREAD);
+ if(fdf==nil){
+ sys->fprint(stderr, "mv: can't open %s: %r\n", fromname);
+ return -1;
+ }
+ (j,dirt):=sys->stat(toname);
+ fdt := sys->create(toname, Sys->OWRITE, dirb.mode);
+ if(fdt == nil){
+ sys->fprint(stderr, "mv: can't create %s: %r\n", toname);
+ return -1;
+ }
+ if ((stat := copy1(fdf, fdt, fromname, toname)) != -1)
+ fdf = nil; # temp bug: sometimes can't remove open file
+ if (sys->remove(fromname) < 0) {
+ sys->fprint(stderr, "mv: can't remove %s: %r\n", fromname);
+ return -1;
+ }
+ return stat;
+}
+
+
+copy1(fdf, fdt : ref Sys->FD,from, fto : string): int
+{
+ n : int;
+ buf:=array[Sys->ATOMICIO] of byte;
+ for(;;) {
+ n = sys->read(fdf, buf, len buf);
+ if (n<=0)
+ break;
+ n1 := sys->write(fdt, buf, n);
+ if(n1 != n) {
+ sys->fprint(stderr, "mv: error writing %s: %r\n", fto);
+ return -1;
+ }
+ }
+ if(n < 0) {
+ sys->fprint(stderr, "mv: error reading %s: %r\n", from);
+ return -1;
+ }
+ return 0;
+}
+
+split(name : string): (string,string)
+{
+ (d,t) := str->splitr(name, "/");
+ if(d!=nil)
+ return(d,t);
+ else if(name=="..")
+ return("../",".");
+ else
+ return("./",name);
+}
+
+samefile(a,b : string): int
+{
+ if(a==b)
+ return 1;
+ (i,da):=sys->stat(a);
+ (j,db):=sys->stat(b);
+ if(i < 0 || j < 0)
+ return 0;
+ i= (da.qid.path==db.qid.path && da.qid.vers==db.qid.vers &&
+ da.dev==db.dev && da.dtype==db.dtype);
+ return i;
+}
+
+hardremove(a: string)
+{
+ if(sys->remove(a) == -1){
+ sys->fprint(stderr, "mv: can't remove %s: %r\n", a);
+ raise "fail:mv";
+ }
+ do; while(sys->remove(a) != -1);
+}
diff --git a/appl/cmd/ndb/cs.b b/appl/cmd/ndb/cs.b
new file mode 100644
index 00000000..1506e9ba
--- /dev/null
+++ b/appl/cmd/ndb/cs.b
@@ -0,0 +1,676 @@
+implement Cs;
+
+#
+# Connection server translates net!machine!service into
+# /net/tcp/clone 135.104.9.53!564
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "srv.m";
+ srv: Srv;
+
+include "bufio.m";
+include "attrdb.m";
+ attrdb: Attrdb;
+ Attr, Db, Dbentry, Tuples: import attrdb;
+
+include "ip.m";
+ ip: IP;
+include "ipattr.m";
+ ipattr: IPattr;
+
+include "arg.m";
+
+Cs: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# signature of dial-on-demand module
+CSdial: module
+{
+ init: fn(nil: ref Draw->Context): string;
+ connect: fn(): string;
+};
+
+Reply: adt
+{
+ fid: int;
+ pid: int;
+ addrs: list of string;
+ err: string;
+};
+
+Cached: adt
+{
+ expire: int;
+ query: string;
+ addrs: list of string;
+};
+
+Ncache: con 16;
+cache:= array[Ncache] of ref Cached;
+nextcache := 0;
+
+rlist: list of ref Reply;
+
+ndbfile := "/lib/ndb/local";
+ndb: ref Db;
+mntpt := "/net";
+myname: string;
+
+stderr: ref Sys->FD;
+
+verbose := 0;
+dialmod: CSdial;
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ cantload(Attrdb->PATH);
+ attrdb->init();
+ ip = load IP IP->PATH;
+ if(ip == nil)
+ cantload(IP->PATH);
+ ip->init();
+ ipattr = load IPattr IPattr->PATH;
+ if(ipattr == nil)
+ cantload(IPattr->PATH);
+ ipattr->init(attrdb, ip);
+
+ svcname := "#scs";
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ cantload(Arg->PATH);
+ arg->init(args);
+ arg->setusage("cs [-v] [-x mntpt] [-f database] [-d dialmod]");
+ while((c := arg->opt()) != 0)
+ case c {
+ 'v' or 'D' =>
+ verbose++;
+ 'd' => # undocumented hack to replace svc/cs/cs
+ f := arg->arg();
+ if(f != nil){
+ dialmod = load CSdial f;
+ if(dialmod == nil)
+ cantload(f);
+ }
+ 'f' =>
+ ndbfile = arg->earg();
+ 'x' =>
+ mntpt = arg->earg();
+ svcname = "#scs"+svcpt(mntpt);
+ * =>
+ arg->usage();
+ }
+
+ if(arg->argv() != nil)
+ arg->usage();
+ arg = nil;
+
+ srv = load Srv Srv->PATH; # hosted Inferno only
+ if(srv != nil)
+ srv->init();
+
+ sys->remove(svcname+"/cs");
+ sys->unmount(svcname, mntpt);
+ publish(svcname);
+ if(sys->bind(svcname, mntpt, Sys->MBEFORE) < 0)
+ error(sys->sprint("can't bind #s on %s: %r", mntpt));
+ file := sys->file2chan(mntpt, "cs");
+ if(file == nil)
+ error(sys->sprint("can't make %s/cs: %r", mntpt));
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ refresh();
+ if(dialmod != nil){
+ e := dialmod->init(ctxt);
+ if(e != nil)
+ error(sys->sprint("can't initialise dial-on-demand: %s", e));
+ }
+ spawn cs(file);
+}
+
+svcpt(s: string): string
+{
+ for(i:=0; i<len s; i++)
+ if(s[i] == '/')
+ s[i] = '_';
+ return s;
+}
+
+publish(dir: string)
+{
+ d := Sys->nulldir;
+ d.mode = 8r777;
+ if(sys->wstat(dir, d) < 0)
+ sys->fprint(sys->fildes(2), "cs: can't publish %s: %r\n", dir);
+}
+
+cantload(m: string)
+{
+ error(sys->sprint("cannot load %s: %r", m));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "cs: %s\n", s);
+ raise "fail:error";
+}
+
+refresh()
+{
+ myname = sysname();
+ if(ndb == nil){
+ ndb2 := Db.open(ndbfile);
+ if(ndb2 == nil){
+ err := sys->sprint("%r");
+ ndb2 = Db.open("/lib/ndb/inferno"); # try to get service map at least
+ if(ndb2 == nil)
+ sys->fprint(sys->fildes(2), "cs: warning: can't open %s: %s\n", ndbfile, err); # continue without it
+ }
+ ndb = Db.open(mntpt+"/ndb");
+ if(ndb != nil)
+ ndb = ndb.append(ndb2);
+ else
+ ndb = ndb2;
+ }else
+ ndb.reopen();
+}
+
+sysname(): string
+{
+ t := rf("/dev/sysname");
+ if(t != nil)
+ return t;
+ t = rf("#e/sysname");
+ if(t == nil){
+ s := rf(mntpt+"/ndb");
+ if(s != nil){
+ db := Db.sopen(t);
+ if(db != nil){
+ (e, nil) := db.find(nil, "sys");
+ if(e != nil)
+ t = e.findfirst("sys");
+ }
+ }
+ }
+ if(t != nil){
+ fd := sys->open("/dev/sysname", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "%s", t);
+ }
+ return t;
+}
+
+rf(name: string): string
+{
+ fd := sys->open(name, Sys->OREAD);
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ return nil;
+ return string buf[0:n];
+}
+
+cs(file: ref Sys->FileIO)
+{
+ pidc := chan of int;
+ donec := chan of ref Reply;
+ for (;;) {
+ alt {
+ (nil, buf, fid, wc) := <-file.write =>
+ cleanfid(fid); # each write cancels previous requests
+ if(dialmod != nil){
+ e := dialmod->connect();
+ if(e != nil){
+ if(len e > 5 && e[0:5]=="fail:")
+ e = e[5:];
+ if(e == "")
+ e = "unknown error";
+ wc <-= (0, "cs: dial on demand: "+e);
+ break;
+ }
+ }
+ if(wc != nil){
+ nbytes := len buf;
+ query := string buf;
+ if(query == "refresh"){
+ refresh();
+ wc <-= (nbytes, nil);
+ break;
+ }
+ now := time();
+ r := ref Reply;
+ r.fid = fid;
+ spawn request(r, query, nbytes, now, wc, pidc, donec);
+ r.pid = <-pidc;
+ rlist = r :: rlist;
+ }
+
+ (off, nbytes, fid, rc) := <-file.read =>
+ if(rc != nil){
+ r := findfid(fid);
+ if(r != nil)
+ reply(r, off, nbytes, rc);
+ else
+ rc <-= (nil, "unknown request");
+ } else
+ ; # cleanfid(fid); # compensate for csendq in file2chan
+
+ r := <-donec =>
+ r.pid = 0;
+ }
+ }
+}
+
+findfid(fid: int): ref Reply
+{
+ for(rl := rlist; rl != nil; rl = tl rl){
+ r := hd rl;
+ if(r.fid == fid)
+ return r;
+ }
+ return nil;
+}
+
+cleanfid(fid: int)
+{
+ rl := rlist;
+ rlist = nil;
+ for(; rl != nil; rl = tl rl){
+ r := hd rl;
+ if(r.fid != fid)
+ rlist = r :: rlist;
+ else
+ killgrp(r.pid);
+ }
+}
+
+killgrp(pid: int)
+{
+ if(pid != 0){
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "killgrp") < 0)
+ sys->fprint(stderr, "cs: can't killgrp %d: %r\n", pid);
+ }
+}
+
+request(r: ref Reply, query: string, nbytes: int, now: int, wc: chan of (int, string), pidc: chan of int, donec: chan of ref Reply)
+{
+ pidc <-= sys->pctl(Sys->NEWPGRP, nil);
+ if(query != nil && query[0] == '!'){
+ # general query
+ (r.addrs, r.err) = genquery(query[1:]);
+ }else{
+ (r.addrs, r.err) = xlate(query, now);
+ if(r.addrs == nil && r.err == nil)
+ r.err = "cs: can't translate address";
+ }
+ if(r.err != nil){
+ if(verbose)
+ sys->fprint(stderr, "cs: %s: %s\n", query, r.err);
+ wc <-= (0, r.err);
+ } else
+ wc <-= (nbytes, nil);
+ donec <-= r;
+}
+
+reply(r: ref Reply, off: int, nbytes: int, rc: chan of (array of byte, string))
+{
+ if(r.err != nil){
+ rc <-= (nil, r.err);
+ return;
+ }
+ addr: string = nil;
+ if(r.addrs != nil){
+ addr = hd r.addrs;
+ r.addrs = tl r.addrs;
+ }
+ off = 0; # this version ignores offset
+ rc <-= reads(addr, off, nbytes);
+}
+
+#
+# return the file2chan reply for a read of the given string
+#
+reads(str: string, off, nbytes: int): (array of byte, string)
+{
+ bstr := array of byte str;
+ slen := len bstr;
+ if(off < 0 || off >= slen)
+ return (nil, nil);
+ if(off + nbytes > slen)
+ nbytes = slen - off;
+ if(nbytes <= 0)
+ return (nil, nil);
+ return (bstr[off:off+nbytes], nil);
+}
+
+lookcache(query: string, now: int): ref Cached
+{
+ for(i:=0; i<len cache; i++){
+ c := cache[i];
+ if(c != nil && c.query == query && now < c.expire){
+ if(verbose)
+ sys->print("cache: %s -> %s\n", query, hd c.addrs);
+ return c;
+ }
+ }
+ return nil;
+}
+
+putcache(query: string, addrs: list of string, now: int)
+{
+ ce := ref Cached;
+ ce.expire = now+120;
+ ce.query = query;
+ ce.addrs = addrs;
+ cache[nextcache] = ce;
+ nextcache = (nextcache+1)%Ncache;
+}
+
+xlate(address: string, now: int): (list of string, string)
+{
+ n: int;
+ l, rl, results: list of string;
+ repl, netw, mach, service: string;
+
+ ce := lookcache(address, now);
+ if(ce != nil && ce.addrs != nil)
+ return (ce.addrs, nil);
+
+ (n, l) = sys->tokenize(address, "!\n");
+ if(n < 2)
+ return (nil, "bad format request");
+
+ netw = hd l;
+ if(netw == "net")
+ netw = "tcp"; # TO DO: better (needs lib/ndb)
+ if(!isnetwork(netw))
+ return (nil, "network unavailable "+netw);
+ l = tl l;
+
+ if(!isipnet(netw)) {
+ repl = mntpt + "/" + netw + "/clone ";
+ for(;;){
+ repl += hd l;
+ if((l = tl l) == nil)
+ break;
+ repl += "!";
+ }
+ return (repl :: nil, nil); # no need to cache
+ }
+
+ if(n != 3)
+ return (nil, "bad format request");
+ mach = hd l;
+ service = hd tl l;
+
+ if(!isnumeric(service)) {
+ s := xlatesvc(netw, service);
+ if(s == nil){
+ if(srv != nil)
+ s = srv->ipn2p(netw, service);
+ if(s == nil)
+ return (nil, "cs: can't translate service");
+ }
+ service = s;
+ }
+
+ attr := ipattr->dbattr(mach);
+ if(mach == "*")
+ l = "" :: nil;
+ else if(attr != "ip") {
+ # Symbolic server == "$SVC"
+ if(mach[0] == '$' && len mach > 1 && ndb != nil){
+ (s, nil) := ipattr->findnetattr(ndb, "sys", myname, mach[1:]);
+ if(s == nil){
+ names := dblook("infernosite", "", mach[1:]);
+ if(names == nil)
+ return (nil, "cs: can't translate "+mach);
+ s = hd names;
+ }
+ mach = s;
+ attr = ipattr->dbattr(mach);
+ }
+ if(attr == "sys"){
+ results = dblook("sys", mach, "ip");
+ if(results != nil)
+ attr = "ip";
+ }
+ if(attr != "ip"){
+ err: string;
+ (results, err) = querydns(mach, "ip");
+ if(err != nil)
+ return (nil, err);
+ }else if(results == nil)
+ results = mach :: nil;
+ l = results;
+ if(l == nil){
+ if(srv != nil)
+ l = srv->iph2a(mach);
+ if(l == nil)
+ return (nil, "cs: unknown host");
+ }
+ } else
+ l = mach :: nil;
+
+ while(l != nil) {
+ s := hd l;
+ l = tl l;
+ if(s != "")
+ s[len s] = '!';
+ s += service;
+
+ repl = mntpt+"/"+netw+"/clone "+s;
+ if(verbose)
+ sys->fprint(stderr, "cs: %s!%s!%s -> %s\n", netw, mach, service, repl);
+
+ rl = repl :: rl;
+ }
+ rl = reverse(rl);
+ putcache(address, rl, now);
+ return (rl, nil);
+}
+
+querydns(name: string, rtype: string): (list of string, string)
+{
+ fd := sys->open(mntpt+"/dns", Sys->ORDWR);
+ if(fd == nil)
+ return (nil, nil);
+ if(sys->fprint(fd, "%s %s", name, rtype) < 0)
+ return (nil, sys->sprint("%r"));
+ rl: list of string;
+ buf := array[256] of byte;
+ sys->seek(fd, big 0, 0);
+ while((n := sys->read(fd, buf, len buf)) > 0){
+ # name rtype value
+ (nf, fld) := sys->tokenize(string buf[0:n], " \t");
+ if(nf != 3){
+ sys->fprint(stderr, "cs: odd result from dns: %s\n", string buf[0:n]);
+ continue;
+ }
+ rl = hd tl tl fld :: rl;
+ }
+ return (reverse(rl), nil);
+}
+
+dblook(attr: string, val: string, rattr: string): list of string
+{
+ rl: list of string;
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ (e, ptr) = ndb.findbyattr(ptr, attr, val, rattr);
+ if(e == nil)
+ break;
+ for(l := e.findbyattr(attr, val, rattr); l != nil; l = tl l){
+ (nil, al) := hd l;
+ for(; al != nil; al = tl al)
+ if(!inlist((hd al).val, rl))
+ rl = (hd al).val :: rl;
+ }
+ }
+ return reverse(rl);
+}
+
+inlist(s: string, l: list of string): int
+{
+ for(; l != nil; l = tl l)
+ if(hd l == s)
+ return 1;
+ return 0;
+}
+
+reverse(l: list of string): list of string
+{
+ t: list of string;
+ for(; l != nil; l = tl l)
+ t = hd l :: t;
+ return t;
+}
+
+isnumeric(a: string): int
+{
+ i, c: int;
+
+ for(i = 0; i < len a; i++) {
+ c = a[i];
+ if(c < '0' || c > '9')
+ return 0;
+ }
+ return 1;
+}
+
+nets: list of string;
+
+isnetwork(s: string) : int
+{
+ if(find(s, nets))
+ return 1;
+ (ok, nil) := sys->stat(mntpt+"/"+s+"/clone");
+ if(ok >= 0) {
+ nets = s :: nets;
+ return 1;
+ }
+ return 0;
+}
+
+find(e: string, l: list of string) : int
+{
+ for(; l != nil; l = tl l)
+ if (e == hd l)
+ return 1;
+ return 0;
+}
+
+isipnet(s: string) : int
+{
+ return s == "net" || s == "tcp" || s == "udp" || s == "il";
+}
+
+xlatesvc(proto: string, s: string): string
+{
+ if(ndb == nil || s == nil || isnumeric(s))
+ return s;
+ (e, nil) := ndb.findbyattr(nil, proto, s, "port");
+ if(e == nil)
+ return nil;
+ matches := e.findbyattr(proto, s, "port");
+ if(matches == nil)
+ return nil;
+ (ts, al) := hd matches;
+ restricted := "";
+ if(ts.hasattr("restricted"))
+ restricted = "!r";
+ if(verbose > 1)
+ sys->print("%s=%q port=%s%s\n", proto, s, (hd al).val, restricted);
+ return (hd al).val+restricted;
+}
+
+time(): int
+{
+ timefd := sys->open("/dev/time", Sys->OREAD);
+ if(timefd == nil)
+ return 0;
+ buf := array[128] of byte;
+ sys->seek(timefd, big 0, 0);
+ n := sys->read(timefd, buf, len buf);
+ if(n < 0)
+ return 0;
+ return int ((big string buf[0:n]) / big 1000000);
+}
+
+#
+# general query: attr1=val1 attr2=val2 ... finds matching tuple(s)
+# where attr1 is the key and val1 can't be *
+#
+genquery(query: string): (list of string, string)
+{
+ (tups, err) := attrdb->parseline(query, 0);
+ if(err != nil)
+ return (nil, "bad query: "+err);
+ if(tups == nil)
+ return (nil, "bad query");
+ pairs := tups.pairs;
+ a0 := (hd pairs).attr;
+ if(a0 == "ipinfo")
+ return (nil, "ipinfo not yet supported");
+ v0 := (hd pairs).val;
+
+ # if((a0 == "dom" || a0 == "ip") && v0 != nil){
+ # query dns ...
+ # }
+
+ ptr: ref Attrdb->Dbptr;
+ e: ref Dbentry;
+ for(;;){
+ (e, ptr) = ndb.findpair(ptr, a0, v0);
+ if(e == nil)
+ break;
+ for(l := e.lines; l != nil; l = tl l)
+ if(qmatch(hd l, tl pairs)){
+ ls: list of string;
+ for(l = e.lines; l != nil; l = tl l)
+ ls = tuptext(hd l) :: ls;
+ return (reverse(ls), nil);
+ }
+ }
+ return (nil, "no match");
+}
+
+#
+# see if set of tuples t contains every non-* attr/val pair
+#
+qmatch(t: ref Tuples, av: list of ref Attr): int
+{
+Match:
+ for(; av != nil; av = tl av){
+ a := hd av;
+ for(pl := t.pairs; pl != nil; pl = tl pl)
+ if((hd pl).attr == a.attr &&
+ (a.val == "*" || a.val == (hd pl).val))
+ continue Match;
+ return 0;
+ }
+ return 1;
+}
+
+tuptext(t: ref Tuples): string
+{
+ s: string;
+ for(pl := t.pairs; pl != nil; pl = tl pl){
+ p := hd pl;
+ if(s != nil)
+ s[len s] = ' ';
+ s += sys->sprint("%s=%q", p.attr, p.val);
+ }
+ return s;
+}
diff --git a/appl/cmd/ndb/csquery.b b/appl/cmd/ndb/csquery.b
new file mode 100644
index 00000000..61690617
--- /dev/null
+++ b/appl/cmd/ndb/csquery.b
@@ -0,0 +1,97 @@
+implement Csquery;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+
+Csquery: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: csquery [-x /net] [-s server] [address ...]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ cantload(Bufio->PATH);
+
+ net := "/net";
+ server: string;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ cantload(Arg->PATH);
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'x' =>
+ net = arg->arg();
+ if(net == nil)
+ usage();
+ 's' =>
+ server = arg->arg();
+ if(server == nil)
+ usage();
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(server == nil)
+ server = net+"/cs";
+ if(args != nil){
+ for(; args != nil; args = tl args)
+ csquery(server, hd args);
+ }else{
+ f := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ if(f == nil)
+ exit;
+ for(;;){
+ sys->print("> ");
+ s := f.gets('\n');
+ if(s == nil)
+ break;
+ csquery(server, s[0:len s-1]);
+ }
+ }
+}
+
+cantload(s: string)
+{
+ sys->fprint(sys->fildes(2), "csquery: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+csquery(server: string, addr: string)
+{
+ cs := sys->open(server, Sys->ORDWR);
+ if(cs == nil){
+ sys->fprint(sys->fildes(2), "csquery: can't open %s: %r\n", server);
+ raise "fail:open";
+ }
+ stdout := sys->fildes(1);
+ b := array of byte addr;
+ if(sys->write(cs, b, len b) > 0){
+ sys->seek(cs, big 0, Sys->SEEKSTART);
+ buf := array[256] of byte;
+ while((n := sys->read(cs, buf, len buf)) > 0)
+ sys->print("%s\n", string buf[0:n]);
+ if(n == 0)
+ return;
+ }
+ sys->print("%s: %r\n", addr);
+}
diff --git a/appl/cmd/ndb/dns.b b/appl/cmd/ndb/dns.b
new file mode 100644
index 00000000..8aa1fc0a
--- /dev/null
+++ b/appl/cmd/ndb/dns.b
@@ -0,0 +1,1860 @@
+implement DNS;
+
+#
+# domain name service
+#
+# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+# RFCs: 1034, 1035, 2181, 2308
+#
+# TO DO:
+# server side:
+# database; inmyzone; ptr generation; separate zone transfer
+# currently doesn't implement loony rules on case
+# limit work
+# check data
+# Call
+# ipv6
+#
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+
+include "draw.m";
+
+include "bufio.m";
+
+include "srv.m";
+ srv: Srv;
+
+include "ip.m";
+ ip: IP;
+ IPaddrlen, IPaddr, IPv4off, OUdphdrlen: import ip;
+
+include "arg.m";
+
+include "attrdb.m";
+ attrdb: Attrdb;
+ Db, Dbentry, Tuples: import attrdb;
+
+include "ipattr.m";
+ ipattr: IPattr;
+ dbattr: import ipattr;
+
+DNS: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Reply: adt
+{
+ fid: int;
+ pid: int;
+ query: string;
+ attr: string;
+ addrs: list of string;
+ err: string;
+};
+
+rlist: list of ref Reply;
+
+dnsfile := "/lib/ndb/local";
+myname: string;
+mntpt := "/net";
+DNSport: con 53;
+debug := 0;
+referdns := 0;
+usehost := 1;
+now: int;
+
+servers: list of string;
+
+# domain name from dns/db
+domain: string;
+dnsdomains: list of string;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ cantload(Arg->PATH);
+ arg->init(args);
+ arg->setusage("dns [-Drh] [-f dnsfile] [-x mntpt]");
+ svcname := "#sdns";
+ while((c := arg->opt()) != 0)
+ case c {
+ 'D' =>
+ debug = 1;
+ 'f' =>
+ dnsfile = arg->earg();
+ 'h' =>
+ usehost = 0;
+ 'r' =>
+ referdns = 1;
+ 'x' =>
+ mntpt = arg->earg();
+ svcname = "#sdns"+svcpt(mntpt);
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ arg->usage();
+ arg = nil;
+
+ if(usehost){
+ srv = load Srv Srv->PATH; # hosted Inferno only
+ if(srv != nil)
+ srv->init();
+ }
+ ip = load IP IP->PATH;
+ if(ip == nil)
+ cantload(IP->PATH);
+ ip->init();
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ cantload(Attrdb->PATH);
+ attrdb->init();
+ ipattr = load IPattr IPattr->PATH;
+ if(ipattr == nil)
+ cantload(IPattr->PATH);
+ ipattr->init(attrdb, ip);
+
+ sys->pctl(Sys->NEWPGRP | Sys->FORKFD, nil);
+ myname = sysname();
+ stderr = sys->fildes(2);
+ readservers();
+ now = time();
+ sys->remove(svcname+"/dns");
+ sys->unmount(svcname, mntpt);
+ publish(svcname);
+ if(sys->bind(svcname, mntpt, Sys->MBEFORE) < 0)
+ error(sys->sprint("can't bind #s on %s: %r", mntpt));
+ file := sys->file2chan(mntpt, "dns");
+ if(file == nil)
+ error(sys->sprint("can't make %s/dns: %r", mntpt));
+ sync := chan of int;
+ spawn dnscache(sync);
+ <-sync;
+ spawn dns(file);
+}
+
+publish(dir: string)
+{
+ d := Sys->nulldir;
+ d.mode = 8r777;
+ if(sys->wstat(dir, d) < 0)
+ sys->fprint(sys->fildes(2), "cs: can't publish %s: %r\n", dir);
+}
+
+svcpt(s: string): string
+{
+ for(i:=0; i<len s; i++)
+ if(s[i] == '/')
+ s[i] = '_';
+ return s;
+}
+
+cantload(s: string)
+{
+ error(sys->sprint("can't load %s: %r", s));
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "dns: %s\n", s);
+ raise "fail:error";
+}
+
+dns(file: ref Sys->FileIO)
+{
+ pidc := chan of int;
+ donec := chan of ref Reply;
+ for(;;){
+ alt {
+ (nil, buf, fid, wc) := <-file.write =>
+ now = time();
+ cleanfid(fid); # each write cancels previous requests
+ if(wc != nil){
+ r := ref Reply;
+ r.fid = fid;
+ spawn request(r, buf, wc, pidc, donec);
+ r.pid = <-pidc;
+ rlist = r :: rlist;
+ }
+
+ (off, nbytes, fid, rc) := <-file.read =>
+ now = time();
+ if(rc != nil){
+ r := findfid(fid);
+ if(r != nil)
+ reply(r, off, nbytes, rc);
+ else
+ rc <-= (nil, "unknown request");
+ }
+
+ r := <-donec =>
+ now = time();
+ r.pid = 0;
+ if(r.err != nil)
+ cleanfid(r.fid);
+ }
+ }
+}
+
+findfid(fid: int): ref Reply
+{
+ for(rl := rlist; rl != nil; rl = tl rl){
+ r := hd rl;
+ if(r.fid == fid)
+ return r;
+ }
+ return nil;
+}
+
+cleanfid(fid: int)
+{
+ rl := rlist;
+ rlist = nil;
+ for(; rl != nil; rl = tl rl){
+ r := hd rl;
+ if(r.fid != fid)
+ rlist = r :: rlist;
+ else
+ killgrp(r.pid);
+ }
+}
+
+killgrp(pid: int)
+{
+ if(pid != 0){
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "killgrp") < 0)
+ sys->fprint(stderr, "dns: can't killgrp %d: %r\n", pid);
+ }
+}
+
+request(r: ref Reply, data: array of byte, wc: chan of (int, string), pidc: chan of int, donec: chan of ref Reply)
+{
+ pidc <-= sys->pctl(Sys->NEWPGRP, nil);
+ query := string data;
+ for(i := 0; i < len query; i++)
+ if(query[i] == ' ')
+ break;
+ r.query = query[0:i];
+ for(; i < len query && query[i] == ' '; i++)
+ ;
+ r.attr = query[i:];
+ attr := rrtype(r.attr);
+ if(attr < 0)
+ r.err = "unknown type";
+ else
+ (r.addrs, r.err) = dnslookup(r.query, attr);
+ if(r.addrs == nil && r.err == nil)
+ r.err = "not found";
+ if(r.err != nil){
+ if(debug)
+ sys->fprint(stderr, "dns: %s: %s\n", query, r.err);
+ wc <-= (0, "dns: "+r.err);
+ } else
+ wc <-= (len data, nil);
+ donec <-= r;
+}
+
+reply(r: ref Reply, off: int, nbytes: int, rc: chan of (array of byte, string))
+{
+ if(r.err != nil || r.addrs == nil){
+ rc <-= (nil, r.err);
+ return;
+ }
+ addr: string;
+ if(r.addrs != nil){
+ addr = hd r.addrs;
+ r.addrs = tl r.addrs;
+ }
+ off = 0; # this version ignores offsets
+# rc <-= reads(r.query+" "+r.attr+" "+addr, off, nbytes);
+ rc <-= reads(addr, off, nbytes);
+}
+
+#
+# return the file2chan reply for a read of the given string
+#
+reads(str: string, off, nbytes: int): (array of byte, string)
+{
+ bstr := array of byte str;
+ slen := len bstr;
+ if(off < 0 || off >= slen)
+ return (nil, nil);
+ if(off + nbytes > slen)
+ nbytes = slen - off;
+ if(nbytes <= 0)
+ return (nil, nil);
+ return (bstr[off:off+nbytes], nil);
+}
+
+sysname(): string
+{
+ t := rf("/dev/sysname");
+ if(t != nil)
+ return t;
+ t = rf("#e/sysname");
+ if(t == nil){
+ s := rf(mntpt+"/ndb");
+ if(s != nil){
+ db := Db.sopen(t);
+ if(db != nil){
+ (e, nil) := db.find(nil, "sys");
+ if(e != nil)
+ t = e.findfirst("sys");
+ }
+ }
+ }
+ if(t != nil){
+ fd := sys->open("/dev/sysname", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "%s", t);
+ }
+ return t;
+}
+
+rf(name: string): string
+{
+ fd := sys->open(name, Sys->OREAD);
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ return nil;
+ return string buf[0:n];
+}
+
+samefile(d1, d2: Sys->Dir): int
+{
+ # ``it was black ... it was white! it was dark ... it was light! ah yes, i remember it well...''
+ return d1.dev==d2.dev && d1.dtype==d2.dtype &&
+ d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers &&
+ d1.mtime==d2.mtime;
+}
+
+#
+# database
+# dnsdomain= suffix to add to unqualified unrooted names
+# dns= dns server to try
+# dom= domain name
+# ip= IP address
+# ns= name server
+# soa=
+# soa=delegated
+# infernosite= set of site-wide parameters
+#
+
+#
+# basic Domain Name Service resolver
+#
+
+laststat := 0; # time last stat'd (to reduce churn)
+dnsdb: ref Db;
+
+readservers(): list of string
+{
+ if(laststat != 0 && now < laststat+2*60)
+ return servers;
+ laststat = now;
+ if(dnsdb == nil){
+ db := Db.open(dnsfile);
+ if(db == nil){
+ sys->fprint(stderr, "dns: can't open %s: %r\n", dnsfile);
+ return nil;
+ }
+ dyndb := Db.open(mntpt+"/ndb");
+ if(dyndb != nil)
+ dnsdb = dyndb.append(db);
+ else
+ dnsdb = db;
+ }else{
+ if(!dnsdb.changed())
+ return servers;
+ dnsdb.reopen();
+ }
+ if((l := dblooknet("sys", myname, "dnsdomain")) == nil)
+ l = dblook("infernosite", "", "dnsdomain");
+ dnsdomains = "" :: l;
+ if((l = dblooknet("sys", myname, "dns")) == nil)
+ l = dblook("infernosite", "", "dns");
+ servers = l;
+# zones := dblook("soa", "", "dom");
+#printlist("zones", zones);
+ if(debug)
+ printlist("dnsdomains", dnsdomains);
+ if(debug)
+ printlist("servers", servers);
+ return servers;
+}
+
+printlist(w: string, l: list of string)
+{
+ sys->print("%s:", w);
+ for(; l != nil; l = tl l)
+ sys->print(" %q", hd l);
+ sys->print("\n");
+}
+
+dblookns(dom: string): list of ref RR
+{
+ domns := dblook("dom", dom, "ns");
+ hosts: list of ref RR;
+ for(; domns != nil; domns = tl domns){
+ s := hd domns;
+ if(debug)
+ sys->print("dns db: dom=%s ns=%s\n", dom, s);
+ ipl: list of ref RR = nil;
+ addrs := dblook("dom", s, "ip");
+ for(; addrs != nil; addrs = tl addrs){
+ a := parseip(hd addrs);
+ if(a != nil){
+ ipl = ref RR.A(s, Ta, Cin, now+60, 0, a) :: ipl;
+ if(debug)
+ sys->print("dom=%s ip=%s\n", s, hd addrs);
+ }
+ }
+ if(ipl != nil){
+ # only use ones for which we've got addresses
+ cachec <-= (ipl, 0);
+ hosts = ref RR.Host(dom, Tns, Cin, now+60, 0, s) :: hosts;
+ }
+ }
+ if(hosts == nil){
+ if(debug)
+ sys->print("dns: no ns for dom=%s in db\n", dom);
+ return nil;
+ }
+ cachec <-= (hosts, 0);
+ cachec <-= Sync;
+ return hosts;
+}
+
+defaultresolvers(): list of ref NS
+{
+ resolvers := readservers();
+ al: list of ref RR;
+ for(; resolvers != nil; resolvers = tl resolvers){
+ nm := hd resolvers;
+ a := parseip(nm);
+ if(a == nil){
+ # try looking it up as a domain name with an ip address
+ for(addrs := dblook("dom", nm, "ip"); addrs != nil; addrs = tl addrs){
+ a = parseip(hd addrs);
+ if(a != nil)
+ al = ref RR.A("defaultns", Ta, Cin, now+60, 0, a) :: al;
+ }
+ }else
+ al = ref RR.A("defaultns", Ta, Cin, now+60, 0, a) :: al;
+ }
+ if(al == nil){
+ if(debug)
+ sys->print("dns: no default resolvers\n");
+ return nil;
+ }
+ return ref NS("defaultns", al, 1, now+60) :: nil;
+}
+
+dblook(attr: string, val: string, rattr: string): list of string
+{
+ rl: list of string;
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ (e, ptr) = dnsdb.findbyattr(ptr, attr, val, rattr);
+ if(e == nil)
+ break;
+ for(l := e.findbyattr(attr, val, rattr); l != nil; l = tl l){
+ (nil, al) := hd l;
+ for(; al != nil; al = tl al)
+ if(!inlist((hd al).val, rl))
+ rl = (hd al).val :: rl;
+ }
+ }
+ return reverse(rl);
+}
+
+#
+# starting from the ip= associated with attr=val, search over all
+# containing networks for the nearest values of rattr
+#
+dblooknet(attr: string, val: string, rattr: string): list of string
+{
+#sys->print("dblooknet: %s=%s -> %s\n", attr, val, rattr);
+ (results, nil) := ipattr->findnetattrs(dnsdb, attr, val, rattr::nil);
+ rl: list of string;
+ for(; results != nil; results = tl results){
+ (nil, nattrs) := hd results;
+ for(; nattrs != nil; nattrs = tl nattrs){
+ na := hd nattrs;
+ if(na.name == rattr){
+ for(pairs := na.pairs; pairs != nil; pairs = tl pairs)
+ if((s := (hd pairs).val) != nil && !inlist(s, rl))
+ rl = s :: rl;
+ }
+ }
+ }
+ if(rl == nil)
+ return dblook(attr, val, rattr);
+ return reverse(rl);
+}
+
+inlist(s: string, l: list of string): int
+{
+ for(; l != nil; l = tl l)
+ if(hd l == s)
+ return 1;
+ return 0;
+}
+
+reverse[T](l: list of T): list of T
+{
+ r: list of T;
+ for(; l != nil; l = tl l)
+ r = hd l :: r;
+ return r;
+}
+
+append(h: list of string, s: string): list of string
+{
+ if(h == nil)
+ return s :: nil;
+ return hd h :: append(tl h, s);
+}
+
+#
+# subset of RR types
+#
+Ta: con 1;
+Tns: con 2;
+Tcname: con 5;
+Tsoa: con 6;
+Tmb: con 7;
+Tptr: con 12;
+Thinfo: con 13;
+Tmx: con 15;
+Tall: con 255;
+
+#
+# classes
+#
+Cin: con 1;
+Call: con 255;
+
+#
+# opcodes
+#
+Oquery: con 0<<11; # normal query
+Oinverse: con 1<<11; # inverse query
+Ostatus: con 2<<11; # status request
+Omask: con 16rF<<11; # mask for opcode
+
+#
+# response codes
+#
+Rok: con 0;
+Rformat: con 1; # format error
+Rserver: con 2; # server failure
+Rname: con 3; # bad name
+Runimplemented: con 4; # unimplemented operation
+Rrefused: con 5; # permission denied, not supported
+Rmask: con 16rF; # mask for response
+
+#
+# other flags in opcode
+#
+Fresp: con 1<<15; # message is a response
+Fauth: con 1<<10; # true if an authoritative response
+Ftrunc: con 1<<9; # truncated message
+Frecurse: con 1<<8; # request recursion
+Fcanrecurse: con 1<<7; # server can recurse
+
+QR: adt {
+ name: string;
+ rtype: int;
+ class: int;
+
+ text: fn(q: self ref QR): string;
+};
+
+RR: adt {
+ name: string;
+ rtype: int;
+ class: int;
+ ttl: int;
+ flags: int;
+ pick {
+ Error =>
+ reason: string; # cached negative
+ Host =>
+ host: string;
+ Hinfo =>
+ cpu: string;
+ os: string;
+ Mx =>
+ pref: int;
+ host: string;
+ Soa =>
+ soa: ref SOA;
+ A or
+ Other =>
+ rdata: array of byte;
+ }
+
+ islive: fn(r: self ref RR): int;
+ outlives: fn(a: self ref RR, b: ref RR): int;
+ match: fn(a: self ref RR, b: ref RR): int;
+ text: fn(a: self ref RR): string;
+};
+
+SOA: adt {
+ mname: string;
+ rname: string;
+ serial: int;
+ refresh: int;
+ retry: int;
+ expire: int;
+ minttl: int;
+
+ text: fn(nil: self ref SOA): string;
+};
+
+DNSmsg: adt {
+ id: int;
+ flags: int;
+ qd: list of ref QR;
+ an: list of ref RR;
+ ns: list of ref RR;
+ ar: list of ref RR;
+ err: string;
+
+ pack: fn(m: self ref DNSmsg, hdrlen: int): array of byte;
+ unpack: fn(a: array of byte): ref DNSmsg;
+ text: fn(m: self ref DNSmsg): string;
+};
+
+NM: adt {
+ name: string;
+ rr: list of ref RR;
+ stats: ref Stats;
+};
+
+Stats: adt {
+ rtt: int;
+};
+
+cachec: chan of (list of ref RR, int);
+cache: array of list of ref NM;
+Sync: con (nil, 0); # empty list sent to ensure that last cache update done
+
+hash(s: string): array of list of ref NM
+{
+ h := 0;
+ for(i:=0; i<len s; i++){ # hashpjw
+ c := s[i];
+ if(c >= 'A' && c <= 'Z')
+ c += 'a'-'A';
+ h = (h<<4) + c;
+ if((g := h & int 16rF0000000) != 0)
+ h ^= ((g>>24) & 16rFF) | g;
+ }
+ return cache[(h&~(1<<31))%len cache:];
+}
+
+lower(s: string): string
+{
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ if(c >= 'A' && c <= 'Z'){
+ n := s;
+ for(; i < len n; i++){
+ c = n[i];
+ if(c >= 'A' && c <= 'Z')
+ n[i] = c+('a'-'A');
+ }
+ return n;
+ }
+ }
+ return s;
+}
+
+#
+# split rrl into a list of those RRs that match rr and a list of those that don't
+#
+partrrl(rr: ref RR, rrl: list of ref RR): (list of ref RR, list of ref RR)
+{
+ m: list of ref RR;
+ nm: list of ref RR;
+ name := lower(rr.name);
+ for(; rrl != nil; rrl = tl rrl){
+ t := hd rrl;
+ if(t.rtype == rr.rtype && t.class == rr.class &&
+ (t.name == name || lower(t.name) == name))
+ m = t :: m;
+ else
+ nm = t :: nm;
+ }
+ return (m, nm);
+}
+
+copyrrl(rrl: list of ref RR): list of ref RR
+{
+ nl: list of ref RR;
+ for(; rrl != nil; rrl = tl rrl)
+ nl = ref *hd rrl :: nl;
+# return revrrl(rrl);
+ return rrl; # probably don't care about order
+}
+
+dnscache(sync: chan of int)
+{
+ cache = array[32] of list of ref NM;
+ cachec = chan of (list of ref RR, int);
+ sync <-= sys->pctl(0, nil);
+ for(;;){
+ (rrl, flags) := <-cachec;
+ #now = time();
+ List:
+ while(rrl != nil){
+ rrset: list of ref RR;
+ (rrset, rrl) = partrrl(hd rrl, rrl);
+ rr := hd rrset;
+ rr.flags = flags;
+ name := lower(rr.name);
+ hb := hash(name);
+ for(ces := hb[0]; ces != nil; ces = tl ces){
+ ce := hd ces;
+ if(ce.name == name){
+ rr.name = ce.name; # share string
+ x := ce.rr;
+ ce.rr = insertrrset(ce.rr, rr, rrset);
+ if(x != ce.rr && debug)
+ sys->print("insertrr %s:%s\n", name, rrsettext(rrset));
+ continue List;
+ }
+ }
+ if(debug)
+ sys->print("newrr %s:%s\n", name, rrsettext(rrset));
+ hb[0] = ref NM(name, rrset, nil) :: hb[0];
+ }
+ }
+}
+
+lookcache(name: string, rtype: int, rclass: int): (list of ref RR, string)
+{
+ results: list of ref RR;
+ name = lower(name);
+ for(ces := hash(name)[0]; ces != nil; ces = tl ces){
+ ce := hd ces;
+ if(ce.name == name){
+ for(zl := ce.rr; zl != nil; zl = tl zl){
+ r := hd zl;
+ if((r.rtype == rtype || r.rtype == Tall || rtype == Tall) && r.class == rclass && r.name == name && r.islive()){
+ pick ar := r {
+ Error =>
+ if(rtype != Tall || ar.reason != "resource does not exist"){
+ if(debug)
+ sys->print("lookcache: %s[%s]: !%s\n", name, rrtypename(rtype), ar.reason);
+ return (nil, ar.reason);
+ }
+ * =>
+ results = ref *r :: results;
+ }
+ }
+ }
+ }
+ }
+ if(debug)
+ sys->print("lookcache: %s[%s]: %s\n", name, rrtypename(rtype), rrsettext(results));
+ return (results, nil);
+}
+
+#
+# insert RRset new in existing list of RRsets rrl
+# if that's desirable (it's the whole RRset or nothing, see rfc2181)
+#
+insertrrset(rrl: list of ref RR, rr: ref RR, new: list of ref RR): list of ref RR
+{
+ # TO DO: expire entries
+ match := 0;
+ for(l := rrl; l != nil; l = tl l){
+ orr := hd l;
+ if(orr.rtype == rr.rtype && orr.class == rr.class){ # name already known to match
+ match = 1;
+ if(!orr.islive())
+ break; # prefer new, unexpired data
+ if(tagof rr == tagof RR.Error && tagof orr != tagof RR.Error)
+ return rrl; # prefer unexpired positive
+ if(rr.flags & Fauth)
+ break; # prefer newly-arrived authoritative data
+ if(orr.flags & Fauth)
+ return rrl; # prefer authoritative data
+ if(orr.outlives(rr))
+ return rrl; # prefer longer-lived data
+ }
+ }
+ if(match){
+ # strip out existing RR set
+ l = rrl;
+ rrl = nil;
+ for(; l != nil; l = tl l){
+ orr := hd l;
+ if((orr.rtype != rr.rtype || orr.class != rr.class) && orr.islive()){
+ rrl = orr :: rrl;}
+ }
+ }
+ # add new RR set
+ for(; new != nil; new = tl new){
+ nrr := hd new;
+ nrr.name = rr.name;
+ rrl = nrr :: rrl;
+ }
+ return rrl;
+}
+
+rrsettext(rrl: list of ref RR): string
+{
+ s := "";
+ for(; rrl != nil; rrl = tl rrl)
+ s += " ["+(hd rrl).text()+"]";
+ return s;
+}
+
+QR.text(qr: self ref QR): string
+{
+ s := sys->sprint("%s %s", qr.name, rrtypename(qr.rtype));
+ if(qr.class != Cin)
+ s += sys->sprint(" [c=%d]", qr.class);
+ return s;
+}
+
+RR.islive(rr: self ref RR): int
+{
+ return rr.ttl >= now;
+}
+
+RR.outlives(a: self ref RR, b: ref RR): int
+{
+ return a.ttl > b.ttl;
+}
+
+RR.match(a: self ref RR, b: ref RR): int
+{
+ # compare content, not ttl
+ return a.rtype == b.rtype && a.class == b.class && a.name == b.name;
+}
+
+RR.text(rr: self ref RR): string
+{
+ s := sys->sprint("%s %s", rr.name, rrtypename(rr.rtype));
+ pick ar := rr {
+ Host =>
+ s += sys->sprint("\t%s", ar.host);
+ Hinfo =>
+ s += sys->sprint("\t%s %s", ar.cpu, ar.os);
+ Mx =>
+ s += sys->sprint("\t%ud %s", ar.pref, ar.host);
+ Soa =>
+ s += sys->sprint("\t%s", ar.soa.text());
+ A =>
+ if(len ar.rdata == 4){
+ a := ar.rdata;
+ s += sys->sprint("\t%d.%d.%d.%d", int a[0], int a[1], int a[2], int a[3]);
+ }
+ Error =>
+ s += sys->sprint("\t!%s", ar.reason);
+ }
+ return s;
+}
+
+SOA.text(soa: self ref SOA): string
+{
+ return sys->sprint("%s %s %ud %ud %ud %ud %ud", soa.mname, soa.rname,
+ soa.serial, soa.refresh, soa.retry, soa.expire, soa.minttl);
+}
+
+NS: adt {
+ name: string;
+ addr: list of ref RR;
+ canrecur: int;
+ ttl: int;
+};
+
+dnslookup(name: string, attr: int): (list of string, string)
+{
+ case attr {
+ Ta =>
+ case dbattr(name) {
+ "sys" =>
+ # could apply domains
+ ;
+ "dom" =>
+ ;
+ * =>
+ return (nil, "invalid host name");
+ }
+ if(srv != nil){ # try the host's map first
+ l := srv->iph2a(name);
+ if(l != nil)
+ return (fullresult(name, "ip", l), nil);
+ }
+ Tptr =>
+ if(srv != nil){ # try host's map first
+ l := srv->ipa2h(arpa2addr(name));
+ if(l != nil)
+ return (fullresult(name, "ptr", l), nil);
+ }
+ }
+ return dnslookup1(name, attr);
+}
+
+fullresult(name: string, attr: string, l: list of string): list of string
+{
+ rl: list of string;
+ for(; l != nil; l = tl l)
+ rl = sys->sprint("%s %s\t%s", name, attr, hd l) :: rl;
+ return reverse(rl);
+}
+
+arpa2addr(a: string): string
+{
+ (nf, flds) := sys->tokenize(a, ".");
+ rl: list of string;
+ for(; flds != nil && lower(s := hd flds) != "in-addr"; flds = tl flds)
+ rl = s :: rl;
+ dom: string;
+ for(; rl != nil; rl = tl rl){
+ if(dom != nil)
+ dom[len dom] = '.';
+ dom += hd rl;
+ }
+ return dom;
+}
+
+dnslookup1(label: string, attr: int): (list of string, string)
+{
+ (rrl, err) := fulldnsquery(label, attr, 0);
+ if(err != nil || rrl == nil)
+ return (nil, err);
+ r: list of string;
+ for(; rrl != nil; rrl = tl rrl)
+ r = (hd rrl).text() :: r;
+ return (reverse(r), nil);
+}
+
+trimdot(s: string): string
+{
+ while(s != nil && s[len s - 1] == '.')
+ s = s[0:len s -1];
+ return s;
+}
+
+parent(s: string): string
+{
+ if(s == "")
+ return ".";
+ for(i := 0; i < len s; i++)
+ if(s[i] == '.')
+ return s[i+1:];
+ return "";
+}
+
+rootservers(): list of ref NS
+{
+ slist := ref NS("a.root-servers.net",
+ ref RR.A("a.root-servers.net", Ta, Cin, 1<<31, 0,
+ array[] of {byte 198, byte 41, byte 0, byte 4})::nil, 0, 1<<31) :: nil;
+ return slist;
+}
+
+#
+# this broadly follows the algorithm given in RFC 1034
+# as adjusted and qualified by several other RFCs.
+# `label' is 1034's SNAME, `attr' is `STYPE'
+#
+# TO DO:
+# keep statistics for name servers
+
+fulldnsquery(label: string, attr: int, depth: int): (list of ref RR, string)
+{
+ slist: list of ref NS;
+ fd: ref Sys->FD;
+ if(depth > 10)
+ return (nil, "dns loop");
+ ncname := 0;
+Step1:
+ for(tries:=0; tries<10; tries++){
+
+ # 1. see if in local information, and if so, return it
+ (x, err) := lookcache(label, attr, Cin);
+ if(x != nil)
+ return (x, nil);
+ if(err != nil)
+ return (nil, err);
+ if(attr != Tcname){
+ if(++ncname > 10)
+ return (nil, "cname alias loop");
+ (x, err) = lookcache(label, Tcname, Cin);
+ if(x != nil){
+ pick rx := hd x {
+ Host =>
+ label = rx.host;
+ continue;
+ }
+ }
+ }
+
+ # 2. find the best servers to ask
+ slist = nil;
+ for(d := trimdot(label); d != "."; d = parent(d)){
+ nsl: list of ref RR;
+ (nsl, err) = lookcache(d, Tns, Cin);
+ if(nsl == nil)
+ nsl = dblookns(d);
+ # add each to slist; put ones with known addresses first
+ known: list of ref NS = nil;
+ for(; nsl != nil; nsl = tl nsl){
+ pick ns := hd nsl {
+ Host =>
+ (addrs, err2) := lookcache(ns.host, Ta, Cin);
+ if(addrs != nil)
+ known = ref NS(ns.host, addrs, 0, 1<<31) :: known;
+ else if(err2 == nil)
+ slist = ref NS(ns.host, nil, 0, 1<<31) :: slist;
+ }
+
+ }
+ for(; known != nil; known = tl known)
+ slist = hd known :: slist;
+ if(slist != nil)
+ break;
+ }
+ # if no servers, resort to safety belt
+ if(slist == nil){
+ slist = defaultresolvers();
+ if(slist == nil){
+ slist = rootservers();
+ if(slist == nil)
+ return (nil, "no dns servers configured");
+ }
+ }
+ (id, query, err1) := mkquery(attr, Cin, label);
+ if(err1 != nil){
+ sys->fprint(stderr, "dns: %s\n", err1);
+ return (nil, err1);
+ }
+
+ if(debug)
+ printnslist(sys->sprint("ns for %s: ", d), slist);
+
+ # 3. send them queries until one returns a response
+ for(qset := slist; qset != nil; qset = tl qset){
+ ns := hd qset;
+ if(ns.addr == nil){
+ if(debug)
+ sys->print("recursive[%d] query for %s address\n", depth+1, ns.name);
+ (ns.addr, nil) = fulldnsquery(ns.name, Ta, depth+1);
+ if(ns.addr == nil)
+ continue;
+ }
+ if(fd == nil){
+ fd = udpport();
+ if(fd == nil)
+ return (nil, sys->sprint("%r"));
+ }
+ (dm, err2) := udpquery(fd, id, query, ns.name, hd ns.addr);
+ if(dm == nil){
+ sys->fprint(stderr, "dns: %s: %s\n", ns.name, err2);
+ # TO DO: remove from slist
+ continue;
+ }
+ # 4. analyse the response
+ # a. answers the question or has Rname, cache it and return to client
+ # b. delegation to other NS? cache and goto step 2.
+ # c. if response is CNAME and QTYPE!=CNAME change SNAME to the
+ # canonical name (data) of the CNAME RR and goto step 1.
+ # d. if response is server failure or otherwise odd, delete server from SLIST
+ # and goto step 3.
+ auth := (dm.flags & Fauth) != 0;
+ soa: ref RR.Soa;
+ (soa, dm.ns) = soaof(dm.ns);
+ if((dm.flags & Rmask) != Rok){
+ # don't repeat the request on an error
+ # TO DO: should return `best error'
+ if(tl qset != nil && ((dm.flags & Rmask) != Rname || !auth))
+ continue;
+ cause := reason(dm.flags & Rmask);
+ if(auth && soa != nil){
+ # rfc2038 says to cache soa with cached negatives, and the
+ # negative to be retrieved for all attributes if name does not exist
+ if((ttl := soa.soa.minttl) > 0)
+ ttl += now;
+ else
+ ttl = now+10*60;
+ a := attr;
+ if((dm.flags & Rmask) == Rname)
+ a = Tall;
+ cachec <-= (ref RR.Error(label, a, Cin, ttl, auth, cause)::soa::nil, auth);
+ }
+ return (nil, cause);
+ }
+ if(dm.an != nil){
+ if(1 && dm.ns != nil)
+ cachec <-= (dm.ns, 0);
+ if(1 && dm.ar != nil)
+ cachec <-= (dm.ar, 0);
+ cachec <-= (dm.an, auth);
+ cachec <-= Sync;
+ if(isresponse(dm, attr))
+ return (dm.an, nil);
+ if(attr != Tcname && (cn := cnameof(dm)) != nil){
+ if(++ncname > 10)
+ return (nil, "cname alias loop");
+ label = cn;
+ continue Step1;
+ }
+ }
+ if(auth){
+ if(soa != nil && (ttl := soa.soa.minttl) > 0)
+ ttl += now;
+ else
+ ttl = now+10*60;
+ cachec <-= (ref RR.Error(label, attr, Cin, ttl, auth, "resource does not exist")::soa::nil, auth);
+ return (nil, "resource does not exist");
+ }
+ if(isdelegation(dm)){
+ # cache valid name servers and hints
+ cachec <-= (dm.ns, 0);
+ if(dm.ar != nil)
+ cachec <-= (dm.ar, 0);
+ cachec <-= Sync;
+ continue Step1;
+ }
+ }
+ }
+ return (nil, "server failed");
+}
+
+isresponse(dn: ref DNSmsg, attr: int): int
+{
+ if(dn == nil || dn.an == nil)
+ return 0;
+ return (hd dn.an).rtype == attr;
+}
+
+cnameof(dn: ref DNSmsg): string
+{
+ if(dn != nil && dn.an != nil && (rr := hd dn.an).rtype == Tcname)
+ pick ar := rr {
+ Host =>
+ return ar.host;
+ }
+ return nil;
+}
+
+soaof(rrl: list of ref RR): (ref RR.Soa, list of ref RR)
+{
+ for(l := rrl; l != nil; l = tl l)
+ pick rr := hd l {
+ Soa =>
+ rest := tl l;
+ for(; rrl != l; rrl = tl rrl)
+ if(tagof hd rrl != tagof RR.Soa) # (just in case)
+ rest = hd rrl :: rest;
+ return (rr, rest);
+ }
+ return (nil, rrl);
+}
+
+isdelegation(dn: ref DNSmsg): int
+{
+ if(dn.an != nil)
+ return 0;
+ for(al := dn.ns; al != nil; al = tl al)
+ if((hd al).rtype == Tns)
+ return 1;
+ return 0;
+}
+
+printnslist(prefix: string, nsl: list of ref NS)
+{
+ s := prefix;
+ for(; nsl != nil; nsl = tl nsl){
+ ns := hd nsl;
+ s += sys->sprint(" [%s %s]", ns.name, rrsettext(ns.addr));
+ }
+ sys->print("%s\n", s);
+}
+
+#
+# DNS message format
+#
+
+Udpdnslim: con 512;
+
+Labels: adt {
+ names: list of (string, int);
+
+ new: fn(): ref Labels;
+ look: fn(labs: self ref Labels, s: string): int;
+ install: fn(labs: self ref Labels, s: string, o: int);
+};
+
+Labels.new(): ref Labels
+{
+ return ref Labels;
+}
+
+Labels.look(labs: self ref Labels, s: string): int
+{
+ for(nl := labs.names; nl != nil; nl = tl nl){
+ (t, o) := hd nl;
+ if(s == t)
+ return 16rC000 | o;
+ }
+ return 0;
+}
+
+Labels.install(labs: self ref Labels, s: string, off: int)
+{
+ labs.names = (s, off) :: labs.names;
+}
+
+put2(a: array of byte, o: int, val: int): int
+{
+ if(o < 0)
+ return o;
+ if(o + 2 > len a)
+ return -o;
+ a[o] = byte (val>>8);
+ a[o+1] = byte val;
+ return o+2;
+}
+
+put4(a: array of byte, o: int, val: int): int
+{
+ if(o < 0)
+ return o;
+ if(o + 4 > len a)
+ return -o;
+ a[o] = byte (val>>24);
+ a[o+1] = byte (val>>16);
+ a[o+2] = byte (val>>8);
+ a[o+3] = byte val;
+ return o+4;
+}
+
+puta(a: array of byte, o: int, b: array of byte): int
+{
+ if(o < 0)
+ return o;
+ l := len b;
+ if(l > 255 || o+l+1 > len a)
+ return -(o+l+1);
+ a[o++] = byte l;
+ a[o:] = b;
+ return o+len b;
+}
+
+puts(a: array of byte, o: int, s: string): int
+{
+ return puta(a, o, array of byte s);
+}
+
+get2(a: array of byte, o: int): (int, int)
+{
+ if(o < 0)
+ return (0, o);
+ if(o + 2 > len a)
+ return (0, -o);
+ val := (int a[o] << 8) | int a[o+1];
+ return (val, o+2);
+}
+
+get4(a: array of byte, o: int): (int, int)
+{
+ if(o < 0)
+ return (0, o);
+ if(o + 4 > len a)
+ return (0, -o);
+ val := (((((int a[o] << 8)| int a[o+1]) << 8) | int a[o+2]) << 8) | int a[o+3];
+ return (val, o+4);
+}
+
+gets(a: array of byte, o: int): (string, int)
+{
+ if(o < 0)
+ return (nil, o);
+ if(o+1 > len a)
+ return (nil, -o);
+ l := int a[o++];
+ if(o+l > len a)
+ return (nil, -o);
+ return (string a[o:o+l], o+l);
+}
+
+putdn(a: array of byte, o: int, name: string, labs: ref Labels): int
+{
+ if(o < 0)
+ return o;
+ o0 := o;
+ while(name != "") {
+ n := labs.look(name);
+ if(n != 0){
+ o = put2(a, o, n);
+ if(o < 0)
+ return -o0;
+ return o;
+ }
+ for(l := 0; l < len name && name[l] != '.'; l++)
+ ;
+ if(o+l+1 > len a)
+ return -o0;
+ labs.install(name, o);
+ a[o++] = byte l;
+ for(i := 0; i < l; i++)
+ a[o++] = byte name[i];
+ for(; l < len name && name[l] == '.'; l++)
+ ;
+ name = name[l:];
+ }
+ if(o >= len a)
+ return -o0;
+ a[o++] = byte 0;
+ return o;
+}
+
+getdn(a: array of byte, o: int, depth: int): (string, int)
+{
+ if(depth > 30)
+ return (nil, -o);
+ if(o < 0)
+ return (nil, o);
+ name := "";
+ while(o < len a && (l := int a[o++]) != 0) {
+ if((l & 16rC0) == 16rC0) { # pointer
+ if(o >= len a)
+ return (nil, -o);
+ po := ((l & 16r3F)<<8) | int a[o];
+ if(po >= len a)
+ return ("", -o);
+ o++;
+ pname: string;
+ (pname, po) = getdn(a, po, depth+1);
+ if(po < 1)
+ return (nil, -o);
+ name += pname;
+ break;
+ }
+ if((l & 16rC0) != 0)
+ return (nil, -o); # format error
+ if(o + l > len a)
+ return (nil, -o);
+ name += string a[o:o+l];
+ o += l;
+ if(o < len a && a[o] != byte 0)
+ name += ".";
+ }
+ return (lower(name), o);
+}
+
+putqrl(a: array of byte, o: int, qrl: list of ref QR, labs: ref Labels): int
+{
+ for(; qrl != nil && o >= 0; qrl = tl qrl){
+ q := hd qrl;
+ o = putdn(a, o, q.name, labs);
+ o = put2(a, o, q.rtype);
+ o = put2(a, o, q.class);
+ }
+ return o;
+}
+
+getqrl(nq: int, a: array of byte, o: int): (list of ref QR, int)
+{
+ if(o < 0)
+ return (nil, o);
+ qrl: list of ref QR;
+ for(i := 0; i < nq; i++) {
+ qd := ref QR;
+ (qd.name, o) = getdn(a, o, 0);
+ (qd.rtype, o) = get2(a, o);
+ (qd.class, o) = get2(a, o);
+ if(o < 1)
+ break;
+ qrl = qd :: qrl;
+ }
+ q: list of ref QR;
+ for(; qrl != nil; qrl = tl qrl)
+ q = hd qrl :: q;
+ return (q, o);
+}
+
+putrrl(a: array of byte, o: int, rrl: list of ref RR, labs: ref Labels): int
+{
+ if(o < 0)
+ return o;
+ for(; rrl != nil; rrl = tl rrl){
+ rr := hd rrl;
+ o0 := o;
+ o = putdn(a, o, rr.name, labs);
+ o = put2(a, o, rr.rtype);
+ o = put2(a, o, rr.class);
+ o = put4(a, o, rr.ttl);
+ pick ar := rr {
+ Host =>
+ o = putdn(a, o, ar.host, labs);
+ Hinfo =>
+ o = puts(a, o, ar.cpu);
+ o = puts(a, o, ar.os);
+ Mx =>
+ o = put2(a, o, ar.pref);
+ o = putdn(a, o, ar.host, labs);
+ Soa =>
+ soa := ar.soa;
+ o = putdn(a, o, soa.mname, labs);
+ o = putdn(a, o, soa.rname, labs);
+ o = put4(a, o, soa.serial);
+ o = put4(a, o, soa.refresh);
+ o = put4(a, o, soa.retry);
+ o = put4(a, o, soa.expire);
+ o = put4(a, o, soa.minttl);
+ A or
+ Other =>
+ dlen := len ar.rdata;
+ o = put2(a, o, dlen);
+ if(o < 1)
+ return -o0;
+ if(o + dlen > len a)
+ return -o0;
+ a[o:] = ar.rdata;
+ o += dlen;
+ }
+ }
+ return o;
+}
+
+getrrl(nr: int, a: array of byte, o: int): (list of ref RR, int)
+{
+ if(o < 0)
+ return (nil, o);
+ rrl: list of ref RR;
+ for(i := 0; i < nr; i++) {
+ name: string;
+ rtype, rclass, ttl: int;
+ (name, o) = getdn(a, o, 0);
+ (rtype, o) = get2(a, o);
+ (rclass, o) = get2(a, o);
+ (ttl, o) = get4(a, o);
+ if(ttl <= 0)
+ ttl = 0;
+ #ttl = 1*60;
+ ttl += now;
+ dlen: int;
+ (dlen, o) = get2(a, o);
+ if(o < 1)
+ return (rrl, o);
+ if(o+dlen > len a)
+ return (rrl, -(o+dlen));
+ rr: ref RR;
+ dname: string;
+ case rtype {
+ Tsoa =>
+ soa := ref SOA;
+ (soa.mname, o) = getdn(a, o, 0);
+ (soa.rname, o) = getdn(a, o, 0);
+ (soa.serial, o) = get4(a, o);
+ (soa.refresh, o) = get4(a, o);
+ (soa.retry, o) = get4(a, o);
+ (soa.expire, o) = get4(a, o);
+ (soa.minttl, o) = get4(a, o);
+ rr = ref RR.Soa(name, rtype, rclass, ttl, 0, soa);
+ Thinfo =>
+ cpu, os: string;
+ (cpu, o) = gets(a, o);
+ (os, o) = gets(a, o);
+ rr = ref RR.Hinfo(name, rtype, rclass, ttl, 0, cpu, os);
+ Tmx =>
+ pref: int;
+ host: string;
+ (pref, o) = get2(a, o);
+ (host, o) = getdn(a, o, 0);
+ rr = ref RR.Mx(name, rtype, rclass, ttl, 0, pref, host);
+ Tcname or
+ Tns or
+ Tptr =>
+ (dname, o) = getdn(a, o, 0);
+ rr = ref RR.Host(name, rtype, rclass, ttl, 0, dname);
+ Ta =>
+ rdata := array[dlen] of byte;
+ rdata[0:] = a[o:o+dlen];
+ rr = ref RR.A(name, rtype, rclass, ttl, 0, rdata);
+ o += dlen;
+ * =>
+ rdata := array[dlen] of byte;
+ rdata[0:] = a[o:o+dlen];
+ rr = ref RR.Other(name, rtype, rclass, ttl, 0, rdata);
+ o += dlen;
+ }
+ rrl = rr :: rrl;
+ }
+ r: list of ref RR;
+ for(; rrl != nil; rrl = tl rrl)
+ r = (hd rrl) :: r;
+ return (r, o);
+}
+
+DNSmsg.pack(msg: self ref DNSmsg, hdrlen: int): array of byte
+{
+ a := array[Udpdnslim+hdrlen] of byte;
+
+ l := hdrlen;
+ l = put2(a, l, msg.id);
+ l = put2(a, l, msg.flags);
+ l = put2(a, l, len msg.qd);
+ l = put2(a, l, len msg.an);
+ l = put2(a, l, len msg.ns);
+ l = put2(a, l, len msg.ar);
+ labs := Labels.new();
+ l = putqrl(a, l, msg.qd, labs);
+ l = putrrl(a, l, msg.an, labs);
+ l = putrrl(a, l, msg.ns, labs);
+ l = putrrl(a, l, msg.ar, labs);
+ if(l < 1)
+ return nil;
+ return a[0:l];
+}
+
+DNSmsg.unpack(a: array of byte): ref DNSmsg
+{
+ msg := ref DNSmsg;
+ msg.flags = Rformat;
+ l := 0;
+ (msg.id, l) = get2(a, l);
+ (msg.flags, l) = get2(a, l);
+ if(l < 0 || l > len a){
+ msg.err = "length error";
+ return msg;
+ }
+ if(l >= len a)
+ return msg;
+
+ nqd, nan, nns, nar: int;
+ (nqd, l) = get2(a, l);
+ (nan, l) = get2(a, l);
+ (nns, l) = get2(a, l);
+ (nar, l) = get2(a, l);
+ if(l >= len a)
+ return msg;
+ (msg.qd, l) = getqrl(nqd, a, l);
+ (msg.an, l) = getrrl(nan, a, l);
+ (msg.ns, l) = getrrl(nns, a, l);
+ (msg.ar, l) = getrrl(nar, a, l);
+ if(l < 1){
+ sys->fprint(stderr, "l=%d format error\n", l);
+ msg.err = "format error";
+ return msg;
+ }
+ return msg;
+}
+
+DNSmsg.text(msg: self ref DNSmsg): string
+{
+ s := sys->sprint("id=%ud flags=#%ux[%s]\n", msg.id, msg.flags, flagtext(msg.flags));
+ s += " QR:\n";
+ for(x := msg.qd; x != nil; x = tl x)
+ s += "\t"+(hd x).text()+"\n";
+ s += " AN:\n";
+ for(l := msg.an; l != nil; l = tl l)
+ s += "\t"+(hd l).text()+"\n";
+ s += " NS:\n";
+ for(l = msg.ns; l != nil; l = tl l)
+ s += "\t"+(hd l).text()+"\n";
+ s += " AR:\n";
+ for(l = msg.ar; l != nil; l = tl l)
+ s += "\t"+(hd l).text()+"\n";
+ return s;
+}
+
+flagtext(f: int): string
+{
+ s := "";
+ if(f & Fresp)
+ s += "R";
+ if(f & Fauth)
+ s += "A";
+ if(f & Ftrunc)
+ s += "T";
+ if(f & Frecurse)
+ s += "r";
+ if(f & Fcanrecurse)
+ s += "c";
+ if((f & Fresp) == 0)
+ return s;
+ if(s != "")
+ s += ",";
+ return s+reason(f & Rmask);
+}
+
+rcodes := array[] of {
+ Rok => "no error",
+ Rformat => "format error",
+ Rserver => "server failure",
+ Rname => "name does not exist",
+ Runimplemented => "unimplemented",
+ Rrefused => "refused",
+};
+
+reason(n: int): string
+{
+ if(n < 0 || n > len rcodes)
+ return sys->sprint("error %d", n);
+ return rcodes[n];
+}
+
+rrtype(s: string): int
+{
+ case s {
+ "ip" => return Ta;
+ "ns" => return Tns;
+ "cname" => return Tcname;
+ "soa" => return Tsoa;
+ "ptr" => return Tptr;
+ "mx" => return Tmx;
+ "hinfo" => return Thinfo;
+ "all" or "any" => return Tall;
+ * => return -1;
+ }
+}
+
+rrtypename(t: int): string
+{
+ case t {
+ Ta => return "ip";
+ Tns => return "ns";
+ Tcname => return "cname";
+ Tsoa => return "soa";
+ Tptr => return "ptr";
+ Tmx => return "mx";
+ Tall => return "all";
+ Thinfo => return "hinfo";
+ * => return string t;
+ }
+}
+
+#
+# format of UDP head read and written in `oldheaders' mode
+#
+Udphdrsize: con OUdphdrlen;
+Udpraddr: con 0;
+Udpladdr: con IPaddrlen;
+Udprport: con 2*IPaddrlen;
+Udplport: con 2*IPaddrlen+2;
+dnsid := 1;
+
+mkquery(qtype: int, qclass: int, name: string): (int, array of byte, string)
+{
+ qd := ref QR(name, qtype, qclass);
+ dm := ref DNSmsg;
+ dm.id = dnsid++; # doesn't matter if two different procs use it
+ dm.flags = Oquery;
+ if(referdns || !debug)
+ dm.flags |= Frecurse;
+ dm.qd = qd :: nil;
+ a: array of byte;
+ a = dm.pack(Udphdrsize);
+ if(a == nil)
+ return (0, nil, "dns: bad query message"); # should only happen if a name is ridiculous
+ for(i:=0; i<Udphdrsize; i++)
+ a[i] = byte 0;
+ a[Udprport] = byte (DNSport>>8);
+ a[Udprport+1] = byte DNSport;
+ return (dm.id, a, nil);
+}
+
+udpquery(fd: ref Sys->FD, id: int, query: array of byte, sname: string, addr: ref RR): (ref DNSmsg, string)
+{
+ # TO DO: check address and ports?
+
+ if(debug)
+ sys->print("udp query %s\n", sname);
+ pick ar := addr {
+ A =>
+ query[Udpraddr:] = ip->v4prefix[0:IPv4off];
+ query[Udpraddr+IPv4off:] = ar.rdata[0:4];
+ * =>
+ return (nil, "not A resource");
+ }
+ dm: ref DNSmsg;
+ pidc := chan of int;
+ c := chan of array of byte;
+ spawn reader(fd, c, pidc);
+ rpid := <-pidc;
+ spawn timer(c, pidc);
+ tpid := <-pidc;
+ for(ntries := 0; ntries < 8; ntries++){
+ if(debug){
+ ipa := query[Udpraddr+IPv4off:];
+ sys->print("send udp!%d.%d.%d.%d!%d [%d] %d\n", int ipa[0], int ipa[1],
+ int ipa[2], int ipa[3], get2(query, Udprport).t0, ntries, len query);
+ }
+ n := sys->write(fd, query, len query);
+ if(n != len query)
+ return (nil, sys->sprint("udp write err: %r"));
+ buf := <-c;
+ if(buf != nil){
+ buf = buf[Udphdrsize:];
+ dm = DNSmsg.unpack(buf);
+ if(dm == nil){
+ kill(tpid);
+ kill(rpid);
+ return (nil, "bad udp reply message");
+ }
+ if(dm.flags & Fresp && dm.id == id){
+ if(dm.flags & Ftrunc && dm.ns == nil){
+ if(debug)
+ sys->print("id=%d was truncated\n", dm.id);
+ }else
+ break;
+ }else if(debug)
+ sys->print("id=%d got flags #%ux id %d\n", id, dm.flags, dm.id);
+ }else if(debug)
+ sys->print("timeout\n");
+ }
+ kill(tpid);
+ kill(rpid);
+ if(dm == nil)
+ return (nil, "no reply");
+ if(dm.err != nil){
+ sys->fprint(stderr, "bad reply: %s\n", dm.err);
+ return (nil, dm.err);
+ }
+ if(debug)
+ sys->print("reply: %s\n", dm.text());
+ return (dm, nil);
+}
+
+reader(fd: ref Sys->FD, c: chan of array of byte, pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil);
+ for(;;){
+ buf := array[4096+Udphdrsize] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n > 0){
+ if(debug)
+ sys->print("rcvd %d\n", n);
+ c <-= buf[0:n];
+ }else
+ c <-= nil;
+ }
+}
+
+timer(c: chan of array of byte, pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil);
+ for(;;){
+ sys->sleep(5*1000);
+ c <-= nil;
+ }
+}
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+}
+
+udpport(): ref Sys->FD
+{
+ (ok, conn) := sys->announce(mntpt+"/udp!*!0");
+ if(ok < 0)
+ return nil;
+ if(sys->fprint(conn.cfd, "headers") < 0){
+ sys->fprint(stderr, "dns: can't set headers mode: %r\n");
+ return nil;
+ }
+ sys->fprint(conn.cfd, "oldheaders"); # plan 9 interface
+ conn.dfd = sys->open(conn.dir+"/data", Sys->ORDWR);
+ if(conn.dfd == nil){
+ sys->fprint(stderr, "dns: can't open %s/data: %r\n", conn.dir);
+ return nil;
+ }
+ return conn.dfd;
+}
+
+#
+# TCP/IP can be used to get the whole of a truncated message
+#
+tcpquery(query: array of byte): (ref DNSmsg, string)
+{
+ # TO DO: check request id, ports etc.
+
+ ipa := query[Udpraddr+IPv4off:];
+ addr := sys->sprint("tcp!%d.%d.%d.%d!%d", int ipa[0], int ipa[1], int ipa[2], int ipa[3], DNSport);
+ (ok, conn) := sys->dial(addr, nil);
+ if(ok < 0)
+ return (nil, sys->sprint("can't dial %s: %r", addr));
+ query = query[Udphdrsize-2:];
+ put2(query, 0, len query-2); # replace UDP header by message length
+ n := sys->write(conn.dfd, query[Udphdrsize:], len query);
+ if(n != len query)
+ return (nil, sys->sprint("dns: %s: write err: %r", addr));
+ buf := readn(conn.dfd, 2); # TCP/DNS record header
+ (mlen, nil) := get2(buf, 0);
+ if(mlen < 2 || mlen > 16384)
+ return (nil, sys->sprint("dns: %s: bad reply msg length=%d", addr, mlen));
+ buf = readn(conn.dfd, mlen);
+ if(buf == nil)
+ return (nil, sys->sprint("dns: %s: read err: %r", addr));
+ dm := DNSmsg.unpack(buf);
+ if(dm == nil)
+ return (nil, "dns: bad reply message");
+ if(dm.err != nil){
+ sys->fprint(stderr, "dns: %s: bad reply: %s\n", addr, dm.err);
+ return (nil, dm.err);
+ }
+ return (dm, nil);
+}
+
+readn(fd: ref Sys->FD, nb: int): array of byte
+{
+ buf:= array[nb] of byte;
+ for(n:=0; n<nb;){
+ m := sys->read(fd, buf[n:], nb-n);
+ if(m <= 0)
+ return nil;
+ n += m;
+ }
+ return buf;
+}
+
+timefd: ref Sys->FD;
+
+time(): int
+{
+ if(timefd == nil){
+ timefd = sys->open("/dev/time", Sys->OREAD);
+ if(timefd == nil)
+ return 0;
+ }
+ buf := array[128] of byte;
+ sys->seek(timefd, big 0, 0);
+ n := sys->read(timefd, buf, len buf);
+ if(n < 0)
+ return 0;
+ return int ((big string buf[0:n]) / big 1000000);
+}
+
+parseip(s: string): array of byte
+{
+ (ok, a) := IPaddr.parse(s);
+ if(ok < 0 || !a.isv4())
+ return nil;
+ return a.v4();
+}
diff --git a/appl/cmd/ndb/dnsquery.b b/appl/cmd/ndb/dnsquery.b
new file mode 100644
index 00000000..194c08a2
--- /dev/null
+++ b/appl/cmd/ndb/dnsquery.b
@@ -0,0 +1,177 @@
+implement Dnsquery;
+
+#
+# Copyright © 2003 Vita Nuova Holdings LImited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+
+Dnsquery: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: dnsquery [-x /net] [-s server] [address ...]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ cantload(Bufio->PATH);
+
+ net := "/net";
+ server: string;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ cantload(Arg->PATH);
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'x' =>
+ net = arg->arg();
+ if(net == nil)
+ usage();
+ 's' =>
+ server = arg->arg();
+ if(server == nil)
+ usage();
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(server == nil)
+ server = net+"/dns";
+ if(args != nil){
+ for(; args != nil; args = tl args)
+ dnsquery(server, hd args);
+ }else{
+ f := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ if(f == nil)
+ exit;
+ for(;;){
+ sys->print("> ");
+ s := f.gets('\n');
+ if(s == nil)
+ break;
+ dnsquery(server, s[0:len s-1]);
+ }
+ }
+}
+
+cantload(s: string)
+{
+ sys->fprint(sys->fildes(2), "dnsquery: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+dnsquery(server: string, query: string)
+{
+ dns := sys->open(server, Sys->ORDWR);
+ if(dns == nil){
+ sys->fprint(sys->fildes(2), "dnsquery: can't open %s: %r\n", server);
+ raise "fail:open";
+ }
+ stdout := sys->fildes(1);
+ for(i := len query; --i >= 0 && query[i] != ' ';)
+ {}
+ if(i < 0){
+ i = len query;
+ case dbattr(query) {
+ "ip" =>
+ query += " ptr";
+ * =>
+ query += " ip";
+ }
+ }
+ if(query[i+1:] == "ptr"){
+ while(i > 0 && query[i-1] == ' ')
+ i--;
+ if(!hastail(query[0:i], ".in-addr.arpa") && !hastail(query[0:i], ".IN-ADDR.ARPA"))
+ query = addr2arpa(query[0:i])+" ptr";
+ }
+ b := array of byte query;
+ if(sys->write(dns, b, len b) > 0){
+ sys->seek(dns, big 0, Sys->SEEKSTART);
+ buf := array[256] of byte;
+ while((n := sys->read(dns, buf, len buf)) > 0)
+ sys->print("%s\n", string buf[0:n]);
+ if(n == 0)
+ return;
+ }
+ sys->print("!%r\n");
+}
+
+hastail(s: string, t: string): int
+{
+ if(len s >= len t && s[len s - len t:] == t)
+ return 1;
+ return 0;
+}
+
+addr2arpa(a: string): string
+{
+ (nf, flds) := sys->tokenize(a, ".");
+ rl: list of string;
+ for(; flds != nil; flds = tl flds)
+ rl = hd flds :: rl;
+ addr: string;
+ for(; rl != nil; rl = tl rl){
+ if(addr != nil)
+ addr[len addr] = '.';
+ addr += hd rl;
+ }
+ return addr+".in-addr.arpa";
+}
+
+dbattr(s: string): string
+{
+ digit := 0;
+ dot := 0;
+ alpha := 0;
+ hex := 0;
+ colon := 0;
+ for(i := 0; i < len s; i++){
+ case c := s[i] {
+ '0' to '9' =>
+ digit = 1;
+ 'a' to 'f' or 'A' to 'F' =>
+ hex = 1;
+ '.' =>
+ dot = 1;
+ ':' =>
+ colon = 1;
+ * =>
+ if(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '-' || c == '&')
+ alpha = 1;
+ }
+ }
+ if(alpha){
+ if(dot)
+ return "dom";
+ return "sys";
+ }
+ if(colon)
+ return "ip";
+ if(dot){
+ if(!hex)
+ return "ip";
+ return "dom";
+ }
+ return "sys";
+}
diff --git a/appl/cmd/ndb/mkfile b/appl/cmd/ndb/mkfile
new file mode 100644
index 00000000..fb1a7074
--- /dev/null
+++ b/appl/cmd/ndb/mkfile
@@ -0,0 +1,28 @@
+<../../../mkconfig
+
+TARG=\
+ cs.dis\
+ csquery.dis\
+ dns.dis\
+ dnsquery.dis\
+ mkhash.dis\
+ query.dis\
+ registry.dis\
+ regquery.dis\
+
+SYSMODULES=\
+ sys.m\
+ draw.m\
+ bufio.m\
+ arg.m\
+ attrdb.m\
+ ip.m\
+ ipattr.m\
+ styx.m\
+ styxservers.m\
+
+MODULES=\
+
+DISBIN=$ROOT/dis/ndb
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/ndb/mkhash.b b/appl/cmd/ndb/mkhash.b
new file mode 100644
index 00000000..f1876355
--- /dev/null
+++ b/appl/cmd/ndb/mkhash.b
@@ -0,0 +1,119 @@
+implement Mkhash;
+
+#
+# for compatibility, this is closely modelled on Plan 9's ndb/mkhash
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+include "attrdb.m";
+ attrdb: Attrdb;
+ Db, Dbf, Dbentry, Tuples, Attr: import attrdb;
+ attrhash: Attrhash;
+ NDBPLEN, NDBHLEN, NDBCHAIN, NDBNAP: import Attrhash;
+
+Mkhash: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ error(sys->sprint("can't load %s: %r", Attrdb->PATH));
+ attrdb->init();
+ attrhash = load Attrhash Attrhash->PATH;
+ if(attrhash == nil)
+ error(sys->sprint("can't load %s: %r", Attrhash->PATH));
+
+ if(len args != 3)
+ error("usage: mkhash file attr");
+ args = tl args;
+ dbname := hd args;
+ args = tl args;
+ attr := hd args;
+ dbf := Dbf.open(dbname);
+ if(dbf == nil)
+ error(sys->sprint("can't open %s: %r", dbname));
+ offset := 0;
+ n := 0;
+ for(;;){
+ (e, nil, next) := dbf.readentry(offset, nil, nil, 0);
+ if(e == nil)
+ break;
+ m := len e.find(attr);
+ if(0 && m != 0)
+ sys->fprint(sys->fildes(2), "%ud [%d]\n", offset, m);
+ n += m;
+ offset = next;
+ }
+ hlen := 2*n+1;
+ chains := n*2*NDBPLEN;
+ file := array[NDBHLEN + hlen*NDBPLEN + chains] of byte;
+ tab := file[NDBHLEN:];
+ for(i:=0; i<len tab; i+=NDBPLEN)
+ put3(tab[i:], NDBNAP);
+ offset = 0;
+ chain := hlen*NDBPLEN;
+ for(;;){
+ (e, nil, next) := dbf.readentry(offset, nil, nil, 0);
+ if(e == nil)
+ break;
+ for(l := e.find(attr); l != nil; l = tl l)
+ for((nil, al) := hd l; al != nil; al = tl al)
+ chain = enter(tab, hd al, hlen, chain, offset);
+ offset = next;
+ }
+ hashfile := dbname+"."+attr;
+ hfd := sys->create(hashfile, Sys->OWRITE, 8r666);
+ if(hfd == nil)
+ error(sys->sprint("can't create %s: %r", hashfile));
+ mtime := 0;
+ if(dbf.dir != nil)
+ mtime = dbf.dir.mtime;
+ put4(file, mtime);
+ put4(file[4:], hlen);
+ if(sys->write(hfd, file, NDBHLEN+chain) != NDBHLEN+chain)
+ error(sys->sprint("error writing %s: %r", hashfile));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "mkhash: %s\n", s);
+ raise "fail:error";
+}
+
+enter(tab: array of byte, a: ref Attr, hlen: int, chain: int, offset: int): int
+{
+ o := attrhash->hash(a.val, hlen)*NDBPLEN;
+ for(; (p := attrhash->get3(tab[o:])) != NDBNAP; o = p & ~NDBCHAIN)
+ if((p & NDBCHAIN) == 0){
+ put3(tab[o:], chain | NDBCHAIN);
+ put3(tab[chain:], p);
+ put3(tab[chain+NDBPLEN:], offset);
+ return chain+2*NDBPLEN;
+ }
+ put3(tab[o:], offset);
+ return chain;
+}
+
+put3(a: array of byte, v: int)
+{
+ a[0] = byte v;
+ a[1] = byte (v>>8);
+ a[2] = byte (v>>16);
+}
+
+put4(a: array of byte, v: int)
+{
+ a[0] = byte v;
+ a[1] = byte (v>>8);
+ a[2] = byte (v>>16);
+ a[3] = byte (v>>24);
+}
diff --git a/appl/cmd/ndb/query.b b/appl/cmd/ndb/query.b
new file mode 100644
index 00000000..b636492d
--- /dev/null
+++ b/appl/cmd/ndb/query.b
@@ -0,0 +1,135 @@
+implement Query;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+
+include "attrdb.m";
+ attrdb: Attrdb;
+ Attr, Tuples, Dbentry, Db: import attrdb;
+
+include "arg.m";
+
+Query: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: query attr [value [rattr]]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ dbfile := "/lib/ndb/local";
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ badload(Arg->PATH);
+ arg->init(args);
+ arg->setusage("query [-a] [-f dbfile] attr [value [rattr]]");
+ all := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'f' => dbfile = arg->earg();
+ 'a' => all = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ attr := hd args;
+ args = tl args;
+ value, rattr: string;
+ vflag := 0;
+ if(args != nil){
+ vflag = 1;
+ value = hd args;
+ args = tl args;
+ if(args != nil)
+ rattr = hd args;
+ }
+ arg = nil;
+
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ badload(Attrdb->PATH);
+ err := attrdb->init();
+ if(err != nil)
+ error(sys->sprint("can't init Attrdb: %s", err));
+
+ db := Db.open(dbfile);
+ if(db == nil)
+ error(sys->sprint("can't open %s: %r", dbfile));
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ if(rattr != nil)
+ (e, ptr) = db.findbyattr(ptr, attr, value, rattr);
+ else if(vflag)
+ (e, ptr) = db.findpair(ptr, attr, value);
+ else
+ (e, ptr) = db.find(ptr, attr);
+ if(e == nil)
+ break;
+ if(rattr != nil){
+ matches: list of (ref Tuples, list of ref Attr);
+ if(rattr != nil)
+ matches = e.findbyattr(attr, value, rattr);
+ else
+ matches = e.find(attr);
+ for(; matches != nil; matches = tl matches){
+ (line, attrs) := hd matches;
+ if(attrs != nil)
+ printvals(attrs, all);
+ if(!all)
+ exit;
+ }
+ }else
+ printentry(e);
+ if(!all)
+ exit;
+ }
+}
+
+badload(s: string)
+{
+ error(sys->sprint("can't load %s: %r", s));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "query: %s\n", s);
+ raise "fail:error";
+}
+
+printentry(e: ref Dbentry)
+{
+ s := "";
+ for(lines := e.lines; lines != nil; lines = tl lines){
+ line := hd lines;
+ for(al := line.pairs; al != nil; al = tl al){
+ a := hd al;
+ s += sys->sprint(" %q=%q", a.attr, a.val);
+ }
+ }
+ if(s != "")
+ s = s[1:];
+ sys->print("%s\n", s);
+}
+
+printvals(al: list of ref Attr, all: int)
+{
+ for(; al != nil; al = tl al){
+ a := hd al;
+ sys->print("%q\n", a.val);
+ if(!all)
+ break;
+ }
+}
diff --git a/appl/cmd/ndb/registry.b b/appl/cmd/ndb/registry.b
new file mode 100644
index 00000000..f720781f
--- /dev/null
+++ b/appl/cmd/ndb/registry.b
@@ -0,0 +1,671 @@
+implement Registry;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "string.m";
+ str: String;
+include "daytime.m";
+ daytime: Daytime;
+include "bufio.m";
+include "attrdb.m";
+ attrdb: Attrdb;
+ Db, Dbf, Dbentry: import attrdb;
+include "styx.m";
+ styx: Styx;
+ Rmsg, Tmsg: import styx;
+include "styxservers.m";
+ styxservers: Styxservers;
+ Styxserver, Fid, Navigator, Navop: import styxservers;
+ Enotdir, Enotfound: import Styxservers;
+include "arg.m";
+
+# files:
+# 'new'
+# write name of new service; (and possibly attribute column names)
+# entry appears in directory of that name
+# can then write attributes/values
+# 'index'
+# read to get info on all services and their attributes.
+# 'find'
+# write to set filter.
+# read to get info on all services with matching attributes
+# 'event' (not needed initially)
+# read to block until changes happen.
+# servicename
+# write to change attributes (only by owner)
+# remove to unregister service.
+
+Registry: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+Qroot,
+Qnew,
+Qindex,
+Qevent,
+Qfind,
+Qsvc: con iota;
+
+
+Shift: con 4;
+Mask: con 2r1111;
+
+Egreg: con "buggy program!";
+Maxreplyidle: con 3;
+
+Service: adt {
+ id: int;
+ slot: int;
+ owner: string;
+ name: string;
+ atime: int;
+ mtime: int;
+ vers: int;
+ fid: int; # fid that created it (NOFID if static)
+ attrs: list of (string, string);
+
+ new: fn(owner: string): ref Service;
+ find: fn(id: int): ref Service;
+ remove: fn(svc: self ref Service);
+ set: fn(svc: self ref Service, attr, val: string);
+ get: fn(svc: self ref Service, attr: string): string;
+};
+
+Filter: adt {
+ id: int; # filter ID (it's a fid)
+ attrs: array of (string, string);
+
+ new: fn(id: int): ref Filter;
+ find: fn(id: int): ref Filter;
+ set: fn(f: self ref Filter, a: array of (string, string));
+ match: fn(f: self ref Filter, attrs: list of (string, string)): int;
+ remove: fn(f: self ref Filter);
+};
+
+filters: list of ref Filter;
+
+
+services := array[9] of ref Service;
+nservices := 0;
+idseq := 0;
+rootvers := 0;
+now: int;
+startdate: int;
+dbfile: string;
+
+srv: ref Styxserver;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ if(str == nil)
+ loaderr(String->PATH);
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ loaderr(Daytime->PATH);
+ styx = load Styx Styx->PATH;
+ if (styx == nil)
+ loaderr(Styx->PATH);
+ styx->init();
+ styxservers = load Styxservers Styxservers->PATH;
+ if (styxservers == nil)
+ loaderr(Styxservers->PATH);
+ styxservers->init(styx);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ loaderr(Arg->PATH);
+ arg->init(args);
+ arg->setusage("ndb/registry [-f initdb]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'f' => dbfile = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->FORKNS|Sys->NEWFD, 0::1::2::nil);
+ startdate = now = daytime->now();
+ if(dbfile != nil){
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ loaderr(Attrdb->PATH);
+ attrdb->init();
+ db := Db.open(dbfile);
+ if(db == nil)
+ error(sys->sprint("can't open %s: %r", dbfile));
+ dbload(db);
+ db = nil; # for now assume it's static
+ }
+ navops := chan of ref Navop;
+ spawn navigator(navops);
+ tchan: chan of ref Tmsg;
+ (tchan, srv) = Styxserver.new(sys->fildes(0), Navigator.new(navops), big Qroot);
+ spawn serve(tchan, navops);
+}
+
+loaderr(p: string)
+{
+ error(sys->sprint("can't load %s: %r", p));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "registry: %s\n", s);
+ raise "fail:error";
+}
+
+serve(tchan: chan of ref Tmsg, navops: chan of ref Navop)
+{
+Serve:
+ while((gm := <-tchan) != nil){
+ now = daytime->now();
+ err := "";
+ pick m := gm {
+ Readerror =>
+ error(sys->sprint("fatal read error: %s\n", m.error));
+ break Serve;
+ Open =>
+ (fid, mode, d, e) := srv.canopen(m);
+ if((err = e) != nil)
+ break;
+ if(fid.qtype & Sys->QTDIR)
+ srv.default(m);
+ else
+ open(m, fid);
+ Read =>
+ (fid, e) := srv.canread(m);
+ if((err = e) != nil)
+ break;
+ if(fid.qtype & Sys->QTDIR)
+ srv.read(m);
+ else
+ err = read(m, fid);
+ Write =>
+ (fid, e) := srv.canwrite(m);
+ if((err = e) != nil)
+ break;
+ err = write(m, fid);
+ if(err == nil)
+ srv.reply(ref Rmsg.Write(m.tag, len m.data));
+ Clunk =>
+ clunk(srv.clunk(m));
+ Remove =>
+ (fid, nil, e) := srv.canremove(m);
+ srv.delfid(fid); # always clunked even on error
+ if((err = e) != nil)
+ break;
+ err = remove(fid);
+ if(err == nil)
+ srv.reply(ref Rmsg.Remove(m.tag));
+ * =>
+ srv.default(gm);
+ }
+ if(err != "")
+ srv.reply(ref Rmsg.Error(gm.tag, err));
+ }
+ navops <-= nil;
+}
+
+open(m: ref Tmsg.Open, fid: ref Fid)
+{
+ path := int fid.path;
+ case path & Mask {
+ Qnew =>
+ svc := Service.new(fid.uname);
+ svc.fid = fid.fid;
+ fid.open(m.mode, (big ((svc.id << Shift)|Qsvc), 0, Sys->QTFILE));
+ * =>
+ fid.open(m.mode, (fid.path, 0, fid.qtype));
+ }
+ srv.reply(ref Rmsg.Open(m.tag, (fid.path, 0, fid.qtype), 0));
+}
+
+read(m: ref Tmsg.Read, fid: ref Fid): string
+{
+ path := int fid.path;
+ case path & Mask {
+ Qindex =>
+ if(fid.data == nil || m.offset == big 0)
+ fid.data = getindexdata(-1, Styx->NOFID);
+ srv.reply(styxservers->readbytes(m, fid.data));
+ Qfind =>
+ if(fid.data == nil || m.offset == big 0)
+ fid.data = getindexdata(-1, fid.fid);
+ srv.reply(styxservers->readbytes(m, fid.data));
+ Qsvc =>
+ if(fid.data == nil || m.offset == big 0){
+ svc := Service.find(path >> Shift);
+ if(svc != nil)
+ svc.atime = now;
+ fid.data = getindexdata(path >> Shift, Styx->NOFID);
+ }
+ srv.reply(styxservers->readbytes(m, fid.data));
+ Qevent =>
+ return "not implemented yet";
+ * =>
+ return Egreg;
+ }
+ return nil;
+}
+
+write(m: ref Tmsg.Write, fid: ref Fid): string
+{
+ path := int fid.path;
+ case path & Mask {
+ Qsvc =>
+ svc := Service.find(path >> Shift);
+ if(svc == nil)
+ return Egreg;
+ s := string m.data;
+ toks := str->unquoted(s);
+ if(toks == nil)
+ return "bad syntax";
+ # first write names the service (possibly with attributes)
+ if(svc.name == nil){
+ if((e := svcnameok(hd toks)) != nil)
+ return "bad service name";
+ svc.name = hd toks;
+ toks = tl toks;
+ }
+ if(len toks % 2 != 0)
+ return "odd attribute/value pairs";
+ svc.mtime = now;
+ svc.vers++;
+ for(; toks != nil; toks = tl tl toks)
+ svc.set(hd toks, hd tl toks);
+ Qfind =>
+ s := string m.data;
+ toks := str->unquoted(s);
+ n := len toks;
+ if(n % 2 != 0)
+ return "odd attribute/value pairs";
+ f := Filter.find(fid.fid);
+ if(n != 0){
+ a := array[n/2] of (string, string);
+ for(n=0; toks != nil; n++){
+ a[n] = (hd toks, hd tl toks);
+ toks = tl tl toks;
+ }
+ if(f == nil)
+ f = Filter.new(fid.fid);
+ f.set(a);
+ }else{
+ if(f != nil)
+ f.remove();
+ }
+ * =>
+ return Egreg;
+ }
+ return nil;
+}
+
+clunk(fid: ref Fid)
+{
+ path := int fid.path;
+ case path & Mask {
+ Qsvc =>
+ svc := Service.find(path >> Shift);
+ if(svc != nil && svc.fid == fid.fid && int svc.get("persist") == 0)
+ svc.remove();
+ Qevent =>
+ ; # remove queued events?
+ Qfind =>
+ if((f := Filter.find(fid.fid)) != nil)
+ f.remove();
+ }
+}
+
+remove(fid: ref Fid): string
+{
+ path := int fid.path;
+ if((path & Mask) == Qsvc){
+ svc := Service.find(path >> Shift);
+ if(fid.uname == svc.owner){
+ svc.remove();
+ return nil;
+ }
+ }
+ return "permission denied";
+}
+
+svcnameok(s: string): string
+{
+ # could require that a service name contains at least one (or two) '!' characters.
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ if(c <= 32 || c == '/' || c == 16r7f)
+ return "bad character in service name";
+ }
+ case s {
+ "new" or
+ "event" or
+ "find" or
+ "index" =>
+ return "bad service name";
+ }
+ for(i = 0; i < nservices; i++)
+ if(services[i].name == s)
+ return "duplicate service name";
+ return nil;
+}
+
+getindexdata(id: int, filterid: int): array of byte
+{
+ f: ref Filter;
+ if(filterid != Styx->NOFID)
+ f = Filter.find(filterid);
+ s := "";
+ for(i := 0; i < nservices; i++){
+ svc := services[i];
+ if(svc == nil || svc.name == nil)
+ continue;
+ if(id == -1){
+ if(f != nil && !f.match(svc.attrs))
+ continue;
+ }else if(svc.id != id)
+ continue;
+ s += sys->sprint("%q", services[i].name);
+ for(a := svc.attrs; a != nil; a = tl a){
+ (attr, val) := hd a;
+ s += sys->sprint(" %q %q", attr, val);
+ }
+ s[len s] = '\n';
+ }
+ return array of byte s;
+}
+
+navigator(navops: chan of ref Navop)
+{
+ while((m := <-navops) != nil){
+ path := int m.path;
+ pick n := m {
+ Stat =>
+ n.reply <-= dirgen(int n.path);
+ Walk =>
+ name := n.name;
+ case path & Mask {
+ Qroot =>
+ case name{
+ ".." =>
+ ; # nop
+ "new" =>
+ path = Qnew;
+ "index" =>
+ path = Qindex;
+ "event" =>
+ path = Qevent;
+ "find" =>
+ path = Qfind;
+ * =>
+ for(i := 0; i < nservices; i++)
+ if(services[i].name == name){
+ path = (services[i].id << Shift) | Qsvc;
+ break;
+ }
+ if(i == nservices){
+ n.reply <-= (nil, Enotfound);
+ continue;
+ }
+ }
+ * =>
+ if(name == ".."){
+ path = Qroot;
+ break;
+ }
+ n.reply <-= (nil, Enotdir);
+ continue;
+ }
+ n.reply <-= dirgen(path);
+ Readdir =>
+ d: array of int;
+ case path & Mask {
+ Qroot =>
+ Nstatic: con 3;
+ d = array[Nstatic + nservices] of int;
+ d[0] = Qnew;
+ d[1] = Qindex;
+ d[2] = Qfind;
+# d[3] = Qevent;
+ for(i := 0; i < nservices; i++)
+ if(services[i].name != nil)
+ d[i + Nstatic] = (services[i].id<<Shift) | Qsvc;
+ }
+ if(d == nil){
+ n.reply <-= (nil, Enotdir);
+ break;
+ }
+ for (i := n.offset; i < len d; i++)
+ n.reply <-= dirgen(d[i]);
+ n.reply <-= (nil, nil);
+ }
+ }
+}
+
+dirgen(path: int): (ref Sys->Dir, string)
+{
+ name: string;
+ perm: int;
+ svc: ref Service;
+ case path & Mask {
+ Qroot =>
+ name = ".";
+ perm = 8r777|Sys->DMDIR;
+ Qnew =>
+ name = "new";
+ perm = 8r666;
+ Qindex =>
+ name = "index";
+ perm = 8r444;
+ Qevent =>
+ name = "event";
+ perm = 8r444;
+ Qfind =>
+ name = "find";
+ perm = 8r666;
+ Qsvc =>
+ id := path >> Shift;
+ for(i := 0; i < nservices; i++)
+ if(services[i].id == id)
+ break;
+ if(i >= nservices)
+ return (nil, Enotfound);
+ svc = services[i];
+ name = svc.name;
+ perm = 8r644;
+ * =>
+ return (nil, Enotfound);
+ }
+ return (dir(path, name, perm, svc), nil);
+}
+
+dir(path: int, name: string, perm: int, svc: ref Service): ref Sys->Dir
+{
+ d := ref sys->zerodir;
+ d.qid.path = big path;
+ if(perm & Sys->DMDIR)
+ d.qid.qtype = Sys->QTDIR;
+ d.mode = perm;
+ d.name = name;
+ if(svc != nil){
+ d.uid = svc.owner;
+ d.gid = svc.owner;
+ d.atime = svc.atime;
+ d.mtime = svc.mtime;
+ d.qid.vers = svc.vers;
+ }else{
+ d.uid = "registry";
+ d.gid = "registry";
+ d.atime = startdate;
+ d.mtime = startdate;
+ if(path == Qroot)
+ d.qid.vers = rootvers;
+ }
+ return d;
+}
+
+blanksvc: Service;
+Service.new(owner: string): ref Service
+{
+ if(nservices == len services){
+ s := array[nservices * 3 / 2] of ref Service;
+ s[0:] = services;
+ services = s;
+ }
+ svc := ref blanksvc;
+ svc.id = idseq++;
+ svc.owner = owner;
+ svc.atime = now;
+ svc.mtime = now;
+
+ services[nservices] = svc;
+ svc.slot = nservices;
+ nservices++;
+ rootvers++;
+ return svc;
+}
+
+Service.find(id: int): ref Service
+{
+ for(i := 0; i < nservices; i++)
+ if(services[i].id == id)
+ return services[i];
+ return nil;
+}
+
+Service.remove(svc: self ref Service)
+{
+ slot := svc.slot;
+ services[slot] = nil;
+ nservices--;
+ rootvers++;
+ if(slot != nservices){
+ services[slot] = services[nservices];
+ services[slot].slot = slot;
+ services[nservices] = nil;
+ }
+}
+
+Service.get(svc: self ref Service, attr: string): string
+{
+ for(a := svc.attrs; a != nil; a = tl a)
+ if((hd a).t0 == attr)
+ return (hd a).t1;
+ return nil;
+}
+
+Service.set(svc: self ref Service, attr, val: string)
+{
+ for(a := svc.attrs; a != nil; a = tl a)
+ if((hd a).t0 == attr)
+ break;
+ if(a == nil){
+ svc.attrs = (attr, val) :: svc.attrs;
+ return;
+ }
+ attrs := (attr, val) :: tl a;
+ for(a = svc.attrs; a != nil; a = tl a){
+ if((hd a).t0 == attr)
+ break;
+ attrs = hd a :: attrs;
+ }
+ svc.attrs = attrs;
+}
+
+Filter.new(id: int): ref Filter
+{
+ f := ref Filter(id, nil);
+ filters = f :: filters;
+ return f;
+}
+
+Filter.find(id: int): ref Filter
+{
+ if(id != Styx->NOFID)
+ for(fl := filters; fl != nil; fl = tl fl)
+ if((hd fl).id == id)
+ return hd fl;
+ return nil;
+}
+
+Filter.set(f: self ref Filter, a: array of (string, string))
+{
+ f.attrs = a;
+}
+
+Filter.remove(f: self ref Filter)
+{
+ rl: list of ref Filter;
+ for(l := filters; l != nil; l = tl l)
+ if((hd l).id != f.id)
+ rl = hd l :: rl;
+ filters = rl;
+}
+
+Filter.match(f: self ref Filter, attrs: list of (string, string)): int
+{
+ for(i := 0; i < len f.attrs; i++){
+ (qn, qv) := f.attrs[i];
+ for(al := attrs; al != nil; al = tl al){
+ (n, v) := hd al;
+ if(n == qn && (qv == "*" || v == qv))
+ break;
+ }
+ if(al == nil)
+ break;
+ }
+ return i == len f.attrs;
+}
+
+dbload(db: ref Db)
+{
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ (e, ptr) = db.find(ptr, "service");
+ if(e == nil)
+ break;
+ svcname := e.findfirst("service");
+ if(svcname == nil || svcnameok(svcname) != nil)
+ continue;
+ svc := Service.new("registry"); # TO DO: read user's name
+ svc.name = svcname;
+ svc.fid = Styx->NOFID;
+ for(l := e.lines; l != nil; l = tl l){
+ for(al := (hd l).pairs; al != nil; al = tl al){
+ a := hd al;
+ if(a.attr != "service")
+ svc.set(a.attr, a.val);
+ }
+ }
+ }
+}
+
+# return index i >= start such that
+# s[i-1] == eoc, or len s if no such index exists.
+# eoc shouldn't be '
+qsplit(s: string, start: int, eoc: int): int
+{
+ inq := 0;
+ for(i := start; i < len s;){
+ c := s[i++];
+ if(inq){
+ if(c == '\'' && i < len s){
+ if(s[i] == '\'')
+ i++;
+ else
+ inq = 0;
+ }
+ }else{
+ if(c == eoc)
+ return i;
+ if(c == '\'')
+ inq = 1;
+ }
+ }
+ return i;
+}
diff --git a/appl/cmd/ndb/regquery.b b/appl/cmd/ndb/regquery.b
new file mode 100644
index 00000000..f7f32462
--- /dev/null
+++ b/appl/cmd/ndb/regquery.b
@@ -0,0 +1,104 @@
+implement Regquery;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+
+Regquery: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ cantload(Bufio->PATH);
+ str = load String String->PATH;
+ if(str == nil)
+ cantload(String->PATH);
+
+ mntpt := "/mnt/registry";
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ cantload(Arg->PATH);
+ arg->init(args);
+ arg->setusage("regquery [-m mntpt] [-n] [attr val attr val ...]");
+ namesonly := 0;
+ while((c := arg->opt()) != 0)
+ case c {
+ 'm' => mntpt = arg->earg();
+ 'n' => namesonly = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ finder := mntpt+"/find";
+ if(args != nil){
+ s := "";
+ for(; args != nil; args = tl args)
+ s += sys->sprint(" %q", hd args);
+ if(s != nil)
+ s = s[1:];
+ regquery(finder, s, namesonly);
+ }else{
+ f := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ if(f == nil)
+ exit;
+ for(;;){
+ sys->print("> ");
+ s := f.gets('\n');
+ if(s == nil)
+ break;
+ regquery(finder, s[0:len s-1], namesonly);
+ }
+ }
+}
+
+cantload(s: string)
+{
+ sys->fprint(sys->fildes(2), "regquery: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+regquery(server: string, addr: string, namesonly: int)
+{
+ fd := sys->open(server, Sys->ORDWR);
+ if(fd == nil){
+ sys->fprint(sys->fildes(2), "regquery: can't open %s: %r\n", server);
+ raise "fail:open";
+ }
+ stdout := sys->fildes(1);
+ b := array of byte addr;
+ if(sys->write(fd, b, len b) >= 0){
+ sys->seek(fd, big 0, Sys->SEEKSTART);
+ if(namesonly){
+ bio := bufio->fopen(fd, Bufio->OREAD);
+ while((s := bio.gets('\n')) != nil){
+ l := str->unquoted(s);
+ if(l != nil)
+ sys->print("%s\n", hd l);
+ }
+ return;
+ }else{
+ buf := array[Sys->ATOMICIO] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0)
+ sys->print("%s", string buf[0:n]);
+ if(n == 0)
+ return;
+ }
+ }
+ sys->fprint(sys->fildes(2), "regquery: %r\n");
+}
diff --git a/appl/cmd/netkey.b b/appl/cmd/netkey.b
new file mode 100644
index 00000000..fc68c22f
--- /dev/null
+++ b/appl/cmd/netkey.b
@@ -0,0 +1,166 @@
+implement Netkey;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ keyring: Keyring;
+
+Netkey: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+ANAMELEN: con 28;
+DESKEYLEN: con 7;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ keyring = load Keyring Keyring->PATH;
+
+ if(len args > 1){
+ sys->fprint(sys->fildes(2), "usage: netkey\n");
+ raise "fail:usage";
+ }
+ (pw, err) := readconsline("Password: ", 1);
+ if(err != nil){
+ sys->fprint(sys->fildes(2), "netkey: %s\n", err);
+ raise "fail:error";
+ }
+ if(pw != nil)
+ while((chal := readconsline("challenge: ", 0).t0) != nil)
+ sys->print("response: %s\n", netcrypt(passtokey(pw), string int chal));
+}
+
+readconsline(prompt: string, raw: int): (string, string)
+{
+ fd := sys->open("/dev/cons", Sys->ORDWR);
+ if(fd == nil)
+ return (nil, sys->sprint("can't open cons: %r"));
+ sys->fprint(fd, "%s", prompt);
+ fdctl: ref Sys->FD;
+ if(raw){
+ fdctl = sys->open("/dev/consctl", sys->OWRITE);
+ if(fdctl == nil || sys->fprint(fdctl, "rawon") < 0)
+ return (nil, sys->sprint("can't open consctl: %r"));
+ }
+ line := array[256] of byte;
+ o := 0;
+ err: string;
+ buf := array[1] of byte;
+ Read:
+ while((r := sys->read(fd, buf, len buf)) > 0){
+ c := int buf[0];
+ case c {
+ 16r7F =>
+ err = "interrupt";
+ break Read;
+ '\b' =>
+ if(o > 0)
+ o--;
+ '\n' or '\r' or 16r4 =>
+ break Read;
+ * =>
+ if(o > len line){
+ err = "line too long";
+ break Read;
+ }
+ line[o++] = byte c;
+ }
+ }
+ if(r < 0)
+ err = sys->sprint("can't read cons: %r");
+ if(raw){
+ sys->fprint(fdctl, "rawoff");
+ sys->fprint(fd, "\n");
+ }
+ if(err != nil)
+ return (nil, err);
+ return (string line[0:o], err);
+}
+
+#
+# duplicates auth9 but keeps this self-contained
+#
+
+netcrypt(key: array of byte, chal: string): string
+{
+ buf := array[8] of {* => byte 0};
+ a := array of byte chal;
+ if(len a > 7)
+ a = a[0:7];
+ buf[0:] = a;
+ encrypt(key, buf, len buf);
+ return sys->sprint("%.2ux%.2ux%.2ux%.2ux", int buf[0], int buf[1], int buf[2], int buf[3]);
+}
+
+passtokey(p: string): array of byte
+{
+ a := array of byte p;
+ n := len a;
+ if(n >= ANAMELEN)
+ n = ANAMELEN-1;
+ buf := array[ANAMELEN] of {* => byte ' '};
+ buf[0:] = a[0:n];
+ buf[n] = byte 0;
+ key := array[DESKEYLEN] of {* => byte 0};
+ t := 0;
+ for(;;){
+ for(i := 0; i < DESKEYLEN; i++)
+ key[i] = byte ((int buf[t+i] >> i) + (int buf[t+i+1] << (8 - (i+1))));
+ if(n <= 8)
+ return key;
+ n -= 8;
+ t += 8;
+ if(n < 8){
+ t -= 8 - n;
+ n = 8;
+ }
+ encrypt(key, buf[t:], 8);
+ }
+}
+
+parity := array[] of {
+ byte 16r01, byte 16r02, byte 16r04, byte 16r07, byte 16r08, byte 16r0b, byte 16r0d, byte 16r0e,
+ byte 16r10, byte 16r13, byte 16r15, byte 16r16, byte 16r19, byte 16r1a, byte 16r1c, byte 16r1f,
+ byte 16r20, byte 16r23, byte 16r25, byte 16r26, byte 16r29, byte 16r2a, byte 16r2c, byte 16r2f,
+ byte 16r31, byte 16r32, byte 16r34, byte 16r37, byte 16r38, byte 16r3b, byte 16r3d, byte 16r3e,
+ byte 16r40, byte 16r43, byte 16r45, byte 16r46, byte 16r49, byte 16r4a, byte 16r4c, byte 16r4f,
+ byte 16r51, byte 16r52, byte 16r54, byte 16r57, byte 16r58, byte 16r5b, byte 16r5d, byte 16r5e,
+ byte 16r61, byte 16r62, byte 16r64, byte 16r67, byte 16r68, byte 16r6b, byte 16r6d, byte 16r6e,
+ byte 16r70, byte 16r73, byte 16r75, byte 16r76, byte 16r79, byte 16r7a, byte 16r7c, byte 16r7f,
+ byte 16r80, byte 16r83, byte 16r85, byte 16r86, byte 16r89, byte 16r8a, byte 16r8c, byte 16r8f,
+ byte 16r91, byte 16r92, byte 16r94, byte 16r97, byte 16r98, byte 16r9b, byte 16r9d, byte 16r9e,
+ byte 16ra1, byte 16ra2, byte 16ra4, byte 16ra7, byte 16ra8, byte 16rab, byte 16rad, byte 16rae,
+ byte 16rb0, byte 16rb3, byte 16rb5, byte 16rb6, byte 16rb9, byte 16rba, byte 16rbc, byte 16rbf,
+ byte 16rc1, byte 16rc2, byte 16rc4, byte 16rc7, byte 16rc8, byte 16rcb, byte 16rcd, byte 16rce,
+ byte 16rd0, byte 16rd3, byte 16rd5, byte 16rd6, byte 16rd9, byte 16rda, byte 16rdc, byte 16rdf,
+ byte 16re0, byte 16re3, byte 16re5, byte 16re6, byte 16re9, byte 16rea, byte 16rec, byte 16ref,
+ byte 16rf1, byte 16rf2, byte 16rf4, byte 16rf7, byte 16rf8, byte 16rfb, byte 16rfd, byte 16rfe,
+};
+
+des56to64(k56: array of byte): array of byte
+{
+ k64 := array[8] of byte;
+ hi := (int k56[0]<<24)|(int k56[1]<<16)|(int k56[2]<<8)|int k56[3];
+ lo := (int k56[4]<<24)|(int k56[5]<<16)|(int k56[6]<<8);
+
+ k64[0] = parity[(hi>>25)&16r7f];
+ k64[1] = parity[(hi>>18)&16r7f];
+ k64[2] = parity[(hi>>11)&16r7f];
+ k64[3] = parity[(hi>>4)&16r7f];
+ k64[4] = parity[((hi<<3)|int ((big lo & big 16rFFFFFFFF)>>29))&16r7f]; # watch the sign extension
+ k64[5] = parity[(lo>>22)&16r7f];
+ k64[6] = parity[(lo>>15)&16r7f];
+ k64[7] = parity[(lo>>8)&16r7f];
+ return k64;
+}
+
+encrypt(key: array of byte, data: array of byte, n: int)
+{
+ ds := keyring->dessetup(des56to64(key), nil);
+ keyring->desecb(ds, data, n, Keyring->Encrypt);
+}
diff --git a/appl/cmd/netstat.b b/appl/cmd/netstat.b
new file mode 100644
index 00000000..c9ce2216
--- /dev/null
+++ b/appl/cmd/netstat.b
@@ -0,0 +1,91 @@
+implement Netstat;
+
+include "sys.m";
+sys: Sys;
+FD, Dir: import sys;
+fildes, open, fstat, read, dirread, fprint, print, tokenize: import sys;
+
+include "draw.m";
+Context: import Draw;
+
+Netstat: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+stderr: ref FD;
+
+init(nil: ref Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ stderr = fildes(2);
+
+ nstat("/net/tcp", 1);
+ nstat("/net/udp", 1);
+ nstat("/net/il", 0);
+}
+
+nstat(file: string, whine: int)
+{
+ dir: Dir;
+ i, ok: int;
+
+ fd := open(file, sys->OREAD);
+ if(fd == nil) {
+ if(whine)
+ fprint(stderr, "netstat: %s: %r\n", file);
+ return;
+ }
+
+ (ok, dir) = fstat(fd);
+ if(ok == -1) {
+ fprint(stderr, "netstat: fstat %s: %r\n", file);
+ fd = nil;
+ return;
+ }
+ if((dir.mode&Sys->DMDIR) == 0) {
+ fprint(stderr, "netstat: not a protocol directory: %s\n", file);
+ return;
+ }
+ for(;;) {
+ (n, d) := dirread(fd);
+ if(n <= 0)
+ break;
+ for(i = 0; i < n; i++)
+ if(d[i].name[0] <= '9')
+ nsprint(file+"/"+d[i].name, d[i].uid);
+ }
+}
+
+fc(file: string): string
+{
+ fd := open(file, sys->OREAD);
+ if(fd == nil)
+ return "??";
+
+ buf := array[64] of byte;
+ n := read(fd, buf, len buf);
+ if(n <= 1)
+ return "??";
+ if(int buf[n-1] == '\n')
+ n--;
+
+ return string buf[0:n];
+}
+
+nsprint(name, user: string)
+{
+ n: int;
+ s: list of string;
+
+ sr := fc(name+"/status");
+ (n, s) = tokenize(sr, " ");
+
+ print("%-10s %-10s %-12s %-20s %s\n",
+ name[5:],
+ user,
+ hd s,
+ fc(name+"/local"),
+ fc(name+"/remote"));
+}
diff --git a/appl/cmd/newer.b b/appl/cmd/newer.b
new file mode 100644
index 00000000..ce0f743d
--- /dev/null
+++ b/appl/cmd/newer.b
@@ -0,0 +1,36 @@
+implement Newer;
+
+#
+# test if a file is up to date
+#
+
+include "sys.m";
+
+include "draw.m";
+
+Newer: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys := load Sys Sys->PATH;
+ if(len args != 3){
+ sys->fprint(sys->fildes(2), "usage: newer newfile oldfile\n");
+ raise "fail:usage";
+ }
+ args = tl args;
+ (ok1, d1) := sys->stat(hd args);
+ if(ok1 < 0)
+ raise sys->sprint("fail:new:%r");
+ if(d1.mode & Sys->DMDIR)
+ raise "fail:new:directory";
+ (ok2, d2) := sys->stat(hd tl args);
+ if(ok2 < 0)
+ raise sys->sprint("fail:old:%r");
+ if(d2.mode & Sys->DMDIR)
+ raise "fail:old:directory";
+ if(d2.mtime > d1.mtime)
+ raise "fail:older";
+}
diff --git a/appl/cmd/ns.b b/appl/cmd/ns.b
new file mode 100644
index 00000000..38bb86af
--- /dev/null
+++ b/appl/cmd/ns.b
@@ -0,0 +1,157 @@
+# ns - display the construction of the current namespace (loosely based on plan 9's ns)
+implement Ns;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+Ns: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+SHELLMETA: con "' \t\\$#";
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: ns [-r] [pid]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil){
+ sys->fprint(sys->fildes(2), "ns: can't load %s: %r\n", Arg->PATH);
+ raise "fail:load";
+ }
+ arg->init(args);
+ pid := sys->pctl(0, nil);
+ raw := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'r' =>
+ raw = 1;
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(len args > 1)
+ usage();
+ if(len args > 0)
+ pid = int hd args;
+
+ nsname := sys->sprint("/prog/%d/ns", pid);
+ nsfd := sys->open(nsname, Sys->OREAD);
+ if(nsfd == nil) {
+ sys->fprint(sys->fildes(2), "ns: can't open %s: %r\n", nsname);
+ raise "fail:open";
+ }
+
+ buf := array[2048] of byte;
+ while((l := sys->read(nsfd, buf, len buf)) > 0){
+ (nstr, lstr) := sys->tokenize(string buf[0:l], " \n");
+ if(nstr < 2)
+ continue;
+ cmd := hd lstr;
+ lstr = tl lstr;
+ if(cmd == "cd" && lstr != nil){
+ sys->print("%s %s\n", cmd, quoted(hd lstr));
+ continue;
+ }
+
+ sflag := "";
+ if((hd lstr)[0] == '-') {
+ sflag = hd lstr + " ";
+ lstr = tl lstr;
+ }
+ if(len lstr < 2)
+ continue;
+
+ src := hd lstr;
+ lstr = tl lstr;
+ if(len src >= 3 && (src[0:2] == "#/" || src[0:2] == "#U")) # remove unnecesary #/'s and #U's
+ src = src[2:];
+
+ # remove "#." from beginning of destination path
+ dest := hd lstr;
+ if(dest == "#M") {
+ dest = dest[2:];
+ if(dest == "")
+ dest = "/";
+ }
+
+ if(cmd == "mount" && !raw)
+ src = netaddr(src); # optionally rewrite network files to network address
+
+ # quote arguments if "#" found
+ sys->print("%s %s%s %s\n", cmd, sflag, quoted(src), quoted(dest));
+ }
+ if(l < 0)
+ sys->fprint(sys->fildes(2), "ns: error reading %s: %r\n", nsname);
+}
+
+netaddr(f: string): string
+{
+ if(len f < 1 || f[0] != '/')
+ return f;
+ (nf, flds) := sys->tokenize(f, "/"); # expect /net[.alt]/proto/2/data
+ if(nf < 4)
+ return f;
+ netdir := hd flds;
+ if(netdir != "net" && netdir != "net.alt")
+ return f;
+ proto := hd tl flds;
+ d := hd tl tl flds;
+ if(hd tl tl tl flds != "data")
+ return f;
+ fd := sys->open(sys->sprint("/%s/%s/%s/remote", hd flds, proto, d), Sys->OREAD);
+ if(fd == nil)
+ return f;
+ buf := array[256] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ return f;
+ if(buf[n-1] == byte '\n')
+ n--;
+ if(netdir != "net")
+ proto = "/"+netdir+"/"+proto;
+ return sys->sprint("%s!%s", proto, string buf[0:n]);
+}
+
+any(c: int, t: string): int
+{
+ for(j := 0; j < len t; j++)
+ if(c == t[j])
+ return 1;
+ return 0;
+}
+
+contains(s: string, t: string): int
+{
+ for(i := 0; i<len s; i++)
+ if(any(s[i], t))
+ return 1;
+ return 0;
+}
+
+quoted(s: string): string
+{
+ if(!contains(s, SHELLMETA))
+ return s;
+ r := "'";
+ for(i := 0; i < len s; i++){
+ if(s[i] == '\'')
+ r[len r] = '\'';
+ r[len r] = s[i];
+ }
+ r[len r] = '\'';
+ return r;
+}
diff --git a/appl/cmd/nsbuild.b b/appl/cmd/nsbuild.b
new file mode 100644
index 00000000..36c5b86a
--- /dev/null
+++ b/appl/cmd/nsbuild.b
@@ -0,0 +1,41 @@
+implement Nsbuild;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+include "newns.m";
+
+stderr: ref Sys->FD;
+
+Nsbuild: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ ns := load Newns "/dis/lib/newns.dis";
+ if(ns == nil) {
+ sys->fprint(stderr, "nsbuild: can't load %s: %r", Newns->PATH);
+ raise "fail:load";
+ }
+
+ if(len argv > 2) {
+ sys->fprint(stderr, "Usage: nsbuild [nsfile]\n");
+ raise "fail:usage";
+ }
+
+ nsfile := "namespace";
+ if(len argv == 2)
+ nsfile = hd tl argv;
+
+ e := ns->newns(nil, nsfile);
+ if(e != ""){
+ sys->fprint(stderr, "nsbuild: error building namespace: %s\n", e);
+ raise "fail:newns";
+ }
+}
diff --git a/appl/cmd/os.b b/appl/cmd/os.b
new file mode 100644
index 00000000..c51faa2a
--- /dev/null
+++ b/appl/cmd/os.b
@@ -0,0 +1,155 @@
+implement Os;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+
+Os: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ if(str == nil)
+ fail(sys->sprint("cannot load %s: %r", String->PATH));
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ fail(sys->sprint("cannot load %s: %r", Arg->PATH));
+
+ arg->init(args);
+ arg->setusage("os [-d dir] [-n] command [arg...]");
+
+ nice := 0;
+ nicearg: string;
+ workdir := "";
+ mntpoint := "";
+ while((opt := arg->opt()) != 0) {
+ case opt {
+ 'd' =>
+ workdir = arg->earg();
+ 'm' =>
+ mntpoint = arg->earg();
+ 'n' =>
+ nice = 1;
+ 'N' =>
+ nice = 1;
+ nicearg = sys->sprint(" %q", arg->earg());
+ * =>
+ arg->usage();
+ }
+ }
+ args = arg->argv();
+ if (args == nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->FORKNS, nil);
+ sys->bind("#p", "/prog", Sys->MREPL); # don't worry if it fails
+ if(mntpoint == nil){
+ mntpoint = "/cmd";
+ if(sys->stat(mntpoint+"/clone").t0 == -1)
+ if(sys->bind("#C", "/", Sys->MBEFORE) < 0)
+ fail(sys->sprint("bind #C /: %r"));
+ }
+
+ cfd := sys->open(mntpoint+"/clone", sys->ORDWR);
+ if(cfd == nil)
+ fail(sys->sprint("cannot open /cmd/clone: %r"));
+
+ buf := array[32] of byte;
+ if((n := sys->read(cfd, buf, len buf)) <= 0)
+ fail(sys->sprint("cannot read /cmd/clone: %r"));
+
+ dir := mntpoint+"/"+string buf[0:n];
+
+ wfd := sys->open(dir+"/wait", Sys->OREAD);
+ if(nice && sys->fprint(cfd, "nice%s", nicearg) < 0)
+ sys->fprint(sys->fildes(2), "os: warning: can't set nice priority: %r\n");
+
+ if(workdir != nil && sys->fprint(cfd, "dir %s", workdir) < 0)
+ fail(sys->sprint("cannot set cwd %q: %r", workdir));
+
+ if(sys->fprint(cfd, "killonclose") < 0)
+ sys->fprint(sys->fildes(2), "os: warning: cannot write killonclose: %r\n");
+
+ if(sys->fprint(cfd, "exec %s", str->quoted(args)) < 0)
+ fail(sys->sprint("cannot exec: %r"));
+
+ if((tocmd := sys->open(dir+"/data", sys->OWRITE)) == nil)
+ fail(sys->sprint("canot open %s/data for writing: %r", dir));
+
+ if((fromcmd := sys->open(dir+"/data", sys->OREAD)) == nil)
+ fail(sys->sprint("cannot open %s/data for reading: %r", dir));
+
+ spawn copy(sync := chan of int, nil, sys->fildes(0), tocmd);
+ pid := <-sync;
+ sync = nil;
+ tocmd = nil;
+
+ spawn copy(nil, done := chan of int, fromcmd, sys->fildes(1));
+
+ # cfd is still open, so if we're killgrp'ed and we're on a platform
+ # (e.g. windows) where the fromcmd read is uninterruptible,
+ # cfd will be closed, so the command will be killed (due to killonclose), and
+ # the fromcmd read should complete, allowing that process to be killed.
+
+ <-done;
+ kill(pid);
+
+ if(wfd != nil){
+ status := array[1024] of byte;
+ n = sys->read(wfd, status, len status);
+ if(n < 0)
+ fail(sys->sprint("wait error: %r"));
+ s := string status[0:n];
+ if(s != nil){
+ # pid user sys real status
+ flds := str->unquoted(s);
+ if(len flds < 5)
+ fail(sys->sprint("wait error: odd status: %q", s));
+ s = hd tl tl tl tl flds;
+ if(0)
+ sys->fprint(sys->fildes(2), "WAIT: %q\n", s);
+ if(s != nil)
+ raise "fail:host: "+s;
+ }
+ }
+}
+
+copy(sync, done: chan of int, f, t: ref Sys->FD)
+{
+ if(sync != nil)
+ sync <-= sys->pctl(0, nil);
+ buf := array[8192] of byte;
+ for(;;) {
+ r := sys->read(f, buf, len buf);
+ if(r <= 0)
+ break;
+ w := sys->write(t, buf, r);
+ if(w != r)
+ break;
+ }
+ if(done != nil)
+ done <-= 1;
+}
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE);
+ sys->fprint(fd, "kill");
+}
+
+fail(msg: string)
+{
+ sys->fprint(sys->fildes(2), "os: %s\n", msg);
+ raise "fail:"+msg;
+}
diff --git a/appl/cmd/p.b b/appl/cmd/p.b
new file mode 100644
index 00000000..519797a9
--- /dev/null
+++ b/appl/cmd/p.b
@@ -0,0 +1,141 @@
+implement P;
+# Original by Steve Arons, based on Plan 9 p
+
+include "sys.m";
+ sys: Sys;
+ FD: import Sys;
+include "draw.m";
+include "string.m";
+ str: String;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "sh.m";
+
+stderr: ref FD;
+outb, cons: ref Iobuf;
+drawctxt: ref Draw->Context;
+
+nlines := 22; # 1/3rd 66-line nroff page (!)
+progname := "p";
+
+P: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(stderr, "Usage: p [-number] [file...]\n");
+ raise "fail:usage";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ nomod(Bufio->PATH);
+ str = load String String->PATH;
+ if(str == nil)
+ nomod(String->PATH);
+ sys->pctl(Sys->FORKFD, nil);
+ drawctxt = ctxt;
+
+ stderr = sys->fildes(2);
+
+ if((stdout := sys->fildes(1)) != nil)
+ outb = bufio->fopen(stdout, bufio->OWRITE);
+ if(outb == nil){
+ sys->fprint(stderr, "p: can't open stdout: %r\n");
+ raise "fail:stdout";
+ }
+ cons = bufio->open("/dev/cons", bufio->OREAD);
+ if(cons == nil){
+ sys->fprint(stderr, "p: can't open /dev/cons: %r\n");
+ raise "fail:cons";
+ }
+
+ if(argv != nil){
+ progname = hd argv;
+ argv = tl argv;
+ if(argv != nil){
+ s := hd argv;
+ if(len s > 1 && s[0] == '-'){
+ (x, y) := str->toint(s[1:],10);
+ if(y == "" && x > 0)
+ nlines = x;
+ else
+ usage();
+ argv = tl argv;
+ }
+ }
+ }
+ if(argv == nil)
+ argv = "-" :: nil;
+ for(; argv != nil; argv = tl argv){
+ file := hd argv;
+ fd: ref Sys->FD;
+ if(file == "-"){
+ file = "stdin";
+ fd = sys->fildes(0);
+ }else
+ fd = sys->open(file, Sys->OREAD);
+ if(fd == nil){
+ sys->fprint(stderr, "%s: can't open %s: %r\n", progname, file);
+ continue;
+ }
+ page(fd);
+ fd = nil;
+ }
+}
+
+nomod(m: string)
+{
+ sys->fprint(sys->fildes(2), "%s: can't load %s: %r\n", progname, m);
+ raise "fail:load";
+}
+
+page(fd: ref Sys->FD)
+{
+ inb := bufio->fopen(fd, bufio->OREAD);
+ nl := nlines;
+ while((line := inb.gets('\n')) != nil){
+ outb.puts(line);
+ if(--nl == 0){
+ outb.flush();
+ nl = nlines;
+ pause();
+ }
+ }
+ outb.flush();
+}
+
+pause()
+{
+ for(;;){
+ cmdline := cons.gets('\n');
+ if(cmdline == nil || cmdline[0] == 'q') # catch ^d
+ exit;
+ else if(cmdline[0] == '!') {
+ done := chan of int;
+ spawn command(cmdline[1:], done);
+ <-done;
+ }else
+ break;
+ }
+}
+
+command(cmdline: string, done: chan of int)
+{
+ sh := load Sh Sh->PATH;
+ if(sh == nil) {
+ sys->fprint(stderr, "%s: can't load %s: %r\n", progname, Sh->PATH);
+ done <-= 0;
+ return;
+ }
+ sys->pctl(Sys->FORKFD, nil);
+ sys->dup(cons.fd.fd, 0);
+ sh->system(drawctxt, cmdline);
+ done <-= 1;
+}
diff --git a/appl/cmd/palm/connex.b b/appl/cmd/palm/connex.b
new file mode 100644
index 00000000..2cd66fd8
--- /dev/null
+++ b/appl/cmd/palm/connex.b
@@ -0,0 +1,124 @@
+implement Connex;
+
+#
+# temporary test program for palmsrv development
+#
+# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "palm.m";
+ palm: Palm;
+ Record: import palm;
+ palmdb: Palmdb;
+ DB, PDB, PRC: import palmdb;
+
+include "desklink.m";
+ desklink: Desklink;
+ SysInfo: import desklink;
+
+Connex: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ palm = load Palm Palm->PATH;
+ if(palm == nil)
+ error(sys->sprint("can't load %s: %r", palm->PATH));
+ desklink = load Desklink Desklink->PATH1;
+ if(desklink == nil)
+ error(sys->sprint("can't load Desklink: %r"));
+
+ palm->init();
+
+ err: string;
+ (palmdb, err) = desklink->connect("/chan/palmsrv");
+ if(palmdb == nil)
+ error(sys->sprint("can't init Desklink: %s", err));
+ desklink->init(palm);
+ sysinfo := desklink->ReadSysInfo();
+ if(sysinfo == nil)
+ error(sys->sprint("can't read sys Info: %r"));
+ sys->print("ROM: %8.8ux locale: %8.8ux product: '%s'\n", sysinfo.romversion, sysinfo.locale, sysinfo.product);
+ user := desklink->ReadUserInfo();
+ if(user == nil)
+ error(sys->sprint("can't read user info"));
+ sys->print("userid: %d viewerid: %d lastsyncpc: %d succsync: %8.8ux lastsync: %8.8ux uname: '%s' password: %s\n",
+ user.userid, user.viewerid, user.lastsyncpc, user.succsynctime, user.lastsynctime, user.username, ba(user.password));
+ sys->print("Storage:\n");
+ for(cno:=0;;){
+ (cards, more, err) := desklink->ReadStorageInfo(cno);
+ for(i:=0; i<len cards; i++){
+ sys->print("%2d v=%d c=%d romsize=%d ramsize=%d ramfree=%d name='%s' maker='%s'\n",
+ cards[i].cardno, cards[i].version, cards[i].creation, cards[i].romsize, cards[i].ramsize,
+ cards[i].ramfree, cards[i].name, cards[i].maker);
+ cno = cards[i].cardno+1;
+ }
+ if(!more)
+ break;
+ }
+ sys->print("ROM DBs:\n");
+ listdbs(Desklink->DBListROM);
+ sys->print("RAM DBs:\n");
+ listdbs(Desklink->DBListRAM);
+
+ (db, ee) := DB.open("AddressDB", Palmdb->OREAD);
+ if(db == nil){
+ sys->print("error: AddressDB: %s\n", ee);
+ exit;
+ }
+ pdb := db.records();
+ if(pdb == nil){
+ sys->print("error: AddressDB: %r\n");
+ exit;
+ }
+ dumpfd := sys->create("dump", Sys->OWRITE, 8r600);
+ for(i:=0; (r := pdb.read(i)) != nil; i++)
+ sys->write(dumpfd, r.data, len r.data);
+# desklink->EndOfSync(Desklink->SyncNormal);
+ desklink->hangup();
+}
+
+listdbs(sort: int)
+{
+ index := 0;
+ for(;;){
+ (dbs, more, e) := desklink->ReadDBList(0, sort, index);
+ if(dbs == nil){
+ if(e != nil)
+ sys->print("ReadDBList: %s\n", e);
+ break;
+ }
+ for(i := 0; i < len dbs; i++){
+ sys->print("#%4.4ux '%s'\n", dbs[i].index, dbs[i].name);
+ index = dbs[i].index+1;
+ }
+ if(!more)
+ break;
+ }
+}
+
+ba(a: array of byte): string
+{
+ s := "";
+ for(i := 0; i < len a; i++)
+ s += sys->sprint("%2.2ux", int a[i]);
+ return s;
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "tconn: %s\n", s);
+ fd := sys->open("/prog/"+string sys->pctl(0,nil)+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+ raise "fail:error";
+}
diff --git a/appl/cmd/palm/desklink.b b/appl/cmd/palm/desklink.b
new file mode 100644
index 00000000..23dafaa7
--- /dev/null
+++ b/appl/cmd/palm/desklink.b
@@ -0,0 +1,843 @@
+implement Palmdb, Desklink;
+
+#
+# Palm Desk Link Protocol (DLP)
+#
+# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+# Request and response formats were extracted from
+# include/Core/System/DLCommon.h in the PalmOS SDK-5
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "daytime.m";
+ daytime: Daytime;
+ Tm: import daytime;
+
+include "palm.m";
+ palm: Palm;
+ DBInfo, Record, Resource, id2s, s2id, get2, put2, get4, put4, gets, argsize, packargs, unpackargs: import palm;
+
+include "timers.m";
+
+include "desklink.m";
+
+Maxrecbytes: con 16rFFFF;
+
+# operations defined by Palm
+
+T_ReadUserInfo, T_WriteUserInfo, T_ReadSysInfo, T_GetSysDateTime,
+T_SetSysDateTime, T_ReadStorageInfo, T_ReadDBList, T_OpenDB, T_CreateDB,
+T_CloseDB, T_DeleteDB, T_ReadAppBlock, T_WriteAppBlock, T_ReadSortBlock,
+T_WriteSortBlock, T_ReadNextModifiedRec, T_ReadRecord, T_WriteRecord,
+T_DeleteRecord, T_ReadResource, T_WriteResource, T_DeleteResource,
+T_CleanUpDatabase, T_ResetSyncFlags, T_CallApplication, T_ResetSystem,
+T_AddSyncLogEntry, T_ReadOpenDBInfo, T_MoveCategory, T_ProcessRPC,
+T_OpenConduit, T_EndOfSync, T_ResetDBIndex, T_ReadRecordIDList,
+# DLP 1.1 functions
+T_ReadNextRecInCategory, T_ReadNextModifiedRecInCategory,
+T_ReadAppPreference, T_WriteAppPreference, T_ReadNetSyncInfo,
+T_WriteNetSyncInfo, T_ReadFeature,
+# DLP 1.2 functions
+T_FindDB, T_SetDBInfo,
+# DLP 1.3 functions
+T_LoopBackTest, T_ExpSlotEnumerate, T_ExpCardPresent, T_ExpCardInfo: con 16r10+iota;
+# then there's a group of VFS requests that we don't currently use
+
+Response: con 16r80;
+
+Maxname: con 32;
+
+A1, A2: con Palm->ArgIDbase+iota; # argument IDs have request-specific interpretation (most have only one ID)
+
+Timeout: con 30; # seconds time out used by Palm's headers
+srvfd: ref Sys->FD;
+selfdb: Palmdb;
+
+errorlist := array [] of {
+ "no error",
+ "general Pilot system error",
+ "unknown request",
+ "out of dynamic memory on device",
+ "invalid parameter",
+ "not found",
+ "no open databases",
+ "database already open",
+ "too many open databases",
+ "database already exists",
+ "cannot open database",
+ "record previously deleted",
+ "record busy",
+ "operation not supported",
+ "unexpected error (ErrUnused1)",
+ "read only object",
+ "not enough space",
+ "size limit exceeded",
+ "sync cancelled",
+ "bad arg wrapper",
+ "argument missing",
+ "bad argument size",
+};
+
+Eshort: con "desklink protocol: response too short";
+
+debug := 0;
+
+connect(srvfile: string): (Palmdb, string)
+{
+ sys = load Sys Sys->PATH;
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ return (nil, sys->sprint("can't load %s: %r", Daytime->PATH));
+ srvfd = sys->open(srvfile, Sys->ORDWR);
+ if(srvfd == nil)
+ return (nil, sys->sprint("can't open %s: %r", srvfile));
+ selfdb = load Palmdb "$self";
+ if(selfdb == nil)
+ return (nil, sys->sprint("can't load self as Palmdb: %r"));
+ return (selfdb, nil);
+}
+
+hangup(): int
+{
+ srvfd = nil;
+ return 0;
+}
+
+#
+# set the system error string
+#
+e(s: string): string
+{
+ if(s != nil){
+ s = "palm: "+s;
+ sys->werrstr(s);
+ }
+ return s;
+}
+
+#
+# sent before each conduit is opened by the desktop,
+# apparently to detect a pending cancel request (on the device)
+#
+OpenConduit(): int
+{
+ return nexec(T_OpenConduit, A1, nil);
+}
+
+#
+# end of sync on desktop
+#
+EndOfSync(status: int): int
+{
+ req := array[2] of byte;
+ put2(req, status);
+ return nexec(T_EndOfSync, A1, req);
+}
+
+ReadSysInfo(): ref SysInfo
+{
+ if((reply := dexec(T_ReadSysInfo, A1, nil, 14)) == nil)
+ return nil;
+ s := ref SysInfo;
+ s.romversion = get4(reply);
+ s.locale = get4(reply[4:]);
+ l := int reply[9]; # should be at most 4 apparently?
+ s.product = gets(reply[10:10+l]);
+ return s;
+}
+
+ReadSysInfoVer(): (int, int, int)
+{
+ req := array[4] of byte;
+ put2(req, 1); # major version
+ put2(req, 2); # minor version
+ if((reply := dexec(T_ReadSysInfo, A2, req, 12)) == nil)
+ return (0, 0, 0);
+ return (get4(reply), get4(reply[4:]), get4(reply[8:]));
+}
+
+ReadUserInfo(): ref User
+{
+ if((reply := dexec(T_ReadUserInfo, 0, nil, 30)) == nil)
+ return nil;
+ u := ref User;
+ u.userid = get4(reply);
+ u.viewerid = get4(reply[4:]);
+ u.lastsyncpc = get4(reply[8:]);
+ u.succsynctime = getdate(reply[12:]);
+ u.lastsynctime = getdate(reply[20:]);
+ userlen := int reply[28];
+ pwlen := int reply[29];
+ u.username = gets(reply[30:30+userlen]);
+ u.password = array[pwlen] of byte;
+ u.password[0:] = reply[30+userlen:30+userlen+pwlen];
+ return u;
+}
+
+WriteUserInfo(u: ref User, flags: int): int
+{
+ req := array[22+Maxname] of byte;
+ put4(req, u.userid);
+ put4(req[4:], u.viewerid);
+ put4(req[8:], u.lastsyncpc);
+ putdate(req[12:], u.lastsynctime);
+ req[20] = byte flags;
+ l := puts(req[22:], u.username);
+ req[21] = byte l;
+ return nexec(T_WriteUserInfo, A1, req[0:22+l]);
+}
+
+GetSysDateTime(): int
+{
+ if((reply := dexec(T_GetSysDateTime, A1, nil, 8)) == nil)
+ return -1;
+ return getdate(reply);
+}
+
+SetSysDateTime(time: int): int
+{
+ return nexec(T_SetSysDateTime, A1, putdate(array[8] of byte, time));
+}
+
+ReadStorageInfo(cardno: int): (array of ref CardInfo, int, string)
+{
+ req := array[2] of byte;
+ req[0] = byte cardno;
+ req[1] = byte 0;
+ (reply, err) := rexec(T_ReadStorageInfo, A1, req, 30);
+ if(reply == nil)
+ return (nil, 0, err);
+ nc := int reply[3];
+ if(nc <= 0)
+ return (nil, 0, nil);
+ more := int reply[1] != 0;
+ a := array[nc] of ref CardInfo;
+ p := 4;
+ for(i:=0; i<nc; i++){
+ nb: int;
+ (a[i], nb) = unpackcard(reply[p:]);
+ p += nb;
+ }
+ return (a, more, nil);
+}
+
+unpackcard(a: array of byte): (ref CardInfo, int)
+{
+ nb := int a[0]; # total size of this card's info
+ c := ref CardInfo;
+ c.cardno = int a[1];
+ c.version = get2(a[2:]);
+ c.creation = getdate(a[4:]);
+ c.romsize = get4(a[12:]);
+ c.ramsize = get4(a[16:]);
+ c.ramfree = get4(a[20:]);
+ l1 := int a[24] + 26;
+ l2 := int a[25];
+ c.name = gets(a[26:l1]);
+ c.maker = gets(a[l1:l1+l2]);
+ return (c, nb);
+}
+
+ReadDBCount(cardno: int): (int, int)
+{
+ req := array[2] of byte;
+ req[0] = byte cardno;
+ req[1] = byte 0;
+ if((reply := dexec(T_ReadStorageInfo, A2, req, 20)) == nil)
+ return (-1, -1);
+ return (get2(req[0:]), get2(req[2:]));
+}
+
+unpackdbinfo(a: array of byte): (ref DBInfo, int)
+{
+ size := int a[0];
+ misc := int a[1];
+ info := ref DBInfo;
+ info.attr = get2(a[2:]);
+ info.dtype = id2s(get4(a[4:]));
+ info.creator = id2s(get4(a[8:]));
+ info.version = get2(a[12:]);
+ info.modno = get4(a[14:]);
+ info.ctime = getdate(a[18:]);
+ info.mtime = getdate(a[26:]);
+ info.btime = getdate(a[34:]);
+ info.index = get2(a[42:]);
+ if(size > len a)
+ size = len a;
+ info.name = gets(a[44:size]);
+ return (info, size);
+}
+
+ReadDBList(cardno: int, flags: int, start: int): (array of ref DBInfo, int, string)
+{
+ req := array[4] of byte;
+ req[0] = byte (flags | DBListMultiple);
+ req[1] = byte cardno;
+ put2(req[2:], start);
+ (reply, err) := rexec(T_ReadDBList, A1, req, 48);
+ if(reply == nil || int reply[3] == 0)
+ return (nil, 0, err);
+ # lastindex[2] flags[1] actcount[1]
+ # flags is 16r80 => more to list
+ more := (reply[2] & byte 16r80) != byte 0;
+ dbs := array[int reply[3]] of ref DBInfo;
+#sys->print("ndb=%d more=%d lastindex=#%4.4ux\n", len dbs, more, get2(reply));
+ a := reply[4:];
+ for(i := 0; i < len dbs; i++){
+ (db, n) := unpackdbinfo(a);
+ dbs[i] = db;
+ a = a[n:];
+ }
+ return (dbs, more, nil);
+}
+
+matchdb(cardno: int, flag: int, start: int, dbname: string, dtype: string, creator: string): (ref DBInfo, int)
+{
+ for(;;){
+ (dbs, more, err) := ReadDBList(cardno, flag, start);
+ if(dbs == nil)
+ break;
+ for(i := 0; i < len dbs; i++){
+ info := dbs[i];
+ if((dbname == nil || info.name == dbname) &&
+ (dtype == nil || info.dtype == dtype) &&
+ (creator == nil || info.creator == creator))
+ return (info, info.index);
+ start = info.index+1;
+ }
+ }
+ return (nil, 0);
+}
+
+
+FindDBInfo(cardno: int, start: int, dbname: string, dtype: string, creator: string): ref DBInfo
+{
+ if(start < 16r1000) {
+ (info, i) := matchdb(cardno, 16r80, start, dbname, dtype, creator);
+ if(info != nil)
+ return info;
+ }
+ (info, i) := matchdb(cardno, 16r40, start&~16r1000, dbname, dtype, creator);
+ if(info != nil)
+ info.index |= 16r1000;
+ return info;
+}
+
+DeleteDB(name: string): int
+{
+ (cardno, dbname) := parsedb(name);
+ req := array[2+Maxname] of byte;
+ req[0] = byte cardno;
+ req[1] = byte 0;
+ n := puts(req[2:], dbname);
+ return nexec(T_DeleteDB, A1, req[0:2+n]);
+}
+
+ResetSystem(): int
+{
+ return nexec(T_ResetSystem, 0, nil);
+}
+
+CloseDB_All(): int
+{
+ return nexec(T_CloseDB, A2, nil);
+}
+
+AddSyncLogEntry(entry: string): int
+{
+ req := array[256] of byte;
+ n := puts(req, entry);
+ return nexec(T_AddSyncLogEntry, A1, req[0:n]);
+}
+
+#
+# this implements a Palmdb->DB directly accessed using the desklink protocol
+#
+
+init(m: Palm): string
+{
+ palm = m;
+ return nil;
+}
+
+#
+# syntax is [cardno/]dbname
+# where cardno defaults to 0
+#
+parsedb(name: string): (int, string)
+{
+ (nf, flds) := sys->tokenize(name, "/");
+ if(nf > 1)
+ return (int hd flds, hd tl flds);
+ return (0, name);
+}
+
+DB.open(name: string, mode: int): (ref DB, string)
+{
+ (cardno, dbname) := parsedb(name);
+ req := array[2+Maxname] of byte;
+ req[0] = byte cardno;
+ req[1] = byte mode;
+ n := puts(req[2:], dbname);
+ (reply, err) := rexec(T_OpenDB, A1, req[0:2+n], 1);
+ if(reply == nil)
+ return (nil, err);
+ db := ref DB;
+ db.x = int reply[0];
+ inf := db.stat();
+ if(inf == nil)
+ return (nil, sys->sprint("can't get DBInfo: %r"));
+ db.attr = inf.attr; # mainly need to know whether it's Fresource or not
+ return (db, nil);
+}
+
+DB.create(name: string, nil: int, nil: int, inf: ref DBInfo): (ref DB, string)
+{
+ (cardno, dbname) := parsedb(name);
+ req := array[14+Maxname] of byte;
+ put4(req, s2id(inf.creator));
+ put4(req[4:], s2id(inf.dtype));
+ req[8] = byte cardno;
+ req[9] = byte 0;
+ put2(req[10:], inf.attr);
+ put2(req[12:], inf.version);
+ n := puts(req[14:], dbname);
+ (reply, err) := rexec(T_CreateDB, A1, req[0:14+n], 1);
+ if(reply == nil)
+ return (nil, err);
+ db := ref DB;
+ db.x = int reply[0];
+ db.attr = inf.attr;
+ return (db, nil);
+}
+
+DB.stat(db: self ref DB): ref DBInfo
+{
+ (reply, err) := rexec(T_FindDB, A2, array[] of {byte 16r80, byte db.x}, 54);
+ if(err != nil)
+ return nil;
+ return unpackdbinfo(reply[10:]).t0;
+}
+
+DB.wstat(db: self ref DB, inf: ref DBInfo, flags: int)
+{
+ # TO DO
+}
+
+DB.close(db: self ref DB): string
+{
+ return rexec(T_CloseDB, A1, array[] of {byte db.x}, 0).t1;
+}
+
+DB.records(db: self ref DB): ref PDB
+{
+ if(db.attr & Palm->Fresource){
+ sys->werrstr("not a database file");
+ return nil;
+ }
+ return ref PDB(db);
+}
+
+DB.resources(db: self ref DB): ref PRC
+{
+ if((db.attr & Palm->Fresource) == 0){
+ sys->werrstr("not a resource file");
+ return nil;
+ }
+ return ref PRC(db);
+}
+
+DB.readidlist(db: self ref DB, sort: int): array of int
+{
+ req := array[6] of byte;
+ req[0] = byte db.x;
+ if(sort)
+ req[1] = byte 16r80;
+ else
+ req[1] = byte 0;
+ put2(req[2:], 0);
+ put2(req[4:], -1);
+ p := dexec(T_ReadRecordIDList, A1, req, 2);
+ if(p == nil)
+ return nil;
+ ret := get2(p);
+ ids := array[ret] of int;
+ p = p[8:];
+ for (i := 0; i < ret; p = p[4:])
+ ids[i++] = get4(p);
+ return ids;
+}
+
+DB.nentries(db: self ref DB): int
+{
+ if((reply := dexec(T_ReadOpenDBInfo, A1, array[] of {byte db.x}, 2)) == nil)
+ return -1;
+ return get2(reply);
+}
+
+DB.rdappinfo(db: self ref DB): (array of byte, string)
+{
+ req := array[6] of byte;
+ req[0] = byte db.x;
+ req[1] = byte 0;
+ put2(req[2:], 0); # offset
+ put2(req[4:], -1); # to end
+ (reply, err) := rexec(T_ReadAppBlock, A1, req, 2);
+ if(reply == nil)
+ return (nil, err);
+ if(get2(reply) < len reply-2)
+ return (nil, "short reply");
+ return (reply[2:], nil);
+}
+
+DB.wrappinfo(db: self ref DB, data: array of byte): string
+{
+ req := array[4 + len data] of byte;
+ req[0] = byte db.x;
+ req[1] = byte 0;
+ put2(req[2:], len data);
+ req[4:] = data;
+ return rexec(T_WriteAppBlock, A1, req, 0).t1;
+}
+
+DB.rdsortinfo(db: self ref DB): (array of int, string)
+{
+ req := array[6] of byte;
+ req[0] = byte db.x;
+ req[1] = byte 0;
+ put2(req[2:], 0);
+ put2(req[4:], -1);
+ (reply, err) := rexec(T_ReadSortBlock, A1, req, 2);
+ if(reply == nil)
+ return (nil, err);
+ n := len reply;
+ a := reply[2:n];
+ n = (n-2)/2;
+ s := array[n] of int;
+ for(i := 0; i < n; i++)
+ s[i] = get2(a[i*2:]);
+ return (s, nil);
+}
+
+DB.wrsortinfo(db: self ref DB, s: array of int): string
+{
+ n := len s;
+ req := array[4+2*n] of byte;
+ req[0] = byte db.x;
+ req[1] = byte 0;
+ put2(req[2:], 2*n);
+ for(i := 0; i < n; i++)
+ put2(req[2+i*2:], s[i]);
+ return rexec(T_WriteSortBlock, A1, req, 0).t1;
+}
+
+PDB.purge(db: self ref PDB): string
+{
+ return rexec(T_CleanUpDatabase, A1, array[] of {byte db.db.x}, 0).t1;
+}
+
+DB.resetsyncflags(db: self ref DB): string
+{
+ return rexec(T_ResetSyncFlags, A1, array[] of {byte db.x}, 0).t1;
+}
+
+#
+# .pdb and other data base files
+#
+
+PDB.read(db: self ref PDB, index: int): ref Record
+{
+ req := array[8] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put2(req[2:], index);
+ put2(req[4:], 0); # offset
+ put2(req[6:], Maxrecbytes);
+ return unpackrec(dexec(T_ReadRecord, A2, req, 10)).t0;
+}
+
+PDB.readid(db: self ref PDB, id: int): (ref Record, int)
+{
+ req := array[10] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], id);
+ put2(req[6:], 0); # offset
+ put2(req[8:], Maxrecbytes);
+ return unpackrec(dexec(T_ReadRecord, A1, req, 10));
+}
+
+PDB.write(db: self ref PDB, r: ref Record): string
+{
+ req := array[8+len r.data] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], r.id);
+ req[6] = byte (r.attr & Palm->Rsecret);
+ req[7] = byte r.cat;
+ req[8:] = r.data;
+ (reply, err) := rexec(T_WriteRecord, A1, req, 4);
+ if(reply == nil)
+ return err;
+ if(r.id == 0)
+ r.id = get4(reply);
+ return nil;
+}
+
+PDB.movecat(db: self ref PDB, from: int, tox: int): string
+{
+ req := array[4] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte from;
+ req[2] = byte tox;
+ req[3] = byte 0;
+ return rexec(T_MoveCategory, A1, req, 0).t1;
+}
+
+PDB.resetnext(db: self ref PDB): int
+{
+ return nexec(T_ResetDBIndex, A1, array[] of {byte db.db.x});
+}
+
+PDB.readnextmod(db: self ref PDB): (ref Record, int)
+{
+ return unpackrec(dexec(T_ReadNextModifiedRec, A1, array[] of {byte db.db.x}, 10));
+}
+
+PDB.delete(db: self ref PDB, id: int): string
+{
+ req := array[6] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], id);
+ return rexec(T_DeleteRecord, A1, req, 0).t1;
+}
+
+PDB.deletecat(db: self ref PDB, cat: int): string
+{
+ return rexec(T_DeleteRecord, A1, array[] of {byte db.db.x, byte 16r40, 2 to 6 => byte 0, 7=>byte cat}, 0).t1;
+}
+
+PDB.truncate(db: self ref PDB): string
+{
+ return rexec(T_DeleteRecord, A1, array[] of {byte db.db.x, byte 16r80, 2 to 7 => byte 0}, 0).t1;
+}
+
+#
+# .prc resource files
+#
+
+PRC.write(db: self ref PRC, r: ref Resource): string
+{
+ req := array[8+len r.data] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], r.name);
+ put2(req[6:], r.id);
+ put2(req[8:], len r.data);
+ return rexec(T_WriteResource, A1, req, 0).t1;
+}
+
+PRC.delete(db: self ref PRC, name: int, id: int): string
+{
+ req := array[8] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], name);
+ put4(req[6:], id);
+ return rexec(T_DeleteResource, A1, req, 0).t1;
+}
+
+PRC.readtype(db: self ref PRC, name: int, id: int): (ref Resource, int)
+{
+ req := array[12] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], name);
+ put2(req[6:], id);
+ put2(req[8:], 0); # Offset into record
+ put2(req[10:], Maxrecbytes);
+ return unpackresource(dexec(T_ReadResource, A2, req, 10));
+}
+
+PRC.truncate(db: self ref PRC): string
+{
+ return rexec(T_DeleteResource, A1, array[] of {byte db.db.x, byte 16r80, 2 to 7 => byte 0}, 0).t1;
+}
+
+PRC.read(db: self ref PRC, index: int): ref Resource
+{
+ req := array[8] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put2(req[2:], index);
+ put2(req[4:], 0); # offset
+ put2(req[6:], Maxrecbytes);
+ return unpackresource(dexec(T_ReadResource, A1, req, 12)).t0;
+}
+
+#
+# DL protocol
+#
+# request
+# id: byte # operation
+# argc: byte # arg count
+# args: byte[]
+#
+# response
+# id: byte # cmd|16r80
+# argc: byte # argc response arguments follow header
+# error: byte[2] # error code
+# args: byte[]
+#
+# args wrapped by Palm->packargs etc.
+#
+
+#
+# RPC exchange with device
+#
+rpc(req: array of byte): (array of (int, array of byte), string)
+{
+ if(sys->write(srvfd, req, len req) != len req)
+ return (nil, sys->sprint("link: %r"));
+ reply := array[65536] of byte;
+ nb := sys->read(srvfd, reply, len reply);
+ if(nb == 0)
+ return (nil, "link: hangup");
+ if(nb < 0)
+ return (nil, sys->sprint("link: %r"));
+ r := int reply[0];
+ if((r & Response) == 0)
+ return (nil, e(sys->sprint("received request #%2.2x not response", r)));
+ if(r != (Response|int req[0]))
+ return (nil, e(sys->sprint("wrong response #%x", r)));
+ if(nb < 4)
+ return (nil, e(Eshort));
+ rc := get2(reply[2:]);
+ if(rc != 0){
+ if(rc < 0 || rc >= len errorlist)
+ return (nil, e(sys->sprint("unknown error %d", rc)));
+ return (nil, e(errorlist[rc]));
+ }
+ argc := int reply[1]; # count of following arguments
+ if(argc == 0)
+ return (nil, nil);
+ return unpackargs(argc, reply[4:nb]);
+}
+
+rexec(cmd: int, argid: int, arg: array of byte, minlen: int): (array of byte, string)
+{
+ args: array of (int, array of byte);
+ if(arg != nil)
+ args = array[] of {(argid, arg)};
+ req := array[2+argsize(args)] of byte;
+ req[0] = byte cmd;
+ req[1] = byte len args;
+ packargs(req[2:], args);
+ (replies, err) := rpc(req);
+ if(replies == nil){
+ if(err != nil)
+ return (nil, err);
+ if(minlen > 0)
+ return (nil, e(Eshort));
+ return (nil, nil);
+ }
+ (nil, reply) := replies[0];
+ if(len reply < minlen)
+ return (nil, e(Eshort));
+ return (reply, nil);
+}
+
+dexec(cmd: int, argid: int, msg: array of byte, minlen: int): array of byte
+{
+ (reply, nil) := rexec(cmd, argid, msg, minlen);
+ return reply;
+}
+
+nexec(cmd: int, argid: int, msg: array of byte): int
+{
+ (nil, err) := rexec(cmd, argid, msg, 0);
+ if(err != nil)
+ return -1;
+ return 0;
+}
+
+unpackresource(a: array of byte): (ref Resource, int)
+{
+ nb := len a;
+ if(nb < 10)
+ return (nil, -1);
+ size := get2(a[8:]);
+ if(nb-10 < size)
+ return (nil, -1);
+ r := Resource.new(get4(a), get2(a[4:]), size);
+ r.data[0:] = a[10:10+size];
+ return (r, get2(a[6:]));
+}
+
+unpackrec(a: array of byte): (ref Record, int)
+{
+ nb := len a;
+ if(nb < 10)
+ return (nil, -1);
+ size := get2(a[6:]);
+ if(nb-10 < size)
+ return (nil, -1);
+ r := Record.new(get4(a), int a[8], int a[9], size);
+ r.data[0:] = a[10:10+size];
+ return (r, get2(a[4:]));
+}
+
+#
+# pack string (must be Latin1) as zero-terminated array of byte
+#
+puts(a: array of byte, s: string): int
+{
+ for(i := 0; i < len s && i < len a-1; i++)
+ a[i] = byte s[i];
+ a[i++] = byte 0;
+ return i;
+}
+
+#
+# the conversion via local time might be wrong,
+# since the computers might be in different time zones,
+# but is hard to avoid
+#
+
+getdate(data: array of byte): int
+{
+ yr := (int data[0] << 8) | int data[1];
+ if(yr == 0)
+ return 0; # unspecified
+ t := ref Tm;
+ t.sec = int data[6];
+ t.min = int data[5];
+ t.hour = int data[4];
+ t.mday = int data[3];
+ t.mon = int data[2] - 1;
+ t.year = yr - 1900;
+ t.wday = 0;
+ t.yday = 0;
+ return daytime->tm2epoch(t);
+}
+
+putdate(data: array of byte, time: int): array of byte
+{
+ t := daytime->local(time);
+ y := t.year + 1900;
+ if(time == 0)
+ y = 0; # `unchanged'
+ data[7] = byte 0; # pad
+ data[6] = byte t.sec;
+ data[5] = byte t.min;
+ data[4] = byte t.hour;
+ data[3] = byte t.mday;
+ data[2] = byte (t.mon + 1);
+ data[0] = byte ((y >> 8) & 16rff);
+ data[1] = byte (y & 16rff);
+ return data;
+}
diff --git a/appl/cmd/palm/desklink.m b/appl/cmd/palm/desklink.m
new file mode 100644
index 00000000..cc8d69c4
--- /dev/null
+++ b/appl/cmd/palm/desklink.m
@@ -0,0 +1,90 @@
+
+#
+# desktop/Pilot link protocol
+#
+
+Desklink: module {
+
+ PATH1: con "/dis/palm/desklink.dis";
+
+ User: adt {
+ userid: int;
+ viewerid: int;
+ lastsyncpc: int;
+ succsynctime: int;
+ lastsynctime: int;
+ username: string;
+ password: array of byte;
+ };
+
+ SysInfo: adt {
+ romversion: int;
+ locale: int;
+ product: string;
+ };
+
+ CardInfo: adt {
+ cardno: int;
+ version: int;
+ creation: int;
+ romsize: int;
+ ramsize: int;
+ ramfree: int;
+ name: string;
+ maker: string;
+ };
+
+ connect: fn(srvfile: string): (Palmdb, string);
+ hangup: fn(): int;
+
+ #
+ # Desk Link Protocol functions (usually with the same names as in PalmOS)
+ #
+
+ ReadUserInfo: fn(): ref User;
+ WriteUserInfo: fn(u: ref User, flags: int): int;
+
+ # WriteUserInfo update flags
+ UserInfoModUserID: con 16r80;
+ UserInfoModSyncPC: con 16r40;
+ UserInfoModSyncDate: con 16r20;
+ UserInfoModName: con 16r10;
+ UserInfoModViewerID: con 16r08;
+
+ ReadSysInfo: fn(): ref SysInfo;
+ ReadSysInfoVer: fn(): (int, int, int); # DLP 1.2
+
+ GetSysDateTime: fn(): int;
+ SetSysDateTime: fn(nil: int): int;
+
+ ReadStorageInfo: fn(cardno: int): (array of ref CardInfo, int, string);
+ ReadDBCount: fn(cardno: int): (int, int);
+
+ ReadDBList: fn(cardno: int, flags: int, start: int): (array of ref Palm->DBInfo, int, string); # flags must contain DBListRAM and/or DBListROM
+ FindDBInfo: fn(cardno: int, start: int, name: string, dtype, creator: string): ref Palm->DBInfo;
+
+ # list location and options
+ DBListRAM: con 16r80;
+ DBListROM: con 16r40;
+ DBListMultiple: con 16r20; # ok to return multiple entries
+
+ # OpenDB, CreateDB, ReadAppBlock, ... ResetSyncFlags, ReadOpenDBInfo, MoveCategory are functions in DB
+ CloseDB_All: fn(): int;
+ DeleteDB: fn(name: string): int;
+
+ ResetSystem: fn(): int;
+
+ OpenConduit: fn(): int;
+ EndOfSync: fn(status: int): int;
+
+ # EndOfSync status parameter
+ SyncNormal, SyncOutOfMemory, SyncCancelled, SyncError, SyncIncompatible: con iota;
+
+ AddSyncLogEntry: fn(entry: string): int;
+
+ #
+ # Palmdb implementation
+ #
+
+ init: fn(m: Palm): string;
+};
diff --git a/appl/cmd/palm/mkfile b/appl/cmd/palm/mkfile
new file mode 100644
index 00000000..2b0cb209
--- /dev/null
+++ b/appl/cmd/palm/mkfile
@@ -0,0 +1,16 @@
+<../../../mkconfig
+
+TARG=\
+ palmsrv.dis\
+ desklink.dis\
+ connex.dis\
+
+MODULES=\
+ desklink.m\
+
+SYSMODULES=\
+ palm.m\
+
+DISBIN=$ROOT/dis/palm
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/palm/palmsrv.b b/appl/cmd/palm/palmsrv.b
new file mode 100644
index 00000000..f878be03
--- /dev/null
+++ b/appl/cmd/palm/palmsrv.b
@@ -0,0 +1,901 @@
+implement Palmsrv;
+
+#
+# serve up a Palm using SLP and PADP
+#
+# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+# forsyth@vitanuova.com
+#
+# TO DO
+# USB and possibly other transports
+# tickle
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "timers.m";
+ timers: Timers;
+ Timer, Sec: import timers;
+
+include "palm.m";
+
+include "arg.m";
+
+Palmsrv: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+debug := 0;
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: palm/palmsrv [-d /dev/eia0] [-s 57600]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+
+ device, speed: string;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ error(sys->sprint("can't load %s: %r", Arg->PATH));
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'D' =>
+ debug++;
+ 'd' =>
+ device = arg->arg();
+ 's' =>
+ speed = arg->arg();
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(device == nil)
+ device = "/dev/eia0";
+ if(speed == nil)
+ speed = "57600";
+
+ dfd := sys->open(device, Sys->ORDWR);
+ if(dfd == nil)
+ error(sys->sprint("can't open %s: %r", device));
+ cfd := sys->open(device+"ctl", Sys->OWRITE);
+
+ timers = load Timers Timers->PATH;
+ if(timers == nil)
+ error(sys->sprint("can't load %s: %r", Timers->PATH));
+ srvio := sys->file2chan("/chan", "palmsrv");
+ if(srvio == nil)
+ error(sys->sprint("can't create channel /chan/palmsrv: %r"));
+ timers->init(Sec/100);
+ p := Pchan.init(dfd, cfd);
+ spawn server(srvio, p);
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "palmsrv: %s\n", s);
+ raise "fail:error";
+}
+
+Xact: adt
+{
+ fid: int;
+ reply: array of byte;
+ error: string;
+};
+
+server(srv: ref Sys->FileIO, p: ref Pchan)
+{
+ actions: list of ref Xact;
+ nuser := 0;
+ for(;;)alt{
+ (nil, nbytes, fid, rc) := <-srv.read =>
+ if(rc == nil){
+ actions = delact(actions, fid);
+ break;
+ }
+ act := findact(actions, fid);
+ if(act == nil){
+ rc <-= (nil, "no transaction in progress");
+ break;
+ }
+ actions = delact(actions, fid);
+ if(p.shutdown)
+ rc <-= (nil, "link shut down");
+ else if(act.error != nil)
+ rc <-= (nil, act.error);
+ else if(act.reply != nil)
+ rc <-= (act.reply, nil);
+ else
+ rc <-= (nil, "no reply"); # probably shouldn't happen
+
+ (nil, data, fid, wc) := <-srv.write =>
+ actions = delact(actions, fid); # discard result of any previous transaction
+ if(wc == nil){
+ if(--nuser <= 0){
+ nuser = 0;
+ p.stop();
+ }
+ break;
+ }
+ if(len data == 4 && string data == "exit"){
+ p.close();
+ wc <-= (len data, nil);
+ exit;
+ }
+ if(p.shutdown){
+ wc <-= (0, "link shut down"); # must close then reopen
+ break;
+ }
+ if(!p.started){
+ err := p.start();
+ if(err != nil){
+ wc <-= (0, sys->sprint("can't start protocol: %s", err));
+ break;
+ }
+ nuser++;
+ }
+ (result, err) := p.padp_xchg(data, 20*1000);
+ if(err != nil){
+ wc <-= (0, err);
+ break;
+ }
+ actions = ref Xact(fid, result, err) :: actions;
+ wc <-= (len data, nil);
+ }
+}
+
+findact(l: list of ref Xact, fid: int): ref Xact
+{
+ for(; l != nil; l = tl l)
+ if((a := hd l).fid == fid)
+ return a;
+ return nil;
+}
+
+delact(l: list of ref Xact, fid: int): list of ref Xact
+{
+ ol := l;
+ l = nil;
+ for(; ol != nil; ol = tl ol)
+ if((a := hd ol).fid != fid)
+ l = a :: l;
+ return l;
+}
+
+killpid(pid: int)
+{
+ if(pid != 0){
+ fd := sys->open("/prog/"+string pid+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+ }
+}
+
+#
+# protocol implementation
+# Serial Link Protocol (framing)
+# Connection Management Protocol (wakeup, negotiation)
+# Packet Assembly/Disassembly Protocol (reliable delivery fragmented datagram)
+#
+
+DATALIM: con 1024;
+
+# SLP packet types
+SLP_System, SLP_Unused, SLP_PAD, SLP_Loop: con iota;
+
+# SLP block content, without framing
+Sblock: adt {
+ src: int; # socket ID
+ dst: int; # socket ID
+ proto: int; # packet type
+ xid: int; # transaction ID
+ data: array of byte;
+
+ new: fn(): ref Sblock;
+ print: fn(sb: self ref Sblock, dir: string);
+};
+
+#
+# Palm channel
+#
+Pchan: adt {
+ started: int;
+ shutdown: int;
+
+ protocol: int;
+ lport: byte;
+ rport: byte;
+
+ fd: ref Sys->FD;
+ cfd: ref Sys->FD;
+ baud: int;
+
+ rpid: int;
+ lastid: int;
+ rd: chan of ref Sblock;
+ reply: ref Sblock; # data replacing lost ack
+
+ init: fn(dfd: ref Sys->FD, cfd: ref Sys->FD): ref Pchan;
+ start: fn(p: self ref Pchan): string;
+ stop: fn(p: self ref Pchan);
+ close: fn(p: self ref Pchan): int;
+ slp_read: fn(p: self ref Pchan, nil: int): (ref Sblock, string);
+ slp_write: fn(p: self ref Pchan, xid: int, nil: array of byte): string;
+
+ setbaud: fn(p: self ref Pchan, nil: int);
+
+ padp_read: fn(p: self ref Pchan, xid: int, timeout: int): (array of byte, string);
+ padp_write: fn(p: self ref Pchan, msg: array of byte, xid: int): string;
+ padp_xchg: fn(p: self ref Pchan, msg: array of byte, timeout: int): (array of byte, string);
+ tickle: fn(p: self ref Pchan);
+
+ connect: fn(p: self ref Pchan): string;
+ accept: fn(p: self ref Pchan, baud: int): string;
+
+ nextseq: fn(p: self ref Pchan): int;
+};
+
+Pchan.init(dfd: ref Sys->FD, cfd: ref Sys->FD): ref Pchan
+{
+ p := ref Pchan;
+ p.fd = dfd;
+ p.cfd = cfd;
+ p.baud = InitBaud;
+ p.protocol = SLP_PAD;
+ p.rport = byte 3;
+ p.lport = byte 3;
+ p.rd = chan of ref Sblock;
+ p.lastid = 0;
+ p.rpid = 0;
+ p.started = 0;
+ p.shutdown = 0;
+ return p;
+}
+
+Pchan.start(p: self ref Pchan): string
+{
+ if(p.started)
+ return nil;
+ p.shutdown = 0;
+ p.baud = InitBaud;
+ p.reply = nil;
+ ctl(p, "f");
+ ctl(p, "d1");
+ ctl(p, "r1");
+ ctl(p, "i8");
+ ctl(p, "q8192");
+ ctl(p, sys->sprint("b%d", InitBaud));
+ pidc := chan of int;
+ spawn slp_recv(p, pidc);
+ p.started = 1;
+ p.rpid = <-pidc;
+ err := p.accept(57600);
+ if(err != nil)
+ p.stop();
+ return err;
+}
+
+ctl(p: ref Pchan, s: string)
+{
+ if(p.cfd != nil)
+ sys->fprint(p.cfd, "%s", s);
+}
+
+Pchan.setbaud(p: self ref Pchan, baud: int)
+{
+ if(p.baud != baud){
+ p.baud = baud;
+ ctl(p, sys->sprint("b%d", baud));
+ sys->sleep(200);
+ }
+}
+
+Pchan.stop(p: self ref Pchan)
+{
+ p.shutdown = 0;
+ if(!p.started)
+ return;
+ killpid(p.rpid);
+ p.rpid = 0;
+ p.reply = nil;
+# ctl(p, "f");
+# ctl(p, "d0");
+# ctl(p, "r0");
+# ctl(p, sys->sprint("b%d", InitBaud));
+ p.started = 0;
+}
+
+Pchan.close(p: self ref Pchan): int
+{
+ if(p.started)
+ p.stop();
+ p.reply = nil;
+ p.cfd = nil;
+ p.fd = nil;
+ timers->shutdown();
+ return 0;
+}
+
+# CMP protocol for connection management
+# See include/Core/System/CMCommon.h, Palm SDK
+# There are two major versions: the original V1, still always used in wakeup messsages;
+# and V2, which is completely different (similar structure to Desklink) and used by newer devices, but the headers
+# are the same length. Start off in V1 announcing version 2.x, then switch to that.
+# My device supports only V1, so I use that.
+
+CMPHDRLEN: con 10; # V1: type[1] flags[1] vermajor[1] verminor[1] mbz[2] baud[4]
+ # V2: type[1] cmd[1] error[2] argc[1] mbz[1] mbz[4]
+
+# CMP V1
+Cmajor: con 1;
+Cminor: con 2;
+
+InitBaud: con 9600;
+
+# type
+Cwake, Cinit, Cabort, Cextended: con 1+iota;
+
+# Cinit flags
+ChangeBaud: con 16r80;
+RcvTimeout1: con 16r40; # tell Palm to set receive timeout to 1 minute (CMP v1.1)
+RcvTimeout2: con 16r20; # tell Palm to set receive timeout to 2 minutes (v1.1)
+
+# Cinit and Cwake flag
+LongPacketEnable: con 16r10; # enable long packet support (v1.2)
+
+# Cabort flags
+WrongVersion: con 16r80; # incompatible com versions
+
+# CMP V2
+Carg1: con Palm->ArgIDbase;
+Cresponse: con 16r80;
+Cxchgprefs, Chandshake: con 16r10+iota;
+
+Pchan.connect(p: self ref Pchan): string
+{
+ (nil, e1) := cmp_write(p, Cwake, 0, Cmajor, Cminor, 57600);
+ if(e1 != nil)
+ return e1;
+ (op, flag, nil, nil, baud, e2) := cmp_read(p, 0);
+ if(e2 != nil)
+ return e2;
+ case op {
+ Cinit=>
+ if(flag & ChangeBaud)
+ p.setbaud(baud);
+ return nil;
+
+ Cabort=>
+ return "Palm rejected connect";
+
+ * =>
+ return sys->sprint("Palm connect: reply %d", op);
+ }
+ return nil;
+}
+
+Pchan.accept(p: self ref Pchan, maxbaud: int): string
+{
+ (op, nil, major, minor, baud, err) := cmp_read(p, 0);
+ if(err != nil)
+ return err;
+ if(major != 1){
+ sys->fprint(sys->fildes(2), "palmsrv: comm version mismatch: %d.%d\n", major, minor);
+ cmp_write(p, Cabort, WrongVersion, Cmajor, 0, 0);
+ return sys->sprint("comm version mismatch: %d.%d", major, minor);
+ }
+ if(baud > maxbaud)
+ baud = maxbaud;
+ flag := 0;
+ if(baud != InitBaud)
+ flag = ChangeBaud;
+ (nil, err) = cmp_write(p, Cinit, flag, Cmajor, Cminor, baud);
+ if(err != nil)
+ return err;
+ p.setbaud(baud);
+ return nil;
+}
+
+cmp_write(p: ref Pchan, op: int, flag: int, major: int, minor: int, baud: int): (int, string)
+{
+ cmpbuf := array[CMPHDRLEN] of byte;
+ cmpbuf[0] = byte op;
+ cmpbuf[1] = byte flag;
+ cmpbuf[2] = byte major;
+ cmpbuf[3] = byte minor;
+ cmpbuf[4] = byte 0;
+ cmpbuf[5] = byte 0;
+ put4(cmpbuf[6:], baud);
+
+ if(op == Cwake)
+ return (16rFF, p.padp_write(cmpbuf, 16rFF));
+ xid := p.nextseq();
+ return (xid, p.padp_write(cmpbuf, xid));
+}
+
+cmp_read(p: ref Pchan, xid: int): (int, int, int, int, int, string)
+{
+ (c, err) := p.padp_read(xid, 20*Sec);
+ if(err != nil)
+ return (0, 0, 0, 0, 0, err);
+ if(len c != CMPHDRLEN)
+ return (0, 0, 0, 0, 0, "CMP: bad response");
+ return (int c[0], int c[1], int c[2], int c[3], get4(c[6:]), nil);
+}
+
+#
+# Palm PADP protocol
+# ``The Packet Assembly/Disassembly Protocol'' in
+# Developing Palm OS Communications, US Robotics, 1996, pp. 53-68.
+#
+# forsyth@caldo.demon.co.uk, 1997
+#
+
+FIRST: con 16r80;
+LAST: con 16r40;
+MEMERROR: con 16r20;
+
+# packet types
+Pdata: con 1;
+Pack: con 2;
+Ptickle: con 4;
+Pabort: con 8;
+
+PADPHDRLEN: con 4; # type[1] flags[1] size[2]
+
+RetryInterval: con 4*Sec;
+MaxRetries: con 14; # they say 14 `seconds', but later state they might need 20 for heap mgmt, so i'll assume 14 attempts (at 4sec ea)
+
+Pchan.padp_xchg(p: self ref Pchan, msg: array of byte, timeout: int): (array of byte, string)
+{
+ xid := p.nextseq();
+ err := p.padp_write(msg, xid);
+ if(err != nil)
+ return (nil, err);
+ return p.padp_read(xid, timeout);
+}
+
+#
+# PADP header
+# type[1] flags[2] size[2], high byte first for size
+#
+# max block size is 2^16-1
+# must ack within 2 seconds
+# wait at most 10 seconds for next chunk
+# 10 retries
+#
+
+Pchan.padp_write(p: self ref Pchan, buf: array of byte, xid: int): string
+{
+ count := len buf;
+ if(count >= 1<<16)
+ return "padp: write too big";
+ p.reply = nil;
+ flags := FIRST;
+ mem := buf[0:];
+ offset := 0;
+ while(count > 0){
+ n := count;
+ if(n > DATALIM)
+ n = DATALIM;
+ else
+ flags |= LAST;
+ ob := array[PADPHDRLEN+n] of byte;
+ ob[0] = byte Pdata;
+ ob[1] = byte flags;
+ l: int;
+ if(flags & FIRST)
+ l = count; # total size in first segment
+ else
+ l = offset; # offset in rest
+ put2(ob[2:], l);
+ ob[PADPHDRLEN:] = mem[0:n];
+ if(debug)
+ padp_dump(ob, "Tx");
+ p.slp_write(xid, ob);
+ retries := 0;
+ for(;;){
+ (ib, nil) := p.slp_read(RetryInterval);
+ if(ib == nil){
+ sys->print("padp write: ack timeout\n");
+ retries++;
+ if(retries > MaxRetries){
+ # USR says not to give up if (flags&LAST)!=0; giving up seems safer
+ sys->print("padp write: give up\n");
+ return "PADP: no response";
+ }
+ p.slp_write(xid, ob);
+ continue;
+ }
+ if(ib.proto != SLP_PAD || len ib.data < PADPHDRLEN || ib.xid != xid && ib.xid != 16rFF){
+ sys->print("padp write: ack wrong type(%d) or xid(%d,%d), or len %d\n", ib.proto, ib.xid, xid, len ib.data);
+ continue;
+ }
+ if(ib.xid == 16rFF){ # connection management
+ if(int ib.data[0] == Ptickle)
+ continue;
+ if(int ib.data[0] == Pabort){
+ sys->print("padp write: device abort\n");
+ p.shutdown = 1;
+ return "device cancelled operation";
+ }
+ }
+ if(int ib.data[0] != Pack){
+ if(int ib.data[0] == Ptickle)
+ continue;
+ # right transaction ... if it's acceptable data, USR says to save it & treat as ack
+ sys->print("padp write: type %d, not ack\n", int ib.data[0]);
+ if(int ib.data[0] == Pdata && flags & LAST && int ib.data[1] & FIRST){
+ p.reply = ib;
+ break;
+ }
+ continue;
+ }
+ if(int ib.data[1] & MEMERROR)
+ return "padp: pilot out of memory";
+ if((flags&(FIRST|LAST)) != (int ib.data[1]&(FIRST|LAST)) ||
+ get2(ib.data[2:]) != get2(ob[2:])){
+ sys->print("padp write: ack, wrong flags (#%x,#%x) or offset (%d,%d)\n", int ib.data[1], flags, get2(ib.data[2:]), get2(ob[2:]));
+ continue;
+ }
+ if(debug)
+ sys->print("padp write: ack %d %d\n", xid, get2(ob[2:]));
+ break;
+ }
+ mem = mem[n:];
+ count -= n;
+ offset += n;
+ flags &= ~FIRST;
+ }
+ return nil;
+}
+
+Pchan.padp_read(p: self ref Pchan, xid, timeout: int): (array of byte, string)
+{
+ buf, mem: array of byte;
+
+ offset := 0;
+ ready := 0;
+ retries := 0;
+ ack := array[PADPHDRLEN] of byte;
+ for(;;){
+ b := p.reply;
+ if(b == nil){
+ err: string;
+ (b, err) = p.slp_read(timeout);
+ if(b == nil){
+ sys->print("padp read: timeout %d\n", retries);
+ if(++retries <= 5)
+ continue;
+ sys->print("padp read: gave up\n");
+ return (nil, err);
+ }
+ retries = 0;
+ } else
+ p.reply = nil;
+ if(debug)
+ padp_dump(b.data, "Rx");
+ if(len b.data < PADPHDRLEN){
+ sys->print("padp read: length\n");
+ continue;
+ }
+ if(b.proto != SLP_PAD){
+ sys->print("padp read: bad proto (%d)\n", b.proto);
+ continue;
+ }
+ if(int b.data[0] == Pabort && b.xid == 16rFF){
+ p.shutdown = 1;
+ return (nil, "device cancelled transaction");
+ }
+ if(int b.data[0] != Pdata || xid != 0 && b.xid != xid){
+ sys->print("padp read mismatch: type (%d) or xid(%d::%d)\n", int b.data[0], b.xid, xid);
+ continue;
+ }
+ f := int b.data[1];
+ o := get2(b.data[2:]);
+ if(f & FIRST){
+ buf = array[o] of byte;
+ ready = 1;
+ offset = 0;
+ o = 0;
+ mem = buf;
+ timeout = 4*Sec;
+ }
+ if(!ready || o != offset){
+ sys->print("padp read: offset %d, expected %d\n", o, offset);
+ continue;
+ }
+ n := len b.data - PADPHDRLEN;
+ if(n > len mem){
+ sys->print("padp read: record too long (%d/%d)\n", n, len mem);
+ # it's probably fatal, but retrying does no harm
+ continue;
+ }
+ mem[0:] = b.data[PADPHDRLEN:PADPHDRLEN+n];
+ mem = mem[n:];
+ offset += n;
+ ack[0:] = b.data[0:PADPHDRLEN];
+ ack[0] = byte Pack;
+ p.slp_write(xid, ack);
+ if(f & LAST)
+ break;
+ }
+ if(offset != len buf)
+ return (buf[0:offset], nil);
+ return (buf, nil);
+}
+
+Pchan.nextseq(p: self ref Pchan): int
+{
+ n := p.lastid + 1;
+ if(n >= 16rFF)
+ n = 1;
+ p.lastid = n;
+ return n;
+}
+
+Pchan.tickle(p: self ref Pchan)
+{
+ xid := p.nextseq();
+ data := array[PADPHDRLEN] of byte;
+ data[0] = byte Ptickle;
+ data[1] = byte (FIRST|LAST);
+ put2(data[2:], 0);
+ if(debug)
+ sys->print("PADP: tickle\n");
+ p.slp_write(xid, data);
+}
+
+padp_dump(data: array of byte, dir: string)
+{
+ stype: string;
+
+ case int data[0] {
+ Pdata => stype = "Data";
+ Pack => stype = "Ack";
+ Ptickle => stype = "Tickle";
+ Pabort => stype = "Abort";
+ * => stype = sys->sprint("#%x", int data[0]);
+ }
+
+ sys->print("PADP %s %s flags=#%x len=%d\n", stype, dir, int data[1], get2(data[2:]));
+
+ if(debug > 1 && (data[0] != byte Pack || len data > 4)){
+ data = data[4:];
+ for(i := 0; i < len data;){
+ sys->print(" %.2x", int data[i]);
+ if(++i%16 == 0)
+ sys->print("\n");
+ }
+ sys->print("\n");
+ }
+}
+
+#
+# Palm's Serial Link Protocol
+# See include/Core/System/SerialLinkMgr.h in Palm SDK
+# and the description in the USR document mentioned above.
+#
+
+SLPHDRLEN: con 10; # BE[1] EF[1] ED[1] dest[1] src[1] type[1] size[2] xid[1] check[1] body[size] crc[2]
+SLP_MTU: con SLPHDRLEN+PADPHDRLEN+DATALIM;
+
+Sblock.new(): ref Sblock
+{
+ return ref Sblock(0, 0, 0, 16rFF, nil);
+}
+
+#
+# format and write an SLP frame
+#
+Pchan.slp_write(p: self ref Pchan, xid: int, b: array of byte): string
+{
+ d := array[SLPHDRLEN] of byte;
+ cb := array[2] of byte;
+
+ nb := len b;
+ d[0] = byte 16rBE;
+ d[1] = byte 16rEF;
+ d[2] = byte 16rED;
+ d[3] = byte p.rport;
+ d[4] = byte p.lport;
+ d[5] = byte p.protocol;
+ d[6] = byte (nb >> 8);
+ d[7] = byte (nb & 16rFF);
+ d[8] = byte xid;
+ d[9] = byte 0;
+ n := 0;
+ for(i:=0; i<len d; i++)
+ n += int d[i];
+ d[9] = byte (n & 16rFF);
+ if(debug)
+ printbytes(d, "SLP Tx hdr");
+ crc := crc16(d, 0);
+ put2(cb, crc16(b, crc));
+
+ if(sys->write(p.fd, d, SLPHDRLEN) != SLPHDRLEN ||
+ sys->write(p.fd, b, nb) != len b ||
+ sys->write(p.fd, cb, 2) != 2)
+ return sys->sprint("%r");
+ return nil;
+}
+
+Pchan.slp_read(p: self ref Pchan, timeout: int): (ref Sblock, string)
+{
+ clock := Timer.start(timeout);
+ alt {
+ <-clock.timeout =>
+ if(debug)
+ sys->print("SLP: timeout\n");
+ return (nil, "SLP: timeout");
+ b := <-p.rd =>
+ clock.stop();
+ return (b, nil);
+ }
+}
+
+slp_recv(p: ref Pchan, pidc: chan of int)
+{
+ n: int;
+
+ pidc <-= sys->pctl(0, nil);
+ buf := array[2*SLP_MTU] of byte;
+ sb := Sblock.new();
+ rd := wr := 0;
+Work:
+ for(;;){
+
+ if(wr != rd){
+ # data already in buffer might start a new frame
+ if(rd != 0){
+ buf[0:] = buf[rd:wr];
+ wr -= rd;
+ rd = 0;
+ }
+ }else
+ rd = wr = 0;
+
+ # header
+ while(wr < SLPHDRLEN){
+ n = sys->read(p.fd, buf[wr:], SLPHDRLEN-wr);
+ if(n <= 0)
+ break Work;
+ wr += n;
+ }
+# {for(i:=0; i<wr;i++)sys->print("%.2x", int buf[i]);sys->print("\n");}
+ if(buf[0] != byte 16rBE || buf[1] != byte 16rEF || buf[2] != byte 16rED){
+ rd++;
+ continue;
+ }
+ if(debug)
+ printbytes(buf[0:wr], "SLP Rx hdr");
+ n = 0;
+ for(i:=0; i<SLPHDRLEN-1; i++)
+ n += int buf[i];
+ if((n & 16rFF) != int buf[9]){
+ rd += 3;
+ continue;
+ }
+ hdr := buf[0:SLPHDRLEN];
+ sb.dst = int hdr[3];
+ sb.src = int hdr[4];
+ sb.proto = int hdr[5];
+ size := (int hdr[6]<<8) | int hdr[7];
+ sb.xid = int hdr[8];
+ sb.data = array[size] of byte;
+ crc := crc16(hdr, 0);
+ rd += SLPHDRLEN;
+ if(rd == wr)
+ rd = wr = 0;
+
+ # data and CRC
+ while(wr-rd < size+2){
+ n = sys->read(p.fd, buf[wr:], size+2-(wr-rd));
+ if(n <= 0)
+ break Work;
+ wr += n;
+ }
+ crc = crc16(buf[rd:rd+size], crc);
+ if(crc != get2(buf[rd+size:])){
+ if(debug)
+ sys->print("CRC error: local=#%.4ux pilot=#%.4ux\n", crc, get2(buf[rd+size:]));
+ for(; rd < wr && buf[rd] != byte 16rBE; rd++)
+ ; # hunt for next header
+ continue;
+ }
+ if(sb.proto != SLP_Loop){
+ sb.data[0:] = buf[rd:rd+size];
+ if(debug)
+ sb.print("Rx");
+ rd += size+2;
+ p.rd <-= sb;
+ sb = Sblock.new();
+ } else {
+ # should we reflect these?
+ if(debug)
+ sb.print("Loop");
+ rd += size+2;
+ }
+ }
+ p.rd <-= nil;
+}
+
+Sblock.print(b: self ref Sblock, dir: string)
+{
+ sys->print("SLP %s %d->%d len=%d proto=%d xid=#%.2x\n",
+ dir, int b.src, int b.dst, len b.data, int b.proto, int b.xid);
+}
+
+printbytes(d: array of byte, what: string)
+{
+ buf := sys->sprint("%s[", what);
+ for(i:=0; i<len d; i++)
+ buf += sys->sprint(" #%.2x", int d[i]);
+ buf += "]";
+ sys->print("%s\n", buf);
+}
+
+get4(p: array of byte): int
+{
+ return (int p[0]<<24) | (int p[1]<<16) | (int p[2]<<8) | int p[3];
+}
+
+get3(p: array of byte): int
+{
+ return (int p[1]<<16) | (int p[2]<<8) | int p[3];
+}
+
+get2(p: array of byte): int
+{
+ return (int p[0]<<8) | int p[1];
+}
+
+put4(p: array of byte, v: int)
+{
+ p[0] = byte (v>>24);
+ p[1] = byte (v>>16);
+ p[2] = byte (v>>8);
+ p[3] = byte (v & 16rFF);
+}
+
+put3(p: array of byte, v: int)
+{
+ p[0] = byte (v>>16);
+ p[1] = byte (v>>8);
+ p[2] = byte (v & 16rFF);
+}
+
+put2(p: array of byte, v: int)
+{
+ p[0] = byte (v>>8);
+ p[1] = byte (v & 16rFF);
+}
+
+# this will be done by table look up;
+# polynomial is xⁱ⁶+xⁱ⁲+x⁵+1
+
+crc16(buf: array of byte, crc: int): int
+{
+ for(j := 0; j < len buf; j++){
+ crc = crc ^ (int buf[j]) << 8;
+ for(i := 0; i < 8; i++)
+ if(crc & 16r8000)
+ crc = (crc << 1) ^ 16r1021;
+ else
+ crc = crc << 1;
+ }
+ return crc & 16rffff;
+}
diff --git a/appl/cmd/pause.b b/appl/cmd/pause.b
new file mode 100644
index 00000000..ab8cfbe4
--- /dev/null
+++ b/appl/cmd/pause.b
@@ -0,0 +1,17 @@
+implement Pause;
+#
+# init program to do nothing but pause
+#
+
+include "sys.m";
+include "draw.m";
+
+Pause: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ <-chan of int;
+}
diff --git a/appl/cmd/plumb.b b/appl/cmd/plumb.b
new file mode 100644
index 00000000..88879715
--- /dev/null
+++ b/appl/cmd/plumb.b
@@ -0,0 +1,115 @@
+implement Plumb;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "arg.m";
+ arg: Arg;
+
+include "plumbmsg.m";
+ plumbmsg: Plumbmsg;
+ Msg, Attr: import plumbmsg;
+
+include "workdir.m";
+ workdir: Workdir;
+
+Plumb: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+usage()
+{
+ sys->fprint(stderr(), "Usage: plumb [-s src] [-d dest] [-D dir] [-k kind] [-a name val] ... data ...\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ plumbmsg = load Plumbmsg Plumbmsg->PATH;
+ if(plumbmsg == nil)
+ nomod(Plumbmsg->PATH);
+ workdir = load Workdir Workdir->PATH;
+ if(workdir == nil)
+ nomod(Workdir->PATH);
+
+ if(plumbmsg->init(1, nil, 0) < 0)
+ err(sys->sprint("can't connect to plumb: %r"));
+
+ attrs: list of ref Attr;
+ m := ref Msg("plumb", nil, workdir->init(), "text", nil, nil);
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 's' =>
+ m.src = use(arg->arg(), c);
+ 'd' =>
+ m.dst = use(arg->arg(), c);
+ 'D' =>
+ m.dir = use(arg->arg(), c);
+ 'k' =>
+ m.kind = use(arg->arg(), c);
+ 'a' =>
+ name := use(arg->arg(), c);
+ val := use(arg->arg(), c);
+ attrs = tack(attrs, ref Attr(name, val));
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ usage();
+ nb := 0;
+ for(a := args; a != nil; a = tl a)
+ nb += len array of byte hd a;
+ nb += len args;
+ buf := array[nb] of byte;
+ nb = 0;
+ for(a = args; a != nil; a = tl a){
+ b := array of byte hd a;
+ buf[nb++] = byte ' ';
+ buf[nb:] = b;
+ nb += len b;
+ }
+ m.data = buf[1:];
+ m.attr = plumbmsg->attrs2string(attrs);
+ if(m.send() < 0)
+ err(sys->sprint("can't plumb message: %r"));
+}
+
+tack(l: list of ref Attr, v: ref Attr): list of ref Attr
+{
+ if(l == nil)
+ return v :: nil;
+ return hd l :: tack(tl l, v);
+}
+
+use(s: string, c: int): string
+{
+ if(s == nil)
+ err(sys->sprint("missing value for -%c", c));
+ return s;
+}
+
+nomod(m: string)
+{
+ err(sys->sprint("can't load %s: %r\n", m));
+}
+
+err(s: string)
+{
+ sys->fprint(stderr(), "plumb: %s\n", s);
+ raise "fail:error";
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
diff --git a/appl/cmd/plumber.b b/appl/cmd/plumber.b
new file mode 100644
index 00000000..016eb623
--- /dev/null
+++ b/appl/cmd/plumber.b
@@ -0,0 +1,766 @@
+implement Plumber;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "sh.m";
+
+include "regex.m";
+ regex: Regex;
+
+include "string.m";
+ str: String;
+
+include "../lib/plumbing.m";
+ plumbing: Plumbing;
+ Pattern, Rule: import plumbing;
+
+include "plumbmsg.m";
+ plumbmsg: Plumbmsg;
+ Msg, Attr: import plumbmsg;
+
+include "arg.m";
+
+Plumber: module
+{
+ init: fn(ctxt: ref Draw->Context, argl: list of string);
+};
+
+Input: adt
+{
+ inc: chan of ref Inmesg;
+ resc: chan of int;
+ io: ref Sys->FileIO;
+};
+
+Output: adt
+{
+ name: string;
+ outc: chan of string;
+ io: ref Sys->FileIO;
+ queue: list of array of byte;
+ started: int;
+ startup: string;
+ waiting: int;
+};
+
+Port: adt
+{
+ name: string;
+ startup: string;
+ alwaysstart: int;
+};
+
+Match: adt
+{
+ p0, p1: int;
+};
+
+Inmesg: adt
+{
+ msg: ref Msg;
+ text: string; # if kind is text
+ p0,p1: int;
+ match: array of Match;
+ port: int;
+ startup: string;
+ args: list of string;
+ attrs: list of ref Attr;
+ clearclick: int;
+ set: int;
+ # $ arguments
+ _n: array of string;
+ _dir: string;
+ _file: string;
+};
+
+# Message status after processing
+HANDLED: con -1;
+UNKNOWN: con -2;
+NOTSTARTED: con -3;
+
+output: array of ref Output;
+
+input: ref Input;
+
+stderr: ref Sys->FD;
+pgrp: int;
+rules: list of ref Rule;
+titlectl: chan of string;
+ports: list of ref Port;
+wmstartup := 0;
+wmchan := "/chan/wm";
+verbose := 0;
+
+context: ref Draw->Context;
+
+usage()
+{
+ sys->fprint(stderr, "Usage: plumb [-vw] [-c wmchan] [initfile ...]\n");
+ raise "fail:usage";
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ context = ctxt;
+
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ stderr = sys->fildes(2);
+
+ regex = load Regex Regex->PATH;
+ plumbing = load Plumbing Plumbing->PATH;
+ str = load String String->PATH;
+
+ err: string;
+ nogrp := 0;
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'w' =>
+ wmstartup = 1;
+ 'c' =>
+ if ((wmchan = arg->arg()) == nil)
+ usage();
+ 'v' =>
+ verbose = 1;
+ 'n' =>
+ nogrp = 1;
+ * =>
+ usage();
+ }
+ }
+ args = arg->argv();
+ arg = nil;
+
+ (rules, err) = plumbing->init(regex, args);
+ if(err != nil){
+ sys->fprint(stderr, "plumb: %s\n", err);
+ raise "fail:init";
+ }
+
+ plumbmsg = load Plumbmsg Plumbmsg->PATH;
+ plumbmsg->init(0, nil, 0);
+
+ if(nogrp)
+ pgrp = sys->pctl(0, nil);
+ else
+ pgrp = sys->pctl(sys->NEWPGRP, nil);
+
+ r := rules;
+ for(i:=0; i<len rules; i++){
+ rule := hd r;
+ r = tl r;
+ for(j:=0; j<len rule.action; j++)
+ if(rule.action[j].pred == "to" || rule.action[j].pred == "alwaysstart"){
+ p := findport(rule.action[j].arg);
+ if(p == nil){
+ p = ref Port(rule.action[j].arg, nil, rule.action[j].pred == "alwaysstart");
+ ports = p :: ports;
+ }
+ for(k:=0; k<len rule.action; k++)
+ if(rule.action[k].pred == "start")
+ p.startup = rule.action[k].arg;
+ break;
+ }
+ }
+
+ input = ref Input;
+ input.io = makefile("plumb.input");
+ if(input.io == nil)
+ shutdown();
+ input.inc = chan of ref Inmesg;
+ input.resc = chan of int;
+ spawn receiver(input);
+
+ output = array[len ports] of ref Output;
+
+ pp := ports;
+ for(i=0; i<len output; i++){
+ p := hd pp;
+ pp = tl pp;
+ output[i] = ref Output;
+ output[i].name = p.name;
+ output[i].io = makefile("plumb."+p.name);
+ if(output[i].io == nil)
+ shutdown();
+ output[i].outc = chan of string;
+ output[i].started = 0;
+ output[i].startup = p.startup;
+ output[i].waiting = 0;
+ }
+
+ # spawn so we return without needing to run plumb in background
+ spawn sender(input, output);
+}
+
+findport(name: string): ref Port
+{
+ for(p:=ports; p!=nil; p=tl p)
+ if((hd p).name == name)
+ return hd p;
+ return nil;
+}
+
+makefile(file: string): ref Sys->FileIO
+{
+ io := sys->file2chan("/chan", file);
+ if(io == nil){
+ sys->fprint(stderr, "plumb: can't establish /chan/%s: %r\n", file);
+ return nil;
+ }
+ return io;
+}
+
+receiver(input: ref Input)
+{
+
+ for(;;){
+ (nil, msg, nil, wc) := <-input.io.write;
+ if(wc == nil)
+ ; # not interested in EOF; leave channel open
+ else{
+ input.inc <-= parse(msg);
+ res := <- input.resc;
+ err := "";
+ if(res == UNKNOWN)
+ err = "no matching plumb rule";
+ wc <-= (len msg, err);
+ }
+ }
+}
+
+sender(input: ref Input, output: array of ref Output)
+{
+ outputc := array[len output] of chan of (int, int, int, Sys->Rread);
+
+ for(;;){
+ alt{
+ in := <-input.inc =>
+ if(in == nil){
+ input.resc <-= HANDLED;
+ break;
+ }
+ (j, msg) := process(in);
+ case j {
+ HANDLED =>
+ break;
+ UNKNOWN =>
+ if(in.msg.src != "acme")
+ sys->fprint(stderr, "plumb: don't know who message goes to\n");
+ NOTSTARTED =>
+ sys->fprint(stderr, "plumb: can't start application\n");
+ * =>
+ output[j].queue = append(output[j].queue, msg);
+ outputc[j] = output[j].io.read;
+ }
+ input.resc <-= j;
+
+ (j, tmp) := <-outputc =>
+ (nil, nbytes, nil, rc) := tmp;
+ if(rc == nil) # no interest in EOF
+ break;
+ msg := hd output[j].queue;
+ if(nbytes < len msg){
+ rc <-= (nil, "buffer too short for message");
+ break;
+ }
+ output[j].queue = tl output[j].queue;
+ if(output[j].queue == nil)
+ outputc[j] = nil;
+ rc <-= (msg, nil);
+ }
+ }
+}
+
+parse(a: array of byte): ref Inmesg
+{
+ msg := Msg.unpack(a);
+ if(msg == nil)
+ return nil;
+ i := ref Inmesg;
+ i.msg = msg;
+ if(msg.dst != nil){
+ if(control(i))
+ return nil;
+ toport(i, msg.dst);
+ }else
+ i.port = -1;
+ i.match = array[10] of { * => Match(-1, -1)};
+ i._n = array[10] of string;
+ i.attrs = plumbmsg->string2attrs(i.msg.attr);
+ return i;
+}
+
+append(l: list of array of byte, a: array of byte): list of array of byte
+{
+ if(l == nil)
+ return a :: nil;
+ return hd l :: append(tl l, a);
+}
+
+shutdown()
+{
+ fname := sys->sprint("#p/%d/ctl", pgrp);
+ if((fdesc := sys->open(fname, sys->OWRITE)) != nil)
+ sys->write(fdesc, array of byte "killgrp\n", 8);
+ raise "fail:error";
+}
+
+# Handle control messages
+control(in: ref Inmesg): int
+{
+ msg := in.msg;
+ if(msg.kind!="text" || msg.dst!="plumb")
+ return 0;
+ text := string msg.data;
+ case text {
+ "start" =>
+ start(msg.src, 1);
+ "stop" =>
+ start(msg.src, -1);
+ * =>
+ sys->fprint(stderr, "plumb: unrecognized control message from %s: %s\n", msg.src, text);
+ }
+ return 1;
+}
+
+start(port: string, startstop: int)
+{
+ for(i:=0; i<len output; i++)
+ if(port == output[i].name){
+ output[i].waiting = 0;
+ output[i].started += startstop;
+ return;
+ }
+ sys->fprint(stderr, "plumb: \"start\" message from unrecognized port %s\n", port);
+}
+
+startup(dir, prog: string, args: list of string, wait: chan of int)
+{
+ if(wmstartup){
+ fd := sys->open(wmchan, Sys->OWRITE);
+ if(fd != nil){
+ sys->fprint(fd, "s %s", str->quoted(dir :: prog :: args));
+ wait <-= 1;
+ return;
+ }
+ }
+
+ sys->pctl(Sys->NEWFD|Sys->NEWPGRP|Sys->FORKNS, list of {0, 1, 2});
+ wait <-= 1;
+ wait = nil;
+ mod := load Command prog;
+ if(mod == nil){
+ sys->fprint(stderr, "plumb: can't load %s: %r\n", prog);
+ return;
+ }
+ sys->chdir(dir);
+ mod->init(context, prog :: args);
+}
+
+# See if messages should be queued while waiting for program to connect
+shouldqueue(out: ref Output): int
+{
+ p := findport(out.name);
+ if(p == nil){
+ sys->fprint(stderr, "plumb: can't happen in shouldqueue\n");
+ return 0;
+ }
+ if(p.alwaysstart)
+ return 0;
+ return out.waiting;
+}
+
+# Determine destination of input message, reformat for output
+process(in: ref Inmesg): (int, array of byte)
+{
+ if(!clarify(in))
+ return (UNKNOWN, nil);
+ if(in.port < 0)
+ return (UNKNOWN, nil);
+ a := in.msg.pack();
+ j := in.port;
+ if(a == nil)
+ j = UNKNOWN;
+ else if(output[j].started==0 && !shouldqueue(output[j])){
+ path: string;
+ args: list of string;
+ if(in.startup!=nil){
+ path = macro(in, in.startup);
+ args = expand(in, in.args);
+ }else if(output[j].startup != nil){
+ path = output[j].startup;
+ args = in.text :: nil;
+ }else
+ return (NOTSTARTED, nil);
+ log(sys->sprint("start %s port %s\n", path, output[j].name));
+ wait := chan of int;
+ output[j].waiting = 1;
+ spawn startup(in.msg.dir, path, args, wait);
+ <-wait;
+ return (HANDLED, nil);
+ }else{
+ if(in.msg.kind != "text")
+ text := sys->sprint("message of type %s", in.msg.kind);
+ else{
+ text = in.text;
+ for(i:=0; i<len text; i++){
+ if(text[i]=='\n'){
+ text = text[0:i];
+ break;
+ }
+ if(i > 50) {
+ text = text[0:i]+"...";
+ break;
+ }
+ }
+ }
+ log(sys->sprint("send \"%s\" to %s", text, output[j].name));
+ }
+ return (j, a);
+}
+
+# expand $arguments
+expand(in: ref Inmesg, args: list of string): list of string
+{
+ a: list of string;
+ while(args != nil){
+ a = macro(in, hd args) :: a;
+ args = tl args;
+ }
+ while(a != nil){
+ args = hd a :: args;
+ a = tl a;
+ }
+ return args;
+}
+
+# resolve all ambiguities, fill in any missing fields
+clarify(in: ref Inmesg): int
+{
+ in.clearclick = 0;
+ in.set = 0;
+ msg := in.msg;
+ if(msg.kind != "text")
+ return 0;
+ in.text = string msg.data;
+ if(msg.dst != "")
+ return 1;
+ return dorules(in, rules);
+}
+
+dorules(in: ref Inmesg, rules: list of ref Rule): int
+{
+ if (verbose)
+ log("msg: " + inmesg2s(in));
+ for(r:=rules; r!=nil; r=tl r) {
+ if(matchrule(in, hd r)){
+ applyrule(in, hd r);
+ if (verbose)
+ log("yes");
+ return 1;
+ } else if (verbose)
+ log("no");
+ }
+ return 0;
+}
+
+inmesg2s(in: ref Inmesg): string
+{
+ m := in.msg;
+ s := sys->sprint("src=%s; dst=%s; dir=%s; kind=%s; attr='%s'",
+ m.src, m.dst, m.dir, m.kind, m.attr);
+ if (m.kind == "text")
+ s += "; data='" + string m.data + "'";
+ return s;
+}
+
+matchrule(in: ref Inmesg, r: ref Rule): int
+{
+ pats := r.pattern;
+ for(i:=0; i<len in.match; i++)
+ in.match[i] = (-1,-1);
+ # no rules at all implies success, so return if any fail
+ for(i=0; i<len pats; i++)
+ if(matchpattern(in, pats[i]) == 0)
+ return 0;
+ return 1;
+}
+
+applyrule(in: ref Inmesg, r: ref Rule)
+{
+ acts := r.action;
+ for(i:=0; i<len acts; i++)
+ applypattern(in, acts[i]);
+ if(in.clearclick){
+ al: list of ref Attr;
+ for(l:=in.attrs; l!=nil; l=tl l)
+ if((hd l).name != "click")
+ al = hd l :: al;
+ in.attrs = al;
+ in.msg.attr = plumbmsg->attrs2string(al);
+ if(in.set){
+ in.text = macro(in, "$0");
+ in.msg.data = array of byte in.text;
+ }
+ }
+}
+
+matchpattern(in: ref Inmesg, p: ref Pattern): int
+{
+ msg := in.msg;
+ text: string;
+ case p.field {
+ "src" => text = msg.src;
+ "dst" => text = msg.dst;
+ "dir" => text = msg.dir;
+ "kind" => text = msg.kind;
+ "attr" => text = msg.attr;
+ "data" => text = in.text;
+ * =>
+ sys->fprint(stderr, "plumb: don't recognize pattern field %s\n", p.field);
+ return 0;
+ }
+ if (verbose)
+ log(sys->sprint("'%s' %s '%s'\n", text, p.pred, p.arg));
+ case p.pred {
+ "is" =>
+ return text == p.arg;
+ "isfile" or "isdir" =>
+ text = p.arg;
+ if(p.expand)
+ text = macro(in, text);
+ if(len text == 0)
+ return 0;
+ if(len in.msg.dir!=0 && text[0] != '/' && text[0]!='#')
+ text = in.msg.dir+"/"+text;
+ text = cleanname(text);
+ (ok, dir) := sys->stat(text);
+ if(ok < 0)
+ return 0;
+ if(p.pred=="isfile" && (dir.mode&Sys->DMDIR)==0){
+ in._file = text;
+ return 1;
+ }
+ if(p.pred=="isdir" && (dir.mode&Sys->DMDIR)!=0){
+ in._dir = text;
+ return 1;
+ }
+ return 0;
+ "matches" =>
+ (clickspecified, val) := plumbmsg->lookup(in.attrs, "click");
+ if(p.field != "data")
+ clickspecified = 0;
+ if(!clickspecified){
+ # easy case. must match whole string
+ matches := regex->execute(p.regex, text);
+ if(matches == nil)
+ return 0;
+ (p0, p1) := matches[0];
+ if(p0!=0 || p1!=len text)
+ return 0;
+ in.match = matches;
+ setvars(in, text);
+ return 1;
+ }
+ matches := clickmatch(p.regex, text, int val);
+ if(matches == nil)
+ return 0;
+ (p0, p1) := matches[0];
+ # assumes all matches are in same sequence
+ if(in.match[0].p0 != -1)
+ return p0==in.match[0].p0 && p1==in.match[0].p1;
+ in.match = matches;
+ setvars(in, text);
+ in.clearclick = 1;
+ in.set = 1;
+ return 1;
+ "set" =>
+ text = p.arg;
+ if(p.expand)
+ text = macro(in, text);
+ case p.field {
+ "src" => msg.src = text;
+ "dst" => msg.dst = text;
+ "dir" => msg.dir = text;
+ "kind" => msg.kind = text;
+ "attr" => msg.attr = text;
+ "data" => in.text = text;
+ msg.data = array of byte text;
+ msg.kind = "text";
+ in.set = 0;
+ }
+ return 1;
+ * =>
+ sys->fprint(stderr, "plumb: don't recognize pattern predicate %s\n", p.pred);
+ }
+ return 0;
+}
+
+applypattern(in: ref Inmesg, p: ref Pattern): int
+{
+ if(p.field != "plumb"){
+ sys->fprint(stderr, "plumb: don't recognize action field %s\n", p.field);
+ return 0;
+ }
+ case p.pred {
+ "to" or "alwaysstart" =>
+ if(in.port >= 0) # already specified
+ return 1;
+ toport(in, p.arg);
+ "start" =>
+ in.startup = p.arg;
+ in.args = p.extra;
+ * =>
+ sys->fprint(stderr, "plumb: don't recognize action %s\n", p.pred);
+ }
+ return 1;
+}
+
+toport(in: ref Inmesg, name: string): int
+{
+ for(i:=0; i<len output; i++)
+ if(name == output[i].name){
+ in.msg.dst = name;
+ in.port = i;
+ return i;
+ }
+ in.port = -1;
+ sys->fprint(stderr, "plumb: unrecognized port %s\n", name);
+ return -1;
+}
+
+# simple heuristic: look for leftmost match that reaches click position
+clickmatch(re: ref Regex->Arena, text: string, click: int): array of Match
+{
+ for(i:=0; i<=click && i < len text; i++){
+ matches := regex->executese(re, text, (i, -1), i == 0, 1);
+ if(matches == nil)
+ continue;
+ (p0, p1) := matches[0];
+
+ if(p0>=i && p1>=click)
+ return matches;
+ }
+ return nil;
+}
+
+setvars(in: ref Inmesg, text: string)
+{
+ for(i:=0; i<len in.match && in.match[i].p0>=0; i++)
+ in._n[i] = text[in.match[i].p0:in.match[i].p1];
+ for(; i<len in._n; i++)
+ in._n[i] = "";
+}
+
+macro(in: ref Inmesg, text: string): string
+{
+ word := "";
+ i := 0;
+ j := 0;
+ for(;;){
+ if(i == len text)
+ break;
+ if(text[i++] != '$')
+ continue;
+ if(i == len text)
+ break;
+ word += text[j:i-1];
+ (res, skip) := dollar(in, text[i:]);
+ word += res;
+ i += skip;
+ j = i;
+ }
+ if(j < len text)
+ word += text[j:];
+ return word;
+}
+
+dollar(in: ref Inmesg, text: string): (string, int)
+{
+ if(text[0] == '$')
+ return ("$", 1);
+ if('0'<=text[0] && text[0]<='9')
+ return (in._n[text[0]-'0'], 1);
+ if(len text < 3)
+ return ("$", 0);
+ case text[0:3] {
+ "src" => return (in.msg.src, 3);
+ "dst" => return (in.msg.dst, 3);
+ "dir" => return (in._dir, 3);
+ }
+ if(len text< 4)
+ return ("$", 0);
+ case text[0:4] {
+ "attr" => return (in.msg.attr, 4);
+ "data" => return (in.text, 4);
+ "file" => return (in._file, 4);
+ "kind" => return (in.msg.kind, 4);
+ }
+ return ("$", 0);
+}
+
+# compress ../ references and do other cleanups
+cleanname(name: string): string
+{
+ # compress multiple slashes
+ n := len name;
+ for(i:=0; i<n-1; i++)
+ if(name[i]=='/' && name[i+1]=='/'){
+ name = name[0:i]+name[i+1:];
+ --i;
+ n--;
+ }
+ # eliminate ./
+ for(i=0; i<n-1; i++)
+ if(name[i]=='.' && name[i+1]=='/' && (i==0 || name[i-1]=='/')){
+ name = name[0:i]+name[i+2:];
+ --i;
+ n -= 2;
+ }
+ found: int;
+ do{
+ # compress xx/..
+ found = 0;
+ for(i=1; i<=n-3; i++)
+ if(name[i:i+3] == "/.."){
+ if(i==n-3 || name[i+3]=='/'){
+ found = 1;
+ break;
+ }
+ }
+ if(found)
+ for(j:=i-1; j>=0; --j)
+ if(j==0 || name[j-1]=='/'){
+ i += 3; # character beyond ..
+ if(i<n && name[i]=='/')
+ ++i;
+ name = name[0:j]+name[i:];
+ n -= (i-j);
+ break;
+ }
+ }while(found);
+ # eliminate trailing .
+ if(n>=2 && name[n-2]=='/' && name[n-1]=='.')
+ --n;
+ if(n == 0)
+ return ".";
+ if(n != len name)
+ name = name[0:n];
+ return name;
+}
+
+log(s: string)
+{
+ if(len s == 0)
+ return;
+ if(s[len s-1] != '\n')
+ s[len s] = '\n';
+ sys->print("plumb: %s", s);
+}
diff --git a/appl/cmd/prof.b b/appl/cmd/prof.b
new file mode 100644
index 00000000..55c676c5
--- /dev/null
+++ b/appl/cmd/prof.b
@@ -0,0 +1,243 @@
+implement Prof;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+ arg: Arg;
+include "profile.m";
+ profile: Profile;
+include "sh.m";
+
+stderr: ref Sys->FD;
+
+Prof: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+ init0: fn(nil: ref Draw->Context, argv: list of string): Profile->Prof;
+};
+
+ignored(s: string)
+{
+ sys->fprint(stderr, "prof: warning: %s ignored\n", s);
+}
+
+exits(e: string)
+{
+ if(profile != nil)
+ profile->end();
+ raise "fail:" + e;
+}
+
+pfatal(s: string)
+{
+ sys->fprint(stderr, "prof: %s: %s\n", s, profile->lasterror());
+ exits("error");
+}
+
+badmodule(p: string)
+{
+ sys->fprint(stderr, "prof: cannot load %s: %r\n", p);
+ exits("bad module");
+}
+
+usage(s: string)
+{
+ sys->fprint(stderr, "prof: %s\n", s);
+ sys->fprint(stderr, "usage: prof [-bflnv] [-m modname]... [-s rate] [cmd arg ...]");
+ exits("usage");
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ init0(ctxt, argv);
+}
+
+init0(ctxt: ref Draw->Context, argv: list of string): Profile->Prof
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ badmodule(Arg->PATH);
+ arg->init(argv);
+ profile = load Profile Profile->PATH;
+ if(profile == nil)
+ badmodule(Profile->PATH);
+ if(profile->init() < 0)
+ pfatal("cannot initialize profile device");
+
+ v := 0;
+ begin := 0;
+ rate := 0;
+ ep := 0;
+ wm := 0;
+ exec, mods: list of string;
+ while((c := arg->opt()) != 0){
+ case c {
+ 'b' => begin = 1;
+ 'f' => v |= profile->FUNCTION;
+ 'l' => v |= profile->LINE;
+ 'n' => v |= profile->FULLHDR;
+ 'v' => v |= profile->VERBOSE;
+ 's' =>
+ if((s := arg->arg()) == nil)
+ usage("missing sample rate");
+ rate = int s;
+ if(rate <= 0)
+ usage("bad sample rate: '" + s + "'");
+ 'm' =>
+ if((s := arg->arg()) == nil)
+ usage("missing module name");
+ mods = s :: mods;
+ 'e' =>
+ ep = 1;
+ 'g' =>
+ wm = 1;
+ * =>
+ usage(sys->sprint("unknown option -%c", c));
+ }
+ }
+
+ exec = arg->argv();
+
+ if(begin && v != 0)
+ ignored("output format");
+ if(begin && exec != nil)
+ begin = 0;
+ if(begin == 0 && exec == nil){
+ if(mods != nil)
+ ignored("-m option");
+ if(rate > 0)
+ ignored("-s option");
+ mods = nil;
+ rate = 0;
+ }
+
+ if(rate > 0)
+ profile->sample(rate);
+ for( ; mods != nil; mods = tl mods)
+ profile->profile(hd mods);
+
+ if(begin){
+ if(profile->start() < 0)
+ pfatal("cannot start profiling");
+ exit;
+ }
+ r := 0;
+ if(exec != nil){
+ if(ep)
+ profile->profile(disname(hd exec));
+ if(profile->start() < 0)
+ pfatal("cannot start profiling");
+ # r = run(ctxt, hd exec, exec);
+ wfd := openwait(sys->pctl(0, nil));
+ ci := chan of int;
+ spawn execute(ctxt, hd exec, exec, ci);
+ epid := <- ci;
+ wait(wfd, epid);
+ }
+ if(profile->stop() < 0)
+ pfatal("cannot stop profiling");
+ if(exec == nil || r >= 0){
+ modl := profile->stats();
+ if(modl.mods == nil)
+ pfatal("no profile information");
+ if(wm){
+ profile->end();
+ return modl;
+ }
+ if(!(v&(profile->FUNCTION|profile->LINE)))
+ v |= profile->LINE;
+ if(profile->show(modl, v) < 0)
+ pfatal("cannot show profile");
+ }
+ profile->end();
+ return (nil, 0, nil);
+}
+
+disname(cmd: string): string
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+ if(exists(file))
+ return file;
+ if(file[0]!='/' && file[0:2]!="./")
+ file = "/dis/"+file;
+ # if(exists(file))
+ # return file;
+ return file;
+}
+
+execute(ctxt: ref Draw->Context, cmd : string, argl : list of string, ci: chan of int)
+{
+ ci <-= sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: stderr.fd :: nil);
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+ c := load Command file;
+ if(c == nil) {
+ err := sys->sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ if(c == nil){
+ sys->fprint(stderr, "prof: %s: %s\n", cmd, err);
+ return;
+ }
+ }
+ c->init(ctxt, argl);
+}
+
+# run(ctxt: ref Draw->Context, cmd : string, argl : list of string): int
+# {
+# file := cmd;
+# if(len file<4 || file[len file-4:]!=".dis")
+# file += ".dis";
+# c := load Command file;
+# if(c == nil) {
+# err := sys->sprint("%r");
+# if(file[0]!='/' && file[0:2]!="./"){
+# c = load Command "/dis/"+file;
+# if(c == nil)
+# err = sys->sprint("%r");
+# }
+# if(c == nil){
+# sys->fprint(stderr, "prof: %s: %s\n", cmd, err);
+# return -1;
+# }
+# }
+# c->init(ctxt, argl);
+# return 0;
+# }
+
+openwait(pid : int) : ref Sys->FD
+{
+ w := sys->sprint("#p/%d/wait", pid);
+ fd := sys->open(w, Sys->OREAD);
+ if (fd == nil)
+ pfatal("fd == nil in wait");
+ return fd;
+}
+
+wait(wfd : ref Sys->FD, wpid : int)
+{
+ n : int;
+
+ buf := array[Sys->WAITLEN] of byte;
+ status := "";
+ for(;;) {
+ if ((n = sys->read(wfd, buf, len buf)) < 0)
+ pfatal("bad read in wait");
+ status = string buf[0:n];
+ if (int status == wpid)
+ break;
+ }
+}
+
+exists(f: string): int
+{
+ return sys->open(f, Sys->OREAD) != nil;
+}
diff --git a/appl/cmd/promptstring.b b/appl/cmd/promptstring.b
new file mode 100644
index 00000000..2d648c55
--- /dev/null
+++ b/appl/cmd/promptstring.b
@@ -0,0 +1,66 @@
+RAWON_STR := "*";
+
+RAWON : con 0;
+RAWOFF : con 1;
+
+promptstring(prompt, def: string, mode: int): string
+{
+ if(mode == RAWON || def == nil || def == "")
+ sys->fprint(stdout, "%s: ", prompt);
+ else
+ sys->fprint(stdout, "%s [%s]: ", prompt, def);
+ (eof, resp) := readline(stdin, mode);
+ if(eof)
+ exit;
+ if(resp == "")
+ resp = def;
+ return resp;
+}
+
+readline(fd: ref Sys->FD, mode: int): (int, string)
+{
+ i: int;
+ eof: int;
+ fdctl: ref Sys->FD;
+
+ eof = 0;
+ buf := array[128] of byte;
+ tmp := array[128] of byte;
+
+ if(mode == RAWON){
+ fdctl = sys->open("/dev/consctl", sys->OWRITE);
+ if(fdctl == nil || sys->write(fdctl,array of byte "rawon",5) != 5){
+ sys->fprint(stderr, "unable to change console mode");
+ return (1,nil);
+ }
+ }
+
+ for(sofar := 0; sofar < 128; sofar += i){
+ i = sys->read(fd, tmp, 128 - sofar);
+ if(i <= 0){
+ eof = 1;
+ break;
+ }
+ if(tmp[i-1] == byte '\n'){
+ for(j := 0; j < i-1; j++){
+ buf[sofar+j] = tmp[j];
+ if(mode == RAWON && RAWON_STR != nil)
+ sys->write(stdout,array of byte RAWON_STR,1);
+ }
+ sofar += j;
+ if(mode == RAWON)
+ sys->write(stdout,array of byte "\n",1);
+ break;
+ }
+ else {
+ for(j := 0; j < i; j++){
+ buf[sofar+j] = tmp[j];
+ if(mode == RAWON && RAWON_STR != nil)
+ sys->write(stdout,array of byte RAWON_STR,1);
+ }
+ }
+ }
+ if(mode == RAWON)
+ sys->write(fdctl,array of byte "rawoff",6);
+ return (eof, string buf[0:sofar]);
+}
diff --git a/appl/cmd/ps.b b/appl/cmd/ps.b
new file mode 100644
index 00000000..06c51a2f
--- /dev/null
+++ b/appl/cmd/ps.b
@@ -0,0 +1,61 @@
+implement Ps;
+
+include "sys.m";
+include "draw.m";
+
+FD, Dir: import Sys;
+Context: import Draw;
+
+Ps: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+sys: Sys;
+stderr: ref FD;
+
+init(nil: ref Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ stderr = sys->fildes(2);
+
+ sys->pctl(Sys->FORKNS, nil);
+ if(sys->chdir("/prog") < 0){
+ sys->fprint(stderr, "ps: can't chdir to /prog: %r\n");
+ raise "fail:no /prog";
+ }
+ fd := sys->open(".", sys->OREAD);
+ if(fd == nil) {
+ sys->fprint(stderr, "ps: cannot open /prog: %r\n");
+ raise "fail:no /prog";
+ }
+
+ for(;;) {
+ (n, d) := sys->dirread(fd);
+ if(n <= 0){
+ if(n < 0) {
+ sys->fprint(stderr, "ps: error reading /prog: %r\n");
+ raise "fail:error on /prog";
+ }
+ break;
+ }
+ for(i := 0; i < n; i++)
+ if(d[i].name[0] >= '0' && d[i].name[0] <= '9')
+ ps(int d[i].name);
+ }
+}
+
+ps(pid: int)
+{
+ proc := string pid+"/status";
+ fd := sys->open(proc, sys->OREAD);
+ if(fd == nil) { # process must have died
+ # sys->fprint(stderr, "ps: /prog/%s: %r\n", proc);
+ return;
+ }
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n > 0)
+ sys->print("%s\n", string buf[0:n]);
+}
diff --git a/appl/cmd/puttar.b b/appl/cmd/puttar.b
new file mode 100644
index 00000000..de67105f
--- /dev/null
+++ b/appl/cmd/puttar.b
@@ -0,0 +1,183 @@
+# read list of pathnames on stdin, write POSIX.1 tar on stdout
+# Copyright(c)1996 Lucent Technologies. All Rights Reserved.
+# 22 Dec 1996 ehg@bell-labs.com
+
+implement puttar;
+include "sys.m";
+ sys: Sys;
+ print, sprint, fprint: import sys;
+ stdout, stderr: ref sys->FD;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+puttar: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Warning(mess: string)
+{
+ fprint(stderr,"warning: puttar: %s: %r\n",mess);
+}
+
+Error(mess: string){
+ fprint(stderr,"puttar: %s: %r\n",mess);
+ exit;
+}
+
+TBLOCK: con 512; # tar logical blocksize
+NBLOCK: con 20; # blocking factor for efficient write
+tarbuf := array[NBLOCK*TBLOCK] of byte; # for output
+nblock := 0; # how many blocks of data are in tarbuf
+
+flushblocks(){
+ if(nblock<=0) return;
+ if(nblock<NBLOCK){
+ for(i:=(nblock+1)*TBLOCK;i<NBLOCK*TBLOCK;i++)
+ tarbuf[i] = byte 0;
+ }
+ i := sys->write(stdout,tarbuf,NBLOCK*TBLOCK);
+ if(i!=NBLOCK*TBLOCK)
+ Error("write error");
+ nblock = 0;
+}
+
+putblock(data:array of byte){
+ # all writes are done through here, so we can guarantee
+ # 10kbyte blocks if writing to tape device
+ if(len data!=TBLOCK)
+ Error("putblock wants TBLOCK chunks");
+ tarbuf[nblock*TBLOCK:] = data;
+ nblock++;
+ if(nblock>=NBLOCK)
+ flushblocks();
+}
+
+packname(hdr:array of byte, name:string){
+ utf := array of byte name;
+ n := len utf;
+ if(n<=100){
+ hdr[0:] = utf;
+ return;
+ }
+ for(i:=n-101; i<n && int utf[i] != '/'; i++){}
+ if(i==n) Error(sprint("%s > 100 bytes",name));
+ if(i>155) Error(sprint("%s too long\n",name));
+ hdr[0:] = utf[i+1:n];
+ hdr[345:] = utf[0:i]; # tar supplies implicit slash
+}
+
+octal(width:int, val:int):array of byte{
+ octal := array of byte "01234567";
+ a := array[width] of byte;
+ for(i:=width-1; i>=0; i--){
+ a[i] = octal[val&7];
+ val >>= 3;
+ }
+ return a;
+}
+
+chksum(hdr: array of byte):int{
+ sum := 0;
+ for(i:=0; i<len hdr; i++)
+ sum += int hdr[i];
+ return sum;
+}
+
+hdr, zeros, ibuf : array of byte;
+
+tar(file : string)
+{
+ ifile: ref sys->FD;
+
+ (rc,stat) := sys->stat(file);
+ if(rc<0){ Warning(sprint("cannot stat %s",file)); return; };
+ ifile = sys->open(file,sys->OREAD);
+ if(ifile==nil) Error(sprint("cannot open %s",file));
+ hdr[0:] = zeros;
+ packname(hdr,file);
+ hdr[100:] = octal(7,stat.mode&8r777);
+ hdr[108:] = octal(7,1);
+ hdr[116:] = octal(7,1);
+ hdr[124:] = octal(11,int stat.length);
+ hdr[136:] = octal(11,stat.mtime);
+ hdr[148:] = array of byte " "; # for chksum
+ hdr[156] = byte '0';
+ if(stat.mode&Sys->DMDIR) hdr[156] = byte '5';
+ hdr[257:] = array of byte "ustar";
+ hdr[263:] = array of byte "00";
+ hdr[265:] = array of byte stat.uid; # assumes len uid<=32
+ hdr[297:] = array of byte stat.gid;
+ hdr[329:] = octal(8,stat.dev);
+ hdr[337:] = octal(8,int stat.qid.path);
+ hdr[148:] = octal(7,chksum(hdr));
+ hdr[155] = byte 0;
+ putblock(hdr);
+ for(bytes := int stat.length; bytes>0;){
+ n := len ibuf; if(n>bytes) n = bytes; # min
+ if(sys->read(ifile,ibuf,n)!=n)
+ Error(sprint("read error on %s",file));
+ nb := (n+TBLOCK-1)/TBLOCK;
+ fill := nb*TBLOCK;
+ for(i:=n; i<fill; i++) ibuf[i] = byte 0;
+ for(i=0; i<nb; i++)
+ putblock(ibuf[i*TBLOCK:(i+1)*TBLOCK]);
+ bytes -= n;
+ }
+ ifile = nil;
+}
+
+rtar(file : string)
+{
+ tar(file);
+ # recurse if directory
+ (ok, dir) := sys->stat(file);
+ if (ok < 0){
+ Warning(sprint("cannot stat %s", file));
+ return;
+ }
+ if (dir.mode & Sys->DMDIR) {
+ fd := sys->open(file, sys->OREAD);
+ if (fd == nil)
+ Error(sprint("cannot open %s", file));
+ for (;;) {
+ (n, d) := sys->dirread(fd);
+ if (n <= 0)
+ break;
+ for (i := 0; i < n; i++) {
+ if (file[len file - 1] == '/')
+ rtar(file + d[i].name);
+ else
+ rtar(file + "/" + d[i].name);
+ }
+ }
+ }
+}
+
+init(nil: ref Draw->Context, args: list of string){
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+
+ hdr = array[TBLOCK] of byte;
+ zeros = array[TBLOCK] of {* => byte 0};
+ ibuf = array[len tarbuf] of byte;
+
+ if (tl args == nil) {
+ stdin := bufio->fopen(sys->fildes(0),bufio->OREAD);
+ if(stdin==nil) Error("can't fopen stdin");
+ while((file := stdin.gets('\n'))!=nil){
+ if(file[len file-1]=='\n') file = file[0:len file-1];
+ tar(file);
+ }
+ }
+ else {
+ for (args = tl args; args != nil; args = tl args)
+ rtar(hd args);
+ }
+ putblock(zeros);
+# putblock(zeros); # XXX is this necessary?
+ flushblocks();
+}
diff --git a/appl/cmd/pwd.b b/appl/cmd/pwd.b
new file mode 100644
index 00000000..28fd4d24
--- /dev/null
+++ b/appl/cmd/pwd.b
@@ -0,0 +1,28 @@
+implement Pwd;
+
+include "sys.m";
+include "draw.m";
+include "workdir.m";
+
+Pwd: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys := load Sys Sys->PATH;
+ stderr := sys->fildes(2);
+ gwd := load Workdir Workdir->PATH;
+ if (gwd == nil) {
+ sys->fprint(stderr, "pwd: cannot load %s: %r\n", Workdir->PATH);
+ raise "fail:bad module";
+ }
+
+ wd := gwd->init();
+ if(wd == nil) {
+ sys->fprint(stderr, "pwd: %r\n");
+ raise "fail:error";
+ }
+ sys->print("%s\n", wd);
+}
diff --git a/appl/cmd/ramfile.b b/appl/cmd/ramfile.b
new file mode 100644
index 00000000..677bf18b
--- /dev/null
+++ b/appl/cmd/ramfile.b
@@ -0,0 +1,97 @@
+implement Ramfile;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+# synthesise a file that can be treated just like any other
+# file. limitations of file2chan mean that it's not possible
+# to know when an open should have truncated the file, so
+# we do the only possible thing, and truncate it when we get
+# a write at offset 0. thus it can be edited with an editor,
+# but can't be used to store seekable, writable data records
+# (unless the first record is never written)
+
+# there should be some way to determine when the file should
+# go away - file2chan sends a nil channel whenever the file
+# is closed by anyone, which is not good enough.
+
+stderr: ref Sys->FD;
+
+Ramfile: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ if (len argv < 2 || len argv > 3) {
+ sys->fprint(stderr, "usage: ramfile path [data]\n");
+ return;
+ }
+ path := hd tl argv;
+ (dir, f) := pathsplit(path);
+
+ if (sys->bind("#s", dir, Sys->MBEFORE|Sys->MCREATE) == -1) {
+ sys->fprint(stderr, "ramfile: %r\n");
+ return;
+ }
+ fio := sys->file2chan(dir, f);
+ if (fio == nil) {
+ sys->fprint(stderr, "ramfile: file2chan failed: %r\n");
+ return;
+ }
+ data := array[0] of byte;
+ if (tl tl argv != nil)
+ data = array of byte hd tl tl argv;
+
+ spawn server(fio, data);
+ data = nil;
+}
+
+server(fio: ref Sys->FileIO, data: array of byte)
+{
+ for (;;) alt {
+ (offset, count, fid, rc) := <-fio.read =>
+ if (rc != nil) {
+ if (offset > len data)
+ rc <-= (nil, nil);
+ else {
+ end := offset + count;
+ if (end > len data)
+ end = len data;
+ rc <-= (data[offset:end], nil);
+ }
+ }
+ (offset, d, fid, wc) := <-fio.write =>
+ if (wc != nil) {
+ if (offset == 0)
+ data = array[0] of byte;
+ end := offset + len d;
+ if (end > len data) {
+ ndata := array[end] of byte;
+ ndata[0:] = data;
+ data = ndata;
+ ndata = nil;
+ }
+ data[offset:] = d;
+ wc <-= (len d, nil);
+ }
+ }
+}
+
+pathsplit(p: string): (string, string)
+{
+ for (i := len p - 1; i >= 0; i--)
+ if (p[i] != '/')
+ break;
+ if (i < 0)
+ return (p, nil);
+ p = p[0:i+1];
+ for (i = len p - 1; i >=0; i--)
+ if (p[i] == '/')
+ break;
+ if (i < 0)
+ return (".", p);
+ return (p[0:i+1], p[i+1:]);
+}
diff --git a/appl/cmd/randpass.b b/appl/cmd/randpass.b
new file mode 100644
index 00000000..074b21ac
--- /dev/null
+++ b/appl/cmd/randpass.b
@@ -0,0 +1,45 @@
+implement Randpass;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+
+include "draw.m";
+
+include "keyring.m";
+ kr : Keyring;
+ IPint: import kr;
+
+Randpass: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+
+ if(args != nil)
+ args = tl args;
+ pwlen := 16;
+ if(args != nil){
+ if(!isnumeric(hd args) || (pwlen = int hd args) <= 8 || pwlen > 256){
+ sys->fprint(sys->fildes(2), "Usage: randpass [password-length(<256, default=16)]\n");
+ raise "fail:usage";
+ }
+ }
+
+ rbig := IPint.random(pwlen*8, pwlen*16);
+ rstr := rbig.iptob64();
+
+ sys->print("%s\n", rstr[0:pwlen]);
+}
+
+isnumeric(s: string): int
+{
+ for(i := 0; i < len s; i++)
+ if(!(s[i]>='0' && s[i]<='9'))
+ return 0;
+ return i > 0;
+}
diff --git a/appl/cmd/raw2iaf.b b/appl/cmd/raw2iaf.b
new file mode 100644
index 00000000..11c29e00
--- /dev/null
+++ b/appl/cmd/raw2iaf.b
@@ -0,0 +1,122 @@
+implement Raw2Iaf;
+
+include "sys.m";
+include "draw.m";
+
+sys: Sys;
+FD: import sys;
+stderr: ref FD;
+
+rateK: con "rate";
+rateV: string = "44100";
+chanK: con "chans";
+chanV: string = "2";
+bitsK: con "bits";
+bitsV: string = "16";
+encK: con "enc";
+encV: string = "pcm";
+
+progV: string;
+inV: string = nil;
+outV: string = nil;
+inf: ref FD;
+outf: ref FD;
+
+pad := array[] of { " ", " ", "", " " };
+
+Raw2Iaf: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(stderr, "usage: %s -8124 -ms -bw -aup -o out in\n", progV);
+ exit;
+}
+
+options(s: string)
+{
+ for (i := 0; i < len s; i++) {
+ case s[i] {
+ '8' => rateV = "8000";
+ '1' => rateV = "11025";
+ '2' => rateV = "22050";
+ '4' => rateV = "44100";
+ 'm' => chanV = "1";
+ 's' => chanV = "2";
+ 'b' => bitsV = "8";
+ 'w' => bitsV = "16";
+ 'a' => encV = "alaw";
+ 'u' => encV = "ulaw";
+ 'p' => encV = "pcm";
+ * => usage();
+ }
+ }
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ progV = hd argv;
+ v := tl argv;
+
+ while (v != nil) {
+ a := hd v;
+ v = tl v;
+ if (len a == 0)
+ continue;
+ if (a[0] == '-') {
+ if (len a == 1) {
+ if (inV == nil)
+ inV = "-";
+ else
+ usage();
+ }
+ else if (a[1] == 'o') {
+ if (outV != nil)
+ usage();
+ if (len a > 2)
+ outV = a[2:len a];
+ else if (v == nil)
+ usage();
+ else {
+ outV = hd v;
+ v = tl v;
+ }
+ }
+ else
+ options(a[1:len a]);
+ }
+ else if (inV == nil)
+ inV = a;
+ else
+ usage();
+ }
+ if (inV == nil || inV == "-")
+ inf = sys->fildes(0);
+ else {
+ inf = sys->open(inV, Sys->OREAD);
+ if (inf == nil) {
+ sys->fprint(stderr, "%s: could not open %s: %r\n", progV, inV);
+ exit;
+ }
+ }
+ if (outV == nil || outV == "-")
+ outf = sys->fildes(1);
+ else {
+ outf = sys->create(outV, Sys->OWRITE, 8r666);
+ if (outf == nil) {
+ sys->fprint(stderr, "%s: could not create %s: %r\n", progV, outV);
+ exit;
+ }
+ }
+ s := rateK + "\t" + rateV + "\n"
+ + chanK + "\t" + chanV + "\n"
+ + bitsK + "\t" + bitsV + "\n"
+ + encK + "\t" + encV;
+ sys->fprint(outf, "%s%s\n\n", s, pad[len s % 4]);
+ if (sys->stream(inf, outf, Sys->ATOMICIO) < 0)
+ sys->fprint(stderr, "%s: data copy error: %r\n", progV);
+}
diff --git a/appl/cmd/rawdbfs.b b/appl/cmd/rawdbfs.b
new file mode 100644
index 00000000..cb2daf2c
--- /dev/null
+++ b/appl/cmd/rawdbfs.b
@@ -0,0 +1,813 @@
+implement Dbfs;
+
+#
+# Copyright © 1999, 2002 Vita Nuova Limited. All rights reserved.
+#
+
+# Enhanced to include record locking, index field generation and update notification
+
+# TO DO:
+# make writing & reading more like real files; don't ignore offsets.
+# open with OTRUNC should work.
+# provide some way of compacting a dbfs file.
+
+include "sys.m";
+ sys: Sys;
+ Qid: import Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+include "styx.m";
+ styx: Styx;
+ Rmsg, Tmsg: import styx;
+
+include "styxservers.m";
+ styxservers: Styxservers;
+ Styxserver, Fid, Navigator, Navop: import styxservers;
+ Enotfound, Eperm, Ebadfid, Ebadarg: import styxservers;
+
+include "string.m";
+ str: String;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "sh.m";
+ sh: Sh;
+
+Record: adt {
+ id: int; # file number in directory (if block is allocated)
+ offset: int; # start of data
+ count: int; # length of block (excluding header)
+ datalen: int; # length of data (-1 if block is free)
+ vers: int; # version
+
+ new: fn(offset: int, length: int): ref Record;
+ qid: fn(r: self ref Record): Sys->Qid;
+};
+
+# Record lock
+Lock: adt {
+ qpath: big;
+ fid: int;
+};
+
+HEADLEN: con 10;
+MINSIZE: con 20;
+
+Database: adt {
+ file: ref Iobuf;
+ records: array of ref Record;
+ maxid: int;
+ locking: int;
+ locklist: list of Lock;
+ indexing: int;
+ stats: int;
+ index: int;
+ s_reads: int;
+ s_writes: int;
+ s_creates: int;
+ s_removes: int;
+ updcmd: string;
+ vers: int;
+
+ build: fn(f: ref Iobuf, locking, indexing: int, stats: int, updcmd: string): (ref Database, string);
+ write: fn(db: self ref Database, n: int, data: array of byte): int;
+ read: fn(db: self ref Database, n: int): array of byte;
+ remove: fn(db: self ref Database, n: int);
+ create: fn(db: self ref Database, data: array of byte): ref Record;
+ updated: fn(db: self ref Database);
+ lock: fn(db: self ref Database, c: ref Styxservers->Fid): int;
+ unlock: fn(db: self ref Database, c: ref Styxservers->Fid);
+ ownlock: fn(db: self ref Database, c: ref Styxservers->Fid): int;
+};
+
+Dbfs: module
+{
+ init: fn(ctxt: ref Draw->Context, nil: list of string);
+};
+
+Qdir, Qnew, Qdata, Qindex, Qstats: con iota;
+
+stderr: ref Sys->FD;
+database: ref Database;
+context: ref Draw->Context;
+user: string;
+Eremoved: con "file removed";
+Egreg: con "thermal problems";
+Elocked: con "open/create -- file is locked";
+
+usage()
+{
+ sys->fprint(stderr, "Usage: dbfs [-abcelrxD][-u cmd] file mountpoint\n");
+ raise "fail:usage";
+}
+
+nomod(s: string)
+{
+ sys->fprint(stderr, "dbfs: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ context = ctxt;
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ styx = load Styx Styx->PATH;
+ if(styx == nil)
+ nomod(Styx->PATH);
+ styx->init();
+ styxservers = load Styxservers Styxservers->PATH;
+ if(styxservers == nil)
+ nomod(Styxservers->PATH);
+ styxservers->init(styx);
+ str = load String String->PATH;
+ if(str == nil)
+ nomod(String->PATH);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ nomod(Bufio->PATH);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ arg->init(args);
+ flags := Sys->MREPL;
+ copt := 0;
+ empty := 0;
+ locking := 0;
+ stats := 0;
+ indexing := 0;
+ updcmd := "";
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' => flags = Sys->MAFTER;
+ 'b' => flags = Sys->MBEFORE;
+ 'r' => flags = Sys->MREPL;
+ 'c' => copt = 1;
+ 'e' => empty = 1;
+ 'l' => locking = 1;
+ 'u' => updcmd = arg->arg();
+ if(updcmd == nil)
+ usage();
+ 'x' => indexing = 1;
+ stats = 1;
+ 'D' => styxservers->traceset(1);
+ * => usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(len args != 2)
+ usage();
+ if(copt)
+ flags |= Sys->MCREATE;
+ file := hd args;
+ args = tl args;
+ mountpt := hd args;
+
+ if(updcmd != nil){
+ sh = load Sh Sh->PATH;
+ if(sh == nil)
+ nomod(Sh->PATH);
+ }
+
+ df := bufio->open(file, Sys->ORDWR);
+ if(df == nil && empty){
+ (rc, d) := sys->stat(file);
+ if(rc < 0)
+ df = bufio->create(file, Sys->ORDWR, 8r600);
+ }
+ if(df == nil){
+ sys->fprint(stderr, "dbfs: can't open %s: %r\n", file);
+ raise "fail:cannot open file";
+ }
+ (db, err) := Database.build(df, locking, indexing, stats, updcmd);
+ if(db == nil){
+ sys->fprint(stderr, "dbfs: can't read %s: %s\n", file, err);
+ raise "fail:cannot read db";
+ }
+ database = db;
+
+ sys->pctl(Sys->FORKFD, nil);
+
+ user = rf("/dev/user");
+ if(user == nil)
+ user = "inferno";
+
+ fds := array[2] of ref Sys->FD;
+ if(sys->pipe(fds) < 0){
+ sys->fprint(stderr, "dbfs: can't create pipe: %r\n");
+ raise "fail:pipe";
+ }
+
+ navops := chan of ref Navop;
+ spawn navigator(navops);
+
+ (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qdir);
+ fds[0] = nil;
+
+ pidc := chan of int;
+ spawn serveloop(tchan, srv, pidc, navops);
+ <-pidc;
+
+ if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) {
+ sys->fprint(stderr, "dbfs: mount failed: %r\n");
+ raise "fail:bad mount";
+ }
+}
+
+rf(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ b := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, b, len b);
+ if(n < 0)
+ return nil;
+ return string b[0:n];
+}
+
+serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop)
+{
+ pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, stderr.fd::1::2::database.file.fd.fd::srv.fd.fd::nil);
+# stderr = sys->fildes(stderr.fd);
+ database.file.fd = sys->fildes(database.file.fd.fd);
+Serve:
+ while((gm := <-tchan) != nil){
+ pick m := gm {
+ Readerror =>
+ sys->fprint(stderr, "dbfs: fatal read error: %s\n", m.error);
+ break Serve;
+ Open =>
+ c := srv.getfid(m.fid);
+ open(srv, m);
+ Read =>
+ (c, err) := srv.canread(m);
+ if(c == nil) {
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ if(c.qtype & Sys->QTDIR){
+ srv.read(m);
+ break;
+ }
+ case TYPE(c.path) {
+ Qindex =>
+ if(database.index < 0) {
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ break;
+ }
+ if (m.offset > big 0) {
+ srv.reply(ref Rmsg.Read(m.tag, nil));
+ break;
+ }
+ reply := array of byte string ++database.index;
+ if(m.count < len reply)
+ reply = reply[:m.count];
+ srv.reply(ref Rmsg.Read(m.tag, reply));
+ Qstats =>
+ if (m.offset > big 0) {
+ srv.reply(ref Rmsg.Read(m.tag, nil));
+ break;
+ }
+ reply := array of byte sys->sprint("%d %d %d %d", database.s_reads, database.s_writes,
+ database.s_creates, database.s_removes);
+ if(m.count < len reply) reply = reply[:m.count];
+ srv.reply(ref Rmsg.Read(m.tag, reply));
+ Qdata =>
+ recno := id2recno(FILENO(c.path));
+ if(recno == -1)
+ srv.reply(ref Rmsg.Error(m.tag, Eremoved));
+ else
+ srv.reply(styxservers->readbytes(m, database.read(recno)));
+ * =>
+ srv.reply(ref Rmsg.Error(m.tag, Egreg));
+ }
+ Write =>
+ (c, err) := srv.canwrite(m);
+ if(c == nil){
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ if(!database.ownlock(c)) {
+ # shouldn't happen: open checks
+ srv.reply(ref Rmsg.Error(m.tag, Elocked));
+ break;
+ }
+ case TYPE(c.path) {
+ Qindex =>
+ if(database.index >= 0) {
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ break;
+ }
+ database.index = int string m.data;
+ srv.reply(ref Rmsg.Write(m.tag, len m.data));
+ Qdata =>
+ recno := id2recno(FILENO(c.path));
+ if(recno == -1)
+ srv.reply(ref Rmsg.Error(m.tag, "phase error"));
+ else {
+ changed := 1;
+ if(database.updcmd != nil){
+ oldrec := database.read(recno);
+ changed = !eqbytes(m.data, oldrec);
+ }
+ if(changed && database.write(recno, m.data) == -1){
+ srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ break;
+ }
+ if(changed)
+ database.updated(); # run the command before reply
+ srv.reply(ref Rmsg.Write(m.tag, len m.data));
+ }
+ * =>
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ }
+ Clunk =>
+ c := srv.getfid(m.fid);
+ if(c != nil)
+ database.unlock(c);
+ srv.clunk(m);
+ Remove =>
+ c := srv.getfid(m.fid);
+ database.unlock(c);
+ if(c == nil || c.qtype & Sys->QTDIR || TYPE(c.path) != Qdata){
+ # let it diagnose all the errors
+ srv.remove(m);
+ break;
+ }
+ recno := id2recno(FILENO(c.path));
+ if(recno == -1)
+ srv.reply(ref Rmsg.Error(m.tag, "phase error"));
+ else {
+ database.remove(recno);
+ database.updated();
+ srv.reply(ref Rmsg.Remove(m.tag));
+ }
+ srv.delfid(c);
+ * =>
+ srv.default(gm);
+ }
+ }
+ navops <-= nil; # shut down navigator
+}
+
+eqbytes(a, b: array of byte): int
+{
+ if(len a != len b)
+ return 0;
+ for(i := 0; i < len a; i++)
+ if(a[i] != b[i])
+ return 0;
+ return 1;
+}
+
+id2recno(id: int): int
+{
+ recs := database.records;
+ for(i := 0; i < len recs; i++)
+ if(recs[i].datalen >= 0 && recs[i].id == id)
+ return i;
+ return -1;
+}
+
+open(srv: ref Styxserver, m: ref Tmsg.Open): ref Fid
+{
+ (c, mode, d, err) := srv.canopen(m);
+ if(c == nil){
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ return nil;
+ }
+ if(TYPE(c.path) == Qnew){
+ # generate new file
+ if(c.uname != user){
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ return nil;
+ }
+ r := database.create(array[0] of byte);
+ if(r == nil) {
+ srv.reply(ref Rmsg.Error(m.tag, "create -- i/o error"));
+ return nil;
+ }
+ (d, nil) = dirgen(QPATH(r.id, Qdata));
+ }
+ if(m.mode & Sys->OTRUNC) {
+ # TO DO
+ }
+ c.open(mode, d.qid);
+ if(database.locking && TYPE(c.path) == Qdata && (m.mode & (Sys->OWRITE|Sys->ORDWR))) {
+ if(!database.lock(c)) {
+ srv.reply(ref Rmsg.Error(m.tag, Elocked));
+ return nil;
+ }
+ }
+ srv.reply(ref Rmsg.Open(m.tag, d.qid, srv.iounit()));
+ return c;
+}
+
+dirslot(n: int): int
+{
+ for(i := 0; i < len database.records; i++){
+ r := database.records[i];
+ if(r != nil && r.datalen >= 0){
+ if(n == 0)
+ return i;
+ n--;
+ }
+ }
+ return -1;
+}
+
+dir(qid: Sys->Qid, name: string, length: big, uid: string, perm: int): ref Sys->Dir
+{
+ d := ref sys->zerodir;
+ d.qid = qid;
+ if(qid.qtype & Sys->QTDIR)
+ perm |= Sys->DMDIR;
+ d.mode = perm;
+ d.name = name;
+ d.uid = uid;
+ d.gid = uid;
+ d.length = length;
+ return d;
+}
+
+dirgen(p: big): (ref Sys->Dir, string)
+{
+ case TYPE(p) {
+ Qdir =>
+ return (dir(Qid(QPATH(0, Qdir),database.vers,Sys->QTDIR), "/", big 0, user, 8r700), nil);
+ Qnew =>
+ return (dir(Qid(QPATH(0, Qnew),0,Sys->QTFILE), "new", big 0, user, 8r600), nil);
+ Qindex =>
+ return (dir(Qid(QPATH(0, Qindex),0,Sys->QTFILE), "index", big 0, user, 8r600), nil);
+ Qstats =>
+ return (dir(Qid(QPATH(0, Qstats),0,Sys->QTFILE), "stats", big 0, user, 8r400), nil);
+ * =>
+ n := id2recno(FILENO(p));
+ if(n < 0 || n >= len database.records)
+ return (nil, nil);
+ r := database.records[n];
+ if(r == nil || r.datalen < 0)
+ return (nil, Enotfound);
+ l := r.datalen;
+ if(l < 0)
+ l = 0;
+ return (dir(r.qid(), sys->sprint("%d", r.id), big l, user, 8r600), nil);
+ }
+}
+
+navigator(navops: chan of ref Navop)
+{
+ while((m := <-navops) != nil){
+ pick n := m {
+ Stat =>
+ n.reply <-= dirgen(n.path);
+ Walk =>
+ if(int n.path != Qdir){
+ n.reply <-= (nil, "not a directory");
+ break;
+ }
+ case n.name {
+ ".." =>
+ ; # nop
+ "new" =>
+ n.path = QPATH(0, Qnew);
+ "stats" =>
+ if(!database.indexing){
+ n.reply <-= (nil, Enotfound);
+ continue;
+ }
+ n.path = QPATH(0, Qstats);
+ "index" =>
+ if(!database.indexing){
+ n.reply <-= (nil, Enotfound);
+ continue;
+ }
+ n.path = QPATH(0, Qindex);
+ * =>
+ if(len n.name < 1 || !(n.name[0]>='0' && n.name[0]<='9')){ # weak test for now
+ n.reply <-= (nil, Enotfound);
+ continue;
+ }
+ n.path = QPATH(int n.name, Qdata);
+ }
+ n.reply <-= dirgen(n.path);
+ Readdir =>
+ if(int m.path != Qdir){
+ n.reply <-= (nil, "not a directory");
+ break;
+ }
+ o := 1; # Qnew;
+ stats := -1;
+ indexing := -1;
+ if(database.indexing)
+ indexing = o++;
+ if(database.stats)
+ stats = o++;
+ Dread:
+ for(i := n.offset; --n.count >= 0; i++){
+ case i {
+ 0 =>
+ n.reply <-= dirgen(QPATH(0,Qnew));
+ * =>
+ if(i == indexing)
+ n.reply <-= dirgen(QPATH(0, Qindex));
+ if(i == stats)
+ n.reply <-= dirgen(QPATH(0, Qstats));
+ j := dirslot(i-o); # n² but fine if the file will be small
+ if(j < 0)
+ break Dread;
+ r := database.records[j];
+ n.reply <-= dirgen(QPATH(r.id,Qdata));
+ }
+ }
+ n.reply <-= (nil, nil);
+ }
+ }
+}
+
+QPATH(w, q: int): big
+{
+ return big ((w<<8)|q);
+}
+
+TYPE(path: big): int
+{
+ return int path & 16rFF;
+}
+
+FILENO(path: big): int
+{
+ return (int path >> 8) & 16rFFFFFF;
+}
+
+Database.build(f: ref Iobuf, locking, indexing, stats: int, updcmd: string): (ref Database, string)
+{
+ rl: list of ref Record;
+ offset := 0;
+ maxid := 0;
+ for(;;) {
+ d := array[HEADLEN] of byte;
+ n := f.read(d, HEADLEN);
+ if(n < HEADLEN)
+ break;
+ orig := s := string d;
+ if(len s != HEADLEN)
+ return (nil, "found bad header");
+ r := ref Record;
+ r.vers = 0;
+ (r.count, s) = str->toint(s, 10);
+ (r.datalen, s) = str->toint(s, 10);
+ if(s != "\n")
+ return (nil, sys->sprint("found bad header '%s'\n", orig));
+ r.offset = offset + HEADLEN;
+ offset += r.count + HEADLEN;
+ f.seek(big offset, Bufio->SEEKSTART);
+ r.id = maxid++;
+ rl = r :: rl;
+ }
+ db := ref Database(f, array[maxid] of ref Record, maxid, locking, nil, indexing, stats, -1, 0, 0, 0, 0, updcmd, 0);
+ for(i := len db.records - 1; i >= 0; i--) {
+ db.records[i] = hd rl;
+ rl = tl rl;
+ }
+ return (db, nil);
+}
+
+Database.write(db: self ref Database, recno: int, data: array of byte): int
+{
+ db.s_writes++;
+ r := db.records[recno];
+ r.vers++;
+ if(len data <= r.count) {
+ if(r.count - len data >= HEADLEN + MINSIZE)
+ splitrec(db, recno, len data);
+ writerec(db, recno, data);
+ db.file.flush();
+ } else {
+ freerec(db, recno);
+ n := allocrec(db, len data);
+ if(n == -1)
+ return -1; # BUG: we lose the original data in this case.
+ db.records[n].id = r.id;
+ db.write(n, data);
+ }
+ return 0;
+}
+
+Database.create(db: self ref Database, data: array of byte): ref Record
+{
+ db.s_creates++;
+ db.vers++;
+ n := allocrec(db, len data);
+ if(n < 0)
+ return nil;
+ if(db.write(n, data) < 0){
+ freerec(db, n);
+ return nil;
+ }
+ r := db.records[n];
+ r.id = db.maxid++;
+ return r;
+}
+
+Database.read(db: self ref Database, recno: int): array of byte
+{
+ db.s_reads++;
+ r := db.records[recno];
+ if(r.datalen <= 0)
+ return nil;
+ db.file.seek(big r.offset, Bufio->SEEKSTART);
+ d := array[r.datalen] of byte;
+ n := db.file.read(d, r.datalen);
+ if(n != r.datalen) {
+ sys->fprint(stderr, "dbfs: only read %d bytes (expected %d)\n", n, r.datalen);
+ return nil;
+ }
+ return d;
+}
+
+Database.remove(db: self ref Database, recno: int)
+{
+ db.s_removes++;
+ db.vers++;
+ freerec(db, recno);
+ db.file.flush();
+}
+
+Database.updated(db: self ref Database)
+{
+ if(db.updcmd != nil)
+ sh->system(context, db.updcmd);
+}
+
+# Locking - try to lock a record
+
+Database.lock(db: self ref Database, c: ref Styxservers->Fid): int
+{
+ if(TYPE(c.path) != Qdata || !db.locking)
+ return 1;
+ for(ll := db.locklist; ll != nil; ll = tl ll) {
+ lock := hd ll;
+ if(lock.qpath == c.path)
+ return lock.fid == c.fid;
+ }
+ db.locklist = (c.path, c.fid) :: db.locklist;
+ return 1;
+}
+
+
+# Locking - unlock a record
+
+Database.unlock(db: self ref Database, c: ref Styxservers->Fid)
+{
+ if(TYPE(c.path) != Qdata || !db.locking)
+ return;
+ ll := db.locklist;
+ db.locklist = nil;
+ for(; ll != nil; ll = tl ll){
+ lock := hd ll;
+ if(lock.qpath == c.path && lock.fid == c.fid){
+ # not replaced on list
+ }else
+ db.locklist = hd ll :: db.locklist;
+ }
+}
+
+
+# Locking - check if Fid c has the lock on its record
+
+Database.ownlock(db: self ref Database, c: ref Styxservers->Fid): int
+{
+ if(TYPE(c.path) != Qdata || !db.locking)
+ return 1;
+ for(ll := db.locklist; ll != nil; ll = tl ll) {
+ lock := hd ll;
+ if(lock.qpath == c.path)
+ return lock.fid == c.fid;
+ }
+ return 0;
+}
+
+Record.new(offset: int, length: int): ref Record
+{
+ return ref Record(-1, offset, length, -1, 0);
+}
+
+Record.qid(r: self ref Record): Qid
+{
+ return Qid(QPATH(r.id,Qdata), r.vers, Sys->QTFILE);
+}
+
+freerec(db: ref Database, recno: int)
+{
+ nr := len db.records;
+ db.records[recno].datalen = -1;
+ for(i := recno; i >= 0; i--)
+ if(db.records[i].datalen != -1)
+ break;
+ f := i + 1;
+ nb := 0;
+ for(i = f; i < nr; i++) {
+ if(db.records[i].datalen != -1)
+ break;
+ nb += db.records[i].count + HEADLEN;
+ }
+ db.records[f].count = nb - HEADLEN;
+ writeheader(db.file, db.records[f]);
+ # could blank out freed entries here if we cared.
+ if(i < nr && f < i)
+ db.records[f+1:] = db.records[i:];
+ db.records = db.records[0:nr - (i - f - 1)];
+}
+
+splitrec(db: ref Database, recno: int, pos: int)
+{
+ a := array[len db.records + 1] of ref Record;
+ a[0:] = db.records[0:recno+1];
+ if(recno < len db.records - 1)
+ a[recno+2:] = db.records[recno+1:];
+ db.records = a;
+ r := a[recno];
+ a[recno+1] = Record.new(r.offset + pos + HEADLEN, r.count - HEADLEN - pos);
+ r.count = pos;
+ writeheader(db.file, a[recno+1]);
+}
+
+writerec(db: ref Database, recno: int, data: array of byte): int
+{
+ db.records[recno].datalen = len data;
+ if(writeheader(db.file, db.records[recno]) == -1)
+ return -1;
+ if(db.file.write(data, len data) == Bufio->ERROR)
+ return -1;
+ return 0;
+}
+
+writeheader(f: ref Iobuf, r: ref Record): int
+{
+ f.seek(big r.offset - big HEADLEN, Bufio->SEEKSTART);
+ if(f.puts(sys->sprint("%4d %4d\n", r.count, r.datalen)) == Bufio->ERROR) {
+ sys->fprint(stderr, "dbfs: error writing header (id %d, offset %d, count %d, datalen %d): %r\n",
+ r.id, r.offset, r.count, r.datalen);
+ return -1;
+ }
+ return 0;
+}
+
+# finds or creates a record of the requisite size; does not mark it as allocated.
+allocrec(db: ref Database, nb: int): int
+{
+ if(nb < MINSIZE)
+ nb = MINSIZE;
+ best := -1;
+ n := -1;
+ for(i := 0; i < len db.records; i++) {
+ r := db.records[i];
+ if(r.datalen == -1) {
+ avail := r.count - nb;
+ if(avail >= 0 && (n == -1 || avail < best)) {
+ best = avail;
+ n = i;
+ }
+ }
+ }
+ if(n != -1)
+ return n;
+ nr := len db.records;
+ a := array[nr + 1] of ref Record;
+ a[0:] = db.records[0:];
+ offset := 0;
+ if(nr > 0)
+ offset = a[nr-1].offset + a[nr-1].count;
+ db.file.seek(big offset, Bufio->SEEKSTART);
+ if(db.file.write(array[nb + HEADLEN] of {* => byte(0)}, nb + HEADLEN) == Bufio->ERROR
+ || db.file.flush() == Bufio->ERROR) {
+ sys->fprint(stderr, "dbfs: write of new entry failed: %r\n");
+ return -1;
+ }
+ a[nr] = Record.new(offset + HEADLEN, nb);
+ db.records = a;
+ return nr;
+}
+
+now(fd: ref Sys->FD): int
+{
+ if(fd == nil)
+ return 0;
+ buf := array[128] of byte;
+ sys->seek(fd, big 0, 0);
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return 0;
+ t := (big string buf[0:n]) / big 1000000;
+ return int t;
+}
diff --git a/appl/cmd/rcmd.b b/appl/cmd/rcmd.b
new file mode 100644
index 00000000..43fe9078
--- /dev/null
+++ b/appl/cmd/rcmd.b
@@ -0,0 +1,170 @@
+implement Rcmd;
+
+include "sys.m";
+include "draw.m";
+include "arg.m";
+include "keyring.m";
+include "security.m";
+
+Rcmd: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+DEFAULTALG := "none";
+sys: Sys;
+auth: Auth;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ badmodule(Arg->PATH);
+ arg->init(argv);
+ alg: string;
+ doauth := 1;
+ exportpath := "/";
+ keyfile: string;
+ arg->setusage("rcmd [-A] [-f keyfile] [-a alg] [-e exportpath] tcp!mach cmd");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' =>
+ alg = arg->earg();
+ 'A' =>
+ doauth = 0;
+ 'e' =>
+ exportpath = arg->earg();
+ (n, nil) := sys->stat(exportpath);
+ if (n == -1 || exportpath == nil)
+ arg->usage();
+ 'f' =>
+ keyfile = arg->earg();
+ if (! (keyfile[0] == '/' || (len keyfile > 2 && keyfile[0:2] == "./")))
+ keyfile = "/usr/" + user() + "/keyring/" + keyfile;
+ * =>
+ arg->usage();
+ }
+
+ argv = arg->argv();
+ if(argv == nil)
+ arg->usage();
+ arg = nil;
+
+ if (doauth && alg == nil)
+ alg = DEFAULTALG;
+
+ addr := hd argv;
+ argv = tl argv;
+
+ args := "";
+ while(argv != nil){
+ args += " " + hd argv;
+ argv = tl argv;
+ }
+ if(args == "")
+ args = "sh";
+
+ kr: Keyring;
+ au: Auth;
+ if (doauth) {
+ kr = load Keyring Keyring->PATH;
+ if(kr == nil)
+ badmodule(Keyring->PATH);
+ au = load Auth Auth->PATH;
+ if(au == nil)
+ badmodule(Auth->PATH);
+ if (keyfile == nil)
+ keyfile = "/usr/" + user() + "/keyring/default";
+ }
+
+ (ok, c) := sys->dial(netmkaddr(addr, "tcp", "rstyx"), nil);
+ if(ok < 0)
+ error(sys->sprint("dial server failed: %r"));
+
+ fd := c.dfd;
+ if (doauth) {
+ ai := kr->readauthinfo(keyfile);
+ #
+ # let auth->client handle nil ai
+ # if(ai == nil){
+ # sys->fprint(stderr(), "rcmd: certificate for %s not found\n", addr);
+ # raise "fail:no certificate";
+ # }
+ #
+
+ err := au->init();
+ if(err != nil)
+ error(err);
+
+ (fd, err) = au->client(alg, ai, c.dfd);
+ if(fd == nil){
+ sys->fprint(stderr(), "rcmd: authentication failed: %s\n", err);
+ raise "fail:auth failed";
+ }
+ }
+ t := array of byte sys->sprint("%d\n%s\n", len (array of byte args)+1, args);
+ if(sys->write(fd, t, len t) != len t){
+ sys->fprint(stderr(), "rcmd: cannot write arguments: %r\n");
+ raise "fail:bad arg write";
+ }
+
+ if(sys->export(fd, exportpath, sys->EXPWAIT) < 0) {
+ sys->fprint(stderr(), "rcmd: export: %r\n");
+ raise "fail:export failed";
+ }
+}
+
+exists(f: string): int
+{
+ (ok, nil) := sys->stat(f);
+ return ok >= 0;
+}
+
+user(): string
+{
+ sys = load Sys Sys->PATH;
+
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil)
+ return "";
+
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+
+ return string buf[0:n];
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+badmodule(p: string)
+{
+ sys->fprint(stderr(), "rcmd: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+error(e: string)
+{
+ sys->fprint(stderr(), "rcmd: %s\n", e);
+ raise "fail:errors";
+}
diff --git a/appl/cmd/rdp.b b/appl/cmd/rdp.b
new file mode 100644
index 00000000..80e25f20
--- /dev/null
+++ b/appl/cmd/rdp.b
@@ -0,0 +1,1230 @@
+implement Rdp;
+include "sys.m";
+ sys: Sys;
+ print, sprint: import sys;
+include "draw.m";
+include "string.m";
+ str: String;
+
+df_port: con "/dev/eia0";
+df_bps: con 38400;
+
+Rdp: module
+{
+ init: fn(nil: ref Draw->Context, arg: list of string);
+};
+
+dfd: ref sys->FD;
+cfd: ref sys->FD;
+ifd: ref sys->FD;
+pifd: ref sys->FD;
+p_isopen := 0;
+
+R_R15: con 15;
+R_PC: con 16;
+R_CPSR: con 17;
+R_SPSR: con 18;
+NREG: con 19;
+
+debug := 0;
+nocr := 0;
+tmode := 0;
+# echar := 16r1c; # ctrl-\
+echar := 16r1d; # ctrl-] (because Tk grabs the ctrl-\ )
+
+bint(x: int): array of byte
+{
+ b := array[4] of byte;
+ b[0] = byte x;
+ b[1] = byte (x>>8);
+ b[2] = byte (x>>16);
+ b[3] = byte (x>>24);
+ return b;
+}
+
+intb(b: array of byte): int
+{
+ return int b[0] | (int b[1] << 8)
+ | (int b[2] << 16) | (int b[3] << 24);
+}
+
+
+statusmsg(n: int): string
+{
+ m: string;
+ case n {
+ 0 => m = nil;
+ 1 => m = "Reset";
+ 2 => m = "Undefined instruction";
+ 3 => m = "Software interrupt";
+ 4 => m = "Prefetch abort";
+ 5 => m = "Data abort";
+ 6 => m = "Address exception";
+ 7 => m = "IRQ";
+ 8 => m = "FIQ";
+ 9 => m = "Error";
+ 10 => m = "Branch Through 0";
+ 253 => m = "Insufficient privilege";
+ 254 => m = "Unimplemented message";
+ 255 => m = "Undefined message";
+ * => m = sprint("Status %d", n);
+ }
+ return m;
+}
+
+sdc: chan of (array of byte, int);
+scc: chan of int;
+
+serinp()
+{
+ b: array of byte = nil;
+ save: array of byte = nil;
+ x := 0;
+ for(;;) {
+ m := <- scc;
+ if(m == 0) {
+ save = b[0:x];
+ continue;
+ }
+ b = nil;
+ t: int;
+ do {
+ alt {
+ m = <- scc =>
+ if(m == 0)
+ print("<strange error>\n");
+ b = nil;
+ * =>
+ ;
+ }
+ if(b == nil) {
+ if(m >= 0)
+ t = m;
+ else
+ t = -m;
+ x = 0;
+ b = array[t] of byte;
+ }
+ if(save != nil) {
+ r := len save;
+ if(r > (t-x))
+ r = t-x;
+ b[x:] = save[0:r];
+ save = save[r:];
+ if(len save == 0)
+ save = nil;
+ x += r;
+ continue;
+ }
+ r := sys->read(dfd, b[x:], t-x);
+ if(r < 0)
+ sdc <-= (array of byte sprint("fail:%r"), -1);
+ if(r == 0)
+ sdc <-= (array of byte "fail:hangup", -1);
+ if(debug) {
+ if(r == 1)
+ print("<%ux>", int b[x]);
+ else
+ print("<%ux,%ux...(%d)>", int b[x], int b[x+1], r);
+ }
+ x += r;
+ } while(m >= 0 && x < t);
+ sdc <-= (b, x);
+ }
+}
+
+
+sreadn(n: int): array of byte
+{
+ b: array of byte;
+ if(n == 0)
+ return array[0] of byte;
+ scc <-= n;
+ (b, n) = <- sdc;
+ if(n < 0)
+ raise string b;
+ return b[0:n];
+}
+
+
+# yes, it's kind of a hack...
+fds := array[32] of ref Sys->FD;
+
+oscmd()
+{
+ arg := array[4] of int;
+ buf := array[4] of array of byte;
+ b := sreadn(5);
+ op := intb(b[:4]);
+ argd := int b[4];
+ for(i := 0; i<4; i++) {
+ t := (argd >> (i*2))&3;
+ case t {
+ 0 => ;
+ 1 =>
+ arg[i] = int sreadn(1)[0];
+ 2 =>
+ arg[i] = intb(sreadn(4));
+ 3 =>
+ c := int sreadn(1)[0];
+ if(c < 255) {
+ buf[i] = array[c] of byte;
+ if(c <= 32) {
+ buf[i][0:] = sreadn(c);
+ } else
+ arg[i] = intb(sreadn(4));
+ } else {
+ b: array of byte;
+ b = sreadn(8);
+ c = intb(b[:4]);
+ arg[i] = intb(b[4:8]);
+ buf[i] = array[c] of byte;
+ }
+ }
+ }
+ for(i = 0; i<4; i++)
+ if(buf[i] != nil && len buf[i] > 32)
+ rdi_read(arg[i], buf[i], len buf[i]);
+
+ r := 0;
+ case op {
+ 0 or 2 => ;
+ * =>
+ out("");
+ }
+ case op {
+ 0 =>
+ if(debug)
+ print("SWI_WriteC(%d)\n", arg[0]);
+ out(string byte arg[0]);
+ 2 =>
+ if(debug)
+ print("SWI_Write0(<%d>)\n", len buf[0]);
+ out(string buf[0]);
+ 4 =>
+ if(debug)
+ print("SWI_ReadC()\n");
+ sys->read(ifd, b, 1);
+ r = int b[0];
+ 16r66 =>
+ fname := string buf[0];
+ if(debug)
+ print("SWI_Open(%s, %d)\n", fname, arg[1]);
+ fd: ref Sys->FD;
+ case arg[1] {
+ 0 or 1 =>
+ fd = sys->open(fname, Sys->OREAD);
+ 2 or 3 =>
+ fd = sys->open(fname, Sys->ORDWR);
+ 4 or 5 =>
+ fd = sys->open(fname, Sys->OWRITE);
+ if(fd == nil)
+ fd = sys->create(fname, Sys->OWRITE, 8r666);
+ 6 or 7 =>
+ fd = sys->open(fname, Sys->OWRITE|Sys->OTRUNC);
+ if(fd == nil)
+ fd = sys->create(fname, Sys->OWRITE, 8r666);
+ 8 or 9 =>
+ fd = sys->open(fname, Sys->OWRITE);
+ if(fd == nil)
+ fd = sys->create(fname, Sys->OWRITE, 8r666);
+ else
+ sys->seek(fd, big 0, Sys->SEEKEND);
+ 10 or 11 =>
+ fd = sys->open(fname, Sys->ORDWR);
+ if(fd == nil)
+ fd = sys->create(fname, Sys->ORDWR, 8r666);
+ else
+ sys->seek(fd, big 0, Sys->SEEKEND);
+ }
+ if(fd != nil) {
+ r = fd.fd;
+ if(r >= len fds) {
+ print("<fd %d out of range 1-%d>\n", r, len fds);
+ r = 0;
+ } else
+ fds[r] = fd;
+ }
+ 16r68 =>
+ if(debug)
+ print("SWI_Close(%d)\n", arg[0]);
+ if(arg[0] <= 0 || arg[0] >= len fds)
+ r = -1;
+ else {
+ if(fds[arg[0]] != nil)
+ fds[arg[0]] = nil;
+ else
+ r = -1;
+ }
+ 16r69 =>
+ if(debug)
+ print("SWI_Write(%d, <%d>)\n", arg[0], len buf[1]);
+ if(arg[0] <= 0 || arg[0] >= len fds)
+ r = -1;
+ else
+ r = sys->write(fds[arg[0]], buf[1], len buf[1]);
+ r = arg[2]-r;
+ 16r6a =>
+ if(debug)
+ print("SWI_Read(%d, 0x%ux, %d)\n", arg[0], arg[1], arg[2]);
+ if(arg[0] <= 0 || arg[0] >= len fds)
+ r = -1;
+ else {
+ d := array[arg[2]] of byte;
+ r = sys->read(fds[arg[0]], d, arg[2]);
+ if(r > 0)
+ rdi_write(d, arg[1], r);
+ }
+ r = arg[2]-r;
+ 16r6b =>
+ if(debug)
+ print("SWI_Seek(%d, %d)\n", arg[0], arg[1]);
+ if(arg[0] <= 0 || arg[0] >= len fds)
+ r = -1;
+ else
+ r = int sys->seek(fds[arg[0]], big arg[1], 0);
+ 16r6c =>
+ if(debug)
+ print("SWI_Flen(%d)\n", arg[0]);
+ if(arg[0] <= 0 || arg[0] >= len fds)
+ r = -1;
+ else {
+ d: Sys->Dir;
+ (r, d) = sys->fstat(fds[arg[0]]);
+ if(r >= 0)
+ r = int d.length;
+ }
+ 16r6e =>
+ if(debug)
+ print("SWI_IsTTY(%d)\n", arg[0]);
+ r = 0; # how can we detect if it's a TTY?
+ * =>
+ print("unsupported: SWI 0x%ux\n", op);
+ }
+ b = array[6] of byte;
+ b[0] = byte 16r13;
+ if(debug)
+ print("r0=%d\n", r);
+ if(r >= 0 && r <= 16rff) {
+ b[1] = byte 1;
+ b[2] = byte r;
+ sys->write(dfd, b, 3);
+ } else {
+ b[1] = byte 2;
+ b[2:] = bint(r);
+ sys->write(dfd, b, 6);
+ }
+}
+
+
+terminal()
+{
+ b := array[1024] of byte;
+ c := 3; # num of invalid chars before resetting
+ tmode = 1;
+ for(;;) {
+ n: int;
+ b: array of byte;
+ alt {
+ scc <-= -8192 =>
+ (b, n) = <- sdc;
+ (b, n) = <- sdc =>
+ ;
+ }
+ if(n < 0)
+ raise string b;
+ c -= out(string b[:n]);
+ if(c < 0) {
+ scc <-= 0;
+ raise "rdp:tmode";
+ }
+ if(!tmode) {
+ return;
+ }
+ }
+}
+
+getreply(n: int): (array of byte, int)
+{
+ loop: for(;;) {
+ c := int sreadn(1)[0];
+ case c {
+ 16r21 =>
+ oscmd();
+ 16r7f =>
+ raise "rdp:reset";
+ 16r5f =>
+ break loop;
+ * =>
+ print("<%ux?>", c);
+ scc <-= 0;
+ raise "rdp:tmode";
+ }
+ }
+ b := sreadn(n+1);
+ s := int b[n];
+ if(s != 0) {
+ out("");
+ print("[%s]\n", statusmsg(s));
+ }
+ return (b[:n], s);
+}
+
+outstr: string;
+tpid: int;
+
+timeout(t: int, c: chan of int)
+{
+ tpid = sys->pctl(0, nil);
+ if(t > 0)
+ sys->sleep(t);
+ c <-= 0;
+ tpid = 0;
+}
+
+bsc: chan of string;
+
+bufout()
+{
+ buf := "";
+ tc := chan of int;
+ n: int;
+ s: string;
+ for(;;) {
+ alt {
+ n = <- tc =>
+ print("%s", buf);
+ buf = "";
+ s = <- bsc =>
+ #if(tpid) {
+ # kill(tpid);
+ # tpid = 0;
+ #}
+ if((len buf+len s) >= 1024) {
+ print("%s", buf);
+ buf = s;
+ }
+ if(s == "" || debug) {
+ print("%s", buf);
+ buf = "";
+ } else {
+ buf += s;
+ if(tpid == 0)
+ spawn timeout(300, tc);
+ }
+ }
+ }
+}
+
+out(s: string): int
+{
+ if(bsc == nil) {
+ bsc = chan of string;
+ spawn bufout();
+ }
+ c := 0;
+ if(nocr || tmode) {
+ n := "";
+ for(i:=0; i<len s; i++) {
+ if(!(nocr && s[i] == '\r'))
+ n[len n] = s[i];
+ if(s[i] >= 16r7f)
+ c++;
+ }
+ bsc <-= n;
+ } else
+ bsc <-= s;
+ return c;
+}
+
+reset(r: int)
+{
+ out("");
+ if(debug)
+ print("reset(%d)\n", r);
+ p_isopen = 0;
+ b := array of byte sprint("b9600");
+ sys->write(cfd, b, len b);
+ if(r) {
+ b[0] = byte 127;
+ sys->write(dfd, b, 1);
+ print("<sending reset>");
+ }
+ ok := 0;
+ s := "";
+ for(;;) {
+ n: int;
+ b: array of byte;
+ scc <-= -8192;
+ (b, n) = <- sdc;
+ if(n < 0)
+ raise string b;
+ for(i := 0; i<n; i++) {
+ if(b[i] == byte 127) {
+ if(!ok)
+ print("\n");
+ ok = 1;
+ s = "";
+ continue;
+ }
+ if(b[i] == byte 0) {
+ if(ok && i == n-1) {
+ out(s);
+ out("");
+ return;
+ } else {
+ s = "";
+ continue;
+ }
+ }
+ if(b[i] < byte 127)
+ s += string b[i:i+1];
+ else
+ ok = 0;
+ }
+ }
+}
+
+sa1100_reset()
+{
+ rdi_write(bint(1), int 16r90030000, 4);
+}
+
+setbps(bps: int)
+{
+ # for older Emu's using setserial hacks...
+ if(bps > 38400)
+ sys->write(cfd, array of byte "b38400", 6);
+
+ out("");
+ print("<bps=%d>\n", bps);
+ b := array of byte sprint("b%d", bps);
+ if(sys->write(cfd, b, len b) != len b)
+ print("setbps failed: %r\n");
+}
+
+rdi_open(bps: int)
+{
+ if(debug)
+ print("rdi_open(%d)\n", bps);
+ b := array[7] of byte;
+ usehack := 0;
+ if(!p_isopen) {
+ b[0] = byte 0;
+ b[1] = byte (0 | (1<<1));
+ b[2:] = bint(0);
+ case bps {
+ 9600 => b[6] = byte 1;
+ 19200 => b[6] = byte 2;
+ 38400 => b[6] = byte 3;
+ # 57600 => b[6] = byte 4;
+ # 115200 => b[6] = byte 5;
+ # 230400 => b[6] = byte 6;
+ * =>
+ b[6] = byte 1;
+ usehack = 1;
+ }
+ sys->write(dfd, b, 7);
+ getreply(0);
+ p_isopen = 1;
+ if(usehack)
+ sa1100_setbps(bps);
+ else
+ setbps(bps);
+ }
+}
+
+rdi_close()
+{
+ if(debug)
+ print("rdi_close()\n");
+ b := array[1] of byte;
+ if(p_isopen) {
+ b[0] = byte 1;
+ sys->write(dfd, b, 1);
+ getreply(0);
+ p_isopen = 0;
+ }
+}
+
+rdi_cpuread(reg: array of int, mask: int)
+{
+ if(debug)
+ print("rdi_cpuread(..., 0x%ux)\n", mask);
+ n := 0;
+ for(i := 0; i<NREG; i++)
+ if(mask&(1<<i))
+ n += 4;
+ b := array[6+n] of byte;
+ b[0] = byte 4;
+ b[1] = byte 255; # current mode
+ b[2:] = bint(mask);
+ sys->write(dfd, b, 6);
+ (b, nil) = getreply(n);
+ n = 0;
+ for(i = 0; i<NREG; i++)
+ if(mask&(1<<i)) {
+ reg[i] = intb(b[n:n+4]);
+ n += 4;
+ }
+}
+
+rdi_cpuwrite(reg: array of int, mask: int)
+{
+ if(debug)
+ print("rdi_cpuwrite(..., 0x%ux)\n", mask);
+ n := 0;
+ for(i := 0; i<32; i++)
+ if(mask&(1<<i))
+ n += 4;
+ b := array[6+n] of byte;
+ b[0] = byte 5;
+ b[1] = byte 255; # current mode
+ b[2:] = bint(mask);
+ n = 6;
+ for(i = 0; i<32; i++)
+ if(mask&(1<<i)) {
+ b[n:] = bint(reg[i]);
+ n += 4;
+ }
+ sys->write(dfd, b, n);
+ getreply(0);
+}
+
+dump(b: array of byte, n: int)
+{
+ for(i := 0; i<n; i++)
+ print(" %d: %2.2ux\n", i, int b[i]);
+}
+
+rdi_read(addr: int, b: array of byte, n: int): int
+{
+ if(debug)
+ print("rdi_read(0x%ux, ..., 0x%ux)\n", addr, n);
+ if(n == 0)
+ return 0;
+ sb := array[9] of byte;
+ sb[0] = byte 2;
+ sb[1:] = bint(addr);
+ sb[5:] = bint(n);
+ sys->write(dfd, sb, 9);
+ (b[0:], nil) = getreply(n);
+ # if error, need to read count of bytes transferred
+ return n;
+}
+
+rdi_write(b: array of byte, addr: int, n: int): int
+{
+ if(debug)
+ print("rdi_write(..., 0x%ux, 0x%ux)\n", addr, n);
+ if(n == 0)
+ return 0;
+ sb := array[9+n] of byte;
+ sb[0] = byte 3;
+ sb[1:] = bint(addr);
+ sb[5:] = bint(n);
+ sb[9:] = b[:n];
+ sys->write(dfd, sb, 9);
+ x := 0;
+ while(n) {
+ q := n;
+ if(q > 8192)
+ q = 8192;
+ r := sys->write(dfd, b[x:], q);
+ if(debug)
+ print("rdi_write: r=%d ofs=%d n=%d\n", r, x, n);
+ if(r < 0)
+ raise "fail:hangup";
+ x += r;
+ n -= r;
+ }
+ getreply(0);
+ return n;
+}
+
+rdi_execute()
+{
+ if(debug)
+ print("rdi_execute()\n");
+ sb := array[2] of byte;
+ sb[0] = byte 16r10;
+ sb[1] = byte 0;
+ sys->write(dfd, sb, 2);
+ getreply(0);
+ out("");
+}
+
+rdi_info(n: int, arg: int)
+{
+ sb := array[9] of byte;
+ sb[0] = byte 16r12;
+ sb[1:] = bint(n);
+ sb[5:] = bint(arg);
+ sys->write(dfd, sb, 9);
+ getreply(0);
+}
+
+
+regdump()
+{
+ out("");
+ reg := array[NREG] of int;
+ # rdi_cpuread(reg, 16rffff|(1<<R_PC)|(1<<R_CPSR)|(1<<R_SPSR));
+ rdi_cpuread(reg, 16rffff|(1<<R_PC)|(1<<R_CPSR));
+ for(i := 0; i < 16; i += 4)
+ print(" r%-2d=%8.8ux r%-2d=%8.8ux r%-2d=%8.8ux r%-2d=%8.8ux\n",
+ i, reg[i], i+1, reg[i+1],
+ i+2, reg[i+2], i+3, reg[i+3]);
+ print(" pc=%8.8ux psr=%8.8ux\n",
+ reg[R_PC], reg[R_CPSR]);
+}
+
+printable(b: array of byte): string
+{
+ s := "";
+ for(i := 0; i < len b; i++)
+ if(b[i] >= byte ' ' && b[i] <= byte 126)
+ s += string b[i:i+1];
+ else
+ s += ".";
+ return s;
+}
+
+examine(a: int, n: int)
+{
+ b := array[4] of byte;
+ for(i := 0; i<n; i++) {
+ rdi_read(a, b, 4);
+ print("0x%8.8ux: 0x%8.8ux \"%s\"\n", a, intb(b), printable(b));
+ a += 4;
+ }
+}
+
+atoi(s: string): int
+{
+ b := 10;
+ if(len s < 1)
+ return 0;
+ if(s[0] == '0') {
+ b = 8;
+ s = s[1:];
+ if(len s < 1)
+ return 0;
+ if(s[0] == 'x' || s[0] == 'X') {
+ b = 16;
+ s = s[1:];
+ }
+ }
+ n: int;
+ (n, nil) = str->toint(s, b);
+ return n;
+}
+
+regnum(s: string): int
+{
+ if(len s < 2)
+ return -1;
+ if(s[0] == 'r' && s[1] >= '0' && s[1] <= '9')
+ return atoi(s[1:]);
+ case s {
+ "pc" => return R_PC;
+ "cpsr" or "psr" => return R_CPSR;
+ "spsr" => return R_SPSR;
+ * => return -1;
+ }
+}
+
+cmdhelp()
+{
+ print(" e <addr> [<count>] - examine memory\n");
+ print(" d <addr> [<value>...] - deposit values in memory\n");
+ print(" get <file> <addr> - read file into memory at addr\n");
+ print(" load <file> - load AIF file and set the PC\n");
+ print(" r - print all registers\n");
+ print(" <reg>=<val> - set register value\n");
+ print(" sb - run builtin sboot (pc=0x40; g)\n");
+ print(" reset - trigger SA1100 software reset\n");
+ print(" bps <speed> - change bps rate (SA1100 only)\n");
+ print(" q - quit\n");
+}
+
+cmdmode()
+{
+ b := array[1024] of byte;
+ for(;;) {
+ print("rdp: ");
+ r := sys->read(ifd, b, len b);
+ if(r < 0)
+ raise sprint("fail:%r");
+ if(r == 0 || (r == 1 && b[0] == byte 4))
+ break;
+ n: int;
+ a: list of string;
+ (n, a) = sys->tokenize(string b[0:r], " \t\n=");
+ if(n < 1)
+ continue;
+ case hd a {
+ "sb" =>
+ sbmode();
+ rdi_execute();
+ "q" or "quit" =>
+ return;
+ "r" or "reg" =>
+ regdump();
+ "get" or "getfile" or "l" or "load" =>
+ {
+ if((hd a)[0] == 'l')
+ aifload(hd tl a, -1);
+ else
+ aifload(hd tl a, atoi(hd tl tl a));
+ }exception e{
+ "fail:*" =>
+ print("error: %s\n", e[5:]);
+ continue;
+ }
+ "g" or "go" =>
+ rdi_execute();
+ "reset" =>
+ sa1100_reset();
+ "e" =>
+ a = tl a;
+ x := atoi(hd a);
+ n = 1;
+ a = tl a;
+ if(a != nil)
+ n = atoi(hd a);
+ examine(x, n);
+ "d" =>
+ a = tl a;
+ x := atoi(hd a);
+ for(i := 2; i<n; i++) {
+ a = tl a;
+ rdi_write(bint(atoi(hd a)), x, 4);
+ x += 4;
+ }
+ "info" =>
+ a = tl a;
+ rdi_info(16r180, atoi(hd a));
+ "bps" =>
+ sa1100_setbps(atoi(hd tl a));
+ "help" or "?" =>
+ cmdhelp();
+ * =>
+ if((rn := regnum(hd a)) > -1) {
+ reg := array[NREG] of int;
+ reg[rn] = atoi(hd tl a);
+ rdi_cpuwrite(reg, 1<<rn);
+ } else
+ print("?\n");
+ }
+ }
+}
+
+sbmode()
+{
+ if(debug)
+ print("sbmode()\n");
+ reg := array[NREG] of int;
+ reg[R_PC] = 16r40;
+ rdi_cpuwrite(reg, 1<<R_PC);
+}
+
+sbmodeofs(ofs: int)
+{
+ if(debug)
+ print("sbmode(0x%ux)\n", ofs);
+ reg := array[NREG] of int;
+ reg[0] = ofs;
+ reg[R_PC] = 16r48;
+ rdi_cpuwrite(reg, (1<<0)|(1<<R_PC));
+}
+
+inp: string = "";
+
+help: con "(q)uit, (i)nt, (b)reak, !c(r), !(l)ine, !(t)erminal, (s<bps>), (.)cont, (!cmd)\n";
+
+menu(fi: ref Sys->FD)
+{
+ w := israw;
+ if(israw)
+ raw(0);
+mloop: for(;;) {
+ out("");
+ print("rdp> ");
+ b := array[256] of byte;
+ r := sys->read(fi, b, len b);
+ case int b[0] {
+ 'q' =>
+ killgrp();
+ exit;
+ 'i' =>
+ b[0] = byte 16r18;
+ sys->write(dfd, b[0:1], 1);
+ break mloop;
+ 'b' =>
+ sys->write(cfd, array of byte "k", 1);
+ break mloop;
+ '!' =>
+ cmd := string b[1:r-1];
+ print("!%s\n", cmd);
+ # system(cmd)
+ print("!\n");
+ break mloop;
+ 'l' =>
+ w = !w;
+ break mloop;
+ 'r' =>
+ nocr = !nocr;
+ break mloop;
+ 'd' =>
+ debug = !debug;
+ break mloop;
+ 't' =>
+ sys->write(pifd, array[] of { byte 4 }, 1);
+ sdc <-= (array of byte "rdp:tmode", -1);
+ break mloop;
+ '.' =>
+ break mloop;
+ 's' =>
+ bps := atoi(string b[1:r-1]);
+ setbps(bps);
+ * =>
+ print(help);
+ continue;
+ }
+ }
+ if(israw != w)
+ raw(w);
+}
+
+
+input()
+{
+ fi := sys->fildes(0);
+ b := array[1024] of byte;
+iloop: for(;;) {
+ r := sys->read(fi, b, len b);
+ if(r < 0) {
+ print("stdin: %r");
+ killgrp();
+ exit;
+ }
+ for(i:=0; i<r; i++) {
+ if(b[i] == byte echar) {
+ menu(fi);
+ continue iloop;
+ }
+ }
+ if(r == 0) {
+ b[0] = byte 4; # ctrl-d
+ r = 1;
+ }
+ if(tmode)
+ sys->write(dfd, b, r);
+ else
+ sys->write(pifd, b, r);
+ }
+}
+
+ccfd: ref Sys->FD;
+israw := 0;
+
+raw(on: int)
+{
+ if(ccfd == nil) {
+ ccfd = sys->open("/dev/consctl", Sys->OWRITE);
+ if(ccfd == nil) {
+ print("/dev/consctl: %r\n");
+ return;
+ }
+ }
+ if(on)
+ sys->fprint(ccfd, "rawon");
+ else
+ sys->fprint(ccfd, "rawoff");
+ israw = on;
+}
+
+killgrp()
+{
+ pid := sys->pctl(0, nil);
+ f := "/prog/"+string pid+"/ctl";
+ fd := sys->open(f, Sys->OWRITE);
+ if(fd == nil)
+ print("%s: %r\n", f);
+ else
+ sys->fprint(fd, "killgrp");
+}
+
+kill(pid: int)
+{
+ f := "/prog/"+string pid+"/ctl";
+ fd := sys->open(f, Sys->OWRITE);
+ if(fd == nil)
+ print("%s: %r\n", f);
+ else
+ sys->fprint(fd, "kill");
+}
+
+
+# Code for switching to previously unsupported bps rates:
+
+##define UTCR1 0x4
+##define UTCR2 0x8
+##define UTCR3 0xc
+##define UTDR 0x14
+##define UTSR0 0x1c
+##define UTSR1 0x20
+#
+#TEXT _startup(SB), $-4
+# MOVW $0x80000000,R2
+# ORR $0x00050000,R2
+#
+# MOVW $0, R1
+# MOVW R1, UTDR(R2) /* send ack */
+#
+#wait:
+# MOVW UTSR1(R2), R1
+# TST $1, R1 /* TBY */
+# BNE wait
+#
+# MOVW $0x90000000,R3
+# ORR $0x00000010,R3
+# MOVW (R4),R1
+# ADD $0x5a000,R1 /* 100 ms */
+#delay1:
+# MOVW (R3),R1
+# SUB.S $0x5a000, R1 /* 100 ms */
+# BLO delay1
+#
+# MOVW UTCR3(R2), R5 /* save utcr3 */
+# MOVW $0, R1
+# MOVW R1, UTCR3(R2) /* disable xmt/rcv */
+#
+# MOVW R0, R1
+# AND $0xff, R1
+# MOVW R1, UTCR2(R2)
+# MOVW R0 >> 8, R1
+# MOVW R1, UTCR1(R2)
+#
+# MOVW $0xff, R1
+# MOVW R1, UTSR0(R2) /* clear sticky bits */
+#
+# MOVW $3, R1
+# MOVW R1, UTCR3(R2) /* enable xmt/rcv */
+#
+# MOVW $0, R0
+#sync:
+# MOVW R0, UTDR(R2) /* send sync char */
+#syncwait:
+# MOVW UTSR1(R2), R1
+# TST $1, R1 /* TBY */
+# BNE syncwait
+# TST $2, R1 /* RNE */
+# BEQ sync
+# MOVW UTDR(R2), R0
+# MOVW R0, UTDR(R2) /* echo rcvd char */
+#
+# MOVW $0xff, R1
+# MOVW R1, UTSR0(R2) /* clear sticky bits */
+# MOVW R5, UTCR3(R2) /* re-enable xmt/rcv and interrupts */
+#
+# WORD $0xef000011 /* exit */
+
+
+bpscode := array[] of {
+ 16re3a22102, 16re3822805, 16re3a11000, 16re5821014,
+ 16re5921020, 16re3110001, big 16r1afffffc, 16re3a33209,
+ 16re3833010, 16re5941000, 16re2811a5a, 16re5931000,
+ 16re2511a5a, big 16r3afffffc, 16re592500c, 16re3a11000,
+ 16re582100c, 16re1a11000, 16re20110ff, 16re5821008,
+ 16re1a11420, 16re5821004, 16re3a110ff, 16re582101c,
+ 16re3a11003, 16re582100c, 16re3a00000, 16re5820014,
+ 16re5921020, 16re3110001, big 16r1afffffc, 16re3110002,
+ big 16r0afffff9, 16re5920014, 16re5820014, 16re3a110ff,
+ 16re582101c, 16re582500c, 16ref000011,
+};
+
+sa1100_setbps(bps: int)
+{
+ print("<sa1100_setbps %d>", bps);
+ nb := len bpscode*4;
+ b := array[nb] of byte;
+ for(i := 0; i < len bpscode; i++)
+ b[i*4:] = bint(int bpscode[i]);
+ rdi_write(b, 16r8080, nb);
+ reg := array[NREG] of int;
+ d := (3686400/(bps*16))-1;
+ reg[0] = d;
+ reg[R_PC] = 16r8080;
+ rdi_cpuwrite(reg, (1<<0)|(1<<R_PC));
+ sb := array[2] of byte;
+ sb[0] = byte 16r10;
+ sb[1] = byte 0;
+ sys->write(dfd, sb, 2);
+ rb := sreadn(1);
+ setbps(bps);
+ do rb = sreadn(1);
+ while(rb[0] != byte 0);
+ sb[0] = byte 16rff;
+ sys->write(dfd, sb, 1);
+ do rb = sreadn(1);
+ while(rb[0] != sb[0]);
+ getreply(0);
+}
+
+aifload(fname: string, adr: int)
+{
+ out("");
+ if(adr < 0)
+ print("<aifload %s>\n", fname);
+ fd := sys->open(fname, Sys->OREAD);
+ if(fd == nil)
+ raise sprint("fail:%s:%r", fname);
+ d: Sys->Dir;
+ (nil, d) = sys->fstat(fd);
+ b := array[int d.length] of byte;
+ sys->read(fd, b, len b);
+ if(adr < 0) {
+ if(len b < 128)
+ raise sprint("fail:%s:not aif", fname);
+ tsize := intb(b[20:24]);
+ dsize := intb(b[24:28]);
+ bsize := intb(b[32:36]);
+ tbase := intb(b[40:44]);
+ dbase := intb(b[52:56]);
+ print("%ux/%ux: %ux+%ux+%ux\n", tbase, dbase, tsize, dsize, bsize);
+ rdi_write(b, tbase, tsize+dsize);
+ reg := array[NREG] of int;
+ reg[R_PC] = tbase+8;
+ rdi_cpuwrite(reg, 1<<R_PC);
+ } else
+ rdi_write(b, adr, int d.length);
+}
+
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ port := df_port;
+ bps := df_bps;
+ usecmdmode := 0;
+ ofs := -1;
+ prog: string = nil;
+
+ argv = tl argv;
+ while(argv != nil) {
+ a := hd argv;
+ argv = tl argv;
+ if(len a >= 2 && a[0] == '-')
+ case a[1] {
+ 'c' =>
+ usecmdmode = 1;
+ 'O' =>
+ ofs = atoi(a[2:]);
+ 'd' =>
+ debug = 1;
+ 'p' =>
+ port = a[2:];
+ 's' =>
+ bps = atoi(a[2:]);
+ 'r' =>
+ nocr = 1;
+ 'l' =>
+ raw(1);
+ 'e' =>
+ if(a[2] == '^')
+ echar = a[3]&16r1f;
+ else
+ echar = a[2];
+ 't' =>
+ tmode = 1;
+ 'h' =>
+ print("usage: rdp [-crdlht] [-e<c>] [-O<ofs>] [-p<port>] [-s<bps>] [prog]\n");
+ return;
+ * =>
+ print("invalid option: %s\n", a);
+ return;
+ }
+ else
+ prog = a;
+ }
+
+ print("rdp 0.17 (port=%s, bps=%d)\n", port, bps);
+ dfd = sys->open(port, Sys->ORDWR);
+ if(dfd == nil) {
+ sys->print("open %s failed: %r\n", port);
+ return;
+ }
+ cfd = sys->open(port+"ctl", Sys->OWRITE);
+ if(cfd == nil)
+ sys->print("warning: open %s failed: %r\n", port+"ctl");
+
+ pfd := array[2] of ref Sys->FD;
+ sys->pipe(pfd);
+ ifd = pfd[1];
+ pifd = pfd[0];
+ (scc, sdc) = (chan of int, chan of (array of byte, int));
+ spawn serinp();
+ spawn input();
+ r := 1;
+ {
+ if(tmode)
+ terminal();
+ reset(r);
+ if(!p_isopen) {
+ rdi_open(bps);
+ rdi_info(16r180, (1<<0)|(1<<1)|(1<<3)|(1<<4)|(1<<5)|(1<<6)|(1<<7)|(1<<8));
+ }
+ # print("\n<connection established>\n");
+ print("\n<contact has been made>\n");
+ if(usecmdmode) {
+ cmdmode();
+ } else {
+ if(prog != nil)
+ aifload(prog, -1);
+ else if(ofs != -1)
+ sbmodeofs(ofs);
+ else
+ sbmode();
+ reg := array[NREG] of int;
+ # rdi_cpuread(reg, (1<<R_PC)|(1<<R_CPSR));
+ # print("<execute at %ux; cpsr=%ux>\n", reg[R_PC], reg[R_CPSR]);
+ rdi_cpuread(reg, (1<<R_PC));
+ print("<execute at %ux>\n", reg[R_PC]);
+ rdi_execute();
+ }
+ rdi_close();
+
+ # Warning: this will make Linux emu crash...
+ killgrp();
+ }exception e{
+ "fail:*" =>
+ if(israw)
+ raw(0);
+ killgrp();
+ raise e;
+ "rdp:*" =>
+ out("");
+ if(debug)
+ print("<exception: %s>\n", e);
+ case e {
+ "rdp:error" => ;
+ "rdp:tmode" =>
+ tmode = !tmode;
+ if(tmode)
+ print("<terminal mode>\n");
+ else
+ print("<rdp mode>\n");
+ "rdp:reset" =>
+ r = 0;
+ * =>
+ r = 1;
+ }
+ }
+}
+
diff --git a/appl/cmd/read.b b/appl/cmd/read.b
new file mode 100644
index 00000000..a4a008a9
--- /dev/null
+++ b/appl/cmd/read.b
@@ -0,0 +1,62 @@
+implement Read;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+Read: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: read [-[ero] offset] count\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ # usage: read [-[ero] offset] count
+ count := Sys->ATOMICIO;
+ offset := big 0;
+ seeking := -1;
+ if (argv != nil)
+ argv = tl argv;
+ if (argv != nil && hd argv != nil && (hd argv)[0] == '-') {
+ if (tl argv == nil)
+ usage();
+ case hd argv {
+ "-o" =>
+ seeking = Sys->SEEKSTART;
+ "-e" =>
+ seeking = Sys->SEEKEND;
+ "-r" =>
+ seeking = Sys->SEEKRELA;
+ * =>
+ usage();
+ }
+ offset = big hd tl argv;
+ argv = tl tl argv;
+ }
+ if (argv != nil) {
+ if (tl argv != nil)
+ usage();
+ count = int hd argv;
+ }
+ fd := sys->fildes(0);
+ if (seeking != -1)
+ sys->seek(fd, offset, seeking);
+ if (count == 0)
+ return;
+ buf := array[count] of byte;
+ n := sys->read(fd, buf, len buf);
+ if (n > 0)
+ sys->write(sys->fildes(1), buf, n);
+ else {
+ if (n == -1) {
+ sys->fprint(sys->fildes(2), "read: read error: %r\n");
+ raise "fail:error";
+ }
+ raise "fail:eof";
+ }
+}
diff --git a/appl/cmd/rioimport.b b/appl/cmd/rioimport.b
new file mode 100644
index 00000000..49980000
--- /dev/null
+++ b/appl/cmd/rioimport.b
@@ -0,0 +1,620 @@
+implement Rioimport;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Image, Point, Rect, Display, Screen: import draw;
+include "wmsrv.m";
+ wmsrv: Wmsrv;
+include "sh.m";
+ sh: Sh;
+include "string.m";
+ str: String;
+
+Rioimport: module{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+Client: adt{
+ ptrstarted: int;
+ kbdstarted: int;
+ state: int; # Hidden|Current
+ req: chan of (array of byte, Sys->Rwrite);
+ resize: chan of ref Riowin;
+ ptr: chan of ref Draw->Pointer;
+ riowctl: chan of (ref Riowin, int);
+ wins: list of ref Riowin;
+ winfd: ref Sys->FD;
+ sc: ref Wmsrv->Client;
+};
+
+Riowin: adt {
+ tag: string;
+ img: ref Image;
+ dir: string;
+ state: int;
+ ptrpid: int;
+ kbdpid: int;
+ ctlpid: int;
+ ptrfd: ref Sys->FD;
+ ctlfd: ref Sys->FD;
+};
+
+Hidden, Current: con 1<<iota;
+Ptrsize: con 1+4*12; # 'm' plus 4 12-byte decimal integers
+P9PATH: con "/n/local";
+Borderwidth: con 4; # defined in /sys/include/draw.h
+
+display: ref Display;
+wsysseq := 0;
+screenr := Rect((0, 0), (640, 480)); # no way of getting this reliably from rio
+
+Minwinsize: con Point(100, 42);
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ sh = load Sh Sh->PATH;
+ sh->initialise();
+ str = load String String->PATH;
+ wmsrv = load Wmsrv Wmsrv->PATH;
+
+ wc := chan of (ref Draw->Context, string);
+ spawn rioproxy(wc);
+ (ctxt, err) := <-wc;
+ if(err != nil){
+ sys->fprint(sys->fildes(2), "rioimport: %s\n", err);
+ raise "fail:no display";
+ }
+ sh->run(ctxt, tl argv);
+}
+
+ebind(a, b: string, flag: int)
+{
+ if(sys->bind(a, b, flag) == -1){
+ sys->fprint(sys->fildes(2), "rioimport: cannot bind %q onto %q: %r\n", a, b);
+ raise "fail:error";
+ }
+}
+
+rioproxy(wc: chan of (ref Draw->Context, string))
+{
+ {
+ rioproxy1(wc);
+ } exception e {
+ "fail:*" =>
+ wc <-= (nil, e[5:]);
+ }
+}
+
+rioproxy1(wc: chan of (ref Draw->Context, string))
+{
+ sys->pctl(Sys->NEWFD, 0 :: 1 :: 2 :: nil);
+
+ ebind("#U*", P9PATH, Sys->MREPL);
+ display = Display.allocate(P9PATH + "/dev");
+ if(display == nil)
+ raise sys->sprint("fail:cannot allocate display: %r");
+
+
+ (wm, join, req) := wmsrv->init();
+ if(wm == nil){
+ wc <-= (nil, sys->sprint("%r"));
+ return;
+ }
+ readscreenr();
+ wc <-= (ref Draw->Context(display, nil, wm), nil);
+
+ sys->pctl(Sys->FORKNS, nil);
+ ebind("#₪", "/srv", Sys->MREPL|Sys->MCREATE);
+ if(sys->bind(P9PATH+"/dev/draw", "/dev/draw", Sys->MREPL) == -1)
+ ebind(P9PATH+"/dev", "/dev", Sys->MAFTER);
+ sh->run(nil, "mount" :: "{mntgen}" :: "/mnt" :: nil);
+
+ clients: array of ref Client;
+ nc := 0;
+ for(;;) alt{
+ (sc, rc) := <-join =>
+ if(nc != 0)
+ rc <-= "only one client available";
+ sync := chan of (ref Client, string);
+ spawn clientproc(sc,sync);
+ (c, err) := <-sync;
+ rc <-= err;
+ if(c != nil){
+ if(sc.id >= len clients)
+ clients = (array[sc.id + 1] of ref Client)[0:] = clients;
+ clients[sc.id] = c;
+ }
+ (sc, data, rc) := <-req =>
+ clients[sc.id].req <-= (data, rc);
+ if(rc == nil)
+ clients[sc.id] = nil;
+ }
+}
+zclient: Client;
+clientproc(sc: ref Wmsrv->Client, rc: chan of (ref Client, string))
+{
+ c := ref zclient;
+ c.req = chan of (array of byte, Sys->Rwrite);
+ c.resize = chan of ref Riowin;
+ c.ptr = chan of ref Draw->Pointer;
+ c.riowctl = chan of (ref Riowin, int);
+ c.sc = sc;
+ rc <-= (c, nil);
+
+loop:
+ for(;;) alt{
+ (data, drc) := <-c.req =>
+ if(drc == nil)
+ break loop;
+ err := handlerequest(c, data);
+ n := len data;
+ if(err != nil)
+ n = -1;
+ alt{
+ drc <-= (n, err) =>;
+ * =>;
+ }
+ p := <-c.ptr =>
+ sc.ptr <-= p;
+ w := <-c.resize =>
+ if((c.state & Hidden) == 0)
+ sc.ctl <-= sys->sprint("!reshape %q -1 0 0 0 0 getwin", w.tag);
+ (w, state) := <-c.riowctl =>
+ if((c.state^state)&Current)
+ sc.ctl <-= "haskbdfocus " + string ((state & Current)!=0);
+ if((c.state^state)&Hidden){
+ s := "unhide";
+ if(state&Hidden)
+ s = "hide";
+ for(wl := c.wins; wl != nil; wl = tl wl){
+ if(hd wl != w)
+ rioctl(hd wl, s);
+ if(c.state&Hidden)
+ sc.ctl <-= sys->sprint("!reshape %q -1 0 0 0 0 getwin", (hd wl).tag);
+ }
+ }
+ c.state = state;
+ w.state = state;
+ }
+ sc.stop <-= 1;
+ for(wl := c.wins; wl != nil; wl = tl wl)
+ delwin(hd wl);
+}
+
+handlerequest(c: ref Client, data: array of byte): string
+{
+ req := string data;
+#sys->print("%d: %s\n", c.sc.id, req);
+ if(req == nil)
+ return "no request";
+ args := str->unquoted(req);
+ n := len args;
+ case hd args {
+ "key" =>
+ return "permission denied";
+ "ptr" =>
+ # ptr x y
+ if(n != 3)
+ return "bad arg count";
+ if(c.ptrstarted == 0)
+ return "pointer not active";
+ for(w := c.wins; w != nil; w = tl w){
+ if((hd w).ptrfd != nil){
+ sys->fprint((hd w).ptrfd, "m%11d %11d", int hd tl args, int hd tl tl args);
+ return nil;
+ }
+ }
+ return "no windows";
+ "start" =>
+ if(n != 2)
+ return "bad arg count";
+ case hd tl args {
+ "ptr" or
+ "mouse" =>
+ if(c.ptrstarted == -1)
+ return "already started";
+ sync := chan of int;
+ for(w := c.wins; w != nil; w = tl w){
+ spawn ptrproc(hd w, c.ptr, c.resize, sync);
+ (hd w).ptrpid = <-sync;
+ }
+ c.ptrstarted = 1;
+ return nil;
+ "kbd" =>
+ if(c.kbdstarted == -1)
+ return "already started";
+ sync := chan of int;
+ for(w := c.wins; w != nil; w = tl w){
+ spawn kbdproc(hd w, c.sc.kbd, sync);
+ (hd w).kbdpid = <-sync;
+ }
+ return nil;
+ * =>
+ return "unknown input source";
+ }
+ "!reshape" =>
+ # reshape tag reqid rect [how]
+ # XXX allow "how" to specify that the origin of the window is never
+ # changed - a new window will be created instead.
+ if(n < 7)
+ return "bad arg count";
+ args = tl args;
+ tag := hd args; args = tl args;
+ args = tl args; # skip reqid
+ r: Rect;
+ r.min.x = int hd args; args = tl args;
+ r.min.y = int hd args; args = tl args;
+ r.max.x = int hd args; args = tl args;
+ r.max.y = int hd args; args = tl args;
+ if(r.dx() < Minwinsize.x)
+ r.max.x = r.min.x + Minwinsize.x;
+ if(r.dy() < Minwinsize.y)
+ r.max.y = r.min.y + Minwinsize.y;
+
+ spec := "";
+ if(args != nil){
+ case hd args{
+ "onscreen" =>
+ r = fitrect(r, screenr).inset(-Borderwidth);
+ spec = "-r " + r2s(r);
+ "place" =>
+ r = fitrect(r, screenr).inset(-Borderwidth);
+ spec = "-dx " + string r.dx() + " -dy " + string r.dy();
+ "exact" =>
+ spec = "-r " + r2s(r.inset(-Borderwidth));
+ "max" =>
+ r = screenr; # XXX don't obscure toolbar?
+ spec = "-r " + r2s(r.inset(Borderwidth));
+ "getwin" =>
+ ; # just get the new image
+ * =>
+ return "unkown placement method";
+ }
+ }else
+ spec = "-r " + r2s(r.inset(-Borderwidth));
+ return reshape(c, tag, spec);
+ "delete" =>
+ # delete tag
+ if(tl args == nil)
+ return "tag required";
+ tag := hd tl args;
+ nw: list of ref Riowin;
+ for(w := c.wins; w != nil; w = tl w){
+ if((hd w).tag == tag){
+ delwin(hd w);
+ wmsrv->c.sc.setimage(tag, nil);
+ }else
+ nw = hd w :: nw;
+ }
+ c.wins = nil;
+ for(; nw != nil; nw = tl nw)
+ c.wins = hd nw :: c.wins;
+ "label" =>
+ if(n != 2)
+ return "bad arg count";
+ for(w := c.wins; w != nil; w = tl w)
+ setlabel(hd w, hd tl args);
+ "raise" =>
+ for(w := c.wins; w != nil; w = tl w){
+ rioctl(hd w, "top");
+ if(tl w == nil)
+ rioctl(hd w, "current");
+ }
+ "lower" =>
+ for(w := c.wins; w != nil; w = tl w)
+ rioctl(hd w, "bottom");
+ "task" =>
+ if(n != 2)
+ return "bad arg count";
+ c.state |= Hidden;
+ for(w := c.wins; w != nil; w = tl w){
+ setlabel(hd w, hd tl args);
+ rioctl(hd w, "hide");
+ }
+ "untask" =>
+ wins: list of ref Riowin;
+ for(w := c.wins; w != nil; w = tl w)
+ wins = hd w :: wins;
+ for(; wins != nil; wins = tl wins)
+ rioctl(hd wins, "unhide");
+ "!move" =>
+ # !move tag reqid startx starty
+ if(n != 5)
+ return "bad arg count";
+ args = tl args;
+ tag := hd args; args = tl args;
+ args = tl args;
+ w := wmsrv->c.sc.window(tag);
+ if(w == nil)
+ return "no such tag";
+ return dragwin(c.ptr, c, w, Point(int hd args, int hd tl args));
+ "!size" =>
+ return "nope";
+ "kbdfocus" =>
+ if(n != 2)
+ return "bad arg count";
+ if(int hd tl args){
+ if(c.wins != nil)
+ return rioctl(hd c.wins, "current");
+ }
+ return nil;
+ * =>
+ return "unknown request";
+ }
+ return nil;
+}
+
+dragwin(ptr: chan of ref Draw->Pointer, c: ref Client, w: ref Wmsrv->Window, click: Point): string
+{
+# if(buttons == 0)
+# return "too late";
+ p: ref Draw->Pointer;
+ img := w.img.screen.image;
+ r := img.r;
+ off := click.sub(r.min);
+ do{
+ p = <-ptr;
+ img.origin(r.min, p.xy.sub(off));
+ } while (p.buttons != 0);
+ c.sc.ptr <-= p;
+# buttons = 0;
+ nr: Rect;
+ nr.min = p.xy.sub(off);
+ nr.max = nr.min.add(r.size());
+ if(nr.eq(r))
+ return "not moved";
+ reshape(c, w.tag, "-r " + r2s(nr));
+ return nil;
+}
+
+rioctl(w: ref Riowin, req: string): string
+{
+ if(sys->fprint(w.ctlfd, "%s", req) == -1){
+#sys->print("rioctl fail %s: %s: %r\n", w.dir, req);
+ return sys->sprint("%r");
+}
+#sys->print("rioctl %s: %s\n", w.dir, req);
+ return nil;
+}
+
+reshape(c: ref Client, tag: string, spec: string): string
+{
+ for(wl := c.wins; wl != nil; wl = tl wl)
+ if((hd wl).tag == tag)
+ break;
+ if(wl == nil){
+ (w, e) := newwin(c, tag, spec);
+ if(w == nil){
+sys->print("can't make new win (spec %q): %s\n", spec, e);
+ return e;
+ }
+ c.wins = w :: c.wins;
+ wmsrv->c.sc.setimage(tag, w.img);
+ sync := chan of int;
+ if(c.kbdstarted){
+ spawn kbdproc(w, c.sc.kbd, sync);
+ w.kbdpid = <-sync;
+ }
+ if(c.ptrstarted){
+ spawn ptrproc(w, c.ptr, c.resize, sync);
+ w.ptrpid = <-sync;
+ }
+ return nil;
+ }
+ w := hd wl;
+ if(spec != nil){
+ e := rioctl(w, "resize " + spec);
+ if(e != nil)
+ return e;
+ }
+ getwin(w);
+ if(w.img == nil)
+ return "getwin failed";
+ wmsrv->c.sc.setimage(tag, w.img);
+ return nil;
+}
+
+zriowin: Riowin;
+newwin(c: ref Client, tag, spec: string): (ref Riowin, string)
+{
+ wsys := readfile(P9PATH + "/env/wsys");
+ if(wsys == nil)
+ return (nil, "no $wsys");
+
+ d := "/mnt/"+string wsysseq++;
+ fd := sys->open(wsys, Sys->ORDWR);
+ if(fd == nil)
+ return (nil, sys->sprint("cannot open %q: %r\n", wsys));
+ # XXX this won't multiplex properly - srv9 should export attach files (actually that's what plan 9 should do)
+ if(sys->mount(fd, nil, d, Sys->MREPL, "new "+spec) == -1)
+ return (nil, sys->sprint("mount %q failed: %r", wsys));
+ (ok, nil) := sys->stat(d + "/winname");
+ if(ok == -1)
+ return (nil, "could not make window");
+ w := ref zriowin;
+ w.tag = tag;
+ w.dir = d;
+ getwin(w);
+ w.ctlfd = sys->open(d + "/wctl", Sys->ORDWR);
+ setlabel(w, "inferno "+string sys->pctl(0, nil)+"."+tag);
+ sync := chan of int;
+ spawn ctlproc(w, c.riowctl, sync);
+ w.ctlpid = <-sync;
+ return (w, nil);
+}
+
+setlabel(w: ref Riowin, s: string)
+{
+ fd := sys->open(w.dir + "/label", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "%s", s);
+}
+
+ctlproc(w: ref Riowin, wctl: chan of (ref Riowin, int), sync: chan of int)
+{
+ sync <-= sys->pctl(0, nil);
+ buf := array[1024] of byte;
+ for(;;){
+ n := sys->read(w.ctlfd, buf, len buf);
+ if(n <= 0)
+ break;
+ if(n > 4*12){
+ state := 0;
+ (nil, toks) := sys->tokenize(string buf[4*12:], " ");
+ if(hd toks == "current")
+ state |= Current;
+ if(hd tl toks == "hidden")
+ state |= Hidden;
+ wctl <-= (w, state);
+ }
+ }
+#sys->print("riowctl eof\n");
+}
+
+delwin(w: ref Riowin)
+{
+ sys->unmount(nil, w.dir);
+ kill(w.ptrpid, "kill");
+ kill(w.kbdpid, "kill");
+ kill(w.ctlpid, "kill");
+}
+
+getwin(w: ref Riowin): int
+{
+ s := readfile(w.dir + "/winname");
+#sys->print("getwin %s\n", s);
+ i := display.namedimage(s);
+ if(i == nil)
+ return -1;
+ scr := Screen.allocate(i, display.white, 0);
+ if(scr == nil)
+ return -1;
+ wi := scr.newwindow(i.r.inset(Borderwidth), Draw->Refnone, Draw->Nofill);
+ if(wi == nil)
+ return -1;
+ w.img = wi;
+ return 0;
+}
+
+kbdproc(w: ref Riowin, keys: chan of int, sync: chan of int)
+{
+ sys->pctl(Sys->NEWFD, nil);
+ cctl := sys->open(w.dir + "/consctl", Sys->OWRITE);
+ sys->fprint(cctl, "rawon");
+ fd := sys->open(w.dir + "/cons", Sys->OREAD);
+ if(fd == nil){
+ sync <-= -1;
+ return;
+ }
+ sync <-= sys->pctl(0, nil);
+ buf := array[12] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0){
+ s := string buf[0:n];
+ for(j := 0; j < len s; j++)
+ keys <-= int s[j];
+ }
+#sys->print("eof on kbdproc\n");
+}
+
+# fit a window rectangle to the available space.
+# try to preserve requested location if possible.
+# make sure that the window is no bigger than
+# the screen, and that its top and left-hand edges
+# will be visible at least.
+fitrect(w, r: Rect): Rect
+{
+ if(w.dx() > r.dx())
+ w.max.x = w.min.x + r.dx();
+ if(w.dy() > r.dy())
+ w.max.y = w.min.y + r.dy();
+ size := w.size();
+ if (w.max.x > r.max.x)
+ (w.min.x, w.max.x) = (r.min.x - size.x, r.max.x - size.x);
+ if (w.max.y > r.max.y)
+ (w.min.y, w.max.y) = (r.min.y - size.y, r.max.y - size.y);
+ if (w.min.x < r.min.x)
+ (w.min.x, w.max.x) = (r.min.x, r.min.x + size.x);
+ if (w.min.y < r.min.y)
+ (w.min.y, w.max.y) = (r.min.y, r.min.y + size.y);
+ return w;
+}
+
+ptrproc(w: ref Riowin, ptr: chan of ref Draw->Pointer, resize: chan of ref Riowin, sync: chan of int)
+{
+ w.ptrfd = sys->open(w.dir + "/mouse", Sys->ORDWR);
+ if(w.ptrfd == nil){
+ sync <-= -1;
+ return;
+ }
+ sync <-= sys->pctl(0, nil);
+
+ b:= array[Ptrsize] of byte;
+ while((n := sys->read(w.ptrfd, b, len b)) > 0){
+ if(n > 0 && int b[0] == 'r'){
+#sys->print("ptrproc got resize: %s\n", string b[0:n]);
+ resize <-= w;
+ }else{
+ p := bytes2ptr(b);
+ if(p != nil)
+ ptr <-= p;
+ }
+ }
+#sys->print("eof on ptrproc\n");
+}
+
+bytes2ptr(b: array of byte): ref Draw->Pointer
+{
+ if(len b < Ptrsize || int b[0] != 'm')
+ return nil;
+ x := int string b[1:13];
+ y := int string b[13:25];
+ but := int string b[25:37];
+ msec := int string b[37:49];
+ return ref Draw->Pointer (but, (x, y), msec);
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, sys->OREAD);
+ if(fd == nil)
+ return nil;
+
+ buf := array[8192] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return nil;
+
+ return string buf[0:n];
+}
+
+readscreenr()
+{
+ fd := sys->open(P9PATH + "/dev/screen", Sys->OREAD);
+ if(fd == nil)
+ return ;
+ buf := array[5*12] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= len buf)
+ return;
+ screenr.min.x = int string buf[12:23];
+ screenr.min.y = int string buf[24:35];
+ screenr.max.x = int string buf[36:47];
+ screenr.max.y = int string buf[48:];
+}
+
+r2s(r: Rect): string
+{
+ return string r.min.x + " " + string r.min.y + " " +
+ string r.max.x + " " + string r.max.y;
+}
+
+kill(pid: int, note: string): int
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "%s", note) < 0)
+ return -1;
+ return 0;
+}
diff --git a/appl/cmd/rm.b b/appl/cmd/rm.b
new file mode 100644
index 00000000..af8236be
--- /dev/null
+++ b/appl/cmd/rm.b
@@ -0,0 +1,99 @@
+implement Rm;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+include "readdir.m";
+ readdir: Readdir;
+
+include "arg.m";
+
+Rm: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+quiet := 0;
+force := 0;
+errcount := 0;
+
+usage()
+{
+ sys->fprint(stderr, "Usage: rm [-fr] file ...\n");
+ raise "fail: usage";
+}
+allwrite := Sys->nulldir;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ allwrite.mode = 8r777 | Sys->DMDIR;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil){
+ sys->fprint(stderr, "rm: can't load %s: %r\n", Arg->PATH);
+ raise "fail:load";
+ }
+ arg->init(args);
+ while((o := arg->opt()) != 0)
+ case o {
+ 'r' =>
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ sys->fprint(stderr, "rm: can't load Readdir: %r\n"); # -r is regarded as optional
+ 'f' =>
+ quiet = 1;
+ 'F' =>
+ force = 1;
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+ sys->pctl(Sys->FORKNS, nil);
+ for(; args != nil; args = tl args) {
+ name := hd args;
+ if(sys->remove(name) < 0) {
+ e := sys->sprint("%r");
+ (ok, d) := sys->stat(name);
+ if(readdir != nil && ok >= 0 && (d.mode & Sys->DMDIR) != 0)
+ rmdir(name);
+ else
+ err(name, e);
+ }
+ }
+ if(errcount > 0)
+ raise "fail:errors";
+}
+
+rmdir(name: string)
+{
+ if(force)
+ sys->wstat(name, allwrite);
+ (d, n) := readdir->init(name, Readdir->NONE|Readdir->COMPACT);
+ for(i := 0; i < n; i++){
+ path := name+"/"+d[i].name;
+ if(d[i].mode & Sys->DMDIR)
+ rmdir(path);
+ else
+ remove(path);
+ }
+ remove(name);
+}
+
+remove(name: string)
+{
+ if(sys->remove(name) < 0)
+ err(name, sys->sprint("%r"));
+}
+
+err(name, e: string)
+{
+ if(!quiet) {
+ sys->fprint(stderr, "rm: %s: %s\n", name, e);
+ errcount++;
+ }
+}
diff --git a/appl/cmd/runas.b b/appl/cmd/runas.b
new file mode 100644
index 00000000..d0112396
--- /dev/null
+++ b/appl/cmd/runas.b
@@ -0,0 +1,60 @@
+implement Runas;
+
+include "sys.m";
+include "draw.m";
+include "sh.m";
+
+sys: Sys;
+sh: Sh;
+
+Context: import sh;
+
+Runas: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+init(drawctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sh = load Sh Sh->PATH;
+ if (sh == nil)
+ badmodule(Sh->PATH);
+
+ if (len argv < 3)
+ usage();
+
+ argv = tl argv;
+ user := hd argv;
+ argv = tl argv;
+
+ fd := sys->open("/dev/user", Sys->OWRITE);
+ if (fd == nil)
+ error(sys->sprint("cannot open /dev/user: %r"));
+ u := array of byte user;
+ if (sys->write(fd, u, len u) != len u)
+ error(sys->sprint("cannot set user: %r"));
+ sh->run(drawctxt, argv);
+}
+
+badmodule(p: string)
+{
+ sys->fprint(stderr(), "runas: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+usage()
+{
+ sys->fprint(stderr(), "usage: runas user cmd [args...]\n");
+ raise "fail:usage";
+}
+
+error(e: string)
+{
+ sys->fprint(stderr(), "runas: %s\n", e);
+ raise "fail:error";
+} \ No newline at end of file
diff --git a/appl/cmd/sed.b b/appl/cmd/sed.b
new file mode 100644
index 00000000..30bfbd22
--- /dev/null
+++ b/appl/cmd/sed.b
@@ -0,0 +1,908 @@
+implement Sed;
+
+#
+# partial sed implementation borrowed from plan9 sed.
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+ arg: Arg;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "regex.m";
+ regex: Regex;
+ Re: import regex;
+
+Sed : module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+
+false, true: con iota;
+bool: type int;
+
+Addr: adt {
+ pick {
+ None =>
+ Dollar =>
+ Line =>
+ line: int;
+ Regex =>
+ re: Re;
+ }
+};
+
+Sedcom: adt {
+ command: fn(c: self ref Sedcom);
+ executable: fn(c: self ref Sedcom) : int;
+
+ ad1, ad2: ref Addr;
+ negfl: bool;
+ active: int;
+
+ pick {
+ S =>
+ gfl, pfl: int;
+ re: Re;
+ b: ref Iobuf;
+ rhs: string;
+ D or CD or P or Q or EQ or G or CG or H or CH or N or CN or X or CP or L=>
+ A or C or I =>
+ text: string;
+ R =>
+ filename: string;
+ W =>
+ b: ref Iobuf;
+ Y =>
+ map: list of (int, int);
+ B or T or Lab =>
+ lab: string;
+ }
+};
+
+dflag := false;
+nflag := false;
+gflag := false;
+sflag := 0;
+
+delflag := 0;
+dolflag := 0;
+fhead := 0;
+files: list of string;
+fout: ref Iobuf;
+infile: ref Iobuf;
+jflag := 0;
+lastregex: Re;
+linebuf: string;
+filename := "";
+lnum := 0;
+peekc := 0;
+
+holdsp := "";
+patsp := "";
+
+cmds: list of ref Sedcom;
+appendlist: list of ref Sedcom;
+bufioflush: list of ref Iobuf;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ if ((arg = load Arg Arg->PATH) == nil)
+ fatal(sys->sprint("could not load %s: %r", Arg->PATH));
+
+ if ((bufio = load Bufio Bufio->PATH) == nil)
+ fatal(sys->sprint("could not load %s: %r", Bufio->PATH));
+
+ if ((str = load String String->PATH) == nil)
+ fatal(sys->sprint("could not load %s: %r", String->PATH));
+
+ if ((regex = load Regex Regex->PATH) == nil)
+ fatal(sys->sprint("could not load %s: %r", Regex->PATH));
+
+ arg->init(args);
+
+ compfl := 0;
+ while ((c := arg->opt()) != 0)
+ case c {
+ 'n' =>
+ nflag = true;
+ 'g' =>
+ gflag = true;
+ 'e' =>
+ if ((s := arg->arg()) == nil)
+ usage();
+ filename = "";
+ cmds = compile(bufio->sopen(s + "\n"), cmds);
+ compfl = 1;
+ 'f' => if ((filename = arg->arg()) == nil)
+ usage();
+ b := bufio->open(filename, bufio->OREAD);
+ if (b == nil)
+ fatal(sys->sprint("couldn't open '%s': %r", filename));
+ cmds = compile(b, cmds);
+ compfl = 1;
+ 'd' =>
+ dflag = true;
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ if (compfl == 0) {
+ if (len args == 0)
+ fatal("missing pattern");
+ filename = "";
+ cmds = compile(bufio->sopen(hd args + "\n"), cmds);
+ args = tl args;
+ }
+
+ # reverse command list, we could compile addresses here if required
+ l: list of ref Sedcom;
+ for (p := cmds; p != nil; p = tl p) {
+ l = hd p :: l;
+ }
+ cmds = l;
+
+ # add files to file list (and reverse to get in right order)
+ f: list of string;
+ if (len args == 0)
+ f = "" :: f;
+ else for (; len args != 0; args = tl args)
+ f = hd args :: f;
+ for (;f != nil; f = tl f)
+ files = hd f :: files;
+
+ if ((fout = bufio->fopen(sys->fildes(1), bufio->OWRITE)) == nil)
+ fatal(sys->sprint("couldn't buffer stdout: %r"));
+ bufioflush = fout :: bufioflush;
+ lnum = 0;
+ execute(cmds);
+ exits(nil);
+}
+
+depth := 0;
+maxdepth: con 20;
+cmdend := array [maxdepth] of string;
+cmdcnt := array [maxdepth] of int;
+
+compile(b: ref Iobuf, l: list of ref Sedcom) : list of ref Sedcom
+{
+ lnum = 1;
+
+nextline:
+ for (;;) {
+ err: int;
+ (err, linebuf) = getline(b);
+ if (err < 0)
+ break;
+
+ s := linebuf;
+
+ do {
+ rep: ref Sedcom;
+ ad1, ad2: ref Addr;
+ negfl := 0;
+
+ if (s != "")
+ s = str->drop(s, " \t;");
+
+ if (s == "" || s[0] == '#')
+ continue nextline;
+
+ # read addresses
+ (s, ad1) = address(s);
+ pick a := ad1 {
+ None =>
+ ad2 = ref Addr.None();
+ * =>
+ if (s != "" && (s[0] == ',' || s[0] == ';')) {
+ (s, ad2) = address(s[1:]);
+ }
+ else {
+ ad2 = ref Addr.None();
+ }
+ }
+
+ s = str->drop(s, " \t");
+
+ if (s != "" && str->in(s[0], "!")) {
+ negfl = true;
+ s = str->drop(s, "!");
+ }
+ s = str->drop(s, " \t");
+ if (s == "")
+ break;
+ c := s[0]; s = s[1:];
+
+ # mop up commands that got two addresses but only want one.
+ case c {
+ 'a' or 'c' or 'q' or '=' or 'i' =>
+ if (tagof ad2 != tagof Addr.None)
+ fatal(sys->sprint("only one address allowed: '%s'",
+ linebuf));
+ }
+
+ case c {
+ * =>
+ fatal(sys->sprint("unrecognised command: '%s' (%c)",
+ linebuf, c));
+ 'a' =>
+ if (s != "" && s[0] == '\\')
+ s = s[1:];
+ if (s == "" || s[0] != '\n')
+ fatal("unexpected characters in a command: " + s);
+ rep = ref Sedcom.A (ad1, ad2, negfl, 0, s[1:]);
+ s = "";
+ 'c' =>
+ if (s != "" && s[0] == '\\')
+ s = s[1:];
+ if (s == "" || s[0] != '\n')
+ fatal("unexpected characters in c command: " + s);
+ rep = ref Sedcom.C (ad1, ad2, negfl, 0, s[1:]);
+ s = "";
+ 'i' =>
+ if (s != "" && s[0] == '\\')
+ s = s[1:];
+ if (s == "" || s[0] != '\n')
+ fatal("unexpected characters in i command: " + s);
+ rep = ref Sedcom.I (ad1, ad2, negfl, 0, s[1:]);
+ s = "";
+ 'r' =>
+ s = str->drop(s, " \t");
+ rep = ref Sedcom.R (ad1, ad2, negfl, 0, s);
+ s = "";
+ 'w' =>
+ if (s != "")
+ s = str->drop(s, " \t");
+ if (s == "")
+ fatal("no filename in w command: " + linebuf);
+ bo := bufio->open(s, bufio->OWRITE);
+ if (bo == nil)
+ bo = bufio->create(s, bufio->OWRITE, 8r666);
+ if (bo == nil)
+ fatal(sys->sprint("can't create output file: '%s'", s));
+ bufioflush = bo :: bufioflush;
+ rep = ref Sedcom.W (ad1, ad2, negfl, 0, bo);
+ s = "";
+
+ 'd' =>
+ rep = ref Sedcom.D (ad1, ad2, negfl, 0);
+ 'D' =>
+ rep = ref Sedcom.CD (ad1, ad2, negfl, 0);
+ 'p' =>
+ rep = ref Sedcom.P (ad1, ad2, negfl, 0);
+ 'P' =>
+ rep = ref Sedcom.CP (ad1, ad2, negfl, 0);
+ 'q' =>
+ rep = ref Sedcom.Q (ad1, ad2, negfl, 0);
+ '=' =>
+ rep = ref Sedcom.EQ (ad1, ad2, negfl, 0);
+ 'g' =>
+ rep = ref Sedcom.G (ad1, ad2, negfl, 0);
+ 'G' =>
+ rep = ref Sedcom.CG (ad1, ad2, negfl, 0);
+ 'h' =>
+ rep = ref Sedcom.H (ad1, ad2, negfl, 0);
+ 'H' =>
+ rep = ref Sedcom.CH (ad1, ad2, negfl, 0);
+ 'n' =>
+ rep = ref Sedcom.N (ad1, ad2, negfl, 0);
+ 'N' =>
+ rep = ref Sedcom.CN (ad1, ad2, negfl, 0);
+ 'x' =>
+ rep = ref Sedcom.X (ad1, ad2, negfl, 0);
+ 'l' =>
+ rep = ref Sedcom.L (ad1, ad2, negfl, 0);
+ 'y' =>
+ if (s == "")
+ fatal("expected args: " + linebuf);
+ seof := s[0:1];
+ s = s[1:];
+ if (s == "")
+ fatal("no lhs: " + linebuf);
+ (lhs, s2) := str->splitl(s, seof);
+ if (s2 == "")
+ fatal("no lhs terminator: " + linebuf);
+ s2 = s2[1:];
+ (rhs, s4) := str->splitl(s2, seof);
+ if (s4 == "")
+ fatal("no rhs: " + linebuf);
+ s = s4[1:];
+ if (len lhs != len rhs)
+ fatal("y command needs same length sets: " + linebuf);
+ map: list of (int, int);
+ for (i := 0; i < len lhs; i++)
+ map = (lhs[i], rhs[i]) :: map;
+ rep = ref Sedcom.Y (ad1, ad2, negfl, 0, map);
+ 's' =>
+ seof := s[0:1];
+ re: Re;
+ (re, s) = recomp(s);
+ rhs: string;
+ (s, rhs) = compsub(seof + s);
+
+ gfl := gflag;
+ pfl := 0;
+
+ if (s != "" && s[0] == 'g') {
+ gfl = 1;
+ s = s[1:];
+ }
+ if (s != "" && s[0] == 'p') {
+ pfl = 1;
+ s = s[1:];
+ }
+ if (s != "" && s[0] == 'P') {
+ pfl = 2;
+ s = s[1:];
+ }
+
+ b: ref Iobuf = nil;
+ if (s != "" && s[0] == 'w') {
+ s = s[1:];
+ if (s != "")
+ s = str->drop(s, " \t");
+ if (s == "")
+ fatal("no filename in s with w: " + linebuf);
+ b = bufio->open(s, bufio->OWRITE);
+ if (b == nil)
+ b = bufio->create(s, bufio->OWRITE, 8r666);
+ if (b == nil)
+ fatal(sys->sprint("can't create output file: '%s'", s));
+ bufioflush = b :: bufioflush;
+ s = "";
+ }
+ rep = ref Sedcom.S (ad1, ad2, negfl, 0, gfl, pfl, re, b, rhs);
+ ':' =>
+ if (s != "")
+ s = str->drop(s, " \t");
+ (lab, s1) := str->splitl(s, " \t;#");
+ s = s1;
+ if (lab == "")
+ fatal(sys->sprint("null label: '%s'", linebuf));
+ if (findlabel(lab))
+ fatal(sys->sprint("duplicate label: '%s'", lab));
+ rep = ref Sedcom.Lab (ad1, ad2, negfl, 0, lab);
+ 'b' or 't' =>
+ if (s != "")
+ s = str->drop(s, " \t");
+ (lab, s1) := str->splitl(s, " \t;#");
+ s = s1;
+ if (c == 'b')
+ rep = ref Sedcom.B (ad1, ad2, negfl, 0, lab);
+ else
+ rep = ref Sedcom.T (ad1, ad2, negfl, 0, lab);
+ '{' =>
+ # replace { with branch to }.
+ lab := mklab(depth);
+ depth++;
+ rep = ref Sedcom.B (ad1, ad2, !negfl, 0, lab);
+ s = ";" + s;
+ '}' =>
+ if (tagof ad1 != tagof Addr.None)
+ fatal("did not expect address:" + linebuf);
+ if (--depth < 0)
+ fatal("too many }'s: " + linebuf);
+ lab := mklab(depth);
+ cmdcnt[depth]++;
+ rep = ref Sedcom.Lab ( ad1, ad2, negfl, 0, lab);
+ s = ";" + s;
+ }
+
+ l = rep :: l;
+ } while (s != nil && str->in(s[0], ";{}"));
+
+ if (s != nil)
+ fatal("leftover junk: " + s);
+ }
+ return l;
+}
+
+findlabel(lab: string) : bool
+{
+ for (l := cmds; l != nil; l = tl l)
+ pick x := hd l {
+ Lab =>
+ if (x.lab == lab)
+ return true;
+ }
+ return false;
+}
+
+mklab(depth: int): string
+{
+ return "_" + string cmdcnt[depth] + "_" + string depth;
+}
+
+Sedcom.command(c: self ref Sedcom)
+{
+ pick x := c {
+ S =>
+ m: bool;
+ (m, patsp) = substitute(x, patsp);
+ if (m) {
+ case x.pfl {
+ 0 =>
+ ;
+ 1 =>
+ fout.puts(patsp + "\n");
+ * =>
+ l: string;
+ (l, patsp) = str->splitl(patsp, "\n");
+ fout.puts(l + "\n");
+ break;
+ }
+ if (x.b != nil)
+ x.b.puts(patsp + "\n");
+ }
+ P =>
+ fout.puts(patsp + "\n");
+ CP =>
+ (s, nil) := str->splitl(patsp, "\n");
+ fout.puts(s + "\n");
+ A =>
+ appendlist = c :: appendlist;
+ R =>
+ appendlist = c :: appendlist;
+ C =>
+ delflag++;
+ if (c.active == 1)
+ fout.puts(x.text + "\n");
+ I =>
+ fout.puts(x.text + "\n");
+ W =>
+ x.b.puts(patsp + "\n");
+ G =>
+ patsp = holdsp;
+ CG =>
+ patsp += holdsp;
+ H =>
+ holdsp = patsp;
+ CH =>
+ holdsp += patsp;
+ X =>
+ (holdsp, patsp) = (patsp, holdsp);
+ Y =>
+ # yes this is O(N²).
+ for (i := 0; i < len patsp; i++)
+ for (h := x.map; h != nil; h = tl h) {
+ (s, d) := hd h;
+ if (patsp[i] == s)
+ patsp[i] = d;
+ }
+ D =>
+ delflag++;
+ CD =>
+ # loose upto \n.
+ (s1, s2) := str->splitl(patsp, "\n");
+ if (s2 == nil)
+ patsp = s1;
+ else if (len s2 > 1)
+ patsp = s2[1:];
+ else
+ patsp = "";
+ jflag++;
+ Q =>
+ if (!nflag)
+ fout.puts(patsp + "\n");
+ arout();
+ exits(nil);
+ N =>
+ if (!nflag)
+ fout.puts(patsp + "\n");
+ arout();
+ n: int;
+ (patsp, n) = gline();
+ if (n < 0)
+ delflag++;
+ CN =>
+ arout();
+ (ns, n) := gline();
+ if (n < 0)
+ delflag++;
+ patsp += "\n" + ns;
+ EQ =>
+ fout.puts(sys->sprint("%d\n", lnum));
+ Lab =>
+ # labels don't do anything.
+ B =>
+ jflag = true;
+ T =>
+ if (sflag) {
+ sflag = false;
+ jflag = true;
+ }
+ L =>
+ col := 0;
+ cc := 0;
+ for (i := 0; i < len patsp; i++) {
+ s := "";
+ cc = patsp[i];
+ if (cc >= 16r20 && cc < 16r7F && cc != '\n')
+ s[len s] = cc;
+ else
+ s = trans(cc);
+ for (j := 0; j < len s; j++) {
+ fout.putc(s[j]);
+ if (col++ > 71) {
+ fout.puts("\\\n");
+ col = 0;
+ }
+ }
+ }
+ if (cc == ' ')
+ fout.puts("\\n");
+ fout.putc('\n');
+ * =>
+ fatal("unhandled command");
+ }
+}
+
+trans(ch: int) : string
+{
+ case ch {
+ '\b' =>
+ return "\\b";
+ '\n' =>
+ return "\\n";
+ '\r' =>
+ return "\\r";
+ '\t' =>
+ return "\\t";
+ '\\' =>
+ return "\\\\";
+ * =>
+ return sys->sprint("\\u%4x", ch);
+ }
+}
+
+getline(b: ref Iobuf) : (int, string)
+{
+ w : string;
+
+ lnum++;
+
+ while ((c := b.getc()) != bufio->EOF) {
+ r := c;
+ if (r == '\\') {
+ w[len w] = r;
+ if ((c = b.getc()) == bufio->EOF)
+ break;
+ r = c;
+ }
+ else if (r == '\n')
+ return (1, w);
+ w[len w] = r;
+ }
+ return (-1, w);
+}
+
+address(s: string) : (string, ref Addr)
+{
+ case s[0] {
+ '$' =>
+ return (s[1:], ref Addr.Dollar());
+ '/' =>
+ (r, s1) := recomp(s);
+ if (r == nil)
+ r = lastregex;
+ if (r == nil)
+ fatal("First RE in address may not be null");
+ return (s1, ref Addr.Regex(r));
+ '0' to '9' =>
+ (lno, ls) := str->toint(s, 10);
+ if (lno == 0)
+ fatal("line no 0 is illegal address");
+ return (ls, ref Addr.Line(lno));
+ * =>
+ return (s, ref Addr.None());
+ }
+}
+
+recomp(s :string) : (Re, string)
+{
+ expbuf := "";
+
+ seof := s[0]; s = s[1:];
+ if (s[0] == seof)
+ return (nil, s[1:]); # //
+
+ c := s[0]; s = s[1:];
+ do {
+ if (c == '\0' || c == '\n')
+ fatal("too much text: " + linebuf);
+ if (c == '\\') {
+ expbuf[len expbuf] = c;
+ c = s[0]; s = s[1:];
+ if (c == 'n')
+ c = '\n';
+ }
+ expbuf[len expbuf] = c;
+ c = s[0]; s = s[1:];
+ } while (c != seof);
+
+ (r, err) := regex->compile(expbuf, 1);
+ if (r == nil)
+ fatal(sys->sprint("%s '%s'", err, expbuf));
+
+ lastregex = r;
+
+ return (r, s);
+}
+
+compsub(s: string): (string, string)
+{
+ seof := s[0];
+ rhs := "";
+ for (i := 1; i < len s; i++) {
+ r := s[i];
+ if (r == seof)
+ break;
+ if (r == '\\') {
+ rhs[len rhs] = r;
+ if(++i >= len s)
+ break;
+ r = s[i];
+ }
+ rhs[len rhs] = r;
+ }
+ if (i >= len s)
+ fatal(sys->sprint("no closing %c in replacement text: %s", seof, linebuf));
+ return (s[i+1:], rhs);
+}
+
+execute(l: list of ref Sedcom)
+{
+ for (;;) {
+ n: int;
+
+ (patsp, n) = gline();
+ if (n < 0)
+ break;
+
+cmdloop:
+ for (p := l; p != nil;) {
+ c := hd p;
+ if (!c.executable()) {
+ p = tl p;
+ continue;
+ }
+
+ c.command();
+
+ if (delflag)
+ break;
+ if (jflag) {
+ jflag = 0;
+ pick x := c {
+ B or T =>
+ if (p == nil)
+ break cmdloop;
+ for (p = l; p != nil; p = tl p) {
+ pick cc := hd p {
+ Lab =>
+ if (cc.lab == x.lab)
+ continue cmdloop;
+ }
+ }
+ break cmdloop; # unmatched branch => end of script
+ * =>
+ # don't branch.
+ }
+ }
+ else
+ p = tl p;
+ }
+ if (!nflag && !delflag)
+ fout.puts(patsp + "\n");
+ arout();
+ delflag = 0;
+ }
+}
+
+Sedcom.executable(c: self ref Sedcom) : int
+{
+ if (c.active) {
+ if (c.active == 1)
+ c.active = 2;
+ pick x := c.ad2 {
+ None =>
+ c.active = 0;
+ Dollar =>
+ return !c.negfl;
+ Line =>
+ if (lnum <= x.line) {
+ if (x.line == lnum)
+ c.active = 0;
+ return !c.negfl;
+ }
+ c.active = 0;
+ return c.negfl;
+ Regex =>
+ if (match(x.re, patsp))
+ c.active = false;
+ return !c.negfl;
+ }
+ }
+ pick x := c.ad1 {
+ None =>
+ return !c.negfl;
+ Dollar =>
+ if (dolflag)
+ return !c.negfl;
+ Line =>
+ if (x.line == lnum) {
+ c.active = 1;
+ return !c.negfl;
+ }
+ Regex =>
+ if (match(x.re, patsp)) {
+ c.active = 1;
+ return !c.negfl;
+ }
+ }
+ return c.negfl;
+}
+
+arout()
+{
+ a: list of ref Sedcom;
+
+ while (appendlist != nil) {
+ a = hd appendlist :: a;
+ appendlist = tl appendlist;
+ }
+
+ for (; a != nil; a = tl a)
+ pick x := hd a {
+ A =>
+ fout.puts(x.text + "\n");
+ R =>
+ if ((b := bufio->open(x.filename, bufio->OREAD)) == nil)
+ fatal(sys->sprint("couldn't open '%s'", x.filename));
+ while ((c := b.getc()) != bufio->EOF)
+ fout.putc(c);
+ b.close();
+ * =>
+ fatal("unexpected command on appendlist");
+ }
+}
+
+match(re: Re, s: string) : bool
+{
+ if (re != nil && regex->execute(re, s) != nil)
+ return true;
+ else
+ return false;
+}
+
+substitute(c: ref Sedcom.S, s: string) : (bool, string)
+{
+ if (!match(c.re, s))
+ return (false, s);
+ sflag = true;
+ start := 0;
+
+ do {
+ se := (start, len s);
+ if ((m := regex->executese(c.re, s, se, true, true)) == nil)
+ break;
+ (l, r) := m[0];
+ rep := "";
+ for (i := 0; i < len c.rhs; i++){
+ if (c.rhs[i] != '\\' )
+ rep[len rep] = c.rhs[i];
+ else {
+ i++;
+ case c.rhs[i] {
+ '0' to '9' =>
+ n := c.rhs[i] - '0';
+ # elide if too big
+ if (n < len m) {
+ (beg, end) := m[n];
+ rep += s[beg:end];
+ }
+ 'n' =>
+ rep[len rep] = '\n';
+ * =>
+ rep[len rep] = c.rhs[i];
+ }
+ }
+ }
+ s = s[0:l] + rep + s[r:];
+ start++;
+ } while (c.gfl);
+ return (true, s);
+}
+
+gline() : (string, int)
+{
+ if (infile == nil && opendatafile() < 0)
+ return (nil, -1);
+
+ sflag = false;
+ lnum++;
+
+ s := "";
+ do {
+ c := peekc;
+ if (c == 0)
+ c = infile.getc();
+ for (; c != bufio->EOF; c = infile.getc()) {
+ if (c == '\n') {
+ if ((peekc = infile.getc()) == bufio->EOF)
+ if (fhead == 0)
+ dolflag = 1;
+ return (s, 1);
+ }
+ s[len s] = c;
+ }
+ if (len s != 0) {
+ peekc = bufio->EOF;
+ if (fhead == 0)
+ dolflag = 1;
+ return (s, 1);
+ }
+ peekc = 0;
+ infile = nil;
+ } while (opendatafile() > 0);
+ infile = nil;
+ return (nil, -1);
+}
+
+opendatafile() : int
+{
+ if (files == nil)
+ return -1;
+ if (hd files != nil) {
+ if ((infile = bufio->open(hd files, bufio->OREAD)) == nil)
+ fatal(sys->sprint("can't open '%s'", hd files));
+ }
+ else if ((infile = bufio->fopen(sys->fildes(0), bufio->OREAD)) == nil)
+ fatal("can't buffer stdin");
+
+ files = tl files;
+ return 1;
+}
+
+dbg(s: string)
+{
+ if (dflag)
+ sys->print("dbg: %s\n", s);
+}
+
+usage()
+{
+ sys->fprint(stderr(), "usage: %s [-ngd] [-e expr] [-f file] [expr] [file...]\n",
+ arg->progname());
+ exits("usage");
+}
+
+fatal(s: string)
+{
+ f := filename;
+ if (f == nil)
+ f = "<stdin>";
+ sys->fprint(stderr(), "%s:%d %s\n", f, lnum, s);
+ exits("error");
+}
+
+exits(e: string)
+{
+ for(; bufioflush != nil; bufioflush = tl bufioflush)
+ (hd bufioflush).flush();
+ if (e != nil)
+ raise "fail:" + e;
+ exit;
+}
+
+stderr() : ref Sys->FD
+{
+ return sys->fildes(2);
+}
diff --git a/appl/cmd/sendmail.b b/appl/cmd/sendmail.b
new file mode 100644
index 00000000..9b6d0c17
--- /dev/null
+++ b/appl/cmd/sendmail.b
@@ -0,0 +1,252 @@
+implement Sendmail;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+include "daytime.m";
+include "smtp.m";
+include "env.m";
+
+sprint, fprint : import sys;
+
+DEBUG : con 0;
+STRMAX : con 512;
+
+Sendmail : module
+{
+ PATH : con "/dis/sendmail.dis";
+
+ # argv is list of persons to send mail to (or nil if To: lines present in message)
+ # mail is read from standard input
+ # scans mail for headers (From: , To: , Cc: , Subject: , Re: ) where case is not sensitive
+ init: fn(ctxt : ref Draw->Context, argv : list of string);
+};
+
+init(nil : ref Draw->Context, args : list of string) {
+ from : string;
+ tos, cc : list of string = nil;
+
+ sys = load Sys Sys->PATH;
+ smtp := load Smtp Smtp->PATH;
+ if (smtp == nil)
+ error(sprint("cannot load %s", Smtp->PATH), 1);
+ daytime := load Daytime Daytime->PATH;
+ if (daytime == nil)
+ error(sprint("cannot load %s", Daytime->PATH), 1);
+ msgl := readin();
+ for (ml := msgl; ml != nil; ml = tl ml) {
+ msg := hd ml;
+ lenm := len msg;
+ sol := 1;
+ for (i := 0; i < lenm; i++) {
+ if (sol) {
+ for (j := i; j < lenm; j++)
+ if (msg[j] == '\n')
+ break;
+ s := msg[i:j];
+ if (from == nil) {
+ from = match(s, "from");
+ if (from != nil)
+ from = extract(from);
+ }
+ if (tos == nil)
+ tos = lmatch(s, "to");
+ if (cc == nil)
+ cc = lmatch(s, "cc");
+ sol = 0;
+ }
+ if (msg[i] == '\n')
+ sol = 1;
+ }
+ }
+ if (tos != nil && tl args != nil)
+ error("recipients specified on To: line and as args - aborted", 1);
+ if (from == nil)
+ from = readfile("/dev/user");
+ from = adddom(from);
+ if (tos == nil)
+ tos = tl args;
+ (ok, err) := smtp->open(nil);
+ if (ok < 0) {
+ smtp->close();
+ error(sprint("smtp open failed: %s", err), 1);
+ }
+ dump(from, tos, cc, msgl);
+ msgl = "From " + from + "\t" + daytime->time() + "\n" :: msgl;
+ # msgl = "From: " + from + "\n" + "Date: " + daytime->time() + "\n" :: msgl;
+ (ok, err) = smtp->sendmail(from, tos, cc, msgl);
+ if (ok < 0) {
+ smtp->close();
+ error(sprint("send failed : %s", err), 0);
+ }
+ smtp->close();
+}
+
+readin() : list of string
+{
+ m : string;
+ ls : list of string;
+ nc : int;
+
+ bufio := load Bufio Bufio->PATH;
+ Iobuf : import bufio;
+ b := bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ ls = nil;
+ m = nil;
+ nc = 0;
+ while ((s := b.gets('\n')) != nil) {
+ if (nc > STRMAX) {
+ ls = m :: ls;
+ m = nil;
+ nc = 0;
+ }
+ m += s;
+ nc += len s;
+ }
+ b.close();
+ if (m != nil)
+ ls = m :: ls;
+ return rev(ls);
+}
+
+match(s: string, pat : string) : string
+{
+ ls := len s;
+ lp := len pat;
+ if (ls < lp)
+ return nil;
+ for (i := 0; i < lp; i++) {
+ c := s[i];
+ if (c >= 'A' && c <= 'Z')
+ c += 'a'-'A';
+ if (c != pat[i])
+ return nil;
+ }
+ if (i < len s && s[i] == ':')
+ i++;
+ else if (i < len s - 1 && s[i] == ' ' && s[i+1] == ':')
+ i += 2;
+ else
+ return nil;
+ while (i < len s && (s[i] == ' ' || s[i] == '\t'))
+ i++;
+ j := ls-1;
+ while (j >= 0 && (s[j] == ' ' || s[j] == '\t' || s[j] == '\n'))
+ j--;
+ return s[i:j+1];
+}
+
+lmatch(s : string, pat : string) : list of string
+{
+ r := match(s, pat);
+ if (r != nil) {
+ (ok, lr) := sys->tokenize(r, " ,\t");
+ return lr;
+ }
+ return nil;
+}
+
+extract(s : string) : string
+{
+ ls := len s;
+ for(i := 0; i < ls; i++) {
+ if(s[i] == '<') {
+ for(j := i+1; j < ls; j++)
+ if(s[j] == '>')
+ break;
+ return s[i+1:j];
+ }
+ }
+ return s;
+}
+
+adddom(s : string) : string
+{
+ if (s == nil)
+ return nil;
+ for (i := 0; i < len s; i++)
+ if (s[i] == '@')
+ return s;
+ # better to get it from environment if possible
+ env := load Env Env->PATH;
+ if (env != nil && (dom := env->getenv("DOMAIN")) != nil) {
+ ldom := len dom;
+ if (dom[ldom - 1] == '\n')
+ dom = dom[0:ldom - 1];
+ return s + "@" + dom;
+ }
+ d := readfile("/usr/" + s + "/mail/domain");
+ if (d != nil) {
+ ld := len d;
+ if (d[ld - 1] == '\n')
+ d = d[0:ld - 1];
+ return s + "@" + d;
+ }
+ return s;
+}
+
+readfile(f : string) : string
+{
+ fd := sys->open(f, sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return nil;
+ return string buf[0:n];
+}
+
+rev(l1 : list of string) : list of string
+{
+ l2 : list of string = nil;
+
+ for ( ; l1 != nil; l1 = tl l1)
+ l2 = hd l1 :: l2;
+ return l2;
+}
+
+lprint(fd : ref Sys->FD, ls : list of string)
+{
+ for ( ; ls != nil; ls = tl ls)
+ fprint(fd, "%s ", hd ls);
+ fprint(fd, "\n");
+}
+
+cfd : ref Sys->FD;
+
+opencons()
+{
+ if (cfd == nil)
+ cfd = sys->open("/dev/cons", Sys->OWRITE);
+}
+
+dump(from : string, tos : list of string, cc : list of string, msgl : list of string)
+{
+ if (DEBUG) {
+ opencons();
+ fprint(cfd, "from\n");
+ fprint(cfd, "%s\n", from);
+ fprint(cfd, "to\n");
+ lprint(cfd, tos);
+ fprint(cfd, "cc\n");
+ lprint(cfd, cc);
+ fprint(cfd, "message\n");
+ for ( ; msgl != nil; msgl = tl msgl) {
+ fprint(cfd, "%s", hd msgl);
+ fprint(cfd, "xxxx\n");
+ }
+ }
+}
+
+error(s : string, ex : int)
+{
+ if (DEBUG) {
+ opencons();
+ fprint(cfd, "sendmail: %s\n", s);
+ }
+ fprint(sys->fildes(2), "sendmail: %s\n", s);
+ if (ex)
+ exit;
+}
diff --git a/appl/cmd/sh/arg.b b/appl/cmd/sh/arg.b
new file mode 100644
index 00000000..a0b57b84
--- /dev/null
+++ b/appl/cmd/sh/arg.b
@@ -0,0 +1,181 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("arg: cannot load self: %r"));
+ ctxt.addbuiltin("arg", myself);
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(ctxt: ref Context, nil: Sh,
+ argv: list of ref Listnode, last: int): string
+{
+ case (hd argv).word {
+ "arg" =>
+ return builtin_arg(ctxt, argv, last);
+ }
+ return nil;
+}
+
+runsbuiltin(nil: ref Sh->Context, nil: Sh,
+ nil: list of ref Listnode): list of ref Listnode
+{
+ return nil;
+}
+
+argusage(ctxt: ref Context)
+{
+ ctxt.fail("usage", "usage: arg [opts {command}]... - args");
+}
+
+builtin_arg(ctxt: ref Context, argv: list of ref Listnode, nil: int): string
+{
+ for (args := tl argv; args != nil; args = tl tl args) {
+ if ((hd args).word == "-")
+ break;
+ if ((hd args).cmd != nil && (hd args).word == nil)
+ argusage(ctxt);
+ if (tl args == nil)
+ argusage(ctxt);
+ if ((hd tl args).cmd == nil)
+ argusage(ctxt);
+ }
+ if (args == nil)
+ args = ctxt.get("*");
+ else
+ args = tl args;
+ laststatus := "";
+ ctxt.push();
+ {
+ arg := Arg.init(args);
+ while ((opt := arg.opt()) != 0) {
+ for (argt := tl argv; argt != nil && (hd argt).word != "-"; argt = tl tl argt) {
+ w := (hd argt).word;
+ argcount := 0;
+ for (e := len w - 1; e >= 0; e--) {
+ if (w[e] != '+')
+ break;
+ argcount++;
+ }
+ w = w[0:e+1];
+ if (w == nil)
+ continue;
+ for (i := 0; i < len w; i++)
+ if (w[i] == opt || w[i] == '*')
+ break;
+ if (i < len w) {
+ optstr := ""; optstr[0] = opt;
+ ctxt.setlocal("opt", ref Listnode(nil, optstr) :: nil);
+ args = arg.arg(argcount);
+ if (argcount > 0 && args == nil)
+ ctxt.fail("usage", sys->sprint("option -%c requires %d arguments", opt, argcount));
+ ctxt.setlocal("arg", args);
+ laststatus = ctxt.run(hd tl argt :: nil, 0);
+ break;
+ }
+ }
+ if (argt == nil || (hd argt).word == "-")
+ ctxt.fail("usage", sys->sprint("unknown option -%c", opt));
+ }
+ ctxt.pop();
+ ctxt.set("args", arg.args); # XXX backward compatibility - should go
+ ctxt.set("*", arg.args);
+ return laststatus;
+ }
+ exception e{
+ "fail:*" =>
+ ctxt.pop();
+ if (e[5:] == "break")
+ return laststatus;
+ raise e;
+ }
+}
+
+Arg: adt {
+ args: list of ref Listnode;
+ curropt: string;
+ init: fn(argv: list of ref Listnode): ref Arg;
+ arg: fn(ctxt: self ref Arg, n: int): list of ref Listnode;
+ opt: fn(ctxt: self ref Arg): int;
+};
+
+
+Arg.init(argv: list of ref Listnode): ref Arg
+{
+ return ref Arg(argv, nil);
+}
+
+# get next n option arguments (nil list if not enough arguments found)
+Arg.arg(ctxt: self ref Arg, n: int): list of ref Listnode
+{
+ if (n == 0)
+ return nil;
+
+ args: list of ref Listnode;
+ while (--n >= 0) {
+ if (ctxt.curropt != nil) {
+ args = ref Listnode(nil, ctxt.curropt) :: args;
+ ctxt.curropt = nil;
+ } else if (ctxt.args == nil)
+ return nil;
+ else {
+ args = hd ctxt.args :: args;
+ ctxt.args = tl ctxt.args;
+ }
+ }
+ r: list of ref Listnode;
+ for (; args != nil; args = tl args)
+ r = hd args :: r;
+ return r;
+}
+
+# get next option letter
+# return 0 at end of options
+Arg.opt(ctxt: self ref Arg): int
+{
+ if (ctxt.curropt != "") {
+ opt := ctxt.curropt[0];
+ ctxt.curropt = ctxt.curropt[1:];
+ return opt;
+ }
+
+ if (ctxt.args == nil)
+ return 0;
+
+ nextarg := (hd ctxt.args).word;
+ if (len nextarg < 2 || nextarg[0] != '-')
+ return 0;
+
+ if (nextarg == "--") {
+ ctxt.args = tl ctxt.args;
+ return 0;
+ }
+
+ opt := nextarg[1];
+ if (len nextarg > 2)
+ ctxt.curropt = nextarg[2:];
+ ctxt.args = tl ctxt.args;
+ return opt;
+}
diff --git a/appl/cmd/sh/csv.b b/appl/cmd/sh/csv.b
new file mode 100644
index 00000000..601032d6
--- /dev/null
+++ b/appl/cmd/sh/csv.b
@@ -0,0 +1,244 @@
+implement Shellbuiltin;
+
+# parse/generate comma-separated values.
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("csv: cannot load self: %r"));
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ ctxt.fail("bad module",
+ sys->sprint("csv: cannot load: %s: %r", Bufio->PATH));
+ ctxt.addbuiltin("getcsv", myself);
+ ctxt.addsbuiltin("csv", myself);
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(c: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode, last: int): string
+{
+ return builtin_getcsv(c, cmd, last);
+}
+
+runsbuiltin(c: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode): list of ref Listnode
+{
+ return sbuiltin_csv(c, cmd);
+}
+
+builtin_getcsv(ctxt: ref Context, argv: list of ref Listnode, nil: int) : string
+{
+ n := len argv;
+ if (n != 2 || !iscmd(hd tl argv))
+ builtinusage(ctxt, "getcsv {cmd}");
+ cmd := hd tl argv :: ctxt.get("*");
+ stdin := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ if (stdin == nil)
+ ctxt.fail("bad input", sys->sprint("getcsv: cannot open stdin: %r"));
+ status := "";
+ ctxt.push();
+ for(;;){
+ {
+ for (;;) {
+ line: list of ref Listnode = nil;
+ sl := readcsvline(stdin);
+ if (sl == nil)
+ break;
+ for (; sl != nil; sl = tl sl)
+ line = ref Listnode(nil, hd sl) :: line;
+ ctxt.setlocal("line", line);
+ status = setstatus(ctxt, ctxt.run(cmd, 0));
+ }
+ ctxt.pop();
+ return status;
+ }
+ exception e{
+ "fail:*" =>
+ ctxt.pop();
+ if (loopexcept(e) == BREAK)
+ return status;
+ ctxt.push();
+ }
+ }
+}
+
+CONTINUE, BREAK: con iota;
+loopexcept(ename: string): int
+{
+ case ename[5:] {
+ "break" =>
+ return BREAK;
+ "continue" =>
+ return CONTINUE;
+ * =>
+ raise ename;
+ }
+ return 0;
+}
+
+iscmd(n: ref Listnode): int
+{
+ return n.cmd != nil || (n.word != nil && n.word[0] == '{');
+}
+
+builtinusage(ctxt: ref Context, s: string)
+{
+ ctxt.fail("usage", "usage: " + s);
+}
+
+setstatus(ctxt: ref Context, val: string): string
+{
+ ctxt.setlocal("status", ref Listnode(nil, val) :: nil);
+ return val;
+}
+
+# in csv format, is it possible to distinguish between a line containing
+# one empty field and a line containing no fields at all?
+# what does each one look like?
+readcsvline(iob: ref Iobuf): list of string
+{
+ sl: list of string;
+
+ for(;;) {
+ (s, eof) := readcsvword(iob);
+ if (sl == nil && s == nil && eof)
+ return nil;
+
+ c := Bufio->EOF;
+ if (!eof)
+ c = iob.getc();
+ sl = s :: sl;
+ if (c == '\n' || c == Bufio->EOF)
+ return sl;
+ }
+}
+
+sbuiltin_csv(nil: ref Context, val: list of ref Listnode): list of ref Listnode
+{
+ val = tl val;
+ if (val == nil)
+ return nil;
+ s := s2qv(word(hd val));
+ for (val = tl val; val != nil; val = tl val)
+ s += "," + s2qv(word(hd val));
+ return ref Listnode(nil, s) :: nil;
+}
+
+s2qv(s: string): string
+{
+ needquote := 0;
+ needscan := 0;
+ for (i := 0; i < len s; i++) {
+ c := s[i];
+ if (c == '\n' || c == ',')
+ needquote = 1;
+ else if (c == '"') {
+ needquote = 1;
+ needscan = 1;
+ }
+ }
+ if (!needquote)
+ return s;
+ if (!needscan)
+ return "\"" + s + "\"";
+ r := "\"";
+ for (i = 0; i < len s; i++) {
+ c := s[i];
+ if (c == '"')
+ r[len r] = c;
+ r[len r] = c;
+ }
+ r[len r] = '"';
+ return r;
+}
+
+readcsvword(iob: ref Iobuf): (string, int)
+{
+ s := "";
+ case c := iob.getc() {
+ '"' =>
+ for (;;) {
+ case c = iob.getc() {
+ Bufio->EOF =>
+ return (s, 1);
+ '"' =>
+ case c = iob.getc() {
+ '"' =>
+ s[len s] = '"';
+ '\n' or
+ ',' =>
+ iob.ungetc();
+ return (s, 0);
+ Bufio->EOF =>
+ return (s, 1);
+ * =>
+ # illegal
+ iob.ungetc();
+ (t, eof) := readcsvword(iob);
+ return (s + t, eof);
+ }
+ * =>
+ s[len s] = c;
+ }
+ }
+ ',' or
+ '\n' =>
+ iob.ungetc();
+ return (s, 0);
+ Bufio->EOF =>
+ return (nil, 1);
+ * =>
+ s[len s] = c;
+ for (;;) {
+ case c = iob.getc() {
+ ',' or
+ '\n' =>
+ iob.ungetc();
+ return (s, 0);
+ '"' =>
+ # illegal
+ iob.ungetc();
+ (t, eof) := readcsvword(iob);
+ return (s + t, eof);
+ Bufio->EOF =>
+ return (s, 1);
+ * =>
+ s[len s] = c;
+ }
+ }
+ }
+}
+
+word(n: ref Listnode): string
+{
+ if (n.word != nil)
+ return n.word;
+ if (n.cmd != nil)
+ n.word = sh->cmd2string(n.cmd);
+ return n.word;
+}
diff --git a/appl/cmd/sh/doc/History b/appl/cmd/sh/doc/History
new file mode 100644
index 00000000..5a9b4dca
--- /dev/null
+++ b/appl/cmd/sh/doc/History
@@ -0,0 +1,14 @@
+14/11/96 started
+12/12/96 first mostly working version
+13/12/96 fixed bug in builtin_if
+14/12/96 prompt fixed, dup fixed.
+17/1/97 fiddled with shell script perm checking
+16/2/97 converted to yacc grammar
+18/2/97 got pipes and backquotes working, with only minor hacks...
+2/4/00 revamped:
+ single process, single main module; added load builtin; added ${} operator;
+ added eval and std modules
+17/4/00 added '=' and ':=' operators; removed builtin 'set' and 'local'.
+11/6/00 added tuple assignment
+2/3/01 added n-char lookahead in lexer; ':' no longer so special
+15/2/01 store environment variables in standard quoted format.
diff --git a/appl/cmd/sh/echo.b b/appl/cmd/sh/echo.b
new file mode 100644
index 00000000..2fa85def
--- /dev/null
+++ b/appl/cmd/sh/echo.b
@@ -0,0 +1,96 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("echo: cannot load self: %r"));
+ ctxt.addbuiltin("echo", myself);
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(ctxt: ref Context, nil: Sh,
+ argv: list of ref Listnode, last: int): string
+{
+ case (hd argv).word {
+ "echo" =>
+ return builtin_echo(ctxt, argv, last);
+ }
+ return nil;
+}
+
+runsbuiltin(nil: ref Sh->Context, nil: Sh,
+ nil: list of ref Listnode): list of ref Listnode
+{
+ return nil;
+}
+
+argusage(ctxt: ref Context)
+{
+ ctxt.fail("usage", "usage: arg [opts {command}]... - args");
+}
+
+# converted from /appl/cmd/echo.b.
+# should have exactly the same semantics.
+builtin_echo(nil: ref Context, argv: list of ref Listnode, nil: int): string
+{
+ argv = tl argv;
+ nonewline := 0;
+ if (len argv > 0) {
+ w := (hd argv).word;
+ if (w == "-n" || w == "--") {
+ nonewline = (w == "-n");
+ argv = tl argv;
+ }
+ }
+ s := "";
+ if (argv != nil) {
+ s = word(hd argv);
+ for (argv = tl argv; argv != nil; argv = tl argv)
+ s += " " + word(hd argv);
+ }
+ if (nonewline == 0)
+ s[len s] = '\n';
+ {
+ a := array of byte s;
+ if (sys->write(sys->fildes(1), a, len a) != len a) {
+ sys->fprint(sys->fildes(2), "echo: write error: %r\n");
+ return "write error";
+ }
+ return nil;
+ }
+ exception{
+ "write on closed pipe" =>
+ sys->fprint(sys->fildes(2), "echo: write error: write on closed pipe\n");
+ return "write error";
+ }
+}
+
+word(n: ref Listnode): string
+{
+ if (n.word != nil)
+ return n.word;
+ if (n.cmd != nil)
+ n.word = sh->cmd2string(n.cmd);
+ return n.word;
+}
diff --git a/appl/cmd/sh/expr.b b/appl/cmd/sh/expr.b
new file mode 100644
index 00000000..d613dce2
--- /dev/null
+++ b/appl/cmd/sh/expr.b
@@ -0,0 +1,281 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("expr: cannot load self: %r"));
+
+ ctxt.addsbuiltin("expr", myself);
+ ctxt.addbuiltin("ntest", myself);
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+EQ, GT, LT, GE, LE, PLUS, MINUS, DIVIDE, AND, TIMES, MOD,
+OR, XOR, UMINUS, SHL, SHR, NOT, BNOT, NEQ, REP, SEQ: con iota;
+
+runbuiltin(ctxt: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode, nil: int): string
+{
+ case (hd cmd).word {
+ "ntest" =>
+ if (len cmd != 2)
+ ctxt.fail("usage", "usage: ntest n");
+ if (big (hd tl cmd).word == big 0)
+ return "false";
+ }
+ return nil;
+}
+
+runsbuiltin(ctxt: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode): list of ref Listnode
+{
+ # only one sbuiltin: expr.
+ stk: list of big;
+ lastop := -1;
+ lastn := -1;
+ lastname := "";
+ radix: int;
+ (cmd, radix) = opts(ctxt, tl cmd);
+ for (; cmd != nil; cmd = tl cmd) {
+ w := (hd cmd).word;
+ op := -1;
+ nops := 2;
+ case w {
+ "+" =>
+ op = PLUS;
+ "-" =>
+ op = MINUS;
+ "x" or "*" or "×" =>
+ op = TIMES;
+ "/" =>
+ op = DIVIDE;
+ "%" =>
+ op = MOD;
+ "and" =>
+ op = AND;
+ "or" =>
+ op = OR;
+ "xor" =>
+ op = XOR;
+ "_"=>
+ (op, nops) = (UMINUS, 1);
+ "<<" or "shl" =>
+ op = SHL;
+ ">>" or "shr" =>
+ op = SHR;
+ "=" or "==" or "eq" =>
+ op = EQ;
+ "!=" or "neq" =>
+ op = NEQ;
+ ">" or "gt" =>
+ op = GT;
+ "<" or "lt" =>
+ op = LT;
+ ">=" or "ge" =>
+ op = GE;
+ "<=" or "le" =>
+ op = LE;
+ "!" or "not" =>
+ (op, nops) = (NOT, 1);
+ "~" =>
+ (op, nops) = (BNOT, 1);
+ "rep" =>
+ (op, nops) = (REP, 0);
+ "seq" =>
+ (op, nops) = (SEQ, 2);
+ }
+ if (op == -1)
+ stk = makenum(ctxt, w) :: stk;
+ else
+ stk = operator(ctxt, stk, op, nops, lastop, lastn, w, lastname);
+ lastop = op;
+ lastn = nops;
+ lastname = w;
+ }
+ r: list of ref Listnode;
+ for (; stk != nil; stk = tl stk)
+ r = ref Listnode(nil, big2string(hd stk, radix)) :: r;
+ return r;
+}
+
+opts(ctxt: ref Context, cmd: list of ref Listnode): (list of ref Listnode, int)
+{
+ radix := 10;
+ if (cmd == nil)
+ return (nil, 10);
+ w := (hd cmd).word;
+ if (len w < 2)
+ return (cmd, 10);
+ if (w[0] != '-' || (w[1] >= '0' && w[1] <= '9'))
+ return (cmd, 10);
+ if (w[1] != 'r')
+ ctxt.fail("usage", "usage: expr [-r radix] [arg...]");
+ if (len w > 2)
+ w = w[2:];
+ else {
+ if (tl cmd == nil)
+ ctxt.fail("usage", "usage: expr [-r radix] [arg...]");
+ cmd = tl cmd;
+ w = (hd cmd).word;
+ }
+ r := int w;
+ if (r <= 0 || r > 36)
+ ctxt.fail("usage", "expr: invalid radix " + string r);
+ return (tl cmd, int w);
+}
+
+operator(ctxt: ref Context, stk: list of big, op, nops, lastop, lastn: int,
+ opname, lastopname: string): list of big
+{
+ al: list of big;
+ for (i := 0; i < nops; i++) {
+ if (stk == nil)
+ ctxt.fail("empty stack",
+ sys->sprint("expr: empty stack on op '%s'", opname));
+ al = hd stk :: al;
+ stk = tl stk;
+ }
+ return oper(ctxt, al, op, lastop, lastn, lastopname, stk);
+}
+
+# args are in reverse order
+oper(ctxt: ref Context, args: list of big, op, lastop, lastn: int,
+ lastopname: string, stk: list of big): list of big
+{
+ if (op == REP) {
+ if (lastop == -1 || lastop == SEQ || lastn != 2)
+ ctxt.fail("usage", "expr: bad operator for rep");
+ if (stk == nil || tl stk == nil)
+ return stk;
+ while (tl stk != nil)
+ stk = operator(ctxt, stk, lastop, 2, -1, -1, lastopname, nil);
+ return stk;
+ }
+ n2 := big 0;
+ n1 := hd args;
+ if (tl args != nil)
+ n2 = hd tl args;
+ r := big 0;
+ case op {
+ EQ => r = big(n1 == n2);
+ NEQ => r = big(n1 != n2);
+ GT => r = big(n1 > n2);
+ LT => r = big(n1 < n2);
+ GE => r = big(n1 >= n2);
+ LE => r = big(n1 <= n2);
+ PLUS => r = big(n1 + n2);
+ MINUS => r = big(n1 - n2);
+ NOT => r = big(n1 != big 0);
+ DIVIDE =>
+ if (n2 == big 0)
+ ctxt.fail("divide by zero", "expr: division by zero");
+ r = n1 / n2;
+ MOD =>
+ if (n2 == big 0)
+ ctxt.fail("divide by zero", "expr: division by zero");
+ r = n1 % n2;
+ TIMES => r = n1 * n2;
+ AND => r = n1 & n2;
+ OR => r = n1 | n2;
+ XOR => r = n1 ^ n2;
+ UMINUS => r = -n1;
+ BNOT => r = ~n1;
+ SHL => r = n1 << int n2;
+ SHR => r = n1 >> int n2;
+ SEQ => return seq(n1, n2, stk);
+ }
+ return r :: stk;
+}
+
+seq(n1, n2: big, stk: list of big): list of big
+{
+ incr := big 1;
+ if (n2 < n1)
+ incr = big -1;
+ for (; n1 != n2; n1 += incr)
+ stk = n1 :: stk;
+ return n1 :: stk;
+}
+
+makenum(ctxt: ref Context, s: string): big
+{
+ if (s == nil || (s[0] != '-' && (s[0] < '0' || s[0] > '9')))
+ ctxt.fail("usage", sys->sprint("expr: unknown operator '%s'", s));
+
+ t := s;
+ if (neg := s[0] == '-')
+ s = s[1:];
+ radix := 10;
+ for (i := 0; i < len s && i < 3; i++) {
+ if (s[i] == 'r') {
+ radix = int s;
+ s = s[i+1:];
+ break;
+ }
+ }
+ if (radix == 10)
+ return big t;
+ if (radix == 0 || radix > 36)
+ ctxt.fail("usage", "expr: bad number " + t);
+ n := big 0;
+ for (i = 0; i < len s; i++) {
+ if ('0' <= s[i] && s[i] <= '9')
+ n = (n * big radix) + big(s[i] - '0');
+ else if ('a' <= s[i] && s[i] < 'a' + radix - 10)
+ n = (n * big radix) + big(s[i] - 'a' + 10);
+ else if ('A' <= s[i] && s[i] < 'A' + radix - 10)
+ n = (n * big radix) + big(s[i] - 'A' + 10);
+ else
+ break;
+ }
+ if (neg)
+ return -n;
+ return n;
+}
+
+big2string(n: big, radix: int): string
+{
+ if (neg := n < big 0) {
+ n = -n;
+ }
+ s := "";
+ do {
+ c: int;
+ d := int (n % big radix);
+ if (d < 10)
+ c = '0' + d;
+ else
+ c = 'a' + d - 10;
+ s[len s] = c;
+ n /= big radix;
+ } while (n > big 0);
+ t := s;
+ for (i := len s - 1; i >= 0; i--)
+ t[len s - 1 - i] = s[i];
+ if (radix != 10)
+ t = string radix + "r" + t;
+ if (neg)
+ return "-" + t;
+ return t;
+}
diff --git a/appl/cmd/sh/file2chan.b b/appl/cmd/sh/file2chan.b
new file mode 100644
index 00000000..a54c9965
--- /dev/null
+++ b/appl/cmd/sh/file2chan.b
@@ -0,0 +1,459 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "lock.m";
+ lock: Lock;
+ Semaphore: import lock;
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+
+Tag: adt {
+ tagid, blocked: int;
+ offset, fid: int;
+ pick {
+ Read =>
+ count: int;
+ rc: chan of (array of byte, string);
+ Write =>
+ data: array of byte;
+ wc: chan of (int, string);
+ }
+};
+
+taglock: ref Lock->Semaphore;
+maxtagid := 1;
+tags := array[16] of list of ref Tag;
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("file2chan: cannot load self: %r"));
+
+ lock = load Lock Lock->PATH;
+ if (lock == nil) ctxt.fail("bad module", sys->sprint("file2chan: cannot load %s: %r", Lock->PATH));
+ lock->init();
+
+ taglock = Semaphore.new();
+ if (taglock == nil)
+ ctxt.fail("no lock", "file2chan: cannot make lock");
+
+
+ ctxt.addbuiltin("file2chan", myself);
+ ctxt.addbuiltin("rblock", myself);
+ ctxt.addbuiltin("rread", myself);
+ ctxt.addbuiltin("rreadone", myself);
+ ctxt.addbuiltin("rwrite", myself);
+ ctxt.addbuiltin("rerror", myself);
+ ctxt.addbuiltin("fetchwdata", myself);
+ ctxt.addbuiltin("putrdata", myself);
+ ctxt.addsbuiltin("rget", myself);
+
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(ctxt: ref Context, nil: Sh,
+ cmd: list of ref Listnode, nil: int): string
+{
+ case (hd cmd).word {
+ "file2chan" => return builtin_file2chan(ctxt, cmd);
+ "rblock" => return builtin_rblock(ctxt, cmd);
+ "rread" => return builtin_rread(ctxt, cmd, 0);
+ "rreadone" => return builtin_rread(ctxt, cmd, 1);
+ "rwrite" => return builtin_rwrite(ctxt, cmd);
+ "rerror" => return builtin_rerror(ctxt, cmd);
+ "fetchwdata" => return builtin_fetchwdata(ctxt, cmd);
+ "putrdata" => return builtin_putrdata(ctxt, cmd);
+ }
+ return nil;
+}
+
+runsbuiltin(ctxt: ref Context, nil: Sh,
+ argv: list of ref Listnode): list of ref Listnode
+{
+ # could add ${rtags} to retrieve list of currently outstanding tags
+ case (hd argv).word {
+ "rget" => return sbuiltin_rget(ctxt, argv);
+ }
+ return nil;
+}
+
+builtin_file2chan(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ rcmd, wcmd, ccmd: ref Listnode;
+ path: string;
+
+ n := len argv;
+ if (n < 4 || n > 5)
+ ctxt.fail("usage", "usage: file2chan file {readcmd} {writecmd} [ {closecmd} ]");
+
+ (path, argv) = ((hd tl argv).word, tl tl argv);
+ (rcmd, argv) = (hd argv, tl argv);
+ (wcmd, argv) = (hd argv, tl argv);
+ if (argv != nil)
+ ccmd = hd argv;
+ if (path == nil || !iscmd(rcmd) || !iscmd(wcmd) || (ccmd != nil && !iscmd(ccmd)))
+ ctxt.fail("usage", "usage: file2chan file {readcmd} {writecmd} [ {closecmd} ]");
+
+ (dir, f) := pathsplit(path);
+ if (sys->bind("#s", dir, Sys->MBEFORE|Sys->MCREATE) == -1) {
+ reporterror(ctxt, sys->sprint("file2chan: cannot bind #s: %r"));
+ return "no #s";
+ }
+ fio := sys->file2chan(dir, f);
+ if (fio == nil) {
+ reporterror(ctxt, sys->sprint("file2chan: cannot make %s: %r", path));
+ return "cannot make chan";
+ }
+ sync := chan of int;
+ spawn srv(sync, ctxt, fio, rcmd, wcmd, ccmd);
+ apid := <-sync;
+ ctxt.set("apid", ref Listnode(nil, string apid) :: nil);
+ if (ctxt.options() & ctxt.INTERACTIVE)
+ sys->fprint(sys->fildes(2), "%d\n", apid);
+ return nil;
+}
+
+srv(sync: chan of int, ctxt: ref Context,
+ fio: ref Sys->FileIO, rcmd, wcmd, ccmd: ref Listnode)
+{
+ ctxt = ctxt.copy(1);
+ sync <-= sys->pctl(0, nil);
+ for (;;) {
+ fid, offset, count: int;
+ rc: Sys->Rread;
+ wc: Sys->Rwrite;
+ d: array of byte;
+ t: ref Tag = nil;
+ cmd: ref Listnode = nil;
+ alt {
+ (offset, count, fid, rc) = <-fio.read =>
+ if (rc != nil) {
+ t = ref Tag.Read(0, 0, offset, fid, count, rc);
+ cmd = rcmd;
+ } else
+ continue; # we get a close on both read and write...
+ (offset, d, fid, wc) = <-fio.write =>
+ if (wc != nil) {
+ t = ref Tag.Write(0, 0, offset, fid, d, wc);
+ cmd = wcmd;
+ }
+ }
+ if (t != nil) {
+ addtag(t);
+ ctxt.setlocal("tag", ref Listnode(nil, string t.tagid) :: nil);
+ ctxt.run(cmd :: nil, 0);
+ taglock.obtain();
+ # make a default reply if it hasn't been deliberately blocked.
+ del := 0;
+ if (t.tagid >= 0 && !t.blocked) {
+ pick mt := t {
+ Read =>
+ rreply(mt.rc, nil, "invalid read");
+ Write =>
+ wreply(mt.wc, len mt.data, nil);
+ }
+ del = 1;
+ }
+ taglock.release();
+ if (del)
+ deltag(t.tagid);
+ ctxt.setlocal("tag", nil);
+ } else if (ccmd != nil) {
+ t = ref Tag.Read(0, 0, -1, fid, -1, nil);
+ addtag(t);
+ ctxt.setlocal("tag", ref Listnode(nil, string t.tagid) :: nil);
+ ctxt.run(ccmd :: nil, 0);
+ deltag(t.tagid);
+ ctxt.setlocal("tag", nil);
+ }
+ }
+}
+
+builtin_rread(ctxt: ref Context, argv: list of ref Listnode, one: int): string
+{
+ n := len argv;
+ if (n < 2 || n > 3)
+ ctxt.fail("usage", "usage: "+(hd argv).word+" [tag] data");
+ argv = tl argv;
+
+ t := envgettag(ctxt, argv, n == 3);
+ if (t == nil)
+ ctxt.fail("bad tag", "rread: cannot find tag");
+ if (n == 3)
+ argv = tl argv;
+ mt := etr(ctxt, "rread", t);
+ arg := word(hd argv);
+ d := array of byte arg;
+ if (one) {
+ if (mt.offset >= len d)
+ d = nil;
+ else
+ d = d[mt.offset:];
+ }
+ if (len d > mt.count)
+ d = d[0:mt.count];
+ rreply(mt.rc, d, nil);
+ deltag(t.tagid);
+ return nil;
+}
+
+builtin_rwrite(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ n := len argv;
+ if (n > 3)
+ ctxt.fail("usage", "usage: rwrite [tag [count]]");
+ t := envgettag(ctxt, tl argv, n > 1);
+ if (t == nil)
+ ctxt.fail("bad tag", "rwrite: cannot find tag");
+
+ mt := etw(ctxt, "rwrite", t);
+ count := len mt.data;
+ if (n == 3) {
+ arg := word(hd tl argv);
+ if (!isnum(arg))
+ ctxt.fail("usage", "usage: freply [tag [count]]");
+ count = int arg;
+ }
+ wreply(mt.wc, count, nil);
+ deltag(t.tagid);
+ return nil;
+}
+
+builtin_rblock(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ argv = tl argv;
+ if (len argv > 1)
+ ctxt.fail("usage", "usage: rblock [tag]");
+ t := envgettag(ctxt, argv, argv != nil);
+ if (t == nil)
+ ctxt.fail("bad tag", "rblock: cannot find tag");
+ t.blocked = 1;
+ return nil;
+}
+
+sbuiltin_rget(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ n := len argv;
+ if (n < 2 || n > 3)
+ ctxt.fail("usage", "usage: rget (data|count|offset|fid) [tag]");
+ argv = tl argv;
+ t := envgettag(ctxt, tl argv, tl argv != nil);
+ if (t == nil)
+ ctxt.fail("bad tag", "rget: cannot find tag");
+ s := "";
+ case (hd argv).word {
+ "data" =>
+ s = string etw(ctxt, "rget", t).data;
+ "count" =>
+ s = string etr(ctxt, "rget", t).count;
+ "offset" =>
+ s = string t.offset;
+ "fid" =>
+ s = string t.fid;
+ * =>
+ ctxt.fail("usage", "usage: rget (data|count|offset|fid) [tag]");
+ }
+
+ return ref Listnode(nil, s) :: nil;
+}
+
+builtin_fetchwdata(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ argv = tl argv;
+ if (len argv > 1)
+ ctxt.fail("usage", "usage: fetchwdata [tag]");
+ t := envgettag(ctxt, argv, argv != nil);
+ if (t == nil)
+ ctxt.fail("bad tag", "fetchwdata: cannot find tag");
+ d := etw(ctxt, "fetchwdata", t).data;
+ sys->write(sys->fildes(1), d, len d);
+ return nil;
+}
+
+builtin_putrdata(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ argv = tl argv;
+ if (len argv > 1)
+ ctxt.fail("usage", "usage: putrdata [tag]");
+ t := envgettag(ctxt, argv, argv != nil);
+ if (t == nil)
+ ctxt.fail("bad tag", "putrdata: cannot find tag");
+ mt := etr(ctxt, "putrdata", t);
+ buf := array[mt.count] of byte;
+ n := 0;
+ fd := sys->fildes(0);
+ while (n < mt.count) {
+ nr := sys->read(fd, buf[n:mt.count], mt.count - n);
+ if (nr <= 0)
+ break;
+ n += nr;
+ }
+
+ rreply(mt.rc, buf[0:n], nil);
+ deltag(t.tagid);
+ return nil;
+}
+
+builtin_rerror(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ # usage: ferror [tag] error
+ n := len argv;
+ if (n < 2 || n > 3)
+ ctxt.fail("usage", "usage: ferror [tag] error");
+ t := envgettag(ctxt, tl argv, n == 3);
+ if (t == nil)
+ ctxt.fail("bad tag", "rerror: cannot find tag");
+ if (n == 3)
+ argv = tl argv;
+ err := word(hd tl argv);
+ pick mt := t {
+ Read =>
+ rreply(mt.rc, nil, err);
+ Write =>
+ wreply(mt.wc, 0, err);
+ }
+ deltag(t.tagid);
+ return nil;
+}
+
+envgettag(ctxt: ref Context, args: list of ref Listnode, useargs: int): ref Tag
+{
+ tagid: int;
+ if (useargs)
+ tagid = int (hd args).word;
+ else {
+ args = ctxt.get("tag");
+ if (args == nil || tl args != nil)
+ return nil;
+ tagid = int (hd args).word;
+ }
+ return gettag(tagid);
+}
+
+etw(ctxt: ref Context, cmd: string, t: ref Tag): ref Tag.Write
+{
+ pick mt := t {
+ Write => return mt;
+ }
+ ctxt.fail("bad tag", cmd + ": inappropriate tag id");
+ return nil;
+}
+
+etr(ctxt: ref Context, cmd: string, t: ref Tag): ref Tag.Read
+{
+ pick mt := t {
+ Read => return mt;
+ }
+ ctxt.fail("bad tag", cmd + ": inappropriate tag id");
+ return nil;
+}
+
+wreply(wc: chan of (int, string), count: int, err: string)
+{
+ alt {
+ wc <-= (count, err) => ;
+ * => ;
+ }
+}
+
+rreply(rc: chan of (array of byte, string), d: array of byte, err: string)
+{
+ alt {
+ rc <-= (d, err) => ;
+ * => ;
+ }
+}
+
+word(n: ref Listnode): string
+{
+ if (n.word != nil)
+ return n.word;
+ if (n.cmd != nil)
+ n.word = sh->cmd2string(n.cmd);
+ return n.word;
+}
+
+isnum(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] > '9' || s[i] < '0')
+ return 0;
+ return 1;
+}
+
+iscmd(n: ref Listnode): int
+{
+ return n.cmd != nil || (n.word != nil && n.word[0] == '}');
+}
+
+addtag(t: ref Tag)
+{
+ taglock.obtain();
+ t.tagid = maxtagid++;
+ slot := t.tagid % len tags;
+ tags[slot] = t :: tags[slot];
+ taglock.release();
+}
+
+deltag(tagid: int)
+{
+ taglock.obtain();
+ slot := tagid % len tags;
+ nwl: list of ref Tag;
+ for (wl := tags[slot]; wl != nil; wl = tl wl)
+ if ((hd wl).tagid != tagid)
+ nwl = hd wl :: nwl;
+ else
+ (hd wl).tagid = -1;
+ tags[slot] = nwl;
+ taglock.release();
+}
+
+gettag(tagid: int): ref Tag
+{
+ slot := tagid % len tags;
+ for (wl := tags[slot]; wl != nil; wl = tl wl)
+ if ((hd wl).tagid == tagid)
+ return hd wl;
+ return nil;
+}
+
+pathsplit(p: string): (string, string)
+{
+ for (i := len p - 1; i >= 0; i--)
+ if (p[i] != '/')
+ break;
+ if (i < 0)
+ return (p, nil);
+ p = p[0:i+1];
+ for (i = len p - 1; i >=0; i--)
+ if (p[i] == '/')
+ break;
+ if (i < 0)
+ return (".", p);
+ return (p[0:i+1], p[i+1:]);
+}
+
+reporterror(ctxt: ref Context, err: string)
+{
+ if (ctxt.options() & ctxt.VERBOSE)
+ sys->fprint(sys->fildes(2), "%s\n", err);
+}
diff --git a/appl/cmd/sh/mkfile b/appl/cmd/sh/mkfile
new file mode 100644
index 00000000..383c5ed9
--- /dev/null
+++ b/appl/cmd/sh/mkfile
@@ -0,0 +1,60 @@
+<../../../mkconfig
+
+TARG=sh.dis\
+ arg.dis\
+ expr.dis\
+ file2chan.dis\
+ regex.dis\
+ sexprs.dis\
+ std.dis\
+ string.dis\
+ tk.dis\
+ echo.dis\
+ csv.dis\
+ test.dis\
+
+INS= $ROOT/dis/sh.dis\
+ $ROOT/dis/sh/arg.dis\
+ $ROOT/dis/sh/expr.dis\
+ $ROOT/dis/sh/regex.dis\
+ $ROOT/dis/sh/std.dis\
+ $ROOT/dis/sh/string.dis\
+# $ROOT/dis/sh/tk.dis\
+ $ROOT/dis/sh/echo.dis\
+ $ROOT/dis/sh/csv.dis\
+ $ROOT/dis/sh/test.dis\
+
+SYSMODULES=\
+ bufio.m\
+ draw.m\
+ env.m\
+ filepat.m\
+ lock.m\
+ sexprs.m\
+ sh.m\
+ string.m\
+ sys.m\
+ tk.m\
+ tkclient.m\
+
+DISBIN=$ROOT/dis/sh
+
+<$ROOT/mkfiles/mkdis
+
+all:V: $TARG
+
+install:V: $INS
+
+nuke:V: clean
+ rm -f $INS
+
+clean:V:
+ rm -f *.dis *.sbl
+
+uninstall:V:
+ rm -f $INS
+
+$ROOT/dis/sh.dis: sh.dis
+ rm -f $ROOT/dis/sh.dis && cp sh.dis $ROOT/dis/sh.dis
+
+%.dis: ${SYSMODULES:%=$MODDIR/%}
diff --git a/appl/cmd/sh/regex.b b/appl/cmd/sh/regex.b
new file mode 100644
index 00000000..e761a8ba
--- /dev/null
+++ b/appl/cmd/sh/regex.b
@@ -0,0 +1,220 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+include "regex.m";
+ regex: Regex;
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("regex: cannot load self: %r"));
+ regex = load Regex Regex->PATH;
+ if (regex == nil)
+ ctxt.fail("bad module",
+ sys->sprint("regex: cannot load %s: %r", Regex->PATH));
+ ctxt.addbuiltin("match", myself);
+ ctxt.addsbuiltin("re", myself);
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(ctxt: ref Context, nil: Sh,
+ argv: list of ref Listnode, nil: int): string
+{
+ case (hd argv).word {
+ "match" =>
+ return builtin_match(ctxt, argv);
+ }
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+runsbuiltin(ctxt: ref Context, nil: Sh,
+ argv: list of ref Listnode): list of ref Listnode
+{
+ name := (hd argv).word;
+ case name {
+ "re" =>
+ return sbuiltin_re(ctxt, argv);
+ }
+ return nil;
+}
+
+sbuiltin_re(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ if (tl argv == nil)
+ ctxt.fail("usage", "usage: re (g|v|s|sg|m|mg|M) arg...");
+ argv = tl argv;
+ w := (hd argv).word;
+ case w {
+ "g" or
+ "v" =>
+ return sbuiltin_sel(ctxt, argv, w == "v");
+ "s" or
+ "sg" =>
+ return sbuiltin_sub(ctxt, argv, w == "sg");
+ "m" =>
+ return sbuiltin_match(ctxt, argv, 0);
+ "mg" =>
+ return sbuiltin_gmatch(ctxt, argv);
+ "M" =>
+ return sbuiltin_match(ctxt, argv, 1);
+ * =>
+ ctxt.fail("usage", "usage: re (g|v|s|sg|m|mg|M) arg...");
+ return nil;
+ }
+}
+
+sbuiltin_match(ctxt: ref Context, argv: list of ref Listnode, aflag: int): list of ref Listnode
+{
+ if (len argv != 3)
+ ctxt.fail("usage", "usage: re " + (hd argv).word + " arg");
+ argv = tl argv;
+ re := getregex(ctxt, word(hd argv), aflag);
+ w := word(hd tl argv);
+ a := regex->execute(re, w);
+ if (a == nil)
+ return nil;
+ ret: list of ref Listnode;
+ for (i := len a - 1; i >= 0; i--)
+ ret = ref Listnode(nil, elem(a, i, w)) :: ret;
+ return ret;
+}
+
+sbuiltin_gmatch(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ if (len argv != 3)
+ ctxt.fail("usage", "usage: re mg arg");
+ argv = tl argv;
+ re := getregex(ctxt, word(hd argv), 0);
+ w := word(hd tl argv);
+ ret, nret: list of ref Listnode;
+ beg := 0;
+ while ((a := regex->executese(re, w, (beg, len w), beg == 0, 1)) != nil) {
+ (s, e) := a[0];
+ ret = ref Listnode(nil, w[s:e]) :: ret;
+ if (s == e)
+ break;
+ beg = e;
+ }
+ for (; ret != nil; ret = tl ret)
+ nret = hd ret :: nret;
+ return nret;
+}
+
+sbuiltin_sel(ctxt: ref Context, argv: list of ref Listnode, vflag: int): list of ref Listnode
+{
+ cmd := (hd argv).word;
+ argv = tl argv;
+ if (argv == nil)
+ ctxt.fail("usage", "usage: " + cmd + " regex [arg...]");
+ re := getregex(ctxt, word(hd argv), 0);
+ ret, nret: list of ref Listnode;
+ for (argv = tl argv; argv != nil; argv = tl argv)
+ if (vflag ^ (regex->execute(re, word(hd argv)) != nil))
+ ret = hd argv :: ret;
+ for (; ret != nil; ret = tl ret)
+ nret = hd ret :: nret;
+ return nret;
+}
+
+sbuiltin_sub(ctxt: ref Context, argv: list of ref Listnode, gflag: int): list of ref Listnode
+{
+ cmd := (hd argv).word;
+ argv = tl argv;
+ if (argv == nil || tl argv == nil)
+ ctxt.fail("usage", "usage: " + cmd + " regex subs [arg...]");
+ re := getregex(ctxt, word(hd argv), 1);
+ subs := word(hd tl argv);
+ ret, nret: list of ref Listnode;
+ for (argv = tl tl argv; argv != nil; argv = tl argv)
+ ret = ref Listnode(nil, substitute(word(hd argv), re, subs, gflag).t1) :: ret;
+ for (; ret != nil; ret = tl ret)
+ nret = hd ret :: nret;
+ return nret;
+}
+
+builtin_match(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ if (tl argv == nil)
+ ctxt.fail("usage", "usage: match regexp [arg...]");
+ re := getregex(ctxt, word(hd tl argv), 0);
+ for (argv = tl tl argv; argv != nil; argv = tl argv)
+ if (regex->execute(re, word(hd argv)) == nil)
+ return "no match";
+ return nil;
+}
+
+substitute(w: string, re: Regex->Re, subs: string, gflag: int): (int, string)
+{
+ matched := 0;
+ s := "";
+ beg := 0;
+ do {
+ a := regex->executese(re, w, (beg, len w), beg == 0, 1);
+ if (a == nil)
+ break;
+ matched = 1;
+ s += w[beg:a[0].t0];
+ for (i := 0; i < len subs; i++) {
+ if (subs[i] != '\\' || i == len subs - 1)
+ s[len s] = subs[i];
+ else {
+ c := subs[++i];
+ if (c < '0' || c > '9')
+ s[len s] = c;
+ else
+ s += elem(a, c - '0', w);
+ }
+ }
+ beg = a[0].t1;
+ if (a[0].t0 == a[0].t1)
+ break;
+ } while (gflag && beg < len w);
+ return (matched, s + w[beg:]);
+}
+
+elem(a: array of (int, int), i: int, w: string): string
+{
+ if (i < 0 || i >= len a)
+ return nil; # XXX could raise failure here. (invalid backslash escape)
+ (s, e) := a[i];
+ if (s == -1)
+ return nil;
+ return w[s:e];
+}
+
+# XXX could do regex caching here if it was worth it.
+getregex(ctxt: ref Context, res: string, flag: int): Regex->Re
+{
+ (re, err) := regex->compile(res, flag);
+ if (re == nil)
+ ctxt.fail("bad regex", "regex: bad regex \"" + res + "\": " + err);
+ return re;
+}
+
+word(n: ref Listnode): string
+{
+ if (n.word != nil)
+ return n.word;
+ if (n.cmd != nil)
+ n.word = sh->cmd2string(n.cmd);
+ return n.word;
+}
diff --git a/appl/cmd/sh/sexprs.b b/appl/cmd/sh/sexprs.b
new file mode 100644
index 00000000..1908078a
--- /dev/null
+++ b/appl/cmd/sh/sexprs.b
@@ -0,0 +1,271 @@
+implement Shellbuiltin;
+
+# parse/generate sexprs.
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "sexprs.m";
+ sexprs: Sexprs;
+ Sexp: import sexprs;
+
+# getsexprs cmd
+# islist val
+# ${els se}
+# ${text se}
+# ${textels se}
+
+# ${mktext val}
+# ${mklist [val...]}
+# ${mktextlist [val...]}
+
+Maxerrs: con 10;
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("sexpr: cannot load self: %r"));
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ ctxt.fail("bad module", sys->sprint("sexpr: cannot load: %s: %r", Bufio->PATH));
+ sexprs = load Sexprs Sexprs->PATH;
+ if(sexprs == nil)
+ ctxt.fail("bad module", sys->sprint("sexpr: cannot load: %s: %r", Sexprs->PATH));
+ sexprs->init();
+ ctxt.addbuiltin("getsexprs", myself);
+ ctxt.addbuiltin("islist", myself);
+ ctxt.addsbuiltin("els", myself);
+ ctxt.addsbuiltin("text", myself);
+ ctxt.addsbuiltin("b64", myself);
+ ctxt.addsbuiltin("textels", myself);
+ ctxt.addsbuiltin("mktext", myself);
+ ctxt.addsbuiltin("mklist", myself);
+ ctxt.addsbuiltin("mktextlist", myself);
+
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(c: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode, nil: int): string
+{
+ case (hd cmd).word {
+ "getsexprs" =>
+ return builtin_getsexprs(c, tl cmd);
+ "islist" =>
+ return builtin_islist(c, tl cmd);
+ }
+ return nil;
+}
+
+runsbuiltin(c: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode): list of ref Listnode
+{
+ case (hd cmd).word {
+ "els" =>
+ return sbuiltin_els(c, tl cmd);
+ "text" =>
+ return sbuiltin_text(c, tl cmd);
+ "b64" =>
+ return sbuiltin_b64(c, tl cmd);
+ "textels" =>
+ return sbuiltin_textels(c, tl cmd);
+ "mktext" =>
+ return sbuiltin_mktext(c, tl cmd);
+ "mklist" =>
+ return sbuiltin_mklist(c, tl cmd);
+ "mktextlist" =>
+ return sbuiltin_mktextlist(c, tl cmd);
+ }
+ return nil;
+}
+
+builtin_getsexprs(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ n := len argv;
+ if (n != 1 || !iscmd(hd argv))
+ builtinusage(ctxt, "getsexprs {cmd}");
+ cmd := hd argv :: ctxt.get("*");
+ stdin := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ if (stdin == nil)
+ ctxt.fail("bad input", sys->sprint("getsexprs: cannot open stdin: %r"));
+ status := "";
+ nerrs := 0;
+ ctxt.push();
+ for(;;){
+ {
+ for (;;) {
+ (se, err) := Sexp.read(stdin);
+ if(err != nil){
+ sys->fprint(sys->fildes(2), "getsexprs: error on read: %s\n", err);
+ if(++nerrs > Maxerrs)
+ raise "fail:too many errors";
+ continue;
+ }
+ if(se == nil)
+ break;
+ nerrs = 0;
+ ctxt.setlocal("sexp", ref Listnode(nil, se.text()) :: nil);
+ status = setstatus(ctxt, ctxt.run(cmd, 0));
+ }
+ ctxt.pop();
+ return status;
+ }exception e{
+ "fail:*" =>
+ ctxt.pop();
+ if (loopexcept(e) == BREAK)
+ return status;
+ ctxt.push();
+ }
+ }
+}
+
+builtin_islist(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ if(argv == nil || tl argv != nil)
+ builtinusage(ctxt, "islist sexp");
+ w := word(hd argv);
+ if(w != nil && w[0] =='(')
+ return nil;
+ if(parse(ctxt, hd argv).islist())
+ return nil;
+ return "not a list";
+}
+
+CONTINUE, BREAK: con iota;
+loopexcept(ename: string): int
+{
+ case ename[5:] {
+ "break" =>
+ return BREAK;
+ "continue" =>
+ return CONTINUE;
+ * =>
+ raise ename;
+ }
+ return 0;
+}
+
+iscmd(n: ref Listnode): int
+{
+ return n.cmd != nil || (n.word != nil && n.word[0] == '{');
+}
+
+builtinusage(ctxt: ref Context, s: string)
+{
+ ctxt.fail("usage", "usage: " + s);
+}
+
+setstatus(ctxt: ref Context, val: string): string
+{
+ ctxt.setlocal("status", ref Listnode(nil, val) :: nil);
+ return val;
+}
+
+sbuiltin_els(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode
+{
+ if (val == nil || tl val != nil)
+ builtinusage(ctxt, "els sexp");
+ r, rr: list of ref Listnode;
+ for(els := parse(ctxt, hd val).els(); els != nil; els = tl els)
+ r = ref Listnode(nil, (hd els).text()) :: r;
+ for(; r != nil; r = tl r)
+ rr = hd r :: rr;
+ return rr;
+}
+
+sbuiltin_text(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode
+{
+ if(val == nil || tl val != nil)
+ builtinusage(ctxt, "text sexp");
+ return ref Listnode(nil, parse(ctxt, hd val).astext()) :: nil;
+}
+
+sbuiltin_b64(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode
+{
+ if(val == nil || tl val != nil)
+ builtinusage(ctxt, "b64 sexp");
+ return ref Listnode(nil, parse(ctxt, hd val).b64text()) :: nil;
+}
+
+sbuiltin_textels(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode
+{
+ if (val == nil || tl val != nil)
+ builtinusage(ctxt, "textels sexp");
+ r, rr: list of ref Listnode;
+ for(els := parse(ctxt, hd val).els(); els != nil; els = tl els)
+ r = ref Listnode(nil, (hd els).astext()) :: r;
+ for(; r != nil; r = tl r)
+ rr = hd r :: rr;
+ return rr;
+}
+
+sbuiltin_mktext(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode
+{
+ if (val == nil || tl val != nil)
+ builtinusage(ctxt, "mktext sexp");
+ return ref Listnode(nil, (ref Sexp.String(word(hd val), nil)).text()) :: nil;
+}
+
+sbuiltin_mklist(nil: ref Context, val: list of ref Listnode): list of ref Listnode
+{
+ if(val == nil)
+ return ref Listnode(nil, "()") :: nil;
+ s := "(" + word(hd val);
+ for(val = tl val; val != nil; val = tl val)
+ s += " " + word(hd val);
+ s[len s] = ')';
+ return ref Listnode(nil, s) :: nil;
+}
+
+sbuiltin_mktextlist(nil: ref Context, val: list of ref Listnode): list of ref Listnode
+{
+ if(val == nil)
+ return ref Listnode(nil, "()") :: nil;
+ s := "(" + (ref Sexp.String(word(hd val), nil)).text();
+ for(val = tl val; val != nil; val = tl val)
+ s += " " + (ref Sexp.String(word(hd val), nil)).text();
+ s[len s] = ')';
+ return ref Listnode(nil, s) :: nil;
+}
+
+parse(ctxt: ref Context, val: ref Listnode): ref Sexp
+{
+ (se, rest, err) := Sexp.parse(word(val));
+ if(rest != nil){
+ for(i := 0; i < len rest; i++)
+ if(rest[i] != ' ' && rest[i] != '\t' && rest[i] != '\n')
+ ctxt.fail("bad sexp", sys->sprint("extra text found at end of s-expression %#q", word(val)));
+ }
+ if(err != nil)
+ ctxt.fail("bad sexp", err);
+ return se;
+}
+
+word(n: ref Listnode): string
+{
+ if (n.word != nil)
+ return n.word;
+ if (n.cmd != nil)
+ n.word = sh->cmd2string(n.cmd);
+ return n.word;
+}
diff --git a/appl/cmd/sh/sh.b b/appl/cmd/sh/sh.b
new file mode 100644
index 00000000..6040457f
--- /dev/null
+++ b/appl/cmd/sh/sh.b
@@ -0,0 +1,2843 @@
+implement Sh;
+
+include "sys.m";
+ sys: Sys;
+ sprint: import sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+include "string.m";
+ str: String;
+include "filepat.m";
+ filepat: Filepat;
+include "env.m";
+ env: Env;
+include "sh.m";
+ myself: Sh;
+ myselfbuiltin: Shellbuiltin;
+
+YYSTYPE: adt {
+ node: ref Node;
+ word: string;
+
+ redir: ref Redir;
+ optype: int;
+};
+
+YYLEX: adt {
+ lval: YYSTYPE;
+ err: string; # if error has occurred
+ errline: int; # line it occurred on.
+ path: string; # name of file that's being read.
+
+ # free caret state
+ wasdollar: int;
+ atendword: int;
+ eof: int;
+ cbuf: array of int; # last chars read
+ ncbuf: int; # number of chars in cbuf
+
+ f: ref Bufio->Iobuf;
+ s: string;
+ strpos: int; # string pos/cbuf index
+
+ linenum: int;
+ prompt: string;
+ lastnl: int;
+
+ initstring: fn(s: string): ref YYLEX;
+ initfile: fn(fd: ref Sys->FD, path: string): ref YYLEX;
+ lex: fn(l: self ref YYLEX): int;
+ error: fn(l: self ref YYLEX, err: string);
+ getc: fn(l: self ref YYLEX): int;
+ ungetc: fn(l: self ref YYLEX);
+
+ EOF: con -1;
+};
+
+Options: adt {
+ lflag,
+ nflag: int;
+ ctxtflags: int;
+ carg: string;
+};
+
+
+ # module definition is in shell.m
+DUP: con 57346;
+REDIR: con 57347;
+WORD: con 57348;
+OP: con 57349;
+END: con 57350;
+ERROR: con 57351;
+ANDAND: con 57352;
+OROR: con 57353;
+YYEOFCODE: con 1;
+YYERRCODE: con 2;
+YYMAXDEPTH: con 200;
+
+
+
+EPERM: con "permission denied";
+EPIPE: con "write on closed pipe";
+
+SHELLRC: con "lib/profile";
+LIBSHELLRC: con "/lib/sh/profile";
+BUILTINPATH: con "/dis/sh";
+
+DEBUG: con 0;
+
+ENVSEP: con 0; # word seperator in external environment
+ENVHASHSIZE: con 7; # XXX profile usage of this...
+OAPPEND: con 16r80000; # make sure this doesn't clash with O* constants in sys.m
+OMASK: con 7;
+
+usage()
+{
+ sys->fprint(stderr(), "usage: sh [-ilexn] [-c command] [file [arg...]]\n");
+ raise "fail:usage";
+}
+
+badmodule(path: string)
+{
+ sys->fprint(sys->fildes(2), "sh: cannot load %s: %r\n", path);
+ raise "fail:bad module" ;
+}
+
+initialise()
+{
+ if (sys == nil) {
+ sys = load Sys Sys->PATH;
+
+ filepat = load Filepat Filepat->PATH;
+ if (filepat == nil) badmodule(Filepat->PATH);
+
+ str = load String String->PATH;
+ if (str == nil) badmodule(String->PATH);
+
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) badmodule(Bufio->PATH);
+
+ myself = load Sh "$self";
+ if (myself == nil) badmodule("$self(Sh)");
+
+ myselfbuiltin = load Shellbuiltin "$self";
+ if (myselfbuiltin == nil) badmodule("$self(Shellbuiltin)");
+
+ env = load Env Env->PATH;
+ }
+}
+blankopts: Options;
+init(drawcontext: ref Draw->Context, argv: list of string)
+{
+ initialise();
+ opts := blankopts;
+ if (argv != nil) {
+ if ((hd argv)[0] == '-')
+ opts.lflag++;
+ argv = tl argv;
+ }
+
+ interactive := 0;
+loop: while (argv != nil && hd argv != nil && (hd argv)[0] == '-') {
+ for (i := 1; i < len hd argv; i++) {
+ c := (hd argv)[i];
+ case c {
+ 'i' =>
+ interactive = Context.INTERACTIVE;
+ 'l' =>
+ opts.lflag++; # login (read $home/lib/profile)
+ 'n' =>
+ opts.nflag++; # don't fork namespace
+ 'e' =>
+ opts.ctxtflags |= Context.ERROREXIT;
+ 'x' =>
+ opts.ctxtflags |= Context.EXECPRINT;
+ 'c' =>
+ arg: string;
+ if (i < len hd argv - 1) {
+ arg = (hd argv)[i + 1:];
+ } else if (tl argv == nil || hd tl argv == "") {
+ usage();
+ } else {
+ arg = hd tl argv;
+ argv = tl argv;
+ }
+ argv = tl argv;
+ opts.carg = arg;
+ continue loop;
+ }
+ }
+ argv = tl argv;
+ }
+
+ sys->pctl(Sys->FORKFD, nil);
+ if (!opts.nflag)
+ sys->pctl(Sys->FORKNS, nil);
+ ctxt := Context.new(drawcontext);
+ ctxt.setoptions(opts.ctxtflags, 1);
+ if (opts.carg != nil) {
+ status := ctxt.run(stringlist2list("{" + opts.carg + "}" :: argv), !interactive);
+ if (!interactive) {
+ if (status != nil)
+ raise "fail:" + status;
+ exit;
+ }
+ setstatus(ctxt, status);
+ }
+
+ # if login shell, run standard init script
+ if (opts.lflag)
+ runscript(ctxt, LIBSHELLRC, nil, 0);
+
+ if (argv == nil) {
+ if (opts.lflag)
+ runscript(ctxt, SHELLRC, nil, 0);
+ if (isconsole(sys->fildes(0)))
+ interactive |= ctxt.INTERACTIVE;
+ ctxt.setoptions(interactive, 1);
+ runfile(ctxt, sys->fildes(0), "stdin", nil);
+ } else {
+ ctxt.setoptions(interactive, 1);
+ runscript(ctxt, hd argv, stringlist2list(tl argv), 1);
+ }
+}
+
+parse(s: string): (ref Node, string)
+{
+ initialise();
+
+ lex := YYLEX.initstring(s);
+
+ return doparse(lex, "", 0);
+}
+
+system(drawctxt: ref Draw->Context, cmd: string): string
+{
+ initialise();
+ {
+ (n, err) := parse(cmd);
+ if (err != nil)
+ return err;
+ if (n == nil)
+ return nil;
+ return Context.new(drawctxt).run(ref Listnode(n, nil) :: nil, 0);
+ } exception e {
+ "fail:*" =>
+ return e[5:];
+ }
+}
+
+run(drawctxt: ref Draw->Context, argv: list of string): string
+{
+ initialise();
+ {
+ return Context.new(drawctxt).run(stringlist2list(argv), 0);
+ } exception e {
+ "fail:*" =>
+ return e[5:];
+ }
+}
+
+isconsole(fd: ref Sys->FD): int
+{
+ (ok1, d1) := sys->fstat(fd);
+ (ok2, d2) := sys->stat("/dev/cons");
+ if (ok1 < 0 || ok2 < 0)
+ return 0;
+ return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path;
+}
+
+runscript(ctxt: ref Context, path: string, args: list of ref Listnode, reporterr: int)
+{
+ {
+ fd := sys->open(path, Sys->OREAD);
+ if (fd != nil)
+ runfile(ctxt, fd, path, args);
+ else if (reporterr)
+ ctxt.fail("bad script path", sys->sprint("sh: cannot open %s: %r", path));
+ } exception e {
+ "fail:*" =>
+ if(!reporterr)
+ return;
+ raise;
+ }
+}
+
+runfile(ctxt: ref Context, fd: ref Sys->FD, path: string, args: list of ref Listnode)
+{
+ ctxt.push();
+ {
+ ctxt.setlocal("0", stringlist2list(path :: nil));
+ ctxt.setlocal("*", args);
+ lex := YYLEX.initfile(fd, path);
+ if (DEBUG) debug(sprint("parse(interactive == %d)", (ctxt.options() & ctxt.INTERACTIVE) != 0));
+ prompt := "" :: "" :: nil;
+ laststatus: string;
+ while (!lex.eof) {
+ interactive := ctxt.options() & ctxt.INTERACTIVE;
+ if (interactive) {
+ prompt = list2stringlist(ctxt.get("prompt"));
+ if (prompt == nil)
+ prompt = "; " :: "" :: nil;
+
+ sys->fprint(stderr(), "%s", hd prompt);
+ if (tl prompt == nil) {
+ prompt = hd prompt :: "" :: nil;
+ }
+ }
+ (n, err) := doparse(lex, hd tl prompt, !interactive);
+ if (err != nil) {
+ sys->fprint(stderr(), "sh: %s\n", err);
+ if (!interactive)
+ raise "fail:parse error";
+ } else if (n != nil) {
+ if (interactive) {
+ {
+ laststatus = walk(ctxt, n, 0);
+ } exception e2 {
+ "fail:*" =>
+ laststatus = e2[5:];
+ }
+ } else
+ laststatus = walk(ctxt, n, 0);
+ setstatus(ctxt, laststatus);
+ if ((ctxt.options() & ctxt.ERROREXIT) && laststatus != nil)
+ break;
+ }
+ }
+ if (laststatus != nil)
+ raise "fail:" + laststatus;
+ ctxt.pop();
+ }
+ exception e {
+ "fail:*" =>
+ ctxt.pop();
+ raise;
+ }
+}
+
+nonexistent(e: string): int
+{
+ errs := array[] of {"does not exist", "directory entry not found"};
+ for (i := 0; i < len errs; i++){
+ j := len errs[i];
+ if (j <= len e && e[len e-j:] == errs[i])
+ return 1;
+ }
+ return 0;
+}
+
+Redirword: adt {
+ fd: ref Sys->FD;
+ w: string;
+ r: Redir;
+};
+
+Redirlist: adt {
+ r: list of Redirword;
+};
+
+pipe2cmd(n: ref Node): ref Node
+{
+ if (n == nil || n.ntype != n_PIPE)
+ return n;
+ return mk(n_ADJ, mk(n_BLOCK,n,nil), mk(n_VAR,ref Node(n_WORD,nil,nil,"*",nil),nil));
+}
+
+walk(ctxt: ref Context, n: ref Node, last: int): string
+{
+ if (DEBUG) debug(sprint("walking: %s", cmd2string(n)));
+ # avoid tail recursion stack explosion
+ while (n != nil && n.ntype == n_SEQ) {
+ status := walk(ctxt, n.left, 0);
+ if (ctxt.options() & ctxt.ERROREXIT && status != nil)
+ raise "fail:" + status;
+ setstatus(ctxt, status);
+ n = n.right;
+ }
+ if (n == nil)
+ return nil;
+ case (n.ntype) {
+ n_PIPE =>
+ return waitfor(ctxt, walkpipeline(ctxt, n, nil, -1));
+ n_ASSIGN or n_LOCAL =>
+ assign(ctxt, n);
+ return nil;
+ * =>
+ bg := 0;
+ if (n.ntype == n_NOWAIT) {
+ bg = 1;
+ n = pipe2cmd(n.left);
+ }
+
+ redirs := ref Redirlist(nil);
+ line := glob(glom(ctxt, n, redirs, nil));
+
+ if (bg) {
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 1, line, redirs, startchan);
+ (pid, nil) := <-startchan;
+ redirs = nil;
+ if (DEBUG) debug("started background process "+ string pid);
+ ctxt.set("apid", ref Listnode(nil, string pid) :: nil);
+ return nil;
+ } else {
+ return runsync(ctxt, line, redirs, last);
+ }
+ }
+}
+
+assign(ctxt: ref Context, n: ref Node): list of ref Listnode
+{
+ redirs := ref Redirlist;
+ val: list of ref Listnode;
+ if (n.right != nil && (n.right.ntype == n_ASSIGN || n.right.ntype == n_LOCAL))
+ val = assign(ctxt, n.right);
+ else
+ val = glob(glom(ctxt, n.right, redirs, nil));
+ vars := glom(ctxt, n.left, redirs, nil);
+ if (vars == nil)
+ ctxt.fail("bad assign", "sh: nil variable name");
+ if (redirs.r != nil)
+ ctxt.fail("bad assign", "sh: redirections not allowed in assignment");
+ tval := val;
+ for (; vars != nil; vars = tl vars) {
+ vname := deglob((hd vars).word);
+ if (vname == nil)
+ ctxt.fail("bad assign", "sh: bad variable name");
+ v: list of ref Listnode = nil;
+ if (tl vars == nil)
+ v = tval;
+ else if (tval != nil)
+ v = hd tval :: nil;
+ if (n.ntype == n_ASSIGN)
+ ctxt.set(vname, v);
+ else
+ ctxt.setlocal(vname, v);
+ if (tval != nil)
+ tval = tl tval;
+ }
+ return val;
+}
+
+walkpipeline(ctxt: ref Context, n: ref Node, wrpipe: ref Sys->FD, wfdno: int): list of int
+{
+ if (n == nil)
+ return nil;
+
+ fds := array[2] of ref Sys->FD;
+ pids: list of int;
+ rfdno := -1;
+ if (n.ntype == n_PIPE) {
+ if (sys->pipe(fds) == -1)
+ ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r"));
+ nwfdno := -1;
+ if (n.redir != nil) {
+ (fd1, fd2) := (n.redir.fd2, n.redir.fd1);
+ if (fd2 == -1)
+ (fd1, fd2) = (fd2, fd1);
+ (nwfdno, rfdno) = (fd2, fd1);
+ }
+ pids = walkpipeline(ctxt, n.left, fds[1], nwfdno);
+ fds[1] = nil;
+ n = n.right;
+ }
+ r := ref Redirlist(nil);
+ rlist := glob(glom(ctxt, n, r, nil));
+ if (fds[0] != nil) {
+ if (rfdno == -1)
+ rfdno = 0;
+ r.r = Redirword(fds[0], nil, Redir(Sys->OREAD, rfdno, -1)) :: r.r;
+ }
+ if (wrpipe != nil) {
+ if (wfdno == -1)
+ wfdno = 1;
+ r.r = Redirword(wrpipe, nil, Redir(Sys->OWRITE, wfdno, -1)) :: r.r;
+ }
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 1, rlist, r, startchan);
+ (pid, nil) := <-startchan;
+ if (DEBUG) debug("started pipe process "+string pid);
+ return pid :: pids;
+}
+
+makeredir(f: string, mode: int, fd: int): Redirword
+{
+ return Redirword(nil, f, Redir(mode, fd, -1));
+}
+
+glom(ctxt: ref Context, n: ref Node, redirs: ref Redirlist, onto: list of ref Listnode)
+ : list of ref Listnode
+{
+ if (n == nil) return nil;
+
+ if (n.ntype != n_ADJ)
+ return listjoin(glomoperation(ctxt, n, redirs), onto);
+
+ nlist := glom(ctxt, n.right, redirs, onto);
+
+ if (n.left.ntype != n_ADJ) {
+ # if it's a terminal node
+ nlist = listjoin(glomoperation(ctxt, n.left, redirs), nlist);
+ } else
+ nlist = glom(ctxt, n.left, redirs, nlist);
+ return nlist;
+}
+
+listjoin(left, right: list of ref Listnode): list of ref Listnode
+{
+ l: list of ref Listnode;
+ for (; left != nil; left = tl left)
+ l = hd left :: l;
+ for (; l != nil; l = tl l)
+ right = hd l :: right;
+ return right;
+}
+
+glomoperation(ctxt: ref Context, n: ref Node, redirs: ref Redirlist): list of ref Listnode
+{
+ if (n == nil)
+ return nil;
+
+ nlist: list of ref Listnode;
+ case n.ntype {
+ n_WORD =>
+ nlist = ref Listnode(nil, n.word) :: nil;
+ n_REDIR =>
+ wlist := glob(glom(ctxt, n.left, ref Redirlist(nil), nil));
+ if (len wlist != 1 || (hd wlist).word == nil)
+ ctxt.fail("bad redir", "sh: single redirection operand required");
+
+ # add to redir list
+ redirs.r = Redirword(nil, (hd wlist).word, *n.redir) :: redirs.r;
+ n_DUP =>
+ redirs.r = Redirword(nil, "", *n.redir) :: redirs.r;
+ n_LIST =>
+ nlist = glom(ctxt, n.left, redirs, nil);
+ n_CONCAT =>
+ nlist = concat(ctxt, glom(ctxt, n.left, redirs, nil), glom(ctxt, n.right, redirs, nil));
+ n_VAR or n_SQUASH or n_COUNT =>
+ arg := glom(ctxt, n.left, ref Redirlist(nil), nil);
+ if (len arg == 1 && (hd arg).cmd != nil)
+ nlist = subsbuiltin(ctxt, (hd arg).cmd.left);
+ else if (len arg != 1 || (hd arg).word == nil)
+ ctxt.fail("bad $ arg", "sh: bad variable name");
+ else
+ nlist = ctxt.get(deglob((hd arg).word));
+ case n.ntype {
+ n_VAR =>;
+ n_COUNT =>
+ nlist = ref Listnode(nil, string len nlist) :: nil;
+ n_SQUASH =>
+ # XXX could squash with first char of $ifs, perhaps
+ nlist = ref Listnode(nil, squash(list2stringlist(nlist), " ")) :: nil;
+ }
+ n_BQ or n_BQ2 =>
+ arg := glom(ctxt, n.left, ref Redirlist(nil), nil);
+ seps := "";
+ if (n.ntype == n_BQ) {
+ seps = squash(list2stringlist(ctxt.get("ifs")), "");
+ if (seps == nil)
+ seps = " \t\n\r";
+ }
+ (nlist, nil) = bq(ctxt, glob(arg), seps);
+ n_BLOCK =>
+ nlist = ref Listnode(n, "") :: nil;
+ n_ASSIGN or n_LOCAL =>
+ ctxt.fail("bad assign", "sh: assignment in invalid context");
+ * =>
+ panic("bad node type "+string n.ntype+" in glomop");
+ }
+ return nlist;
+}
+
+subsbuiltin(ctxt: ref Context, n: ref Node): list of ref Listnode
+{
+ if (n == nil || n.ntype == n_SEQ ||
+ n.ntype == n_PIPE || n.ntype == n_NOWAIT)
+ ctxt.fail("bad $ arg", "sh: invalid argument to ${} operator");
+ r := ref Redirlist;
+ cmd := glob(glom(ctxt, n, r, nil));
+ if (r.r != nil)
+ ctxt.fail("bad $ arg", "sh: redirection not allowed in substitution");
+ r = nil;
+ if (cmd == nil || (hd cmd).word == nil || (hd cmd).cmd != nil)
+ ctxt.fail("bad $ arg", "sh: bad builtin name");
+
+ (nil, bmods) := findbuiltin(ctxt.env.sbuiltins, (hd cmd).word);
+ if (bmods == nil)
+ ctxt.fail("builtin not found",
+ sys->sprint("sh: builtin %s not found", (hd cmd).word));
+ return (hd bmods)->runsbuiltin(ctxt, myself, cmd);
+}
+
+
+getbq(nil: ref Context, fd: ref Sys->FD, seps: string): list of ref Listnode
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ buflen := 0;
+ while ((n := sys->read(fd, buf[buflen:], len buf - buflen)) > 0) {
+ buflen += n;
+ if (buflen == len buf) {
+ nbuf := array[buflen * 2] of byte;
+ nbuf[0:] = buf[0:];
+ buf = nbuf;
+ }
+ }
+ l: list of string;
+ if (seps != nil)
+ (nil, l) = sys->tokenize(string buf[0:buflen], seps);
+ else
+ l = string buf[0:buflen] :: nil;
+ buf = nil;
+ return stringlist2list(l);
+}
+
+bq(ctxt: ref Context, cmd: list of ref Listnode, seps: string): (list of ref Listnode, string)
+{
+ fds := array[2] of ref Sys->FD;
+ if (sys->pipe(fds) == -1)
+ ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r"));
+
+ r := rdir(fds[1]);
+ fds[1] = nil;
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 0, cmd, r, startchan);
+ (exepid, exprop) := <-startchan;
+ r = nil;
+ bqlist := getbq(ctxt, fds[0], seps);
+ waitfor(ctxt, exepid :: nil);
+ if (exprop.name != nil)
+ raise exprop.name;
+ return (bqlist, nil);
+}
+
+rdir(fd: ref Sys->FD): ref Redirlist
+{
+ return ref Redirlist(Redirword(fd, nil, Redir(Sys->OWRITE, 1, -1)) :: nil);
+}
+
+
+concatwords(p1, p2: ref Listnode): ref Listnode
+{
+ if (p1.word == nil && p1.cmd != nil)
+ p1.word = cmd2string(p1.cmd);
+ if (p2.word == nil && p2.cmd != nil)
+ p2.word = cmd2string(p2.cmd);
+ return ref Listnode(nil, p1.word + p2.word);
+}
+
+concat(ctxt: ref Context, nl1, nl2: list of ref Listnode): list of ref Listnode
+{
+ if (nl1 == nil || nl2 == nil) {
+ if (nl1 == nil && nl2 == nil)
+ return nil;
+ ctxt.fail("bad concatenation", "sh: null list in concatenation");
+ }
+
+ ret: list of ref Listnode;
+ if (tl nl1 == nil || tl nl2 == nil) {
+ for (p1 := nl1; p1 != nil; p1 = tl p1)
+ for (p2 := nl2; p2 != nil; p2 = tl p2)
+ ret = concatwords(hd p1, hd p2) :: ret;
+ } else {
+ if (len nl1 != len nl2)
+ ctxt.fail("bad concatenation", "sh: lists of differing sizes can't be concatenated");
+ while (nl1 != nil) {
+ ret = concatwords(hd nl1, hd nl2) :: ret;
+ (nl1, nl2) = (tl nl1, tl nl2);
+ }
+ }
+ return revlist(ret);
+}
+
+Expropagate: adt {
+ name: string;
+};
+
+runasync(ctxt: ref Context, copyenv: int, argv: list of ref Listnode, redirs: ref Redirlist,
+ startchan: chan of (int, ref Expropagate))
+{
+ status: string;
+
+ pid := sys->pctl(sys->FORKFD, nil);
+ if (DEBUG) debug(sprint("in async (len redirs: %d)", len redirs.r));
+ ctxt = ctxt.copy(copyenv);
+ exprop := ref Expropagate;
+ {
+ newfdl := doredirs(ctxt, redirs);
+ redirs = nil;
+ if (newfdl != nil)
+ sys->pctl(Sys->NEWFD, newfdl);
+ # stop the old waitfd from holding the intermediate
+ # file descriptor group open.
+ ctxt.waitfd = waitfd();
+ # N.B. it's important that the sync is done here, not
+ # before doredirs, as otherwise there's some sort of
+ # race condition that leads to pipe non-completion.
+ startchan <-= (pid, exprop);
+ startchan = nil;
+ status = ctxt.run(argv, copyenv);
+ } exception e {
+ "fail:*" =>
+ exprop.name = e;
+ if (startchan != nil)
+ startchan <-= (pid, exprop);
+ raise e;
+ }
+ if (status != nil) {
+ # don't propagate bad status as an exception.
+ raise "fail:" + status;
+ }
+}
+
+runsync(ctxt: ref Context, argv: list of ref Listnode,
+ redirs: ref Redirlist, last: int): string
+{
+ if (DEBUG) debug(sys->sprint("in sync (len redirs: %d; last: %d)", len redirs.r, last));
+ if (redirs.r != nil && !last) {
+ # a new process is required to shield redirection side effects
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 0, argv, redirs, startchan);
+ (pid, exprop) := <-startchan;
+ redirs = nil;
+ r := waitfor(ctxt, pid :: nil);
+ if (exprop.name != nil)
+ raise exprop.name;
+ return r;
+ } else {
+ newfdl := doredirs(ctxt, redirs);
+ redirs = nil;
+ if (newfdl != nil)
+ sys->pctl(Sys->NEWFD, newfdl);
+ return ctxt.run(argv, last);
+ }
+}
+
+absolute(p: string): int
+{
+ if (len p < 2)
+ return 0;
+ if (p[0] == '/' || p[0] == '#')
+ return 1;
+ if (len p < 3 || p[0] != '.')
+ return 0;
+ if (p[1] == '/')
+ return 1;
+ if (p[1] == '.' && p[2] == '/')
+ return 1;
+ return 0;
+}
+
+runexternal(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ progname := (hd args).word;
+ disfile := 0;
+ if (len progname >= 4 && progname[len progname-4:] == ".dis")
+ disfile = 1;
+ pathlist: list of string;
+ if (absolute(progname))
+ pathlist = list of {""};
+ else if ((pl := ctxt.get("path")) != nil)
+ pathlist = list2stringlist(pl);
+ else
+ pathlist = list of {"/dis", "."};
+
+ err := "";
+ do {
+ path: string;
+ if (hd pathlist != "")
+ path = hd pathlist + "/" + progname;
+ else
+ path = progname;
+
+ npath := path;
+ if (!disfile)
+ npath += ".dis";
+ mod := load Command npath;
+ if (mod != nil) {
+ argv := list2stringlist(args);
+ export(ctxt.env.localenv);
+
+ if (last) {
+ {
+ sys->pctl(Sys->NEWFD, ctxt.keepfds);
+ mod->init(ctxt.drawcontext, argv);
+ exit;
+ } exception e {
+ EPIPE =>
+ return EPIPE;
+ "fail:*" =>
+ return e[5:];
+ }
+ }
+ extstart := chan of int;
+ spawn externalexec(mod, ctxt.drawcontext, argv, extstart, ctxt.keepfds);
+ pid := <-extstart;
+ if (DEBUG) debug("started external externalexec; pid is "+string pid);
+ return waitfor(ctxt, pid :: nil);
+ }
+ err = sys->sprint("%r");
+ if (nonexistent(err)) {
+ # try and run it as a shell script
+ if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) {
+ (ok, info) := sys->fstat(fd);
+ # make permission checking more accurate later
+ if (ok == 0 && (info.mode & Sys->DMDIR) == 0
+ && (info.mode & 8r111) != 0)
+ return runhashpling(ctxt, fd, path, tl args, last);
+ };
+ err = sys->sprint("%r");
+ }
+ pathlist = tl pathlist;
+ } while (pathlist != nil && nonexistent(err));
+ diagnostic(ctxt, sys->sprint("%s: %s", progname, err));
+ return err;
+}
+
+runhashpling(ctxt: ref Context, fd: ref Sys->FD,
+ path: string, argv: list of ref Listnode, last: int): string
+{
+ header := array[1024] of byte;
+ n := sys->read(fd, header, len header);
+ for (i := 0; i < n; i++)
+ if (header[i] == byte '\n')
+ break;
+ if (i == n || i < 3 || header[0] != byte('#') || header[1] != byte('!')) {
+ diagnostic(ctxt, "bad script header on " + path);
+ return "bad header";
+ }
+ (nil, args) := sys->tokenize(string header[2:i], " \t");
+ if (args == nil) {
+ diagnostic(ctxt, "empty header on " + path);
+ return "bad header";
+ }
+ header = nil;
+ fd = nil;
+ nargs: list of ref Listnode;
+ for (; args != nil; args = tl args)
+ nargs = ref Listnode(nil, hd args) :: nargs;
+ nargs = ref Listnode(nil, path) :: nargs;
+ for (; argv != nil; argv = tl argv)
+ nargs = hd argv :: nargs;
+ return runexternal(ctxt, revlist(nargs), last);
+}
+
+runblock(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ # block execute (we know that hd args represents a block)
+ cmd := (hd args).cmd;
+ if (cmd == nil) {
+ # parse block from first argument
+ lex := YYLEX.initstring((hd args).word);
+
+ err: string;
+ (cmd, err) = doparse(lex, "", 0);
+ if (cmd == nil)
+ ctxt.fail("parse error", "sh: "+err);
+
+ (hd args).cmd = cmd;
+ }
+ # now we've got a parsed block
+ ctxt.push();
+ {
+ ctxt.setlocal("0", hd args :: nil);
+ ctxt.setlocal("*", tl args);
+ if (cmd != nil && cmd.ntype == n_BLOCK)
+ cmd = cmd.left;
+ status := walk(ctxt, cmd, last);
+ ctxt.pop();
+ return status;
+ } exception e{
+ "fail:*" =>
+ ctxt.pop();
+ raise;
+ }
+}
+
+trybuiltin(ctxt: ref Context, args: list of ref Listnode, lseq: int)
+ : (int, string)
+{
+ (n, bmods) := findbuiltin(ctxt.env.builtins, (hd args).word);
+ if (bmods == nil)
+ return (0, nil);
+ return (1, (hd bmods)->runbuiltin(ctxt, myself, args, lseq));
+}
+
+keepfdstr(ctxt: ref Context): string
+{
+ s := "";
+ for (f := ctxt.keepfds; f != nil; f = tl f) {
+ s += string hd f;
+ if (tl f != nil)
+ s += ",";
+ }
+ return s;
+}
+
+externalexec(mod: Command,
+ drawcontext: ref Draw->Context, argv: list of string, startchan: chan of int, keepfds: list of int)
+{
+ if (DEBUG) debug(sprint("externalexec(%s,... [%d args])", hd argv, len argv));
+ sys->pctl(Sys->NEWFD, keepfds);
+ startchan <-= sys->pctl(0, nil);
+ {
+ mod->init(drawcontext, argv);
+ }
+ exception e{
+ EPIPE =>
+ raise "fail:" + EPIPE;
+ }
+}
+
+dup(ctxt: ref Context, fd1, fd2: int): int
+{
+ # shuffle waitfd out of the way if it's being attacked
+ if (ctxt.waitfd.fd == fd2) {
+ ctxt.waitfd = waitfd();
+ if (ctxt.waitfd.fd == fd2)
+ panic(sys->sprint("reopen of waitfd gave same fd (%d)", ctxt.waitfd.fd));
+ }
+ return sys->dup(fd1, fd2);
+}
+
+doredirs(ctxt: ref Context, redirs: ref Redirlist): list of int
+{
+ if (redirs.r == nil)
+ return nil;
+ keepfds := ctxt.keepfds;
+ rl := redirs.r;
+ redirs = nil;
+ for (; rl != nil; rl = tl rl) {
+ (rfd, path, (mode, fd1, fd2)) := hd rl;
+ if (path == nil && rfd == nil) {
+ # dup
+ if (fd1 == -1 || fd2 == -1)
+ ctxt.fail("bad redir", "sh: invalid dup");
+
+ if (dup(ctxt, fd2, fd1) == -1)
+ ctxt.fail("bad redir", sys->sprint("sh: cannot dup: %r"));
+ keepfds = fd1 :: keepfds;
+ continue;
+ }
+ # redir
+ if (fd1 == -1) {
+ if ((mode & OMASK) == Sys->OWRITE)
+ fd1 = 1;
+ else
+ fd1 = 0;
+ }
+ if (rfd == nil) {
+ (append, omode) := (mode & OAPPEND, mode & ~OAPPEND);
+ err := "";
+ case mode {
+ Sys->OREAD =>
+ rfd = sys->open(path, omode);
+ Sys->OWRITE | OAPPEND or
+ Sys->ORDWR =>
+ rfd = sys->open(path, omode);
+ err = sprint("%r");
+ if (rfd == nil && nonexistent(err)) {
+ rfd = sys->create(path, omode, 8r666);
+ err = nil;
+ }
+ Sys->OWRITE =>
+ rfd = sys->create(path, omode, 8r666);
+ err = sprint("%r");
+ if (rfd == nil && err == EPERM) {
+ # try open; can't create on a file2chan (pipe)
+ rfd = sys->open(path, omode);
+ nerr := sprint("%r");
+ if(!nonexistent(nerr))
+ err = nerr;
+ }
+ }
+ if (rfd == nil) {
+ if (err == nil)
+ err = sprint("%r");
+ ctxt.fail("bad redir", sys->sprint("sh: cannot open %s: %s", path, err));
+ }
+ if (append)
+ sys->seek(rfd, big 0, Sys->SEEKEND); # not good enough, but alright for some purposes.
+ }
+ # XXX what happens if rfd.fd == fd1?
+ # it probably gets closed automatically... which is not what we want!
+ dup(ctxt, rfd.fd, fd1);
+ keepfds = fd1 :: keepfds;
+ }
+ ctxt.keepfds = keepfds;
+ return ctxt.waitfd.fd :: keepfds;
+}
+
+
+waitfd(): ref Sys->FD
+{
+ wf := string sys->pctl(0, nil) + "/wait";
+ waitfd := sys->open("#p/"+wf, Sys->OREAD);
+ if (waitfd == nil)
+ waitfd = sys->open("/prog/"+wf, Sys->OREAD);
+ if (waitfd == nil)
+ panic(sys->sprint("cannot open wait file: %r"));
+ return waitfd;
+}
+
+waitfor(ctxt: ref Context, pids: list of int): string
+{
+ if (pids == nil)
+ return nil;
+ status := array[len pids] of string;
+ wcount := len status;
+ buf := array[Sys->WAITLEN] of byte;
+ onebad := 0;
+ for(;;){
+ n := sys->read(ctxt.waitfd, buf, len buf);
+ if(n < 0)
+ panic(sys->sprint("error on wait read: %r"));
+ (who, line, s) := parsewaitstatus(ctxt, string buf[0:n]);
+ if (s != nil) {
+ if (len s >= 5 && s[0:5] == "fail:")
+ s = s[5:];
+ else
+ diagnostic(ctxt, line);
+ }
+ for ((i, pl) := (0, pids); pl != nil; (i, pl) = (i+1, tl pl))
+ if (who == hd pl)
+ break;
+ if (i < len status) {
+ # wait returns two records for a killed process...
+ if (status[i] == nil || s != "killed") {
+ onebad += s != nil;
+ status[i] = s;
+ if (wcount-- <= 1)
+ break;
+ }
+ }
+ }
+ if (!onebad)
+ return nil;
+ r := status[len status - 1];
+ for (i := len status - 2; i >= 0; i--)
+ r += "|" + status[i];
+ return r;
+}
+
+parsewaitstatus(ctxt: ref Context, status: string): (int, string, string)
+{
+ for (i := 0; i < len status; i++)
+ if (status[i] == ' ')
+ break;
+ if (i == len status - 1 || status[i+1] != '"')
+ ctxt.fail("bad wait read",
+ sys->sprint("sh: bad exit status '%s'", status));
+
+ for (i+=2; i < len status; i++)
+ if (status[i] == '"')
+ break;
+ if (i > len status - 2 || status[i+1] != ':')
+ ctxt.fail("bad wait read",
+ sys->sprint("sh: bad exit status '%s'", status));
+
+ return (int status, status, status[i+2:]);
+}
+
+panic(s: string)
+{
+ sys->fprint(stderr(), "sh panic: %s\n", s);
+ raise "panic";
+}
+
+diagnostic(ctxt: ref Context, s: string)
+{
+ if (ctxt.options() & Context.VERBOSE)
+ sys->fprint(stderr(), "sh: %s\n", s);
+}
+
+
+Context.new(drawcontext: ref Draw->Context): ref Context
+{
+ initialise();
+ if (env != nil)
+ env->clone();
+ ctxt := ref Context(
+ ref Environment(
+ ref Builtins(nil, 0),
+ ref Builtins(nil, 0),
+ nil,
+ newlocalenv(nil)
+ ),
+ waitfd(),
+ drawcontext,
+ 0 :: 1 :: 2 :: nil
+ );
+ myselfbuiltin->initbuiltin(ctxt, myself);
+ ctxt.env.localenv.flags = ctxt.VERBOSE;
+ for (vl := ctxt.get("autoload"); vl != nil; vl = tl vl)
+ if ((hd vl).cmd == nil && (hd vl).word != nil)
+ loadmodule(ctxt, (hd vl).word);
+ return ctxt;
+}
+
+Context.copy(ctxt: self ref Context, copyenv: int): ref Context
+{
+ # XXX could check to see that we are definitely in a
+ # new process, because there'll be problems if not (two processes
+ # simultaneously reading the same wait file)
+ nctxt := ref Context(ctxt.env, waitfd(), ctxt.drawcontext, ctxt.keepfds);
+
+ if (copyenv) {
+ if (env != nil)
+ env->clone();
+ nctxt.env = ref Environment(
+ copybuiltins(ctxt.env.sbuiltins),
+ copybuiltins(ctxt.env.builtins),
+ ctxt.env.bmods,
+ copylocalenv(ctxt.env.localenv)
+ );
+ }
+ return nctxt;
+}
+
+Context.set(ctxt: self ref Context, name: string, val: list of ref Listnode)
+{
+ e := ctxt.env.localenv;
+ idx := hashfn(name, len e.vars);
+ for (;;) {
+ v := hashfind(e.vars, idx, name);
+ if (v == nil) {
+ if (e.pushed == nil) {
+ flags := Var.CHANGED;
+ if (noexport(name))
+ flags |= Var.NOEXPORT;
+ hashadd(e.vars, idx, ref Var(name, val, flags));
+ return;
+ }
+ } else {
+ v.val = val;
+ v.flags |= Var.CHANGED;
+ return;
+ }
+ e = e.pushed;
+ }
+}
+
+Context.get(ctxt: self ref Context, name: string): list of ref Listnode
+{
+ if (name == nil)
+ return nil;
+
+ idx := -1;
+ # cope with $1, $2, etc
+ if (name[0] > '0' && name[0] <= '9') {
+ i: int;
+ for (i = 0; i < len name; i++)
+ if (name[i] < '0' || name[i] > '9')
+ break;
+ if (i >= len name) {
+ idx = int name - 1;
+ name = "*";
+ }
+ }
+
+ v := varfind(ctxt.env.localenv, name);
+ if (v != nil) {
+ if (idx != -1)
+ return index(v.val, idx);
+ return v.val;
+ }
+ return nil;
+}
+
+Context.envlist(ctxt: self ref Context): list of (string, list of ref Listnode)
+{
+ t := array[ENVHASHSIZE] of list of ref Var;
+ for (e := ctxt.env.localenv; e != nil; e = e.pushed) {
+ for (i := 0; i < len e.vars; i++) {
+ for (vl := e.vars[i]; vl != nil; vl = tl vl) {
+ v := hd vl;
+ idx := hashfn(v.name, len e.vars);
+ if (hashfind(t, idx, v.name) == nil)
+ hashadd(t, idx, v);
+ }
+ }
+ }
+
+ l: list of (string, list of ref Listnode);
+ for (i := 0; i < ENVHASHSIZE; i++) {
+ for (vl := t[i]; vl != nil; vl = tl vl) {
+ v := hd vl;
+ l = (v.name, v.val) :: l;
+ }
+ }
+ return l;
+}
+
+Context.setlocal(ctxt: self ref Context, name: string, val: list of ref Listnode)
+{
+ e := ctxt.env.localenv;
+ idx := hashfn(name, len e.vars);
+ v := hashfind(e.vars, idx, name);
+ if (v == nil) {
+ flags := Var.CHANGED;
+ if (noexport(name))
+ flags |= Var.NOEXPORT;
+ hashadd(e.vars, idx, ref Var(name, val, flags));
+ } else {
+ v.val = val;
+ v.flags |= Var.CHANGED;
+ }
+}
+
+
+Context.push(ctxt: self ref Context)
+{
+ ctxt.env.localenv = newlocalenv(ctxt.env.localenv);
+}
+
+Context.pop(ctxt: self ref Context)
+{
+ if (ctxt.env.localenv.pushed == nil)
+ panic("unbalanced contexts in shell environment");
+ else {
+ oldv := ctxt.env.localenv.vars;
+ ctxt.env.localenv = ctxt.env.localenv.pushed;
+ for (i := 0; i < len oldv; i++) {
+ for (vl := oldv[i]; vl != nil; vl = tl vl) {
+ if ((v := varfind(ctxt.env.localenv, (hd vl).name)) != nil)
+ v.flags |= Var.CHANGED;
+ else
+ ctxt.set((hd vl).name, nil);
+ }
+ }
+ }
+}
+
+Context.run(ctxt: self ref Context, args: list of ref Listnode, last: int): string
+{
+ if (args == nil || ((hd args).cmd == nil && (hd args).word == nil))
+ return nil;
+ cmd := hd args;
+ if (cmd.cmd != nil || cmd.word[0] == '{') # }
+ return runblock(ctxt, args, last);
+
+ if (ctxt.options() & ctxt.EXECPRINT)
+ sys->fprint(stderr(), "%s\n", quoted(args, 0));
+ (doneit, status) := trybuiltin(ctxt, args, last);
+ if (!doneit)
+ status = runexternal(ctxt, args, last);
+
+ return status;
+}
+
+Context.addmodule(ctxt: self ref Context, name: string, mod: Shellbuiltin)
+{
+ mod->initbuiltin(ctxt, myself);
+ ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods;
+}
+
+Context.addbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ addbuiltin(c.env.builtins, name, mod);
+}
+
+Context.removebuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ removebuiltin(c.env.builtins, name, mod);
+}
+
+Context.addsbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ addbuiltin(c.env.sbuiltins, name, mod);
+}
+
+Context.removesbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ removebuiltin(c.env.sbuiltins, name, mod);
+}
+
+varfind(e: ref Localenv, name: string): ref Var
+{
+ idx := hashfn(name, len e.vars);
+ for (; e != nil; e = e.pushed)
+ for (vl := e.vars[idx]; vl != nil; vl = tl vl)
+ if ((hd vl).name == name)
+ return hd vl;
+ return nil;
+}
+
+Context.fail(ctxt: self ref Context, ename: string, err: string)
+{
+ if (ctxt.options() & Context.VERBOSE)
+ sys->fprint(stderr(), "%s\n", err);
+ raise "fail:" + ename;
+}
+
+Context.setoptions(ctxt: self ref Context, flags, on: int): int
+{
+ old := ctxt.env.localenv.flags;
+ if (on)
+ ctxt.env.localenv.flags |= flags;
+ else
+ ctxt.env.localenv.flags &= ~flags;
+ return old;
+}
+
+Context.options(ctxt: self ref Context): int
+{
+ return ctxt.env.localenv.flags;
+}
+
+hashfn(s: string, n: int): int
+{
+ h := 0;
+ m := len s;
+ for(i:=0; i<m; i++){
+ h = 65599*h+s[i];
+ }
+ return (h & 16r7fffffff) % n;
+}
+
+hashfind(ht: array of list of ref Var, idx: int, n: string): ref Var
+{
+ for (ent := ht[idx]; ent != nil; ent = tl ent)
+ if ((hd ent).name == n)
+ return hd ent;
+ return nil;
+}
+
+hashadd(ht: array of list of ref Var, idx: int, v: ref Var)
+{
+ ht[idx] = v :: ht[idx];
+}
+
+copylocalenv(e: ref Localenv): ref Localenv
+{
+ nvars := array[len e.vars] of list of ref Var;
+ flags := e.flags;
+ for (; e != nil; e = e.pushed)
+ for (i := 0; i < len nvars; i++)
+ for (vl := e.vars[i]; vl != nil; vl = tl vl) {
+ idx := hashfn((hd vl).name, len nvars);
+ if (hashfind(nvars, idx, (hd vl).name) == nil)
+ hashadd(nvars, idx, ref *(hd vl));
+ }
+ return ref Localenv(nvars, nil, flags);
+}
+
+newlocalenv(pushed: ref Localenv): ref Localenv
+{
+ e := ref Localenv(array[ENVHASHSIZE] of list of ref Var, pushed, 0);
+ if (pushed == nil && env != nil) {
+ for (vl := env->getall(); vl != nil; vl = tl vl) {
+ (name, val) := hd vl;
+ hashadd(e.vars, hashfn(name, len e.vars), ref Var(name, envstringtoval(val), 0));
+ }
+ }
+ if (pushed != nil)
+ e.flags = pushed.flags;
+ return e;
+}
+
+copybuiltins(b: ref Builtins): ref Builtins
+{
+ nb := ref Builtins(array[b.n] of (string, list of Shellbuiltin), b.n);
+ nb.ba[0:] = b.ba[0:b.n];
+ return nb;
+}
+
+findbuiltin(b: ref Builtins, name: string): (int, list of Shellbuiltin)
+{
+ lo := 0;
+ hi := b.n - 1;
+ while (lo <= hi) {
+ mid := (lo + hi) / 2;
+ (bname, bmod) := b.ba[mid];
+ if (name < bname)
+ hi = mid - 1;
+ else if (name > bname)
+ lo = mid + 1;
+ else
+ return (mid, bmod);
+ }
+ return (lo, nil);
+}
+
+removebuiltin(b: ref Builtins, name: string, mod: Shellbuiltin)
+{
+ (n, bmods) := findbuiltin(b, name);
+ if (bmods == nil)
+ return;
+ if (hd bmods == mod) {
+ if (tl bmods != nil)
+ b.ba[n] = (name, tl bmods);
+ else {
+ b.ba[n:] = b.ba[n+1:b.n];
+ b.ba[--b.n] = (nil, nil);
+ }
+ }
+}
+
+addbuiltin(b: ref Builtins, name: string, mod: Shellbuiltin)
+{
+ if (mod == nil || (name == "builtin" && mod != myselfbuiltin))
+ return;
+ (n, bmods) := findbuiltin(b, name);
+ if (bmods != nil) {
+ if (hd bmods == myselfbuiltin)
+ b.ba[n] = (name, mod :: bmods);
+ else
+ b.ba[n] = (name, mod :: nil);
+ } else {
+ if (b.n == len b.ba) {
+ nb := array[b.n + 10] of (string, list of Shellbuiltin);
+ nb[0:] = b.ba[0:b.n];
+ b.ba = nb;
+ }
+ b.ba[n+1:] = b.ba[n:b.n];
+ b.ba[n] = (name, mod :: nil);
+ b.n++;
+ }
+}
+
+removebuiltinmod(b: ref Builtins, mod: Shellbuiltin)
+{
+ j := 0;
+ for (i := 0; i < b.n; i++) {
+ (name, bmods) := b.ba[i];
+ if (hd bmods == mod)
+ bmods = tl bmods;
+ if (bmods != nil)
+ b.ba[j++] = (name, bmods);
+ }
+ b.n = j;
+ for (; j < i; j++)
+ b.ba[j] = (nil, nil);
+}
+
+export(e: ref Localenv)
+{
+ if (env == nil)
+ return;
+ if (e.pushed != nil)
+ export(e.pushed);
+
+ for (i := 0; i < len e.vars; i++) {
+ for (vl := e.vars[i]; vl != nil; vl = tl vl) {
+ v := hd vl;
+ # a bit inefficient: a local variable will get several putenvs.
+ if ((v.flags & Var.CHANGED) && !(v.flags & Var.NOEXPORT)) {
+ setenv(v.name, v.val);
+ v.flags &= ~Var.CHANGED;
+ }
+ }
+ }
+}
+
+noexport(name: string): int
+{
+ case name {
+ "0" or "*" or "status" => return 1;
+ }
+ return 0;
+}
+
+index(val: list of ref Listnode, k: int): list of ref Listnode
+{
+ for (; k > 0 && val != nil; k--)
+ val = tl val;
+ if (val != nil)
+ val = hd val :: nil;
+ return val;
+}
+
+getenv(name: string): list of ref Listnode
+{
+ if (env == nil)
+ return nil;
+ return envstringtoval(env->getenv(name));
+}
+
+envstringtoval(v: string): list of ref Listnode
+{
+ return stringlist2list(str->unquoted(v));
+}
+
+XXXenvstringtoval(v: string): list of ref Listnode
+{
+ if (len v == 0)
+ return nil;
+ start := len v;
+ val: list of ref Listnode;
+ for (i := start - 1; i >= 0; i--) {
+ if (v[i] == ENVSEP) {
+ val = ref Listnode(nil, v[i+1:start]) :: val;
+ start = i;
+ }
+ }
+ return ref Listnode(nil, v[0:start]) :: val;
+}
+
+setenv(name: string, val: list of ref Listnode)
+{
+ if (env == nil)
+ return;
+ env->setenv(name, quoted(val, 1));
+}
+
+
+containswildchar(s: string): int
+{
+ # try and avoid being fooled by GLOB characters in quoted
+ # text. we'll only be fooled if the GLOB char is followed
+ # by a wildcard char, or another GLOB.
+ for (i := 0; i < len s; i++) {
+ if (s[i] == GLOB && i < len s - 1) {
+ case s[i+1] {
+ '*' or '[' or '?' or GLOB =>
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+patquote(word: string): string
+{
+ outword := "";
+ for (i := 0; i < len word; i++) {
+ case word[i] {
+ '[' or '*' or '?' or '\\' =>
+ outword[len outword] = '\\';
+ GLOB =>
+ i++;
+ if (i >= len word)
+ return outword;
+ }
+ outword[len outword] = word[i];
+ }
+ return outword;
+}
+
+deglob(s: string): string
+{
+ j := 0;
+ for (i := 0; i < len s; i++) {
+ if (s[i] != GLOB) {
+ if (i != j) # a worthy optimisation???
+ s[j] = s[i];
+ j++;
+ }
+ }
+ if (i == j)
+ return s;
+ return s[0:j];
+}
+
+glob(nl: list of ref Listnode): list of ref Listnode
+{
+ new: list of ref Listnode;
+ while (nl != nil) {
+ n := hd nl;
+ if (containswildchar(n.word)) {
+ qword := patquote(n.word);
+ files := filepat->expand(qword);
+ if (files == nil)
+ files = deglob(n.word) :: nil;
+ while (files != nil) {
+ new = ref Listnode(nil, hd files) :: new;
+ files = tl files;
+ }
+ } else
+ new = n :: new;
+ nl = tl nl;
+ }
+ ret := revlist(new);
+ return ret;
+}
+
+
+list2stringlist(nl: list of ref Listnode): list of string
+{
+ ret: list of string = nil;
+
+ while (nl != nil) {
+ newel: string;
+ el := hd nl;
+ if (el.word != nil || el.cmd == nil)
+ newel = el.word;
+ else
+ el.word = newel = cmd2string(el.cmd);
+ ret = newel::ret;
+ nl = tl nl;
+ }
+
+ sl := revstringlist(ret);
+ return sl;
+}
+
+stringlist2list(sl: list of string): list of ref Listnode
+{
+ ret: list of ref Listnode;
+
+ while (sl != nil) {
+ ret = ref Listnode(nil, hd sl) :: ret;
+ sl = tl sl;
+ }
+ return revlist(ret);
+}
+
+revstringlist(l: list of string): list of string
+{
+ t: list of string;
+
+ while(l != nil) {
+ t = hd l :: t;
+ l = tl l;
+ }
+ return t;
+}
+
+revlist(l: list of ref Listnode): list of ref Listnode
+{
+ t: list of ref Listnode;
+
+ while(l != nil) {
+ t = hd l :: t;
+ l = tl l;
+ }
+ return t;
+}
+
+
+fdassignstr(isassign: int, redir: ref Redir): string
+{
+ l: string = nil;
+ if (redir.fd1 >= 0)
+ l = string redir.fd1;
+
+ if (isassign) {
+ r: string = nil;
+ if (redir.fd2 >= 0)
+ r = string redir.fd2;
+ return "[" + l + "=" + r + "]";
+ }
+ return "[" + l + "]";
+}
+
+redirstr(rtype: int): string
+{
+ case rtype {
+ * or
+ Sys->OREAD => return "<";
+ Sys->OWRITE => return ">";
+ Sys->OWRITE|OAPPEND => return ">>";
+ Sys->ORDWR => return "<>";
+ }
+}
+
+cmd2string(n: ref Node): string
+{
+ if (n == nil)
+ return "";
+
+ s: string;
+ case n.ntype {
+ n_BLOCK => s = "{" + cmd2string(n.left) + "}";
+ n_VAR => s = "$" + cmd2string(n.left);
+ # XXX can this ever occur?
+ if (n.right != nil)
+ s += "(" + cmd2string(n.right) + ")";
+ n_SQUASH => s = "$\"" + cmd2string(n.left);
+ n_COUNT => s = "$#" + cmd2string(n.left);
+ n_BQ => s = "`" + cmd2string(n.left);
+ n_BQ2 => s = "\"" + cmd2string(n.left);
+ n_REDIR => s = redirstr(n.redir.rtype);
+ if (n.redir.fd1 != -1)
+ s += fdassignstr(0, n.redir);
+ s += cmd2string(n.left);
+ n_DUP => s = redirstr(n.redir.rtype) + fdassignstr(1, n.redir);
+ n_LIST => s = "(" + cmd2string(n.left) + ")";
+ n_SEQ => s = cmd2string(n.left) + ";" + cmd2string(n.right);
+ n_NOWAIT => s = cmd2string(n.left) + "&";
+ n_CONCAT => s = cmd2string(n.left) + "^" + cmd2string(n.right);
+ n_PIPE => s = cmd2string(n.left) + "|";
+ if (n.redir != nil && (n.redir.fd1 != -1 || n.redir.fd2 != -1))
+ s += fdassignstr(n.redir.fd2 != -1, n.redir);
+ s += cmd2string(n.right);
+ n_ASSIGN => s = cmd2string(n.left) + "=" + cmd2string(n.right);
+ n_LOCAL => s = cmd2string(n.left) + ":=" + cmd2string(n.right);
+ n_ADJ => s = cmd2string(n.left) + " " + cmd2string(n.right);
+ n_WORD => s = quote(n.word, 1);
+ * => s = sys->sprint("unknown%d", n.ntype);
+ }
+ return s;
+}
+
+quote(s: string, glob: int): string
+{
+ needquote := 0;
+ t := "";
+ for (i := 0; i < len s; i++) {
+ case s[i] {
+ '{' or '}' or '(' or ')' or '`' or '&' or ';' or '=' or '>' or '<' or '#' or
+ '|' or '*' or '[' or '?' or '$' or '^' or ' ' or '\t' or '\n' or '\r' =>
+ needquote = 1;
+ '\'' =>
+ t[len t] = '\'';
+ needquote = 1;
+ GLOB =>
+ if (glob) {
+ if (i < len s - 1)
+ i++;
+ }
+ }
+ t[len t] = s[i];
+ }
+ if (needquote || t == nil)
+ t = "'" + t + "'";
+ return t;
+}
+
+squash(l: list of string, sep: string): string
+{
+ if (l == nil)
+ return nil;
+ s := hd l;
+ for (l = tl l; l != nil; l = tl l)
+ s += sep + hd l;
+ return s;
+}
+
+debug(s: string)
+{
+ if (DEBUG) sys->fprint(stderr(), "%s\n", string sys->pctl(0, nil) + ": " + s);
+}
+
+
+initbuiltin(c: ref Context, nil: Sh): string
+{
+ names := array[] of {"load", "unload", "loaded", "builtin", "syncenv", "whatis", "run", "exit", "@"};
+ for (i := 0; i < len names; i++)
+ c.addbuiltin(names[i], myselfbuiltin);
+ c.addsbuiltin("loaded", myselfbuiltin);
+ c.addsbuiltin("quote", myselfbuiltin);
+ c.addsbuiltin("bquote", myselfbuiltin);
+ c.addsbuiltin("unquote", myselfbuiltin);
+ c.addsbuiltin("builtin", myselfbuiltin);
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+runsbuiltin(ctxt: ref Context, nil: Sh, argv: list of ref Listnode): list of ref Listnode
+{
+ case (hd argv).word {
+ "loaded" => return sbuiltin_loaded(ctxt, argv);
+ "bquote" => return sbuiltin_quote(ctxt, argv, 0);
+ "quote" => return sbuiltin_quote(ctxt, argv, 1);
+ "unquote" => return sbuiltin_unquote(ctxt, argv);
+ "builtin" => return sbuiltin_builtin(ctxt, argv);
+ }
+ return nil;
+}
+
+runbuiltin(ctxt: ref Context, nil: Sh, args: list of ref Listnode, lseq: int): string
+{
+ status := "";
+ name := (hd args).word;
+ case name {
+ "load" => status = builtin_load(ctxt, args, lseq);
+ "loaded" => status = builtin_loaded(ctxt, args, lseq);
+ "unload" => status = builtin_unload(ctxt, args, lseq);
+ "builtin" => status = builtin_builtin(ctxt, args, lseq);
+ "whatis" => status = builtin_whatis(ctxt, args, lseq);
+ "run" => status = builtin_run(ctxt, args, lseq);
+ "exit" => status = builtin_exit(ctxt, args, lseq);
+ "syncenv" => export(ctxt.env.localenv);
+ "@" => status = builtin_subsh(ctxt, args, lseq);
+ }
+ return status;
+}
+
+sbuiltin_loaded(ctxt: ref Context, nil: list of ref Listnode): list of ref Listnode
+{
+ v: list of ref Listnode;
+ for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) {
+ (name, nil) := hd bl;
+ v = ref Listnode(nil, name) :: v;
+ }
+ return v;
+}
+
+sbuiltin_quote(nil: ref Context, argv: list of ref Listnode, quoteblocks: int): list of ref Listnode
+{
+ return ref Listnode(nil, quoted(tl argv, quoteblocks)) :: nil;
+}
+
+sbuiltin_builtin(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode
+{
+ if (args == nil || tl args == nil)
+ builtinusage(ctxt, "builtin command [args ...]");
+ name := (hd tl args).word;
+ (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name);
+ for (; mods != nil; mods = tl mods)
+ if (hd mods == myselfbuiltin)
+ return (hd mods)->runsbuiltin(ctxt, myself, tl args);
+ ctxt.fail("builtin not found", sys->sprint("sh: builtin %s not found", name));
+ return nil;
+}
+
+sbuiltin_unquote(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ argv = tl argv;
+ if (argv == nil || tl argv != nil)
+ builtinusage(ctxt, "unquote arg");
+
+ arg := (hd argv).word;
+ if (arg == nil && (hd argv).cmd != nil)
+ arg = cmd2string((hd argv).cmd);
+ return stringlist2list(str->unquoted(arg));
+}
+
+getself(): Shellbuiltin
+{
+ return myselfbuiltin;
+}
+
+builtinusage(ctxt: ref Context, s: string)
+{
+ ctxt.fail("usage", "sh: usage: " + s);
+}
+
+builtin_exit(nil: ref Context, nil: list of ref Listnode, nil: int): string
+{
+ # XXX using this primitive can cause
+ # environment stack not to be popped properly.
+ exit;
+}
+
+builtin_subsh(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil)
+ return nil;
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 0, tl args, ref Redirlist, startchan);
+ (exepid, exprop) := <-startchan;
+ status := waitfor(ctxt, exepid :: nil);
+ if (exprop.name != nil)
+ raise exprop.name;
+ return status;
+}
+
+builtin_loaded(ctxt: ref Context, nil: list of ref Listnode, nil: int): string
+{
+ b := ctxt.env.builtins;
+ for (i := 0; i < b.n; i++) {
+ (name, bmods) := b.ba[i];
+ sys->print("%s\t%s\n", name, modname(ctxt, hd bmods));
+ }
+ b = ctxt.env.sbuiltins;
+ for (i = 0; i < b.n; i++) {
+ (name, bmods) := b.ba[i];
+ sys->print("${%s}\t%s\n", name, modname(ctxt, hd bmods));
+ }
+ return nil;
+}
+
+builtin_load(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil || (hd tl args).word == nil)
+ builtinusage(ctxt, "load path...");
+ args = tl args;
+ path := (hd args).word;
+ if (args == nil)
+ builtinusage(ctxt, "load path...");
+ status := "";
+ for (; args != nil; args = tl args) {
+ s := loadmodule(ctxt, (hd args).word);
+ if (s != nil)
+ raise "fail:" + s;
+ }
+ return nil;
+}
+
+builtin_unload(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil)
+ builtinusage(ctxt, "unload path...");
+ status := "";
+ for (args = tl args; args != nil; args = tl args)
+ if ((s := unloadmodule(ctxt, (hd args).word)) != nil)
+ status = s;
+ return status;
+}
+
+builtin_run(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil || (hd tl args).word == nil)
+ builtinusage(ctxt, "run path");
+ ctxt.push();
+ {
+ ctxt.setoptions(ctxt.INTERACTIVE, 0);
+ runscript(ctxt, (hd tl args).word, tl tl args, 1);
+ ctxt.pop();
+ return nil;
+ } exception e {
+ "fail:*" =>
+ ctxt.pop();
+ return e[5:];
+ }
+}
+
+builtin_whatis(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (len args < 2)
+ builtinusage(ctxt, "whatis name ...");
+ err := "";
+ for (args = tl args; args != nil; args = tl args)
+ if ((e := whatisit(ctxt, hd args)) != nil)
+ err = e;
+ return err;
+}
+
+whatisit(ctxt: ref Context, el: ref Listnode): string
+{
+ if (el.cmd != nil) {
+ sys->print("%s\n", cmd2string(el.cmd));
+ return nil;
+ }
+ found := 0;
+ name := el.word;
+ if (name != nil && name[0] == '{') { #}
+ sys->print("%s\n", name);
+ return nil;;
+ }
+ if (name == nil)
+ return nil; # XXX questionable
+ w: string;
+ val := ctxt.get(name);
+ if (val != nil) {
+ found++;
+ w += sys->sprint("%s=%s\n", quote(name, 0), quoted(val, 0));
+ }
+ (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name);
+ if (mods != nil) {
+ mod := hd mods;
+ if (mod == myselfbuiltin)
+ w += "${builtin " + name + "}\n";
+ else {
+ mw := mod->whatis(ctxt, myself, name, Shellbuiltin->SBUILTIN);
+ if (mw == nil)
+ mw = "${" + name + "}";
+ w += "load " + modname(ctxt, mod) + "; " + mw + "\n";
+ }
+ found++;
+ }
+ (nil, mods) = findbuiltin(ctxt.env.builtins, name);
+ if (mods != nil) {
+ mod := hd mods;
+ if (mod == myselfbuiltin)
+ sys->print("builtin %s\n", name);
+ else {
+ mw := mod->whatis(ctxt, myself, name, Shellbuiltin->BUILTIN);
+ if (mw == nil)
+ mw = name;
+ w += "load " + modname(ctxt, mod) + "; " + mw + "\n";
+ }
+ found++;
+ } else {
+ disfile := 0;
+ if (len name >= 4 && name[len name-4:] == ".dis")
+ disfile = 1;
+ pathlist: list of string;
+ if (len name >= 2 && (name[0] == '/' || name[0:2] == "./"))
+ pathlist = list of {""};
+ else if ((pl := ctxt.get("path")) != nil)
+ pathlist = list2stringlist(pl);
+ else
+ pathlist = list of {"/dis", "."};
+
+ foundpath := "";
+ while (pathlist != nil) {
+ path: string;
+ if (hd pathlist != "")
+ path = hd pathlist + "/" + name;
+ else
+ path = name;
+ if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) {
+ if (executable(sys->fstat(fd), 8r111)) {
+ foundpath = path;
+ break;
+ }
+ }
+ if (!disfile)
+ path += ".dis";
+ if (executable(sys->stat(path), 8r444)) {
+ foundpath = path;
+ break;
+ }
+ pathlist = tl pathlist;
+ }
+ if (foundpath != nil)
+ w += foundpath + "\n";
+ }
+ for (bmods := ctxt.env.bmods; bmods != nil; bmods = tl bmods) {
+ (modname, mod) := hd bmods;
+ if ((mw := mod->whatis(ctxt, myself, name, Shellbuiltin->OTHER)) != nil)
+ w += "load " + modname + "; " + mw + "\n";
+ }
+ if (w == nil) {
+ sys->fprint(stderr(), "%s: not found\n", name);
+ return "not found";
+ }
+ sys->print("%s", w);
+ return nil;
+}
+
+builtin_builtin(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ if (len args < 2)
+ builtinusage(ctxt, "builtin command [args ...]");
+ name := (hd tl args).word;
+ if (name == nil || name[0] == '{') {
+ diagnostic(ctxt, name + " not found");
+ return "not found";
+ }
+ (nil, mods) := findbuiltin(ctxt.env.builtins, name);
+ for (; mods != nil; mods = tl mods)
+ if (hd mods == myselfbuiltin)
+ return (hd mods)->runbuiltin(ctxt, myself, tl args, last);
+ if (ctxt.options() & ctxt.EXECPRINT)
+ sys->fprint(stderr(), "%s\n", quoted(tl args, 0));
+ return runexternal(ctxt, tl args, last);
+}
+
+modname(ctxt: ref Context, mod: Shellbuiltin): string
+{
+ for (ml := ctxt.env.bmods; ml != nil; ml = tl ml) {
+ (bname, bmod) := hd ml;
+ if (bmod == mod)
+ return bname;
+ }
+ return "builtin";
+}
+
+loadmodule(ctxt: ref Context, name: string): string
+{
+ # avoid loading the same module twice (it's convenient
+ # to have load be a null-op if the module required is already loaded)
+ for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) {
+ (bname, nil) := hd bl;
+ if (bname == name)
+ return nil;
+ }
+ path := name;
+ if (len path < 4 || path[len path-4:] != ".dis")
+ path += ".dis";
+ if (path[0] != '/' && path[0:2] != "./")
+ path = BUILTINPATH + "/" + path;
+ mod := load Shellbuiltin path;
+ if (mod == nil) {
+ diagnostic(ctxt, sys->sprint("load: cannot load %s: %r", path));
+ return "bad module";
+ }
+ s := mod->initbuiltin(ctxt, myself);
+ ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods;
+ if (s != nil) {
+ unloadmodule(ctxt, name);
+ diagnostic(ctxt, "load: module init failed: " + s);
+ }
+ return s;
+}
+
+unloadmodule(ctxt: ref Context, name: string): string
+{
+ bl: list of (string, Shellbuiltin);
+ mod: Shellbuiltin;
+ for (cl := ctxt.env.bmods; cl != nil; cl = tl cl) {
+ (bname, bmod) := hd cl;
+ if (bname == name)
+ mod = bmod;
+ else
+ bl = hd cl :: bl;
+ }
+ if (mod == nil) {
+ diagnostic(ctxt, sys->sprint("module %s not found", name));
+ return "not found";
+ }
+ for (ctxt.env.bmods = nil; bl != nil; bl = tl bl)
+ ctxt.env.bmods = hd bl :: ctxt.env.bmods;
+ removebuiltinmod(ctxt.env.builtins, mod);
+ removebuiltinmod(ctxt.env.sbuiltins, mod);
+ return nil;
+}
+
+executable(s: (int, Sys->Dir), mode: int): int
+{
+ (ok, info) := s;
+ return ok != -1 && (info.mode & Sys->DMDIR) == 0
+ && (info.mode & mode) != 0;
+}
+
+quoted(val: list of ref Listnode, quoteblocks: int): string
+{
+ s := "";
+ for (; val != nil; val = tl val) {
+ el := hd val;
+ if (el.cmd == nil || (quoteblocks && el.word != nil))
+ s += quote(el.word, 0);
+ else {
+ cmd := cmd2string(el.cmd);
+ if (quoteblocks)
+ cmd = quote(cmd, 0);
+ s += cmd;
+ }
+ if (tl val != nil)
+ s[len s] = ' ';
+ }
+ return s;
+}
+
+setstatus(ctxt: ref Context, val: string): string
+{
+ ctxt.setlocal("status", ref Listnode(nil, val) :: nil);
+ return val;
+}
+
+
+doparse(l: ref YYLEX, prompt: string, showline: int): (ref Node, string)
+{
+ l.prompt = prompt;
+ l.err = nil;
+ l.lval.node = nil;
+ yyparse(l);
+ l.lastnl = 0; # don't print secondary prompt next time
+ if (l.err != nil) {
+ s: string;
+ if (l.err == nil)
+ l.err = "unknown error";
+ if (l.errline > 0 && showline)
+ s = sys->sprint("%s:%d: %s", l.path, l.errline, l.err);
+ else
+ s = l.path + ": parse error: " + l.err;
+ return (nil, s);
+ }
+ return (l.lval.node, nil);
+}
+
+blanklex: YYLEX; # for hassle free zero initialisation
+
+YYLEX.initstring(s: string): ref YYLEX
+{
+ ret := ref blanklex;
+ ret.s = s;
+ ret.path="internal";
+ ret.strpos = 0;
+ return ret;
+}
+
+YYLEX.initfile(fd: ref Sys->FD, path: string): ref YYLEX
+{
+ lex := ref blanklex;
+ lex.f = bufio->fopen(fd, bufio->OREAD);
+ lex.path = path;
+ lex.cbuf = array[2] of int; # number of characters of pushback
+ lex.linenum = 1;
+ lex.prompt = "";
+ return lex;
+}
+
+YYLEX.error(l: self ref YYLEX, s: string)
+{
+ if (l.err == nil) {
+ l.err = s;
+ l.errline = l.linenum;
+ }
+}
+
+NOTOKEN: con -1;
+
+YYLEX.lex(l: self ref YYLEX): int
+{
+ # the following are allowed a free caret:
+ # $, word and quoted word;
+ # also, allowed chrs in unquoted word following dollar are [a-zA-Z0-9*_]
+ endword := 0;
+ wasdollar := 0;
+ tok := NOTOKEN;
+ while (tok == NOTOKEN) {
+ case c := l.getc() {
+ l.EOF =>
+ tok = END;
+ '\n' =>
+ tok = '\n';
+ '\r' or '\t' or ' ' =>
+ ;
+ '#' =>
+ while ((c = l.getc()) != '\n' && c != l.EOF)
+ ;
+ l.ungetc();
+ ';' => tok = ';';
+ '&' =>
+ c = l.getc();
+ if(c == '&')
+ tok = ANDAND;
+ else{
+ l.ungetc();
+ tok = '&';
+ }
+ '^' => tok = '^';
+ '{' => tok = '{';
+ '}' => tok = '}';
+ ')' => tok = ')';
+ '(' => tok = '(';
+ '=' => (tok, l.lval.optype) = ('=', n_ASSIGN);
+ '$' =>
+ if (l.atendword) {
+ l.ungetc();
+ tok = '^';
+ break;
+ }
+ case (c = l.getc()) {
+ '#' =>
+ l.lval.optype = n_COUNT;
+ '"' =>
+ l.lval.optype = n_SQUASH;
+ * =>
+ l.ungetc();
+ l.lval.optype = n_VAR;
+ }
+ tok = OP;
+ wasdollar = 1;
+ '"' or '`'=>
+ if (l.atendword) {
+ tok = '^';
+ l.ungetc();
+ break;
+ }
+ tok = OP;
+ if (c == '"')
+ l.lval.optype = n_BQ2;
+ else
+ l.lval.optype = n_BQ;
+ '>' or '<' =>
+ rtype: int;
+ nc := l.getc();
+ if (nc == '>') {
+ if (c == '>')
+ rtype = Sys->OWRITE | OAPPEND;
+ else
+ rtype = Sys->ORDWR;
+ nc = l.getc();
+ } else if (c == '>')
+ rtype = Sys->OWRITE;
+ else
+ rtype = Sys->OREAD;
+ tok = REDIR;
+ if (nc == '[') {
+ (tok, l.lval.redir) = readfdassign(l);
+ if (tok == ERROR)
+ (l.err, l.errline) = ("syntax error in redirection", l.linenum);
+ } else {
+ l.ungetc();
+ l.lval.redir = ref Redir(-1, -1, -1);
+ }
+ if (l.lval.redir != nil)
+ l.lval.redir.rtype = rtype;
+ '|' =>
+ tok = '|';
+ l.lval.redir = nil;
+ if ((c = l.getc()) == '[') {
+ (tok, l.lval.redir) = readfdassign(l);
+ if (tok == ERROR) {
+ (l.err, l.errline) = ("syntax error in pipe redirection", l.linenum);
+ return tok;
+ }
+ tok = '|';
+ } else if(c == '|')
+ tok = OROR;
+ else
+ l.ungetc();
+
+ '\'' =>
+ if (l.atendword) {
+ l.ungetc();
+ tok = '^';
+ break;
+ }
+ startline := l.linenum;
+ s := "";
+ for(;;) {
+ while ((nc := l.getc()) != '\'' && nc != l.EOF)
+ s[len s] = nc;
+ if (nc == l.EOF) {
+ (l.err, l.errline) = ("unterminated string literal", startline);
+ return ERROR;
+ }
+ if (l.getc() != '\'') {
+ l.ungetc();
+ break;
+ }
+ s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy)
+ }
+ l.lval.word = s;
+ tok = WORD;
+ endword = 1;
+
+ * =>
+ if (c == ':') {
+ if (l.getc() == '=') {
+ tok = '=';
+ l.lval.optype = n_LOCAL;
+ break;
+ }
+ l.ungetc();
+ }
+ if (l.atendword) {
+ l.ungetc();
+ tok = '^';
+ break;
+ }
+ allowed: string;
+ if (l.wasdollar)
+ allowed = "a-zA-Z0-9*_";
+ else
+ allowed = "^\n \t\r|$'#<>;^(){}`&=\"";
+ word := "";
+ loop: do {
+ case c {
+ '*' or '?' or '[' or GLOB =>
+ word[len word] = GLOB;
+ ':' =>
+ nc := l.getc();
+ l.ungetc();
+ if (nc == '=')
+ break loop;
+ }
+ word[len word] = c;
+ } while ((c = l.getc()) != l.EOF && str->in(c, allowed));
+ l.ungetc();
+ l.lval.word = word;
+ tok = WORD;
+ endword = 1;
+ }
+ l.atendword = endword;
+ l.wasdollar = wasdollar;
+ }
+ return tok;
+}
+
+tokstr(t: int): string
+{
+ s: string;
+ case t {
+ '\n' => s = "'\\n'";
+ 33 to 127 => s = sprint("'%c'", t);
+ DUP=> s = "DUP";
+ REDIR =>s = "REDIR";
+ WORD => s = "WORD";
+ OP => s = "OP";
+ END => s = "END";
+ ERROR=> s = "ERROR";
+ * =>
+ s = "<unknowntok"+ string t + ">";
+ }
+ return s;
+}
+
+YYLEX.ungetc(lex: self ref YYLEX)
+{
+ lex.strpos--;
+ if (lex.f != nil) {
+ lex.ncbuf++;
+ if (lex.strpos < 0)
+ lex.strpos = len lex.cbuf - 1;
+ }
+}
+
+YYLEX.getc(lex: self ref YYLEX): int
+{
+ if (lex.eof) # EOF sticks
+ return lex.EOF;
+ c: int;
+ if (lex.f != nil) {
+ if (lex.ncbuf > 0) {
+ c = lex.cbuf[lex.strpos++];
+ if (lex.strpos >= len lex.cbuf)
+ lex.strpos = 0;
+ lex.ncbuf--;
+ } else {
+ if (lex.lastnl && lex.prompt != nil)
+ sys->fprint(stderr(), "%s", lex.prompt);
+ c = bufio->lex.f.getc();
+ if (c == bufio->ERROR || c == bufio->EOF) {
+ lex.eof = 1;
+ c = lex.EOF;
+ } else if (c == '\n')
+ lex.linenum++;
+ lex.lastnl = (c == '\n');
+ lex.cbuf[lex.strpos++] = c;
+ if (lex.strpos >= len lex.cbuf)
+ lex.strpos = 0;
+ }
+ } else {
+ if (lex.strpos >= len lex.s) {
+ lex.eof = 1;
+ c = lex.EOF;
+ } else
+ c = lex.s[lex.strpos++];
+ }
+ return c;
+}
+
+readnum(lex: ref YYLEX): int
+{
+ sum := nc := 0;
+ while ((c := lex.getc()) >= '0' && c <= '9') {
+ sum = (sum * 10) + (c - '0');
+ nc++;
+ }
+ lex.ungetc();
+ if (nc == 0)
+ return -1;
+ return sum;
+}
+
+readfdassign(lex: ref YYLEX): (int, ref Redir)
+{
+ n1 := readnum(lex);
+ if ((c := lex.getc()) != '=') {
+ if (c == ']')
+ return (REDIR, ref Redir(-1, n1, -1));
+
+ return (ERROR, nil);
+ }
+ n2 := readnum(lex);
+ if (lex.getc() != ']')
+ return (ERROR, nil);
+ return (DUP, ref Redir(-1, n1, n2));
+}
+
+mkseq(left, right: ref Node): ref Node
+{
+ if (left != nil && right != nil)
+ return mk(n_SEQ, left, right);
+ else if (left == nil)
+ return right;
+ return left;
+}
+
+mk(ntype: int, left, right: ref Node): ref Node
+{
+ return ref Node(ntype, left, right, nil, nil);
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+yyexca := array[] of {-1, 0,
+ 8, 17,
+ 10, 17,
+ 11, 17,
+ 12, 17,
+ 14, 17,
+ 15, 17,
+ 16, 17,
+ -2, 0,
+-1, 1,
+ 1, -1,
+ -2, 0,
+};
+YYNPROD: con 45;
+YYPRIVATE: con 57344;
+yytoknames: array of string;
+yystates: array of string;
+yydebug: con 0;
+YYLAST: con 93;
+yyact := array[] of {
+ 12, 10, 15, 4, 5, 40, 8, 11, 9, 7,
+ 30, 31, 54, 6, 50, 35, 34, 32, 33, 21,
+ 36, 38, 34, 41, 43, 22, 29, 3, 28, 13,
+ 14, 16, 17, 20, 37, 42, 1, 23, 45, 51,
+ 44, 47, 48, 18, 39, 19, 41, 43, 56, 30,
+ 31, 46, 58, 57, 59, 60, 49, 13, 14, 16,
+ 17, 53, 13, 14, 16, 17, 2, 52, 0, 16,
+ 17, 18, 27, 19, 16, 17, 18, 52, 19, 0,
+ 26, 18, 0, 19, 24, 25, 18, 26, 19, 0,
+ 55, 24, 25,
+};
+yypact := array[] of {
+ 25,-1000, 11, 11, 69, 58, 18, 14,-1000, 58,
+ 58,-1000, 5,-1000, 68,-1000,-1000, 68,-1000, 58,
+-1000,-1000,-1000,-1000,-1000,-1000, 58,-1000, 58,-1000,
+ -1,-1000,-1000, 68,-1000, -1,-1000, -5, 63,-1000,
+ -9, 76, 58,-1000, 18, 14, 53,-1000, 58, 63,
+-1000, -1,-1000, 53,-1000,-1000,-1000,-1000,-1000, -1,
+-1000,
+};
+yypgo := array[] of {
+ 0, 1, 0, 44, 8, 6, 36, 7, 35, 4,
+ 9, 2, 66, 5, 34, 13, 3, 33, 21,
+};
+yyr1 := array[] of {
+ 0, 6, 6, 17, 17, 12, 12, 13, 13, 9,
+ 9, 8, 8, 16, 16, 15, 15, 10, 10, 10,
+ 5, 5, 5, 5, 7, 7, 7, 1, 1, 4,
+ 4, 4, 14, 14, 3, 3, 3, 2, 2, 11,
+ 11, 11, 11, 18, 18,
+};
+yyr2 := array[] of {
+ 0, 2, 2, 1, 1, 1, 2, 1, 2, 2,
+ 2, 1, 2, 1, 3, 1, 3, 0, 1, 4,
+ 1, 2, 1, 1, 3, 3, 2, 1, 2, 1,
+ 2, 2, 1, 2, 2, 3, 3, 1, 4, 1,
+ 2, 3, 3, 0, 2,
+};
+yychk := array[] of {
+-1000, -6, -12, 2, -16, -9, -15, -10, -5, -4,
+ -1, -7, -2, 4, 5, -11, 6, 7, 18, 20,
+ -17, 8, 14, -17, 15, 16, 11, -12, 10, 12,
+ -2, -1, -5, 13, 17, -2, -11, -14, -18, -3,
+ -13, -16, -8, -9, -15, -10, -18, -7, -4, -18,
+ 19, -2, 14, -18, 21, 14, -13, -5, -11, -2,
+ -1,
+};
+yydef := array[] of {
+ -2, -2, 0, 0, 5, 17, 13, 15, 18, 20,
+ 22, 23, 29, 27, 0, 37, 39, 0, 43, 17,
+ 1, 3, 4, 2, 9, 10, 17, 6, 17, 43,
+ 30, 31, 21, 26, 43, 28, 40, 0, 32, 43,
+ 0, 7, 17, 11, 14, 16, 0, 24, 25, 0,
+ 41, 34, 44, 33, 42, 12, 8, 19, 38, 35,
+ 36,
+};
+yytok1 := array[] of {
+ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 14, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 16, 3,
+ 18, 19, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 15,
+ 3, 13, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 17, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 20, 12, 21,
+};
+yytok2 := array[] of {
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+};
+yytok3 := array[] of {
+ 0
+};
+
+YYSys: module
+{
+ FD: adt
+ {
+ fd: int;
+ };
+ fildes: fn(fd: int): ref FD;
+ fprint: fn(fd: ref FD, s: string, *): int;
+};
+
+yysys: YYSys;
+yystderr: ref YYSys->FD;
+
+YYFLAG: con -1000;
+
+
+yytokname(yyc: int): string
+{
+ if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil)
+ return yytoknames[yyc-1];
+ return "<"+string yyc+">";
+}
+
+yystatname(yys: int): string
+{
+ if(yys >= 0 && yys < len yystates && yystates[yys] != nil)
+ return yystates[yys];
+ return "<"+string yys+">\n";
+}
+
+yylex1(yylex: ref YYLEX): int
+{
+ c : int;
+ yychar := yylex.lex();
+ if(yychar <= 0)
+ c = yytok1[0];
+ else if(yychar < len yytok1)
+ c = yytok1[yychar];
+ else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2)
+ c = yytok2[yychar-YYPRIVATE];
+ else{
+ n := len yytok3;
+ c = 0;
+ for(i := 0; i < n; i+=2) {
+ if(yytok3[i+0] == yychar) {
+ c = yytok3[i+1];
+ break;
+ }
+ }
+ if(c == 0)
+ c = yytok2[1]; # unknown char
+ }
+ if(yydebug >= 3)
+ yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c));
+ return c;
+}
+
+YYS: adt
+{
+ yyv: YYSTYPE;
+ yys: int;
+};
+
+yyparse(yylex: ref YYLEX): int
+{
+ if(yydebug >= 1 && yysys == nil) {
+ yysys = load YYSys "$Sys";
+ yystderr = yysys->fildes(2);
+ }
+
+ yys := array[YYMAXDEPTH] of YYS;
+
+ yyval: YYSTYPE;
+ yystate := 0;
+ yychar := -1;
+ yynerrs := 0; # number of errors
+ yyerrflag := 0; # error recovery flag
+ yyp := -1;
+ yyn := 0;
+
+yystack:
+ for(;;){
+ # put a state and value onto the stack
+ if(yydebug >= 4)
+ yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+ yyp++;
+ if(yyp >= len yys)
+ yys = (array[len yys * 2] of YYS)[0:] = yys;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = yyval;
+
+ for(;;){
+ yyn = yypact[yystate];
+ if(yyn > YYFLAG) { # simple state
+ if(yychar < 0)
+ yychar = yylex1(yylex);
+ yyn += yychar;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yyn = yyact[yyn];
+ if(yychk[yyn] == yychar) { # valid shift
+ yychar = -1;
+ yyp++;
+ if(yyp >= len yys)
+ yys = (array[len yys * 2] of YYS)[0:] = yys;
+ yystate = yyn;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = yylex.lval;
+ if(yyerrflag > 0)
+ yyerrflag--;
+ if(yydebug >= 4)
+ yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+ continue;
+ }
+ }
+ }
+
+ # default state action
+ yyn = yydef[yystate];
+ if(yyn == -2) {
+ if(yychar < 0)
+ yychar = yylex1(yylex);
+
+ # look through exception table
+ for(yyxi:=0;; yyxi+=2)
+ if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate)
+ break;
+ for(yyxi += 2;; yyxi += 2) {
+ yyn = yyexca[yyxi];
+ if(yyn < 0 || yyn == yychar)
+ break;
+ }
+ yyn = yyexca[yyxi+1];
+ if(yyn < 0){
+ yyn = 0;
+ break yystack;
+ }
+ }
+
+ if(yyn != 0)
+ break;
+
+ # error ... attempt to resume parsing
+ if(yyerrflag == 0) { # brand new error
+ yylex.error("syntax error");
+ yynerrs++;
+ if(yydebug >= 1) {
+ yysys->fprint(yystderr, "%s", yystatname(yystate));
+ yysys->fprint(yystderr, "saw %s\n", yytokname(yychar));
+ }
+ }
+
+ if(yyerrflag != 3) { # incompletely recovered error ... try again
+ yyerrflag = 3;
+
+ # find a state where "error" is a legal shift action
+ while(yyp >= 0) {
+ yyn = yypact[yys[yyp].yys] + YYERRCODE;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yystate = yyact[yyn]; # simulate a shift of "error"
+ if(yychk[yystate] == YYERRCODE)
+ continue yystack;
+ }
+
+ # the current yyp has no shift onn "error", pop stack
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n",
+ yys[yyp].yys, yys[yyp-1].yys );
+ yyp--;
+ }
+ # there is no state on the stack with an error shift ... abort
+ yyn = 1;
+ break yystack;
+ }
+
+ # no shift yet; clobber input char
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar));
+ if(yychar == YYEOFCODE) {
+ yyn = 1;
+ break yystack;
+ }
+ yychar = -1;
+ # try again in the same state
+ }
+
+ # reduction by production yyn
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+ yypt := yyp;
+ yyp -= yyr2[yyn];
+ yym := yyn;
+
+ # consult goto table to find next state
+ yyn = yyr1[yyn];
+ yyg := yypgo[yyn];
+ yyj := yyg + yys[yyp].yys + 1;
+
+ if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+ yystate = yyact[yyg];
+ case yym {
+
+1=>
+{yylex.lval.node = yys[yypt-1].yyv.node; return 0;}
+2=>
+{yylex.lval.node = nil; return 0;}
+5=>
+yyval.node = yys[yyp+1].yyv.node;
+6=>
+{yyval.node = mkseq(yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
+7=>
+yyval.node = yys[yyp+1].yyv.node;
+8=>
+{yyval.node = mkseq(yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
+9=>
+{yyval.node = yys[yypt-1].yyv.node; }
+10=>
+{yyval.node = ref Node(n_NOWAIT, yys[yypt-1].yyv.node, nil, nil, nil); }
+11=>
+yyval.node = yys[yyp+1].yyv.node;
+12=>
+{yyval.node = yys[yypt-1].yyv.node; }
+13=>
+yyval.node = yys[yyp+1].yyv.node;
+14=>
+{
+ yyval.node = mk(n_ADJ,
+ mk(n_ADJ,
+ ref Node(n_WORD,nil,nil,"or",nil),
+ mk(n_BLOCK, yys[yypt-2].yyv.node, nil)
+ ),
+ mk(n_BLOCK,yys[yypt-0].yyv.node,nil)
+ );
+ }
+15=>
+yyval.node = yys[yyp+1].yyv.node;
+16=>
+{
+ yyval.node = mk(n_ADJ,
+ mk(n_ADJ,
+ ref Node(n_WORD,nil,nil,"and",nil),
+ mk(n_BLOCK, yys[yypt-2].yyv.node, nil)
+ ),
+ mk(n_BLOCK,yys[yypt-0].yyv.node,nil)
+ );
+ }
+17=>
+{yyval.node = nil;}
+18=>
+yyval.node = yys[yyp+1].yyv.node;
+19=>
+{yyval.node = ref Node(n_PIPE, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node, nil, yys[yypt-2].yyv.redir); }
+20=>
+yyval.node = yys[yyp+1].yyv.node;
+21=>
+{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
+22=>
+yyval.node = yys[yyp+1].yyv.node;
+23=>
+yyval.node = yys[yyp+1].yyv.node;
+24=>
+{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
+25=>
+{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
+26=>
+{yyval.node = mk(yys[yypt-0].yyv.optype, yys[yypt-1].yyv.node, nil); }
+27=>
+{yyval.node = ref Node(n_DUP, nil, nil, nil, yys[yypt-0].yyv.redir); }
+28=>
+{yyval.node = ref Node(n_REDIR, yys[yypt-0].yyv.node, nil, nil, yys[yypt-1].yyv.redir); }
+29=>
+yyval.node = yys[yyp+1].yyv.node;
+30=>
+{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
+31=>
+{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
+32=>
+{yyval.node = nil;}
+33=>
+yyval.node = yys[yyp+1].yyv.node;
+34=>
+{yyval.node = yys[yypt-0].yyv.node; }
+35=>
+{yyval.node = mk(n_ADJ, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
+36=>
+{yyval.node = mk(n_ADJ, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
+37=>
+yyval.node = yys[yyp+1].yyv.node;
+38=>
+{yyval.node = mk(n_CONCAT, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node); }
+39=>
+{yyval.node = ref Node(n_WORD, nil, nil, yys[yypt-0].yyv.word, nil); }
+40=>
+{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-0].yyv.node, nil); }
+41=>
+{yyval.node = mk(n_LIST, yys[yypt-1].yyv.node, nil); }
+42=>
+{yyval.node = mk(n_BLOCK, yys[yypt-1].yyv.node, nil); }
+ }
+ }
+
+ return yyn;
+}
diff --git a/appl/cmd/sh/sh.y b/appl/cmd/sh/sh.y
new file mode 100644
index 00000000..083357c1
--- /dev/null
+++ b/appl/cmd/sh/sh.y
@@ -0,0 +1,2592 @@
+%{
+include "sys.m";
+ sys: Sys;
+ sprint: import sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+include "string.m";
+ str: String;
+include "filepat.m";
+ filepat: Filepat;
+include "env.m";
+ env: Env;
+include "sh.m";
+ myself: Sh;
+ myselfbuiltin: Shellbuiltin;
+
+YYSTYPE: adt {
+ node: ref Node;
+ word: string;
+
+ redir: ref Redir;
+ optype: int;
+};
+
+YYLEX: adt {
+ lval: YYSTYPE;
+ err: string; # if error has occurred
+ errline: int; # line it occurred on.
+ path: string; # name of file that's being read.
+
+ # free caret state
+ wasdollar: int;
+ atendword: int;
+ eof: int;
+ cbuf: array of int; # last chars read
+ ncbuf: int; # number of chars in cbuf
+
+ f: ref Bufio->Iobuf;
+ s: string;
+ strpos: int; # string pos/cbuf index
+
+ linenum: int;
+ prompt: string;
+ lastnl: int;
+
+ initstring: fn(s: string): ref YYLEX;
+ initfile: fn(fd: ref Sys->FD, path: string): ref YYLEX;
+ lex: fn(l: self ref YYLEX): int;
+ error: fn(l: self ref YYLEX, err: string);
+ getc: fn(l: self ref YYLEX): int;
+ ungetc: fn(l: self ref YYLEX);
+
+ EOF: con -1;
+};
+
+Options: adt {
+ lflag,
+ nflag: int;
+ ctxtflags: int;
+ carg: string;
+};
+
+%}
+
+%module Sh {
+ # module definition is in shell.m
+}
+
+%token DUP REDIR WORD OP END ERROR ANDAND OROR
+
+%type <node> redir word nlsimple simple cmd shell assign
+%type <node> cmdsan cmdsa pipe comword line body list and2 or2
+%type <redir> DUP REDIR '|'
+%type <optype> OP '='
+%type <word> WORD
+
+%start shell
+%%
+shell: line end {yylex.lval.node = $line; return 0;}
+ | error end {yylex.lval.node = nil; return 0;}
+end: END
+ | '\n'
+line: or2
+ | cmdsa line {$$ = mkseq($cmdsa, $line); }
+body: or2
+ | cmdsan body {$$ = mkseq($cmdsan, $body); }
+cmdsa: or2 ';' {$$ = $or2; }
+ | or2 '&' {$$ = ref Node(n_NOWAIT, $or2, nil, nil, nil); }
+cmdsan: cmdsa
+ | or2 '\n' {$$ = $or2; }
+or2: and2
+ | or2 OROR and2 {
+ $$ = mk(n_ADJ,
+ mk(n_ADJ,
+ ref Node(n_WORD,nil,nil,"or",nil),
+ mk(n_BLOCK, $or2, nil)
+ ),
+ mk(n_BLOCK,$and2,nil)
+ );
+ }
+and2: pipe
+ | and2 ANDAND pipe {
+ $$ = mk(n_ADJ,
+ mk(n_ADJ,
+ ref Node(n_WORD,nil,nil,"and",nil),
+ mk(n_BLOCK, $and2, nil)
+ ),
+ mk(n_BLOCK,$pipe,nil)
+ );
+ }
+pipe: {$$ = nil;}
+ | cmd
+ | pipe '|' optnl cmd {$$ = ref Node(n_PIPE, $pipe, $cmd, nil, $2); }
+cmd: simple
+ | redir cmd {$$ = mk(n_ADJ, $redir, $cmd); }
+ | redir
+ | assign
+assign: word '=' assign {$$ = mk($2, $word, $assign); }
+ | word '=' simple {$$ = mk($2, $word, $simple); }
+ | word '=' {$$ = mk($2, $word, nil); }
+redir: DUP {$$ = ref Node(n_DUP, nil, nil, nil, $DUP); }
+ | REDIR word {$$ = ref Node(n_REDIR, $word, nil, nil, $REDIR); }
+simple: word
+ | simple word {$$ = mk(n_ADJ, $simple, $word); }
+ | simple redir {$$ = mk(n_ADJ, $simple, $redir); }
+list: optnl {$$ = nil;}
+ | nlsimple optnl
+nlsimple: optnl word {$$ = $word; }
+ | nlsimple optnl word {$$ = mk(n_ADJ, $nlsimple, $word); }
+ | nlsimple optnl redir {$$ = mk(n_ADJ, $nlsimple, $redir); }
+word: comword
+ | word '^' optnl comword {$$ = mk(n_CONCAT, $word, $comword); }
+comword: WORD {$$ = ref Node(n_WORD, nil, nil, $WORD, nil); }
+ | OP comword {$$ = mk($OP, $comword, nil); }
+ | '(' list ')' {$$ = mk(n_LIST, $list, nil); }
+ | '{' body '}' {$$ = mk(n_BLOCK, $body, nil); }
+optnl: # null
+ | optnl '\n'
+%%
+
+EPERM: con "permission denied";
+EPIPE: con "write on closed pipe";
+
+#SHELLRC: con "lib/profile";
+LIBSHELLRC: con "/lib/sh/profile";
+BUILTINPATH: con "/dis/sh";
+
+DEBUG: con 0;
+
+ENVSEP: con 0; # word seperator in external environment
+ENVHASHSIZE: con 7; # XXX profile usage of this...
+OAPPEND: con 16r80000; # make sure this doesn't clash with O* constants in sys.m
+OMASK: con 7;
+
+usage()
+{
+ sys->fprint(stderr(), "usage: sh [-ilexn] [-c command] [file [arg...]]\n");
+ raise "fail:usage";
+}
+
+badmodule(path: string)
+{
+ sys->fprint(sys->fildes(2), "sh: cannot load %s: %r\n", path);
+ raise "fail:bad module" ;
+}
+
+initialise()
+{
+ if (sys == nil) {
+ sys = load Sys Sys->PATH;
+
+ filepat = load Filepat Filepat->PATH;
+ if (filepat == nil) badmodule(Filepat->PATH);
+
+ str = load String String->PATH;
+ if (str == nil) badmodule(String->PATH);
+
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) badmodule(Bufio->PATH);
+
+ myself = load Sh "$self";
+ if (myself == nil) badmodule("$self(Sh)");
+
+ myselfbuiltin = load Shellbuiltin "$self";
+ if (myselfbuiltin == nil) badmodule("$self(Shellbuiltin)");
+
+ env = load Env Env->PATH;
+ }
+}
+blankopts: Options;
+init(drawcontext: ref Draw->Context, argv: list of string)
+{
+ initialise();
+ opts := blankopts;
+ if (argv != nil) {
+ if ((hd argv)[0] == '-')
+ opts.lflag++;
+ argv = tl argv;
+ }
+
+ interactive := 0;
+loop: while (argv != nil && hd argv != nil && (hd argv)[0] == '-') {
+ for (i := 1; i < len hd argv; i++) {
+ c := (hd argv)[i];
+ case c {
+ 'i' =>
+ interactive = Context.INTERACTIVE;
+ 'l' =>
+ opts.lflag++; # login (read $home/lib/profile)
+ 'n' =>
+ opts.nflag++; # don't fork namespace
+ 'e' =>
+ opts.ctxtflags |= Context.ERROREXIT;
+ 'x' =>
+ opts.ctxtflags |= Context.EXECPRINT;
+ 'c' =>
+ arg: string;
+ if (i < len hd argv - 1) {
+ arg = (hd argv)[i + 1:];
+ } else if (tl argv == nil || hd tl argv == "") {
+ usage();
+ } else {
+ arg = hd tl argv;
+ argv = tl argv;
+ }
+ argv = tl argv;
+ opts.carg = arg;
+ continue loop;
+ }
+ }
+ argv = tl argv;
+ }
+
+ sys->pctl(Sys->FORKFD, nil);
+ if (!opts.nflag)
+ sys->pctl(Sys->FORKNS, nil);
+ ctxt := Context.new(drawcontext);
+ ctxt.setoptions(opts.ctxtflags, 1);
+ if (opts.carg != nil) {
+ status := ctxt.run(stringlist2list("{" + opts.carg + "}" :: argv), !interactive);
+ if (!interactive) {
+ if (status != nil)
+ raise "fail:" + status;
+ exit;
+ }
+ setstatus(ctxt, status);
+ }
+
+ # if login shell, run standard init script
+ if (opts.lflag)
+ runscript(ctxt, LIBSHELLRC, nil, 0);
+
+ if (argv == nil) {
+# if (opts.lflag)
+# runscript(ctxt, SHELLRC, nil, 0);
+ if (isconsole(sys->fildes(0)))
+ interactive |= ctxt.INTERACTIVE;
+ ctxt.setoptions(interactive, 1);
+ runfile(ctxt, sys->fildes(0), "stdin", nil);
+ } else {
+ ctxt.setoptions(interactive, 1);
+ runscript(ctxt, hd argv, stringlist2list(tl argv), 1);
+ }
+}
+
+# XXX should this refuse to parse a non braced-block?
+parse(s: string): (ref Node, string)
+{
+ initialise();
+
+ lex := YYLEX.initstring(s);
+
+ return doparse(lex, "", 0);
+}
+
+system(drawctxt: ref Draw->Context, cmd: string): string
+{
+ initialise();
+ {
+ (n, err) := parse(cmd);
+ if (err != nil)
+ return err;
+ if (n == nil)
+ return nil;
+ return Context.new(drawctxt).run(ref Listnode(n, nil) :: nil, 0);
+ } exception e {
+ "fail:*" =>
+ return e[5:];
+ }
+}
+
+run(drawctxt: ref Draw->Context, argv: list of string): string
+{
+ initialise();
+ {
+ return Context.new(drawctxt).run(stringlist2list(argv), 0);
+ } exception e {
+ "fail:*" =>
+ return e[5:];
+ }
+}
+
+isconsole(fd: ref Sys->FD): int
+{
+ (ok1, d1) := sys->fstat(fd);
+ (ok2, d2) := sys->stat("/dev/cons");
+ if (ok1 < 0 || ok2 < 0)
+ return 0;
+ return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path;
+}
+
+# run commands from file _path_
+runscript(ctxt: ref Context, path: string, args: list of ref Listnode, reporterr: int)
+{
+ {
+ fd := sys->open(path, Sys->OREAD);
+ if (fd != nil)
+ runfile(ctxt, fd, path, args);
+ else if (reporterr)
+ ctxt.fail("bad script path", sys->sprint("sh: cannot open %s: %r", path));
+ } exception e {
+ "fail:*" =>
+ if(!reporterr)
+ return;
+ raise;
+ }
+}
+
+# run commands from the opened file fd.
+# if interactive is non-zero, print a command prompt at appropriate times.
+runfile(ctxt: ref Context, fd: ref Sys->FD, path: string, args: list of ref Listnode)
+{
+ ctxt.push();
+ {
+ ctxt.setlocal("0", stringlist2list(path :: nil));
+ ctxt.setlocal("*", args);
+ lex := YYLEX.initfile(fd, path);
+ if (DEBUG) debug(sprint("parse(interactive == %d)", (ctxt.options() & ctxt.INTERACTIVE) != 0));
+ prompt := "" :: "" :: nil;
+ laststatus: string;
+ while (!lex.eof) {
+ interactive := ctxt.options() & ctxt.INTERACTIVE;
+ if (interactive) {
+ prompt = list2stringlist(ctxt.get("prompt"));
+ if (prompt == nil)
+ prompt = "; " :: "" :: nil;
+
+ sys->fprint(stderr(), "%s", hd prompt);
+ if (tl prompt == nil) {
+ prompt = hd prompt :: "" :: nil;
+ }
+ }
+ (n, err) := doparse(lex, hd tl prompt, !interactive);
+ if (err != nil) {
+ sys->fprint(stderr(), "sh: %s\n", err);
+ if (!interactive)
+ raise "fail:parse error";
+ } else if (n != nil) {
+ if (interactive) {
+ {
+ laststatus = walk(ctxt, n, 0);
+ } exception e2 {
+ "fail:*" =>
+ laststatus = e2[5:];
+ }
+ } else
+ laststatus = walk(ctxt, n, 0);
+ setstatus(ctxt, laststatus);
+ if ((ctxt.options() & ctxt.ERROREXIT) && laststatus != nil)
+ break;
+ }
+ }
+ if (laststatus != nil)
+ raise "fail:" + laststatus;
+ ctxt.pop();
+ }
+ exception e {
+ "fail:*" =>
+ ctxt.pop();
+ raise;
+ }
+}
+
+nonexistent(e: string): int
+{
+ errs := array[] of {"does not exist", "directory entry not found"};
+ for (i := 0; i < len errs; i++){
+ j := len errs[i];
+ if (j <= len e && e[len e-j:] == errs[i])
+ return 1;
+ }
+ return 0;
+}
+
+Redirword: adt {
+ fd: ref Sys->FD;
+ w: string;
+ r: Redir;
+};
+
+Redirlist: adt {
+ r: list of Redirword;
+};
+
+# a hack so that the structure of walk() doesn't change much
+# to accomodate echo|wc&
+# transform the above into {echo|wc}$*&
+# which should amount to exactly the same thing.
+pipe2cmd(n: ref Node): ref Node
+{
+ if (n == nil || n.ntype != n_PIPE)
+ return n;
+ return mk(n_ADJ, mk(n_BLOCK,n,nil), mk(n_VAR,ref Node(n_WORD,nil,nil,"*",nil),nil));
+}
+
+# walk a node tree.
+# last is non-zero if this walk is the last action
+# this shell process will take before exiting (i.e. redirections
+# don't require a new process to avoid side effects)
+walk(ctxt: ref Context, n: ref Node, last: int): string
+{
+ if (DEBUG) debug(sprint("walking: %s", cmd2string(n)));
+ # avoid tail recursion stack explosion
+ while (n != nil && n.ntype == n_SEQ) {
+ status := walk(ctxt, n.left, 0);
+ if (ctxt.options() & ctxt.ERROREXIT && status != nil)
+ raise "fail:" + status;
+ setstatus(ctxt, status);
+ n = n.right;
+ }
+ if (n == nil)
+ return nil;
+ case (n.ntype) {
+ n_PIPE =>
+ return waitfor(ctxt, walkpipeline(ctxt, n, nil, -1));
+ n_ASSIGN or n_LOCAL =>
+ assign(ctxt, n);
+ return nil;
+ * =>
+ bg := 0;
+ if (n.ntype == n_NOWAIT) {
+ bg = 1;
+ n = pipe2cmd(n.left);
+ }
+
+ redirs := ref Redirlist(nil);
+ line := glob(glom(ctxt, n, redirs, nil));
+
+ if (bg) {
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 1, line, redirs, startchan);
+ (pid, nil) := <-startchan;
+ redirs = nil;
+ if (DEBUG) debug("started background process "+ string pid);
+ ctxt.set("apid", ref Listnode(nil, string pid) :: nil);
+ return nil;
+ } else {
+ return runsync(ctxt, line, redirs, last);
+ }
+ }
+}
+
+assign(ctxt: ref Context, n: ref Node): list of ref Listnode
+{
+ redirs := ref Redirlist;
+ val: list of ref Listnode;
+ if (n.right != nil && (n.right.ntype == n_ASSIGN || n.right.ntype == n_LOCAL))
+ val = assign(ctxt, n.right);
+ else
+ val = glob(glom(ctxt, n.right, redirs, nil));
+ vars := glom(ctxt, n.left, redirs, nil);
+ if (vars == nil)
+ ctxt.fail("bad assign", "sh: nil variable name");
+ if (redirs.r != nil)
+ ctxt.fail("bad assign", "sh: redirections not allowed in assignment");
+ tval := val;
+ for (; vars != nil; vars = tl vars) {
+ vname := deglob((hd vars).word);
+ if (vname == nil)
+ ctxt.fail("bad assign", "sh: bad variable name");
+ v: list of ref Listnode = nil;
+ if (tl vars == nil)
+ v = tval;
+ else if (tval != nil)
+ v = hd tval :: nil;
+ if (n.ntype == n_ASSIGN)
+ ctxt.set(vname, v);
+ else
+ ctxt.setlocal(vname, v);
+ if (tval != nil)
+ tval = tl tval;
+ }
+ return val;
+}
+
+walkpipeline(ctxt: ref Context, n: ref Node, wrpipe: ref Sys->FD, wfdno: int): list of int
+{
+ if (n == nil)
+ return nil;
+
+ fds := array[2] of ref Sys->FD;
+ pids: list of int;
+ rfdno := -1;
+ if (n.ntype == n_PIPE) {
+ if (sys->pipe(fds) == -1)
+ ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r"));
+ nwfdno := -1;
+ if (n.redir != nil) {
+ (fd1, fd2) := (n.redir.fd2, n.redir.fd1);
+ if (fd2 == -1)
+ (fd1, fd2) = (fd2, fd1);
+ (nwfdno, rfdno) = (fd2, fd1);
+ }
+ pids = walkpipeline(ctxt, n.left, fds[1], nwfdno);
+ fds[1] = nil;
+ n = n.right;
+ }
+ r := ref Redirlist(nil);
+ rlist := glob(glom(ctxt, n, r, nil));
+ if (fds[0] != nil) {
+ if (rfdno == -1)
+ rfdno = 0;
+ r.r = Redirword(fds[0], nil, Redir(Sys->OREAD, rfdno, -1)) :: r.r;
+ }
+ if (wrpipe != nil) {
+ if (wfdno == -1)
+ wfdno = 1;
+ r.r = Redirword(wrpipe, nil, Redir(Sys->OWRITE, wfdno, -1)) :: r.r;
+ }
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 1, rlist, r, startchan);
+ (pid, nil) := <-startchan;
+ if (DEBUG) debug("started pipe process "+string pid);
+ return pid :: pids;
+}
+
+makeredir(f: string, mode: int, fd: int): Redirword
+{
+ return Redirword(nil, f, Redir(mode, fd, -1));
+}
+
+# expand substitution operators in a node list
+glom(ctxt: ref Context, n: ref Node, redirs: ref Redirlist, onto: list of ref Listnode)
+ : list of ref Listnode
+{
+ if (n == nil) return nil;
+
+ if (n.ntype != n_ADJ)
+ return listjoin(glomoperation(ctxt, n, redirs), onto);
+
+ nlist := glom(ctxt, n.right, redirs, onto);
+
+ if (n.left.ntype != n_ADJ) {
+ # if it's a terminal node
+ nlist = listjoin(glomoperation(ctxt, n.left, redirs), nlist);
+ } else
+ nlist = glom(ctxt, n.left, redirs, nlist);
+ return nlist;
+}
+
+listjoin(left, right: list of ref Listnode): list of ref Listnode
+{
+ l: list of ref Listnode;
+ for (; left != nil; left = tl left)
+ l = hd left :: l;
+ for (; l != nil; l = tl l)
+ right = hd l :: right;
+ return right;
+}
+
+glomoperation(ctxt: ref Context, n: ref Node, redirs: ref Redirlist): list of ref Listnode
+{
+ if (n == nil)
+ return nil;
+
+ nlist: list of ref Listnode;
+ case n.ntype {
+ n_WORD =>
+ nlist = ref Listnode(nil, n.word) :: nil;
+ n_REDIR =>
+ wlist := glob(glom(ctxt, n.left, ref Redirlist(nil), nil));
+ if (len wlist != 1 || (hd wlist).word == nil)
+ ctxt.fail("bad redir", "sh: single redirection operand required");
+
+ # add to redir list
+ redirs.r = Redirword(nil, (hd wlist).word, *n.redir) :: redirs.r;
+ n_DUP =>
+ redirs.r = Redirword(nil, "", *n.redir) :: redirs.r;
+ n_LIST =>
+ nlist = glom(ctxt, n.left, redirs, nil);
+ n_CONCAT =>
+ nlist = concat(ctxt, glom(ctxt, n.left, redirs, nil), glom(ctxt, n.right, redirs, nil));
+ n_VAR or n_SQUASH or n_COUNT =>
+ arg := glom(ctxt, n.left, ref Redirlist(nil), nil);
+ if (len arg == 1 && (hd arg).cmd != nil)
+ nlist = subsbuiltin(ctxt, (hd arg).cmd.left);
+ else if (len arg != 1 || (hd arg).word == nil)
+ ctxt.fail("bad $ arg", "sh: bad variable name");
+ else
+ nlist = ctxt.get(deglob((hd arg).word));
+ case n.ntype {
+ n_VAR =>;
+ n_COUNT =>
+ nlist = ref Listnode(nil, string len nlist) :: nil;
+ n_SQUASH =>
+ # XXX could squash with first char of $ifs, perhaps
+ nlist = ref Listnode(nil, squash(list2stringlist(nlist), " ")) :: nil;
+ }
+ n_BQ or n_BQ2 =>
+ arg := glom(ctxt, n.left, ref Redirlist(nil), nil);
+ seps := "";
+ if (n.ntype == n_BQ) {
+ seps = squash(list2stringlist(ctxt.get("ifs")), "");
+ if (seps == nil)
+ seps = " \t\n\r";
+ }
+ (nlist, nil) = bq(ctxt, glob(arg), seps);
+ n_BLOCK =>
+ nlist = ref Listnode(n, "") :: nil;
+ n_ASSIGN or n_LOCAL =>
+ ctxt.fail("bad assign", "sh: assignment in invalid context");
+ * =>
+ panic("bad node type "+string n.ntype+" in glomop");
+ }
+ return nlist;
+}
+
+subsbuiltin(ctxt: ref Context, n: ref Node): list of ref Listnode
+{
+ if (n == nil || n.ntype == n_SEQ ||
+ n.ntype == n_PIPE || n.ntype == n_NOWAIT)
+ ctxt.fail("bad $ arg", "sh: invalid argument to ${} operator");
+ r := ref Redirlist;
+ cmd := glob(glom(ctxt, n, r, nil));
+ if (r.r != nil)
+ ctxt.fail("bad $ arg", "sh: redirection not allowed in substitution");
+ r = nil;
+ if (cmd == nil || (hd cmd).word == nil || (hd cmd).cmd != nil)
+ ctxt.fail("bad $ arg", "sh: bad builtin name");
+
+ (nil, bmods) := findbuiltin(ctxt.env.sbuiltins, (hd cmd).word);
+ if (bmods == nil)
+ ctxt.fail("builtin not found",
+ sys->sprint("sh: builtin %s not found", (hd cmd).word));
+ return (hd bmods)->runsbuiltin(ctxt, myself, cmd);
+}
+
+#
+# backquote substitution (could be done in a builtin)
+#
+
+getbq(nil: ref Context, fd: ref Sys->FD, seps: string): list of ref Listnode
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ buflen := 0;
+ while ((n := sys->read(fd, buf[buflen:], len buf - buflen)) > 0) {
+ buflen += n;
+ if (buflen == len buf) {
+ nbuf := array[buflen * 2] of byte;
+ nbuf[0:] = buf[0:];
+ buf = nbuf;
+ }
+ }
+ l: list of string;
+ if (seps != nil)
+ (nil, l) = sys->tokenize(string buf[0:buflen], seps);
+ else
+ l = string buf[0:buflen] :: nil;
+ buf = nil;
+ return stringlist2list(l);
+}
+
+bq(ctxt: ref Context, cmd: list of ref Listnode, seps: string): (list of ref Listnode, string)
+{
+ fds := array[2] of ref Sys->FD;
+ if (sys->pipe(fds) == -1)
+ ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r"));
+
+ r := rdir(fds[1]);
+ fds[1] = nil;
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 0, cmd, r, startchan);
+ (exepid, exprop) := <-startchan;
+ r = nil;
+ bqlist := getbq(ctxt, fds[0], seps);
+ waitfor(ctxt, exepid :: nil);
+ if (exprop.name != nil)
+ raise exprop.name;
+ return (bqlist, nil);
+}
+
+# get around compiler temporaries bug
+rdir(fd: ref Sys->FD): ref Redirlist
+{
+ return ref Redirlist(Redirword(fd, nil, Redir(Sys->OWRITE, 1, -1)) :: nil);
+}
+
+#
+# concatenation
+#
+
+concatwords(p1, p2: ref Listnode): ref Listnode
+{
+ if (p1.word == nil && p1.cmd != nil)
+ p1.word = cmd2string(p1.cmd);
+ if (p2.word == nil && p2.cmd != nil)
+ p2.word = cmd2string(p2.cmd);
+ return ref Listnode(nil, p1.word + p2.word);
+}
+
+concat(ctxt: ref Context, nl1, nl2: list of ref Listnode): list of ref Listnode
+{
+ if (nl1 == nil || nl2 == nil) {
+ if (nl1 == nil && nl2 == nil)
+ return nil;
+ ctxt.fail("bad concatenation", "sh: null list in concatenation");
+ }
+
+ ret: list of ref Listnode;
+ if (tl nl1 == nil || tl nl2 == nil) {
+ for (p1 := nl1; p1 != nil; p1 = tl p1)
+ for (p2 := nl2; p2 != nil; p2 = tl p2)
+ ret = concatwords(hd p1, hd p2) :: ret;
+ } else {
+ if (len nl1 != len nl2)
+ ctxt.fail("bad concatenation", "sh: lists of differing sizes can't be concatenated");
+ while (nl1 != nil) {
+ ret = concatwords(hd nl1, hd nl2) :: ret;
+ (nl1, nl2) = (tl nl1, tl nl2);
+ }
+ }
+ return revlist(ret);
+}
+
+Expropagate: adt {
+ name: string;
+};
+
+# run an asynchronous process, first redirecting its I/O
+# as specified in _redirs_.
+# it sends its process ID down _startchan_ before executing.
+# it has to jump through one or two hoops to make sure
+# Sys->FD ref counting is done correctly. this code
+# is more sensitive than you might think.
+runasync(ctxt: ref Context, copyenv: int, argv: list of ref Listnode, redirs: ref Redirlist,
+ startchan: chan of (int, ref Expropagate))
+{
+ status: string;
+
+ pid := sys->pctl(sys->FORKFD, nil);
+ if (DEBUG) debug(sprint("in async (len redirs: %d)", len redirs.r));
+ ctxt = ctxt.copy(copyenv);
+ exprop := ref Expropagate;
+ {
+ newfdl := doredirs(ctxt, redirs);
+ redirs = nil;
+ if (newfdl != nil)
+ sys->pctl(Sys->NEWFD, newfdl);
+ # stop the old waitfd from holding the intermediate
+ # file descriptor group open.
+ ctxt.waitfd = waitfd();
+ # N.B. it's important that the sync is done here, not
+ # before doredirs, as otherwise there's some sort of
+ # race condition that leads to pipe non-completion.
+ startchan <-= (pid, exprop);
+ startchan = nil;
+ status = ctxt.run(argv, copyenv);
+ } exception e {
+ "fail:*" =>
+ exprop.name = e;
+ if (startchan != nil)
+ startchan <-= (pid, exprop);
+ raise e;
+ }
+ if (status != nil) {
+ # don't propagate bad status as an exception.
+ raise "fail:" + status;
+ }
+}
+
+# run a synchronous process
+runsync(ctxt: ref Context, argv: list of ref Listnode,
+ redirs: ref Redirlist, last: int): string
+{
+ if (DEBUG) debug(sys->sprint("in sync (len redirs: %d; last: %d)", len redirs.r, last));
+ if (redirs.r != nil && !last) {
+ # a new process is required to shield redirection side effects
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 0, argv, redirs, startchan);
+ (pid, exprop) := <-startchan;
+ redirs = nil;
+ r := waitfor(ctxt, pid :: nil);
+ if (exprop.name != nil)
+ raise exprop.name;
+ return r;
+ } else {
+ newfdl := doredirs(ctxt, redirs);
+ redirs = nil;
+ if (newfdl != nil)
+ sys->pctl(Sys->NEWFD, newfdl);
+ return ctxt.run(argv, last);
+ }
+}
+
+# path is prefixed with: "/", "#", "./" or "../"
+absolute(p: string): int
+{
+ if (len p < 2)
+ return 0;
+ if (p[0] == '/' || p[0] == '#')
+ return 1;
+ if (len p < 3 || p[0] != '.')
+ return 0;
+ if (p[1] == '/')
+ return 1;
+ if (p[1] == '.' && p[2] == '/')
+ return 1;
+ return 0;
+}
+
+runexternal(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ progname := (hd args).word;
+ disfile := 0;
+ if (len progname >= 4 && progname[len progname-4:] == ".dis")
+ disfile = 1;
+ pathlist: list of string;
+ if (absolute(progname))
+ pathlist = list of {""};
+ else if ((pl := ctxt.get("path")) != nil)
+ pathlist = list2stringlist(pl);
+ else
+ pathlist = list of {"/dis", "."};
+
+ err := "";
+ do {
+ path: string;
+ if (hd pathlist != "")
+ path = hd pathlist + "/" + progname;
+ else
+ path = progname;
+
+ npath := path;
+ if (!disfile)
+ npath += ".dis";
+ mod := load Command npath;
+ if (mod != nil) {
+ argv := list2stringlist(args);
+ export(ctxt.env.localenv);
+
+ if (last) {
+ {
+ sys->pctl(Sys->NEWFD, ctxt.keepfds);
+ mod->init(ctxt.drawcontext, argv);
+ exit;
+ } exception e {
+ EPIPE =>
+ return EPIPE;
+ "fail:*" =>
+ return e[5:];
+ }
+ }
+ extstart := chan of int;
+ spawn externalexec(mod, ctxt.drawcontext, argv, extstart, ctxt.keepfds);
+ pid := <-extstart;
+ if (DEBUG) debug("started external externalexec; pid is "+string pid);
+ return waitfor(ctxt, pid :: nil);
+ }
+ err = sys->sprint("%r");
+ if (nonexistent(err)) {
+ # try and run it as a shell script
+ if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) {
+ (ok, info) := sys->fstat(fd);
+ # make permission checking more accurate later
+ if (ok == 0 && (info.mode & Sys->DMDIR) == 0
+ && (info.mode & 8r111) != 0)
+ return runhashpling(ctxt, fd, path, tl args, last);
+ };
+ err = sys->sprint("%r");
+ }
+ pathlist = tl pathlist;
+ } while (pathlist != nil && nonexistent(err));
+ diagnostic(ctxt, sys->sprint("%s: %s", progname, err));
+ return err;
+}
+
+runhashpling(ctxt: ref Context, fd: ref Sys->FD,
+ path: string, argv: list of ref Listnode, last: int): string
+{
+ header := array[1024] of byte;
+ n := sys->read(fd, header, len header);
+ for (i := 0; i < n; i++)
+ if (header[i] == byte '\n')
+ break;
+ if (i == n || i < 3 || header[0] != byte('#') || header[1] != byte('!')) {
+ diagnostic(ctxt, "bad script header on " + path);
+ return "bad header";
+ }
+ (nil, args) := sys->tokenize(string header[2:i], " \t");
+ if (args == nil) {
+ diagnostic(ctxt, "empty header on " + path);
+ return "bad header";
+ }
+ header = nil;
+ fd = nil;
+ nargs: list of ref Listnode;
+ for (; args != nil; args = tl args)
+ nargs = ref Listnode(nil, hd args) :: nargs;
+ nargs = ref Listnode(nil, path) :: nargs;
+ for (; argv != nil; argv = tl argv)
+ nargs = hd argv :: nargs;
+ return runexternal(ctxt, revlist(nargs), last);
+}
+
+runblock(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ # block execute (we know that hd args represents a block)
+ cmd := (hd args).cmd;
+ if (cmd == nil) {
+ # parse block from first argument
+ lex := YYLEX.initstring((hd args).word);
+
+ err: string;
+ (cmd, err) = doparse(lex, "", 0);
+ if (cmd == nil)
+ ctxt.fail("parse error", "sh: "+err);
+
+ (hd args).cmd = cmd;
+ }
+ # now we've got a parsed block
+ ctxt.push();
+ {
+ ctxt.setlocal("0", hd args :: nil);
+ ctxt.setlocal("*", tl args);
+ if (cmd != nil && cmd.ntype == n_BLOCK)
+ cmd = cmd.left;
+ status := walk(ctxt, cmd, last);
+ ctxt.pop();
+ return status;
+ } exception e{
+ "fail:*" =>
+ ctxt.pop();
+ raise;
+ }
+}
+
+# return (ok, val) where ok is non-zero is builtin was found,
+# val is return status of builtin
+trybuiltin(ctxt: ref Context, args: list of ref Listnode, lseq: int)
+ : (int, string)
+{
+ (n, bmods) := findbuiltin(ctxt.env.builtins, (hd args).word);
+ if (bmods == nil)
+ return (0, nil);
+ return (1, (hd bmods)->runbuiltin(ctxt, myself, args, lseq));
+}
+
+keepfdstr(ctxt: ref Context): string
+{
+ s := "";
+ for (f := ctxt.keepfds; f != nil; f = tl f) {
+ s += string hd f;
+ if (tl f != nil)
+ s += ",";
+ }
+ return s;
+}
+
+externalexec(mod: Command,
+ drawcontext: ref Draw->Context, argv: list of string, startchan: chan of int, keepfds: list of int)
+{
+ if (DEBUG) debug(sprint("externalexec(%s,... [%d args])", hd argv, len argv));
+ sys->pctl(Sys->NEWFD, keepfds);
+ startchan <-= sys->pctl(0, nil);
+ {
+ mod->init(drawcontext, argv);
+ }
+ exception e{
+ EPIPE =>
+ raise "fail:" + EPIPE;
+ }
+}
+
+dup(ctxt: ref Context, fd1, fd2: int): int
+{
+ # shuffle waitfd out of the way if it's being attacked
+ if (ctxt.waitfd.fd == fd2) {
+ ctxt.waitfd = waitfd();
+ if (ctxt.waitfd.fd == fd2)
+ panic(sys->sprint("reopen of waitfd gave same fd (%d)", ctxt.waitfd.fd));
+ }
+ return sys->dup(fd1, fd2);
+}
+
+# with thanks to tiny/sh.b
+# return error status if redirs failed
+doredirs(ctxt: ref Context, redirs: ref Redirlist): list of int
+{
+ if (redirs.r == nil)
+ return nil;
+ keepfds := ctxt.keepfds;
+ rl := redirs.r;
+ redirs = nil;
+ for (; rl != nil; rl = tl rl) {
+ (rfd, path, (mode, fd1, fd2)) := hd rl;
+ if (path == nil && rfd == nil) {
+ # dup
+ if (fd1 == -1 || fd2 == -1)
+ ctxt.fail("bad redir", "sh: invalid dup");
+
+ if (dup(ctxt, fd2, fd1) == -1)
+ ctxt.fail("bad redir", sys->sprint("sh: cannot dup: %r"));
+ keepfds = fd1 :: keepfds;
+ continue;
+ }
+ # redir
+ if (fd1 == -1) {
+ if ((mode & OMASK) == Sys->OWRITE)
+ fd1 = 1;
+ else
+ fd1 = 0;
+ }
+ if (rfd == nil) {
+ (append, omode) := (mode & OAPPEND, mode & ~OAPPEND);
+ err := "";
+ case mode {
+ Sys->OREAD =>
+ rfd = sys->open(path, omode);
+ Sys->OWRITE | OAPPEND or
+ Sys->ORDWR =>
+ rfd = sys->open(path, omode);
+ err = sprint("%r");
+ if (rfd == nil && nonexistent(err)) {
+ rfd = sys->create(path, omode, 8r666);
+ err = nil;
+ }
+ Sys->OWRITE =>
+ rfd = sys->create(path, omode, 8r666);
+ err = sprint("%r");
+ if (rfd == nil && err == EPERM) {
+ # try open; can't create on a file2chan (pipe)
+ rfd = sys->open(path, omode);
+ nerr := sprint("%r");
+ if(!nonexistent(nerr))
+ err = nerr;
+ }
+ }
+ if (rfd == nil) {
+ if (err == nil)
+ err = sprint("%r");
+ ctxt.fail("bad redir", sys->sprint("sh: cannot open %s: %s", path, err));
+ }
+ if (append)
+ sys->seek(rfd, big 0, Sys->SEEKEND); # not good enough, but alright for some purposes.
+ }
+ # XXX what happens if rfd.fd == fd1?
+ # it probably gets closed automatically... which is not what we want!
+ dup(ctxt, rfd.fd, fd1);
+ keepfds = fd1 :: keepfds;
+ }
+ ctxt.keepfds = keepfds;
+ return ctxt.waitfd.fd :: keepfds;
+}
+
+#
+# waiter utility routines
+#
+
+waitfd(): ref Sys->FD
+{
+ wf := string sys->pctl(0, nil) + "/wait";
+ waitfd := sys->open("#p/"+wf, Sys->OREAD);
+ if (waitfd == nil)
+ waitfd = sys->open("/prog/"+wf, Sys->OREAD);
+ if (waitfd == nil)
+ panic(sys->sprint("cannot open wait file: %r"));
+ return waitfd;
+}
+
+waitfor(ctxt: ref Context, pids: list of int): string
+{
+ if (pids == nil)
+ return nil;
+ status := array[len pids] of string;
+ wcount := len status;
+ buf := array[Sys->WAITLEN] of byte;
+ onebad := 0;
+ for(;;){
+ n := sys->read(ctxt.waitfd, buf, len buf);
+ if(n < 0)
+ panic(sys->sprint("error on wait read: %r"));
+ (who, line, s) := parsewaitstatus(ctxt, string buf[0:n]);
+ if (s != nil) {
+ if (len s >= 5 && s[0:5] == "fail:")
+ s = s[5:];
+ else
+ diagnostic(ctxt, line);
+ }
+ for ((i, pl) := (0, pids); pl != nil; (i, pl) = (i+1, tl pl))
+ if (who == hd pl)
+ break;
+ if (i < len status) {
+ # wait returns two records for a killed process...
+ if (status[i] == nil || s != "killed") {
+ onebad += s != nil;
+ status[i] = s;
+ if (wcount-- <= 1)
+ break;
+ }
+ }
+ }
+ if (!onebad)
+ return nil;
+ r := status[len status - 1];
+ for (i := len status - 2; i >= 0; i--)
+ r += "|" + status[i];
+ return r;
+}
+
+parsewaitstatus(ctxt: ref Context, status: string): (int, string, string)
+{
+ for (i := 0; i < len status; i++)
+ if (status[i] == ' ')
+ break;
+ if (i == len status - 1 || status[i+1] != '"')
+ ctxt.fail("bad wait read",
+ sys->sprint("sh: bad exit status '%s'", status));
+
+ for (i+=2; i < len status; i++)
+ if (status[i] == '"')
+ break;
+ if (i > len status - 2 || status[i+1] != ':')
+ ctxt.fail("bad wait read",
+ sys->sprint("sh: bad exit status '%s'", status));
+
+ return (int status, status, status[i+2:]);
+}
+
+panic(s: string)
+{
+ sys->fprint(stderr(), "sh panic: %s\n", s);
+ raise "panic";
+}
+
+diagnostic(ctxt: ref Context, s: string)
+{
+ if (ctxt.options() & Context.VERBOSE)
+ sys->fprint(stderr(), "sh: %s\n", s);
+}
+
+#
+# Sh environment stuff
+#
+
+Context.new(drawcontext: ref Draw->Context): ref Context
+{
+ initialise();
+ if (env != nil)
+ env->clone();
+ ctxt := ref Context(
+ ref Environment(
+ ref Builtins(nil, 0),
+ ref Builtins(nil, 0),
+ nil,
+ newlocalenv(nil)
+ ),
+ waitfd(),
+ drawcontext,
+ 0 :: 1 :: 2 :: nil
+ );
+ myselfbuiltin->initbuiltin(ctxt, myself);
+ ctxt.env.localenv.flags = ctxt.VERBOSE;
+ for (vl := ctxt.get("autoload"); vl != nil; vl = tl vl)
+ if ((hd vl).cmd == nil && (hd vl).word != nil)
+ loadmodule(ctxt, (hd vl).word);
+ return ctxt;
+}
+
+Context.copy(ctxt: self ref Context, copyenv: int): ref Context
+{
+ # XXX could check to see that we are definitely in a
+ # new process, because there'll be problems if not (two processes
+ # simultaneously reading the same wait file)
+ nctxt := ref Context(ctxt.env, waitfd(), ctxt.drawcontext, ctxt.keepfds);
+
+ if (copyenv) {
+ if (env != nil)
+ env->clone();
+ nctxt.env = ref Environment(
+ copybuiltins(ctxt.env.sbuiltins),
+ copybuiltins(ctxt.env.builtins),
+ ctxt.env.bmods,
+ copylocalenv(ctxt.env.localenv)
+ );
+ }
+ return nctxt;
+}
+
+Context.set(ctxt: self ref Context, name: string, val: list of ref Listnode)
+{
+ e := ctxt.env.localenv;
+ idx := hashfn(name, len e.vars);
+ for (;;) {
+ v := hashfind(e.vars, idx, name);
+ if (v == nil) {
+ if (e.pushed == nil) {
+ flags := Var.CHANGED;
+ if (noexport(name))
+ flags |= Var.NOEXPORT;
+ hashadd(e.vars, idx, ref Var(name, val, flags));
+ return;
+ }
+ } else {
+ v.val = val;
+ v.flags |= Var.CHANGED;
+ return;
+ }
+ e = e.pushed;
+ }
+}
+
+Context.get(ctxt: self ref Context, name: string): list of ref Listnode
+{
+ if (name == nil)
+ return nil;
+
+ idx := -1;
+ # cope with $1, $2, etc
+ if (name[0] > '0' && name[0] <= '9') {
+ i: int;
+ for (i = 0; i < len name; i++)
+ if (name[i] < '0' || name[i] > '9')
+ break;
+ if (i >= len name) {
+ idx = int name - 1;
+ name = "*";
+ }
+ }
+
+ v := varfind(ctxt.env.localenv, name);
+ if (v != nil) {
+ if (idx != -1)
+ return index(v.val, idx);
+ return v.val;
+ }
+ return nil;
+}
+
+# return the whole environment.
+Context.envlist(ctxt: self ref Context): list of (string, list of ref Listnode)
+{
+ t := array[ENVHASHSIZE] of list of ref Var;
+ for (e := ctxt.env.localenv; e != nil; e = e.pushed) {
+ for (i := 0; i < len e.vars; i++) {
+ for (vl := e.vars[i]; vl != nil; vl = tl vl) {
+ v := hd vl;
+ idx := hashfn(v.name, len e.vars);
+ if (hashfind(t, idx, v.name) == nil)
+ hashadd(t, idx, v);
+ }
+ }
+ }
+
+ l: list of (string, list of ref Listnode);
+ for (i := 0; i < ENVHASHSIZE; i++) {
+ for (vl := t[i]; vl != nil; vl = tl vl) {
+ v := hd vl;
+ l = (v.name, v.val) :: l;
+ }
+ }
+ return l;
+}
+
+Context.setlocal(ctxt: self ref Context, name: string, val: list of ref Listnode)
+{
+ e := ctxt.env.localenv;
+ idx := hashfn(name, len e.vars);
+ v := hashfind(e.vars, idx, name);
+ if (v == nil) {
+ flags := Var.CHANGED;
+ if (noexport(name))
+ flags |= Var.NOEXPORT;
+ hashadd(e.vars, idx, ref Var(name, val, flags));
+ } else {
+ v.val = val;
+ v.flags |= Var.CHANGED;
+ }
+}
+
+
+Context.push(ctxt: self ref Context)
+{
+ ctxt.env.localenv = newlocalenv(ctxt.env.localenv);
+}
+
+Context.pop(ctxt: self ref Context)
+{
+ if (ctxt.env.localenv.pushed == nil)
+ panic("unbalanced contexts in shell environment");
+ else {
+ oldv := ctxt.env.localenv.vars;
+ ctxt.env.localenv = ctxt.env.localenv.pushed;
+ for (i := 0; i < len oldv; i++) {
+ for (vl := oldv[i]; vl != nil; vl = tl vl) {
+ if ((v := varfind(ctxt.env.localenv, (hd vl).name)) != nil)
+ v.flags |= Var.CHANGED;
+ else
+ ctxt.set((hd vl).name, nil);
+ }
+ }
+ }
+}
+
+Context.run(ctxt: self ref Context, args: list of ref Listnode, last: int): string
+{
+ if (args == nil || ((hd args).cmd == nil && (hd args).word == nil))
+ return nil;
+ cmd := hd args;
+ if (cmd.cmd != nil || cmd.word[0] == '{') # }
+ return runblock(ctxt, args, last);
+
+ if (ctxt.options() & ctxt.EXECPRINT)
+ sys->fprint(stderr(), "%s\n", quoted(args, 0));
+ (doneit, status) := trybuiltin(ctxt, args, last);
+ if (!doneit)
+ status = runexternal(ctxt, args, last);
+
+ return status;
+}
+
+Context.addmodule(ctxt: self ref Context, name: string, mod: Shellbuiltin)
+{
+ mod->initbuiltin(ctxt, myself);
+ ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods;
+}
+
+Context.addbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ addbuiltin(c.env.builtins, name, mod);
+}
+
+Context.removebuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ removebuiltin(c.env.builtins, name, mod);
+}
+
+Context.addsbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ addbuiltin(c.env.sbuiltins, name, mod);
+}
+
+Context.removesbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ removebuiltin(c.env.sbuiltins, name, mod);
+}
+
+varfind(e: ref Localenv, name: string): ref Var
+{
+ idx := hashfn(name, len e.vars);
+ for (; e != nil; e = e.pushed)
+ for (vl := e.vars[idx]; vl != nil; vl = tl vl)
+ if ((hd vl).name == name)
+ return hd vl;
+ return nil;
+}
+
+Context.fail(ctxt: self ref Context, ename: string, err: string)
+{
+ if (ctxt.options() & Context.VERBOSE)
+ sys->fprint(stderr(), "%s\n", err);
+ raise "fail:" + ename;
+}
+
+Context.setoptions(ctxt: self ref Context, flags, on: int): int
+{
+ old := ctxt.env.localenv.flags;
+ if (on)
+ ctxt.env.localenv.flags |= flags;
+ else
+ ctxt.env.localenv.flags &= ~flags;
+ return old;
+}
+
+Context.options(ctxt: self ref Context): int
+{
+ return ctxt.env.localenv.flags;
+}
+
+hashfn(s: string, n: int): int
+{
+ h := 0;
+ m := len s;
+ for(i:=0; i<m; i++){
+ h = 65599*h+s[i];
+ }
+ return (h & 16r7fffffff) % n;
+}
+
+# the following two functions cheat by getting the caller
+# to calculate the actual hash function. this is to avoid
+# the hash function being calculated once in every scope
+# of a context until the variable is found (or stored).
+hashfind(ht: array of list of ref Var, idx: int, n: string): ref Var
+{
+ for (ent := ht[idx]; ent != nil; ent = tl ent)
+ if ((hd ent).name == n)
+ return hd ent;
+ return nil;
+}
+
+hashadd(ht: array of list of ref Var, idx: int, v: ref Var)
+{
+ ht[idx] = v :: ht[idx];
+}
+
+copylocalenv(e: ref Localenv): ref Localenv
+{
+ nvars := array[len e.vars] of list of ref Var;
+ flags := e.flags;
+ for (; e != nil; e = e.pushed)
+ for (i := 0; i < len nvars; i++)
+ for (vl := e.vars[i]; vl != nil; vl = tl vl) {
+ idx := hashfn((hd vl).name, len nvars);
+ if (hashfind(nvars, idx, (hd vl).name) == nil)
+ hashadd(nvars, idx, ref *(hd vl));
+ }
+ return ref Localenv(nvars, nil, flags);
+}
+
+# make new local environment. if it's got no pushed levels,
+# then get all variables from the global environment.
+newlocalenv(pushed: ref Localenv): ref Localenv
+{
+ e := ref Localenv(array[ENVHASHSIZE] of list of ref Var, pushed, 0);
+ if (pushed == nil && env != nil) {
+ for (vl := env->getall(); vl != nil; vl = tl vl) {
+ (name, val) := hd vl;
+ hashadd(e.vars, hashfn(name, len e.vars), ref Var(name, envstringtoval(val), 0));
+ }
+ }
+ if (pushed != nil)
+ e.flags = pushed.flags;
+ return e;
+}
+
+copybuiltins(b: ref Builtins): ref Builtins
+{
+ nb := ref Builtins(array[b.n] of (string, list of Shellbuiltin), b.n);
+ nb.ba[0:] = b.ba[0:b.n];
+ return nb;
+}
+
+findbuiltin(b: ref Builtins, name: string): (int, list of Shellbuiltin)
+{
+ lo := 0;
+ hi := b.n - 1;
+ while (lo <= hi) {
+ mid := (lo + hi) / 2;
+ (bname, bmod) := b.ba[mid];
+ if (name < bname)
+ hi = mid - 1;
+ else if (name > bname)
+ lo = mid + 1;
+ else
+ return (mid, bmod);
+ }
+ return (lo, nil);
+}
+
+removebuiltin(b: ref Builtins, name: string, mod: Shellbuiltin)
+{
+ (n, bmods) := findbuiltin(b, name);
+ if (bmods == nil)
+ return;
+ if (hd bmods == mod) {
+ if (tl bmods != nil)
+ b.ba[n] = (name, tl bmods);
+ else {
+ b.ba[n:] = b.ba[n+1:b.n];
+ b.ba[--b.n] = (nil, nil);
+ }
+ }
+}
+
+# add builtin; if it already exists, then replace it. if mod is nil then remove it.
+# builtins that refer to myselfbuiltin are special - they
+# are never removed, neither are they entirely replaced, only covered.
+# no external module can redefine the name "builtin"
+addbuiltin(b: ref Builtins, name: string, mod: Shellbuiltin)
+{
+ if (mod == nil || (name == "builtin" && mod != myselfbuiltin))
+ return;
+ (n, bmods) := findbuiltin(b, name);
+ if (bmods != nil) {
+ if (hd bmods == myselfbuiltin)
+ b.ba[n] = (name, mod :: bmods);
+ else
+ b.ba[n] = (name, mod :: nil);
+ } else {
+ if (b.n == len b.ba) {
+ nb := array[b.n + 10] of (string, list of Shellbuiltin);
+ nb[0:] = b.ba[0:b.n];
+ b.ba = nb;
+ }
+ b.ba[n+1:] = b.ba[n:b.n];
+ b.ba[n] = (name, mod :: nil);
+ b.n++;
+ }
+}
+
+removebuiltinmod(b: ref Builtins, mod: Shellbuiltin)
+{
+ j := 0;
+ for (i := 0; i < b.n; i++) {
+ (name, bmods) := b.ba[i];
+ if (hd bmods == mod)
+ bmods = tl bmods;
+ if (bmods != nil)
+ b.ba[j++] = (name, bmods);
+ }
+ b.n = j;
+ for (; j < i; j++)
+ b.ba[j] = (nil, nil);
+}
+
+export(e: ref Localenv)
+{
+ if (env == nil)
+ return;
+ if (e.pushed != nil)
+ export(e.pushed);
+
+ for (i := 0; i < len e.vars; i++) {
+ for (vl := e.vars[i]; vl != nil; vl = tl vl) {
+ v := hd vl;
+ # a bit inefficient: a local variable will get several putenvs.
+ if ((v.flags & Var.CHANGED) && !(v.flags & Var.NOEXPORT)) {
+ setenv(v.name, v.val);
+ v.flags &= ~Var.CHANGED;
+ }
+ }
+ }
+}
+
+noexport(name: string): int
+{
+ case name {
+ "0" or "*" or "status" => return 1;
+ }
+ return 0;
+}
+
+index(val: list of ref Listnode, k: int): list of ref Listnode
+{
+ for (; k > 0 && val != nil; k--)
+ val = tl val;
+ if (val != nil)
+ val = hd val :: nil;
+ return val;
+}
+
+getenv(name: string): list of ref Listnode
+{
+ if (env == nil)
+ return nil;
+ return envstringtoval(env->getenv(name));
+}
+
+envstringtoval(v: string): list of ref Listnode
+{
+ return stringlist2list(str->unquoted(v));
+}
+
+XXXenvstringtoval(v: string): list of ref Listnode
+{
+ if (len v == 0)
+ return nil;
+ start := len v;
+ val: list of ref Listnode;
+ for (i := start - 1; i >= 0; i--) {
+ if (v[i] == ENVSEP) {
+ val = ref Listnode(nil, v[i+1:start]) :: val;
+ start = i;
+ }
+ }
+ return ref Listnode(nil, v[0:start]) :: val;
+}
+
+setenv(name: string, val: list of ref Listnode)
+{
+ if (env == nil)
+ return;
+ env->setenv(name, quoted(val, 1));
+}
+
+#
+# globbing and general wildcard handling
+#
+
+containswildchar(s: string): int
+{
+ # try and avoid being fooled by GLOB characters in quoted
+ # text. we'll only be fooled if the GLOB char is followed
+ # by a wildcard char, or another GLOB.
+ for (i := 0; i < len s; i++) {
+ if (s[i] == GLOB && i < len s - 1) {
+ case s[i+1] {
+ '*' or '[' or '?' or GLOB =>
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+# remove GLOBs, and quote other wildcard characters
+patquote(word: string): string
+{
+ outword := "";
+ for (i := 0; i < len word; i++) {
+ case word[i] {
+ '[' or '*' or '?' or '\\' =>
+ outword[len outword] = '\\';
+ GLOB =>
+ i++;
+ if (i >= len word)
+ return outword;
+ }
+ outword[len outword] = word[i];
+ }
+ return outword;
+}
+
+# get rid of GLOB characters
+deglob(s: string): string
+{
+ j := 0;
+ for (i := 0; i < len s; i++) {
+ if (s[i] != GLOB) {
+ if (i != j) # a worthy optimisation???
+ s[j] = s[i];
+ j++;
+ }
+ }
+ if (i == j)
+ return s;
+ return s[0:j];
+}
+
+# expand wildcards in _nl_
+glob(nl: list of ref Listnode): list of ref Listnode
+{
+ new: list of ref Listnode;
+ while (nl != nil) {
+ n := hd nl;
+ if (containswildchar(n.word)) {
+ qword := patquote(n.word);
+ files := filepat->expand(qword);
+ if (files == nil)
+ files = deglob(n.word) :: nil;
+ while (files != nil) {
+ new = ref Listnode(nil, hd files) :: new;
+ files = tl files;
+ }
+ } else
+ new = n :: new;
+ nl = tl nl;
+ }
+ ret := revlist(new);
+ return ret;
+}
+
+#
+# general list manipulation utility routines
+#
+
+# return string equivalent of nl
+list2stringlist(nl: list of ref Listnode): list of string
+{
+ ret: list of string = nil;
+
+ while (nl != nil) {
+ newel: string;
+ el := hd nl;
+ if (el.word != nil || el.cmd == nil)
+ newel = el.word;
+ else
+ el.word = newel = cmd2string(el.cmd);
+ ret = newel::ret;
+ nl = tl nl;
+ }
+
+ sl := revstringlist(ret);
+ return sl;
+}
+
+stringlist2list(sl: list of string): list of ref Listnode
+{
+ ret: list of ref Listnode;
+
+ while (sl != nil) {
+ ret = ref Listnode(nil, hd sl) :: ret;
+ sl = tl sl;
+ }
+ return revlist(ret);
+}
+
+revstringlist(l: list of string): list of string
+{
+ t: list of string;
+
+ while(l != nil) {
+ t = hd l :: t;
+ l = tl l;
+ }
+ return t;
+}
+
+revlist(l: list of ref Listnode): list of ref Listnode
+{
+ t: list of ref Listnode;
+
+ while(l != nil) {
+ t = hd l :: t;
+ l = tl l;
+ }
+ return t;
+}
+
+#
+# node to string conversion functions
+#
+
+fdassignstr(isassign: int, redir: ref Redir): string
+{
+ l: string = nil;
+ if (redir.fd1 >= 0)
+ l = string redir.fd1;
+
+ if (isassign) {
+ r: string = nil;
+ if (redir.fd2 >= 0)
+ r = string redir.fd2;
+ return "[" + l + "=" + r + "]";
+ }
+ return "[" + l + "]";
+}
+
+redirstr(rtype: int): string
+{
+ case rtype {
+ * or
+ Sys->OREAD => return "<";
+ Sys->OWRITE => return ">";
+ Sys->OWRITE|OAPPEND => return ">>";
+ Sys->ORDWR => return "<>";
+ }
+}
+
+cmd2string(n: ref Node): string
+{
+ if (n == nil)
+ return "";
+
+ s: string;
+ case n.ntype {
+ n_BLOCK => s = "{" + cmd2string(n.left) + "}";
+ n_VAR => s = "$" + cmd2string(n.left);
+ # XXX can this ever occur?
+ if (n.right != nil)
+ s += "(" + cmd2string(n.right) + ")";
+ n_SQUASH => s = "$\"" + cmd2string(n.left);
+ n_COUNT => s = "$#" + cmd2string(n.left);
+ n_BQ => s = "`" + cmd2string(n.left);
+ n_BQ2 => s = "\"" + cmd2string(n.left);
+ n_REDIR => s = redirstr(n.redir.rtype);
+ if (n.redir.fd1 != -1)
+ s += fdassignstr(0, n.redir);
+ s += cmd2string(n.left);
+ n_DUP => s = redirstr(n.redir.rtype) + fdassignstr(1, n.redir);
+ n_LIST => s = "(" + cmd2string(n.left) + ")";
+ n_SEQ => s = cmd2string(n.left) + ";" + cmd2string(n.right);
+ n_NOWAIT => s = cmd2string(n.left) + "&";
+ n_CONCAT => s = cmd2string(n.left) + "^" + cmd2string(n.right);
+ n_PIPE => s = cmd2string(n.left) + "|";
+ if (n.redir != nil && (n.redir.fd1 != -1 || n.redir.fd2 != -1))
+ s += fdassignstr(n.redir.fd2 != -1, n.redir);
+ s += cmd2string(n.right);
+ n_ASSIGN => s = cmd2string(n.left) + "=" + cmd2string(n.right);
+ n_LOCAL => s = cmd2string(n.left) + ":=" + cmd2string(n.right);
+ n_ADJ => s = cmd2string(n.left) + " " + cmd2string(n.right);
+ n_WORD => s = quote(n.word, 1);
+ * => s = sys->sprint("unknown%d", n.ntype);
+ }
+ return s;
+}
+
+# convert s into a suitable format for reparsing.
+# if glob is true, then GLOB chars are significant.
+# XXX it might be faster in the more usual cases
+# to run through the string first and only build up
+# a new string once we've discovered it's necessary.
+quote(s: string, glob: int): string
+{
+ needquote := 0;
+ t := "";
+ for (i := 0; i < len s; i++) {
+ case s[i] {
+ '{' or '}' or '(' or ')' or '`' or '&' or ';' or '=' or '>' or '<' or '#' or
+ '|' or '*' or '[' or '?' or '$' or '^' or ' ' or '\t' or '\n' or '\r' =>
+ needquote = 1;
+ '\'' =>
+ t[len t] = '\'';
+ needquote = 1;
+ GLOB =>
+ if (glob) {
+ if (i < len s - 1)
+ i++;
+ }
+ }
+ t[len t] = s[i];
+ }
+ if (needquote || t == nil)
+ t = "'" + t + "'";
+ return t;
+}
+
+squash(l: list of string, sep: string): string
+{
+ if (l == nil)
+ return nil;
+ s := hd l;
+ for (l = tl l; l != nil; l = tl l)
+ s += sep + hd l;
+ return s;
+}
+
+debug(s: string)
+{
+ if (DEBUG) sys->fprint(stderr(), "%s\n", string sys->pctl(0, nil) + ": " + s);
+}
+
+#
+# built-in commands
+#
+
+initbuiltin(c: ref Context, nil: Sh): string
+{
+ names := array[] of {"load", "unload", "loaded", "builtin", "syncenv", "whatis", "run", "exit", "@"};
+ for (i := 0; i < len names; i++)
+ c.addbuiltin(names[i], myselfbuiltin);
+ c.addsbuiltin("loaded", myselfbuiltin);
+ c.addsbuiltin("quote", myselfbuiltin);
+ c.addsbuiltin("bquote", myselfbuiltin);
+ c.addsbuiltin("unquote", myselfbuiltin);
+ c.addsbuiltin("builtin", myselfbuiltin);
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+runsbuiltin(ctxt: ref Context, nil: Sh, argv: list of ref Listnode): list of ref Listnode
+{
+ case (hd argv).word {
+ "loaded" => return sbuiltin_loaded(ctxt, argv);
+ "bquote" => return sbuiltin_quote(ctxt, argv, 0);
+ "quote" => return sbuiltin_quote(ctxt, argv, 1);
+ "unquote" => return sbuiltin_unquote(ctxt, argv);
+ "builtin" => return sbuiltin_builtin(ctxt, argv);
+ }
+ return nil;
+}
+
+runbuiltin(ctxt: ref Context, nil: Sh, args: list of ref Listnode, lseq: int): string
+{
+ status := "";
+ name := (hd args).word;
+ case name {
+ "load" => status = builtin_load(ctxt, args, lseq);
+ "loaded" => status = builtin_loaded(ctxt, args, lseq);
+ "unload" => status = builtin_unload(ctxt, args, lseq);
+ "builtin" => status = builtin_builtin(ctxt, args, lseq);
+ "whatis" => status = builtin_whatis(ctxt, args, lseq);
+ "run" => status = builtin_run(ctxt, args, lseq);
+ "exit" => status = builtin_exit(ctxt, args, lseq);
+ "syncenv" => export(ctxt.env.localenv);
+ "@" => status = builtin_subsh(ctxt, args, lseq);
+ }
+ return status;
+}
+
+sbuiltin_loaded(ctxt: ref Context, nil: list of ref Listnode): list of ref Listnode
+{
+ v: list of ref Listnode;
+ for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) {
+ (name, nil) := hd bl;
+ v = ref Listnode(nil, name) :: v;
+ }
+ return v;
+}
+
+sbuiltin_quote(nil: ref Context, argv: list of ref Listnode, quoteblocks: int): list of ref Listnode
+{
+ return ref Listnode(nil, quoted(tl argv, quoteblocks)) :: nil;
+}
+
+sbuiltin_builtin(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode
+{
+ if (args == nil || tl args == nil)
+ builtinusage(ctxt, "builtin command [args ...]");
+ name := (hd tl args).word;
+ (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name);
+ for (; mods != nil; mods = tl mods)
+ if (hd mods == myselfbuiltin)
+ return (hd mods)->runsbuiltin(ctxt, myself, tl args);
+ ctxt.fail("builtin not found", sys->sprint("sh: builtin %s not found", name));
+ return nil;
+}
+
+sbuiltin_unquote(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ argv = tl argv;
+ if (argv == nil || tl argv != nil)
+ builtinusage(ctxt, "unquote arg");
+
+ arg := (hd argv).word;
+ if (arg == nil && (hd argv).cmd != nil)
+ arg = cmd2string((hd argv).cmd);
+ return stringlist2list(str->unquoted(arg));
+}
+
+getself(): Shellbuiltin
+{
+ return myselfbuiltin;
+}
+
+builtinusage(ctxt: ref Context, s: string)
+{
+ ctxt.fail("usage", "sh: usage: " + s);
+}
+
+builtin_exit(nil: ref Context, nil: list of ref Listnode, nil: int): string
+{
+ # XXX using this primitive can cause
+ # environment stack not to be popped properly.
+ exit;
+}
+
+builtin_subsh(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil)
+ return nil;
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 0, tl args, ref Redirlist, startchan);
+ (exepid, exprop) := <-startchan;
+ status := waitfor(ctxt, exepid :: nil);
+ if (exprop.name != nil)
+ raise exprop.name;
+ return status;
+}
+
+builtin_loaded(ctxt: ref Context, nil: list of ref Listnode, nil: int): string
+{
+ b := ctxt.env.builtins;
+ for (i := 0; i < b.n; i++) {
+ (name, bmods) := b.ba[i];
+ sys->print("%s\t%s\n", name, modname(ctxt, hd bmods));
+ }
+ b = ctxt.env.sbuiltins;
+ for (i = 0; i < b.n; i++) {
+ (name, bmods) := b.ba[i];
+ sys->print("${%s}\t%s\n", name, modname(ctxt, hd bmods));
+ }
+ return nil;
+}
+
+# it's debateable whether this should throw an exception or
+# return a failed exit status - however, most scripts don't
+# check the status and do need the module they're loading,
+# so i think the exception is probably more useful...
+builtin_load(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil || (hd tl args).word == nil)
+ builtinusage(ctxt, "load path...");
+ args = tl args;
+ path := (hd args).word;
+ if (args == nil)
+ builtinusage(ctxt, "load path...");
+ status := "";
+ for (; args != nil; args = tl args) {
+ s := loadmodule(ctxt, (hd args).word);
+ if (s != nil)
+ raise "fail:" + s;
+ }
+ return nil;
+}
+
+builtin_unload(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil)
+ builtinusage(ctxt, "unload path...");
+ status := "";
+ for (args = tl args; args != nil; args = tl args)
+ if ((s := unloadmodule(ctxt, (hd args).word)) != nil)
+ status = s;
+ return status;
+}
+
+builtin_run(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil || (hd tl args).word == nil)
+ builtinusage(ctxt, "run path");
+ ctxt.push();
+ {
+ ctxt.setoptions(ctxt.INTERACTIVE, 0);
+ runscript(ctxt, (hd tl args).word, tl tl args, 1);
+ ctxt.pop();
+ return nil;
+ } exception e {
+ "fail:*" =>
+ ctxt.pop();
+ return e[5:];
+ }
+}
+
+# four categories:
+# environment variables
+# substitution builtins
+# braced blocks
+# builtins (including those defined by externally loaded modules)
+# or external programs
+# other
+builtin_whatis(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (len args < 2)
+ builtinusage(ctxt, "whatis name ...");
+ err := "";
+ for (args = tl args; args != nil; args = tl args)
+ if ((e := whatisit(ctxt, hd args)) != nil)
+ err = e;
+ return err;
+}
+
+whatisit(ctxt: ref Context, el: ref Listnode): string
+{
+ if (el.cmd != nil) {
+ sys->print("%s\n", cmd2string(el.cmd));
+ return nil;
+ }
+ found := 0;
+ name := el.word;
+ if (name != nil && name[0] == '{') { #}
+ sys->print("%s\n", name);
+ return nil;;
+ }
+ if (name == nil)
+ return nil; # XXX questionable
+ w: string;
+ val := ctxt.get(name);
+ if (val != nil) {
+ found++;
+ w += sys->sprint("%s=%s\n", quote(name, 0), quoted(val, 0));
+ }
+ (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name);
+ if (mods != nil) {
+ mod := hd mods;
+ if (mod == myselfbuiltin)
+ w += "${builtin " + name + "}\n";
+ else {
+ mw := mod->whatis(ctxt, myself, name, Shellbuiltin->SBUILTIN);
+ if (mw == nil)
+ mw = "${" + name + "}";
+ w += "load " + modname(ctxt, mod) + "; " + mw + "\n";
+ }
+ found++;
+ }
+ (nil, mods) = findbuiltin(ctxt.env.builtins, name);
+ if (mods != nil) {
+ mod := hd mods;
+ if (mod == myselfbuiltin)
+ sys->print("builtin %s\n", name);
+ else {
+ mw := mod->whatis(ctxt, myself, name, Shellbuiltin->BUILTIN);
+ if (mw == nil)
+ mw = name;
+ w += "load " + modname(ctxt, mod) + "; " + mw + "\n";
+ }
+ found++;
+ } else {
+ disfile := 0;
+ if (len name >= 4 && name[len name-4:] == ".dis")
+ disfile = 1;
+ pathlist: list of string;
+ if (len name >= 2 && (name[0] == '/' || name[0:2] == "./"))
+ pathlist = list of {""};
+ else if ((pl := ctxt.get("path")) != nil)
+ pathlist = list2stringlist(pl);
+ else
+ pathlist = list of {"/dis", "."};
+
+ foundpath := "";
+ while (pathlist != nil) {
+ path: string;
+ if (hd pathlist != "")
+ path = hd pathlist + "/" + name;
+ else
+ path = name;
+ if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) {
+ if (executable(sys->fstat(fd), 8r111)) {
+ foundpath = path;
+ break;
+ }
+ }
+ if (!disfile)
+ path += ".dis";
+ if (executable(sys->stat(path), 8r444)) {
+ foundpath = path;
+ break;
+ }
+ pathlist = tl pathlist;
+ }
+ if (foundpath != nil)
+ w += foundpath + "\n";
+ }
+ for (bmods := ctxt.env.bmods; bmods != nil; bmods = tl bmods) {
+ (modname, mod) := hd bmods;
+ if ((mw := mod->whatis(ctxt, myself, name, Shellbuiltin->OTHER)) != nil)
+ w += "load " + modname + "; " + mw + "\n";
+ }
+ if (w == nil) {
+ sys->fprint(stderr(), "%s: not found\n", name);
+ return "not found";
+ }
+ sys->print("%s", w);
+ return nil;
+}
+
+# execute a command ignoring names defined by externally defined modules
+builtin_builtin(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ if (len args < 2)
+ builtinusage(ctxt, "builtin command [args ...]");
+ name := (hd tl args).word;
+ if (name == nil || name[0] == '{') {
+ diagnostic(ctxt, name + " not found");
+ return "not found";
+ }
+ (nil, mods) := findbuiltin(ctxt.env.builtins, name);
+ for (; mods != nil; mods = tl mods)
+ if (hd mods == myselfbuiltin)
+ return (hd mods)->runbuiltin(ctxt, myself, tl args, last);
+ if (ctxt.options() & ctxt.EXECPRINT)
+ sys->fprint(stderr(), "%s\n", quoted(tl args, 0));
+ return runexternal(ctxt, tl args, last);
+}
+
+modname(ctxt: ref Context, mod: Shellbuiltin): string
+{
+ for (ml := ctxt.env.bmods; ml != nil; ml = tl ml) {
+ (bname, bmod) := hd ml;
+ if (bmod == mod)
+ return bname;
+ }
+ return "builtin";
+}
+
+loadmodule(ctxt: ref Context, name: string): string
+{
+ # avoid loading the same module twice (it's convenient
+ # to have load be a null-op if the module required is already loaded)
+ for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) {
+ (bname, nil) := hd bl;
+ if (bname == name)
+ return nil;
+ }
+ path := name;
+ if (len path < 4 || path[len path-4:] != ".dis")
+ path += ".dis";
+ if (path[0] != '/' && path[0:2] != "./")
+ path = BUILTINPATH + "/" + path;
+ mod := load Shellbuiltin path;
+ if (mod == nil) {
+ diagnostic(ctxt, sys->sprint("load: cannot load %s: %r", path));
+ return "bad module";
+ }
+ s := mod->initbuiltin(ctxt, myself);
+ ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods;
+ if (s != nil) {
+ unloadmodule(ctxt, name);
+ diagnostic(ctxt, "load: module init failed: " + s);
+ }
+ return s;
+}
+
+unloadmodule(ctxt: ref Context, name: string): string
+{
+ bl: list of (string, Shellbuiltin);
+ mod: Shellbuiltin;
+ for (cl := ctxt.env.bmods; cl != nil; cl = tl cl) {
+ (bname, bmod) := hd cl;
+ if (bname == name)
+ mod = bmod;
+ else
+ bl = hd cl :: bl;
+ }
+ if (mod == nil) {
+ diagnostic(ctxt, sys->sprint("module %s not found", name));
+ return "not found";
+ }
+ for (ctxt.env.bmods = nil; bl != nil; bl = tl bl)
+ ctxt.env.bmods = hd bl :: ctxt.env.bmods;
+ removebuiltinmod(ctxt.env.builtins, mod);
+ removebuiltinmod(ctxt.env.sbuiltins, mod);
+ return nil;
+}
+
+executable(s: (int, Sys->Dir), mode: int): int
+{
+ (ok, info) := s;
+ return ok != -1 && (info.mode & Sys->DMDIR) == 0
+ && (info.mode & mode) != 0;
+}
+
+quoted(val: list of ref Listnode, quoteblocks: int): string
+{
+ s := "";
+ for (; val != nil; val = tl val) {
+ el := hd val;
+ if (el.cmd == nil || (quoteblocks && el.word != nil))
+ s += quote(el.word, 0);
+ else {
+ cmd := cmd2string(el.cmd);
+ if (quoteblocks)
+ cmd = quote(cmd, 0);
+ s += cmd;
+ }
+ if (tl val != nil)
+ s[len s] = ' ';
+ }
+ return s;
+}
+
+setstatus(ctxt: ref Context, val: string): string
+{
+ ctxt.setlocal("status", ref Listnode(nil, val) :: nil);
+ return val;
+}
+
+#
+# beginning of parser routines
+#
+
+doparse(l: ref YYLEX, prompt: string, showline: int): (ref Node, string)
+{
+ l.prompt = prompt;
+ l.err = nil;
+ l.lval.node = nil;
+ yyparse(l);
+ l.lastnl = 0; # don't print secondary prompt next time
+ if (l.err != nil) {
+ s: string;
+ if (l.err == nil)
+ l.err = "unknown error";
+ if (l.errline > 0 && showline)
+ s = sys->sprint("%s:%d: %s", l.path, l.errline, l.err);
+ else
+ s = l.path + ": parse error: " + l.err;
+ return (nil, s);
+ }
+ return (l.lval.node, nil);
+}
+
+blanklex: YYLEX; # for hassle free zero initialisation
+
+YYLEX.initstring(s: string): ref YYLEX
+{
+ ret := ref blanklex;
+ ret.s = s;
+ ret.path="internal";
+ ret.strpos = 0;
+ return ret;
+}
+
+YYLEX.initfile(fd: ref Sys->FD, path: string): ref YYLEX
+{
+ lex := ref blanklex;
+ lex.f = bufio->fopen(fd, bufio->OREAD);
+ lex.path = path;
+ lex.cbuf = array[2] of int; # number of characters of pushback
+ lex.linenum = 1;
+ lex.prompt = "";
+ return lex;
+}
+
+YYLEX.error(l: self ref YYLEX, s: string)
+{
+ if (l.err == nil) {
+ l.err = s;
+ l.errline = l.linenum;
+ }
+}
+
+NOTOKEN: con -1;
+
+YYLEX.lex(l: self ref YYLEX): int
+{
+ # the following are allowed a free caret:
+ # $, word and quoted word;
+ # also, allowed chrs in unquoted word following dollar are [a-zA-Z0-9*_]
+ endword := 0;
+ wasdollar := 0;
+ tok := NOTOKEN;
+ while (tok == NOTOKEN) {
+ case c := l.getc() {
+ l.EOF =>
+ tok = END;
+ '\n' =>
+ tok = '\n';
+ '\r' or '\t' or ' ' =>
+ ;
+ '#' =>
+ while ((c = l.getc()) != '\n' && c != l.EOF)
+ ;
+ l.ungetc();
+ ';' => tok = ';';
+ '&' =>
+ c = l.getc();
+ if(c == '&')
+ tok = ANDAND;
+ else{
+ l.ungetc();
+ tok = '&';
+ }
+ '^' => tok = '^';
+ '{' => tok = '{';
+ '}' => tok = '}';
+ ')' => tok = ')';
+ '(' => tok = '(';
+ '=' => (tok, l.lval.optype) = ('=', n_ASSIGN);
+ '$' =>
+ if (l.atendword) {
+ l.ungetc();
+ tok = '^';
+ break;
+ }
+ case (c = l.getc()) {
+ '#' =>
+ l.lval.optype = n_COUNT;
+ '"' =>
+ l.lval.optype = n_SQUASH;
+ * =>
+ l.ungetc();
+ l.lval.optype = n_VAR;
+ }
+ tok = OP;
+ wasdollar = 1;
+ '"' or '`'=>
+ if (l.atendword) {
+ tok = '^';
+ l.ungetc();
+ break;
+ }
+ tok = OP;
+ if (c == '"')
+ l.lval.optype = n_BQ2;
+ else
+ l.lval.optype = n_BQ;
+ '>' or '<' =>
+ rtype: int;
+ nc := l.getc();
+ if (nc == '>') {
+ if (c == '>')
+ rtype = Sys->OWRITE | OAPPEND;
+ else
+ rtype = Sys->ORDWR;
+ nc = l.getc();
+ } else if (c == '>')
+ rtype = Sys->OWRITE;
+ else
+ rtype = Sys->OREAD;
+ tok = REDIR;
+ if (nc == '[') {
+ (tok, l.lval.redir) = readfdassign(l);
+ if (tok == ERROR)
+ (l.err, l.errline) = ("syntax error in redirection", l.linenum);
+ } else {
+ l.ungetc();
+ l.lval.redir = ref Redir(-1, -1, -1);
+ }
+ if (l.lval.redir != nil)
+ l.lval.redir.rtype = rtype;
+ '|' =>
+ tok = '|';
+ l.lval.redir = nil;
+ if ((c = l.getc()) == '[') {
+ (tok, l.lval.redir) = readfdassign(l);
+ if (tok == ERROR) {
+ (l.err, l.errline) = ("syntax error in pipe redirection", l.linenum);
+ return tok;
+ }
+ tok = '|';
+ } else if(c == '|')
+ tok = OROR;
+ else
+ l.ungetc();
+
+ '\'' =>
+ if (l.atendword) {
+ l.ungetc();
+ tok = '^';
+ break;
+ }
+ startline := l.linenum;
+ s := "";
+ for(;;) {
+ while ((nc := l.getc()) != '\'' && nc != l.EOF)
+ s[len s] = nc;
+ if (nc == l.EOF) {
+ (l.err, l.errline) = ("unterminated string literal", startline);
+ return ERROR;
+ }
+ if (l.getc() != '\'') {
+ l.ungetc();
+ break;
+ }
+ s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy)
+ }
+ l.lval.word = s;
+ tok = WORD;
+ endword = 1;
+
+ * =>
+ if (c == ':') {
+ if (l.getc() == '=') {
+ tok = '=';
+ l.lval.optype = n_LOCAL;
+ break;
+ }
+ l.ungetc();
+ }
+ if (l.atendword) {
+ l.ungetc();
+ tok = '^';
+ break;
+ }
+ allowed: string;
+ if (l.wasdollar)
+ allowed = "a-zA-Z0-9*_";
+ else
+ allowed = "^\n \t\r|$'#<>;^(){}`&=\"";
+ word := "";
+ loop: do {
+ case c {
+ '*' or '?' or '[' or GLOB =>
+ word[len word] = GLOB;
+ ':' =>
+ nc := l.getc();
+ l.ungetc();
+ if (nc == '=')
+ break loop;
+ }
+ word[len word] = c;
+ } while ((c = l.getc()) != l.EOF && str->in(c, allowed));
+ l.ungetc();
+ l.lval.word = word;
+ tok = WORD;
+ endword = 1;
+ }
+ l.atendword = endword;
+ l.wasdollar = wasdollar;
+ }
+# sys->print("token %s\n", tokstr(tok));
+ return tok;
+}
+
+tokstr(t: int): string
+{
+ s: string;
+ case t {
+ '\n' => s = "'\\n'";
+ 33 to 127 => s = sprint("'%c'", t);
+ DUP=> s = "DUP";
+ REDIR =>s = "REDIR";
+ WORD => s = "WORD";
+ OP => s = "OP";
+ END => s = "END";
+ ERROR=> s = "ERROR";
+ * =>
+ s = "<unknowntok"+ string t + ">";
+ }
+ return s;
+}
+
+YYLEX.ungetc(lex: self ref YYLEX)
+{
+ lex.strpos--;
+ if (lex.f != nil) {
+ lex.ncbuf++;
+ if (lex.strpos < 0)
+ lex.strpos = len lex.cbuf - 1;
+ }
+}
+
+YYLEX.getc(lex: self ref YYLEX): int
+{
+ if (lex.eof) # EOF sticks
+ return lex.EOF;
+ c: int;
+ if (lex.f != nil) {
+ if (lex.ncbuf > 0) {
+ c = lex.cbuf[lex.strpos++];
+ if (lex.strpos >= len lex.cbuf)
+ lex.strpos = 0;
+ lex.ncbuf--;
+ } else {
+ if (lex.lastnl && lex.prompt != nil)
+ sys->fprint(stderr(), "%s", lex.prompt);
+ c = bufio->lex.f.getc();
+ if (c == bufio->ERROR || c == bufio->EOF) {
+ lex.eof = 1;
+ c = lex.EOF;
+ } else if (c == '\n')
+ lex.linenum++;
+ lex.lastnl = (c == '\n');
+ lex.cbuf[lex.strpos++] = c;
+ if (lex.strpos >= len lex.cbuf)
+ lex.strpos = 0;
+ }
+ } else {
+ if (lex.strpos >= len lex.s) {
+ lex.eof = 1;
+ c = lex.EOF;
+ } else
+ c = lex.s[lex.strpos++];
+ }
+ return c;
+}
+
+# read positive decimal number; return -1 if no number found.
+readnum(lex: ref YYLEX): int
+{
+ sum := nc := 0;
+ while ((c := lex.getc()) >= '0' && c <= '9') {
+ sum = (sum * 10) + (c - '0');
+ nc++;
+ }
+ lex.ungetc();
+ if (nc == 0)
+ return -1;
+ return sum;
+}
+
+# return tuple (toktype, lhs, rhs).
+# -1 signifies no number present.
+# '[' char has already been read.
+readfdassign(lex: ref YYLEX): (int, ref Redir)
+{
+ n1 := readnum(lex);
+ if ((c := lex.getc()) != '=') {
+ if (c == ']')
+ return (REDIR, ref Redir(-1, n1, -1));
+
+ return (ERROR, nil);
+ }
+ n2 := readnum(lex);
+ if (lex.getc() != ']')
+ return (ERROR, nil);
+ return (DUP, ref Redir(-1, n1, n2));
+}
+
+mkseq(left, right: ref Node): ref Node
+{
+ if (left != nil && right != nil)
+ return mk(n_SEQ, left, right);
+ else if (left == nil)
+ return right;
+ return left;
+}
+
+mk(ntype: int, left, right: ref Node): ref Node
+{
+ return ref Node(ntype, left, right, nil, nil);
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
diff --git a/appl/cmd/sh/std.b b/appl/cmd/sh/std.b
new file mode 100644
index 00000000..6a944614
--- /dev/null
+++ b/appl/cmd/sh/std.b
@@ -0,0 +1,812 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+include "filepat.m";
+ filepat: Filepat;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+builtinnames := array[] of {
+ "if", "while", "~", "!", "apply", "for",
+ "status", "pctl", "fn", "subfn", "and", "or",
+ "raise", "rescue", "flag", "getlines", "no",
+};
+
+sbuiltinnames := array[] of {
+ "hd", "tl", "index", "split", "join", "pid", "parse", "env", "pipe",
+};
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("std: cannot load self: %r"));
+ filepat = load Filepat Filepat->PATH;
+ if (filepat == nil)
+ ctxt.fail("bad module",
+ sys->sprint("std: cannot load: %s: %r", Filepat->PATH));
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ ctxt.fail("bad module",
+ sys->sprint("std: cannot load: %s: %r", Bufio->PATH));
+ names := builtinnames;
+ for (i := 0; i < len names; i++)
+ ctxt.addbuiltin(names[i], myself);
+ names = sbuiltinnames;
+ for (i = 0; i < len names; i++)
+ ctxt.addsbuiltin(names[i], myself);
+ env := ctxt.envlist();
+ for (; env != nil; env = tl env) {
+ (name, val) := hd env;
+ if (len name > 3 && name[0:3] == "fn-")
+ fndef(ctxt, name[3:], val, 0);
+ if (len name > 4 && name[0:4] == "sfn-")
+ fndef(ctxt, name[4:], val, 1);
+ }
+ return nil;
+}
+
+whatis(c: ref Sh->Context, sh: Sh, name: string, wtype: int): string
+{
+ ename, fname: string;
+ case wtype {
+ BUILTIN =>
+ (ename, fname) = ("fn-", "fn ");
+ SBUILTIN =>
+ (ename, fname) = ("sfn-", "subfn ");
+ OTHER =>
+ return nil;
+ }
+
+ val := c.get(ename + name);
+ if (val != nil)
+ return fname + name + " " + sh->quoted(hd val :: nil, 0);
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(c: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode, last: int): string
+{
+ status: string;
+ name := (hd cmd).word;
+ val := c.get("fn-" + name);
+ if (val != nil)
+ return c.run(hd val :: tl cmd, last);
+ case name {
+ "if" => status = builtin_if(c, cmd, last);
+ "while" => status = builtin_while(c, cmd, last);
+ "and" => status = builtin_and(c, cmd, last);
+ "apply" => status = builtin_apply(c, cmd, last);
+ "for" => status = builtin_for(c, cmd, last);
+ "or" => status = builtin_or(c, cmd, last);
+ "!" => status = builtin_not(c, cmd, last);
+ "fn" => status = builtin_fn(c, cmd, last, 0);
+ "subfn" => status = builtin_fn(c, cmd, last, 1);
+ "~" => status = builtin_twiddle(c, cmd, last);
+ "status" => status = builtin_status(c, cmd, last);
+ "pctl" => status = builtin_pctl(c, cmd, last);
+ "raise" => status = builtin_raise(c, cmd, last);
+ "rescue" => status = builtin_rescue(c, cmd, last);
+ "flag" => status = builtin_flag(c, cmd, last);
+ "getlines" => status = builtin_getlines(c, cmd, last);
+ "no" => status = builtin_no(c, cmd, last);
+ }
+ return status;
+}
+
+runsbuiltin(c: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode): list of ref Listnode
+{
+ name := (hd cmd).word;
+ val := c.get("sfn-" + name);
+ if (val != nil)
+ return runsubfn(c, val, tl cmd);
+ case name {
+ "pid" =>
+ return ref Listnode(nil, string sys->pctl(0, nil)) :: nil;
+ "hd" =>
+ if (tl cmd == nil)
+ return nil;
+ return hd tl cmd :: nil;
+ "tl" =>
+ if (tl cmd == nil)
+ return nil;
+ return tl tl cmd;
+ "index" =>
+ return sbuiltin_index(c, cmd);
+ "split" =>
+ return sbuiltin_split(c, cmd);
+ "join" =>
+ return sbuiltin_join(c, cmd);
+ "parse" =>
+ return sbuiltin_parse(c, cmd);
+ "env" =>
+ return sbuiltin_env(c, cmd);
+ "pipe" =>
+ return sbuiltin_pipe(c, cmd);
+ }
+ return nil;
+}
+
+runsubfn(ctxt: ref Context, body, args: list of ref Listnode): list of ref Listnode
+{
+ if (body == nil)
+ return nil;
+ ctxt.push();
+ {
+ ctxt.setlocal("result", nil);
+ ctxt.run(hd body :: args, 0);
+ result := ctxt.get("result");
+ ctxt.pop();
+ return result;
+ } exception e {
+ "fail:*" =>
+ ctxt.pop();
+ raise e;
+ }
+}
+
+sbuiltin_index(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode
+{
+ if (len val < 2 || (hd tl val).word == nil)
+ builtinusage(ctxt, "index num list");
+ k := int (hd tl val).word - 1;
+ val = tl tl val;
+ for (; k > 0 && val != nil; k--)
+ val = tl val;
+ if (val != nil)
+ val = hd val :: nil;
+ return val;
+}
+
+# return a parsed version of a string, raising a "parse error" exception if
+# it fails. the string must be a braced command block.
+sbuiltin_parse(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode
+{
+ if (len args != 2)
+ builtinusage(ctxt, "parse arg");
+ args = tl args;
+ if ((hd args).cmd != nil)
+ return ref Listnode((hd args).cmd, nil) :: nil;
+ w := (hd args).word;
+ if (w == nil || w[0] != '{') #}
+ ctxt.fail("parse error", "parse: argument must be a braced block");
+ (n, err) := sh->parse(w);
+ if (err != nil)
+ ctxt.fail("parse error", "parse: " + err);
+ return ref Listnode(n, nil) :: nil;
+}
+
+sbuiltin_env(ctxt: ref Context, nil: list of ref Listnode): list of ref Listnode
+{
+ vl: list of string;
+ for (e := ctxt.envlist(); e != nil; e = tl e) {
+ (n, v) := hd e;
+ if (v != nil) # XXX this is debatable... someone might want to see null local vars.
+ vl = n :: vl;
+ }
+ return sh->stringlist2list(vl);
+}
+
+word(n: ref Listnode): string
+{
+ if (n.word != nil)
+ return n.word;
+ if (n.cmd != nil)
+ n.word = sh->cmd2string(n.cmd);
+ return n.word;
+}
+
+# usage: split [separators] value
+sbuiltin_split(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode
+{
+ n := len args;
+ if (n < 2 || n > 3)
+ builtinusage(ctxt, "split [separators] value");
+ seps: string;
+ if (n == 2) {
+ ifs := ctxt.get("ifs");
+ if (ifs == nil)
+ ctxt.fail("usage", "split: $ifs not set");
+ seps = word(hd ifs);
+ } else {
+ args = tl args;
+ seps = word(hd args);
+ }
+ (nil, toks) := sys->tokenize(word(hd tl args), seps);
+ return sh->stringlist2list(toks);
+}
+
+sbuiltin_join(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode
+{
+ args = tl args;
+ if (args == nil)
+ builtinusage(ctxt, "join separator [arg...]");
+ seps := word(hd args);
+ if (tl args == nil)
+ return ref Listnode(nil, nil) :: nil;
+ s := word(hd tl args);
+ for (args = tl tl args; args != nil; args = tl args)
+ s += seps + word(hd args);
+ return ref Listnode(nil, s) :: nil;
+}
+
+builtin_fn(ctxt: ref Context, args: list of ref Listnode, nil: int, issub: int): string
+{
+ n := len args;
+ title := (hd args).word;
+ if (n < 2)
+ builtinusage(ctxt, title + " [name...] [{body}]");
+ for (al := tl args; tl al != nil; al = tl al)
+ if ((hd al).cmd != nil)
+ builtinusage(ctxt, title + " [name...] [{body}]");
+ if ((hd al).cmd != nil) {
+ cmd := hd al :: nil;
+ for (al = tl args; tl al != nil; al = tl al)
+ fndef(ctxt, (hd al).word, cmd, issub);
+ } else {
+ for (al = tl args; al != nil; al = tl al)
+ fnundef(ctxt, (hd al).word, issub);
+ }
+ return nil;
+}
+
+fndef(ctxt: ref Context, name: string, cmd: list of ref Listnode, issub: int)
+{
+ if (cmd == nil)
+ return;
+ if (issub) {
+ ctxt.set("sfn-" + name, cmd);
+ ctxt.addsbuiltin(name, myself);
+ } else {
+ ctxt.set("fn-" + name, cmd);
+ ctxt.addbuiltin(name, myself);
+ }
+}
+
+fnundef(ctxt: ref Context, name: string, issub: int)
+{
+ if (issub) {
+ ctxt.set("sfn-" + name, nil);
+ ctxt.removesbuiltin(name, myself);
+ } else {
+ ctxt.set("fn-" + name, nil);
+ ctxt.removebuiltin(name, myself);
+ }
+}
+
+builtin_flag(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ n := len args;
+ if (n < 2 || n > 3 || len (hd tl args).word != 1)
+ builtinusage(ctxt, "flag [vxei] [+-]");
+ flag := (hd tl args).word[0];
+ p := "";
+ if (n == 3)
+ p = (hd tl tl args).word;
+ mask := 0;
+ case flag {
+ 'v' => mask = Context.VERBOSE;
+ 'x' => mask = Context.EXECPRINT;
+ 'e' => mask = Context.ERROREXIT;
+ 'i' => mask = Context.INTERACTIVE;
+ * => builtinusage(ctxt, "flag [vxei] [+-]");
+ }
+ case p {
+ "" => if (ctxt.options() & mask)
+ return nil;
+ return "not set";
+ "-" => ctxt.setoptions(mask, 0);
+ "+" => ctxt.setoptions(mask, 1);
+ * => builtinusage(ctxt, "flag [vxei] [+-]");
+ }
+ return nil;
+}
+
+builtin_no(nil: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args != nil)
+ return "yes";
+ return nil;
+}
+
+iscmd(n: ref Listnode): int
+{
+ return n.cmd != nil || (n.word != nil && n.word[0] == '{');
+}
+
+builtin_if(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ args = tl args;
+ nargs := len args;
+ if (nargs < 2)
+ builtinusage(ctxt, "if {cond} {action} [{cond} {action}]... [{elseaction}]");
+
+ status: string;
+ dolstar := ctxt.get("*");
+ while (args != nil) {
+ cmd: ref Listnode = nil;
+ if (tl args == nil) {
+ cmd = hd args;
+ args = tl args;
+ } else {
+ if (!iscmd(hd args))
+ builtinusage(ctxt, "if [{cond} {action}]... [{elseaction}]");
+
+ status = ctxt.run(hd args :: dolstar, 0);
+ if (status == nil) {
+ cmd = hd tl args;
+ args = nil;
+ } else
+ args = tl tl args;
+ setstatus(ctxt, status);
+ }
+ if (cmd != nil) {
+ if (!iscmd(cmd))
+ builtinusage(ctxt, "if [{cond} {action}]... [{elseaction}]");
+
+ status = ctxt.run(cmd :: dolstar, 0);
+ }
+ }
+ return status;
+}
+
+builtin_or(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ s: string;
+ dolstar := ctxt.get("*");
+ for (args = tl args; args != nil; args = tl args) {
+ if (!iscmd(hd args))
+ builtinusage(ctxt, "or [{cmd} ...]");
+ if ((s = ctxt.run(hd args :: dolstar, 0)) == nil)
+ return nil;
+ else
+ setstatus(ctxt, s);
+ }
+ return s;
+}
+
+builtin_and(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ dolstar := ctxt.get("*");
+ for (args = tl args; args != nil; args = tl args) {
+ if (!iscmd(hd args))
+ builtinusage(ctxt, "and [{cmd} ...]");
+ if ((s := ctxt.run(hd args :: dolstar, 0)) != nil)
+ return s;
+ else
+ setstatus(ctxt, nil);
+ }
+ return nil;
+}
+
+builtin_while(ctxt: ref Context, args: list of ref Listnode, nil: int) : string
+{
+ args = tl args;
+ if (len args != 2 || !iscmd(hd args) || !iscmd(hd tl args))
+ builtinusage(ctxt, "while {condition} {cmd}");
+
+ dolstar := ctxt.get("*");
+ cond := hd args :: dolstar;
+ action := hd tl args :: dolstar;
+ status := "";
+
+ for(;;){
+ {
+ while (ctxt.run(cond, 0) == nil)
+ status = setstatus(ctxt, ctxt.run(action, 0));
+ return status;
+ } exception e{
+ "fail:*" =>
+ if (loopexcept(e) == BREAK)
+ return status;
+ }
+ }
+}
+
+builtin_getlines(ctxt: ref Context, argv: list of ref Listnode, nil: int) : string
+{
+ n := len argv;
+ if (n < 2 || n > 3)
+ builtinusage(ctxt, "getlines [separators] {cmd}");
+ argv = tl argv;
+ seps := "\n";
+ if (n == 3) {
+ seps = word(hd argv);
+ argv = tl argv;
+ }
+ if (len seps == 0)
+ builtinusage(ctxt, "getlines [separators] {cmd}");
+ if (!iscmd(hd argv))
+ builtinusage(ctxt, "getlines [separators] {cmd}");
+ cmd := hd argv :: ctxt.get("*");
+ stdin := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ if (stdin == nil)
+ ctxt.fail("bad input", sys->sprint("getlines: cannot open stdin: %r"));
+ status := "";
+ ctxt.push();
+ for(;;){
+ {
+ for (;;) {
+ s: string;
+ if (len seps == 1)
+ s = stdin.gets(seps[0]);
+ else
+ s = stdin.gett(seps);
+ if (s == nil)
+ break;
+ # make sure we don't lose the last unterminated line
+ lastc := s[len s - 1];
+ if (lastc == seps[0])
+ s = s[0:len s - 1];
+ else for (i := 1; i < len seps; i++) {
+ if (lastc == seps[i]) {
+ s = s[0:len s - 1];
+ break;
+ }
+ }
+ ctxt.setlocal("line", ref Listnode(nil, s) :: nil);
+ status = setstatus(ctxt, ctxt.run(cmd, 0));
+ }
+ ctxt.pop();
+ return status;
+ } exception e {
+ "fail:*" =>
+ ctxt.pop();
+ if (loopexcept(e) == BREAK)
+ return status;
+ ctxt.push();
+ }
+ }
+}
+
+# usage: raise [name]
+builtin_raise(ctxt: ref Context, args: list of ref Listnode, nil: int) : string
+{
+ ename: ref Listnode;
+ if (tl args == nil) {
+ e := ctxt.get("exception");
+ if (e == nil)
+ ctxt.fail("bad raise context", "raise: no exception found");
+ ename = (hd e);
+ } else
+ ename = hd tl args;
+ if (ename.word == nil && ename.cmd != nil)
+ ctxt.fail("bad raise context", "raise: bad exception name");
+ xraise("fail:" + ename.word);
+ return nil;
+}
+
+# usage: rescue pattern rescuecmd cmd
+builtin_rescue(ctxt: ref Context, args: list of ref Listnode, last: int) : string
+{
+ args = tl args;
+ if (len args != 3 || !iscmd(hd tl args) || !iscmd(hd tl tl args))
+ builtinusage(ctxt, "rescue pattern {rescuecmd} {cmd}");
+ if ((hd args).word == nil && (hd args).cmd != nil)
+ ctxt.fail("usage", "rescue: bad pattern");
+ dolstar := ctxt.get("*");
+ handler := hd tl args :: dolstar;
+ code := hd tl tl args :: dolstar;
+ {
+ return ctxt.run(code, 0);
+ } exception e {
+ "fail:*" =>
+ ctxt.push();
+ ctxt.set("exception", ref Listnode(nil, e[5:]) :: nil);
+ {
+ status := ctxt.run(handler, last);
+ ctxt.pop();
+ return status;
+ } exception e2{
+ "fail:*" =>
+ ctxt.pop();
+ raise e;
+ }
+ }
+}
+
+builtin_not(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ # syntax: ! cmd [args...]
+ args = tl args;
+ if (args == nil || ctxt.run(args, last) == nil)
+ return "false";
+ return "";
+}
+
+builtin_for(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ Usage: con "for var in [item...] {cmd}";
+ args = tl args;
+ if (args == nil)
+ builtinusage(ctxt, Usage);
+ var := (hd args).word;
+ if (var == nil)
+ ctxt.fail("bad assign", "for: bad variable name");
+ args = tl args;
+ if (args == nil || (hd args).word != "in")
+ builtinusage(ctxt, Usage);
+ args = tl args;
+ if (args == nil)
+ builtinusage(ctxt, Usage);
+ for (eargs := args; tl eargs != nil; eargs = tl eargs)
+ ;
+ cmd := hd eargs;
+ if (!iscmd(cmd))
+ builtinusage(ctxt, Usage);
+
+ status := "";
+ dolstar := ctxt.get("*");
+ for(;;){
+ {
+ for (; tl args != nil; args = tl args) {
+ ctxt.setlocal(var, hd args :: nil);
+ status = setstatus(ctxt, ctxt.run(cmd :: dolstar, 0));
+ }
+ return status;
+ } exception e {
+ "fail:*" =>
+ if (loopexcept(e) == BREAK)
+ return status;
+ args = tl args;
+ }
+ }
+}
+
+CONTINUE, BREAK: con iota;
+loopexcept(ename: string): int
+{
+ case ename[5:] {
+ "break" =>
+ return BREAK;
+ "continue" =>
+ return CONTINUE;
+ * =>
+ raise ename;
+ }
+ return 0;
+}
+
+builtin_apply(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ args = tl args;
+ if (args == nil || !iscmd(hd args))
+ builtinusage(ctxt, "apply {cmd} [val...]");
+
+ status := "";
+ cmd := hd args;
+ for(;;){
+ {
+ for (args = tl args; args != nil; args = tl args)
+ status = setstatus(ctxt, ctxt.run(cmd :: hd args :: nil, 0));
+
+ return status;
+ } exception e{
+ "fail:*" =>
+ if (loopexcept(e) == BREAK)
+ return status;
+ }
+ }
+}
+
+builtin_status(nil: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args != nil)
+ return (hd tl args).word;
+ return "";
+}
+
+pctlnames := array[] of {
+ ("newfd", Sys->NEWFD),
+ ("forkfd", Sys->FORKFD),
+ ("newns", Sys->NEWNS),
+ ("forkns", Sys->FORKNS),
+ ("newpgrp", Sys->NEWPGRP),
+ ("nodevs", Sys->NODEVS)
+};
+
+builtin_pctl(ctxt: ref Context, argv: list of ref Listnode, nil: int): string
+{
+ if (len argv < 2)
+ builtinusage(ctxt, "pctl option... [fdnum...]");
+
+ finalmask := 0;
+ fdlist: list of int;
+ for (argv = tl argv; argv != nil; argv = tl argv) {
+ w := (hd argv).word;
+ if (isnum(w))
+ fdlist = int w :: fdlist;
+ else {
+ for (i := 0; i < len pctlnames; i++) {
+ (name, mask) := pctlnames[i];
+ if (name == w) {
+ finalmask |= mask;
+ break;
+ }
+ }
+ if (i == len pctlnames)
+ ctxt.fail("usage", "pctl: unknown flag " + w);
+ }
+ }
+ sys->pctl(finalmask, fdlist);
+ return nil;
+}
+
+# usage: ~ value pattern...
+builtin_twiddle(ctxt: ref Context, argv: list of ref Listnode, nil: int): string
+{
+ argv = tl argv;
+ if (argv == nil)
+ builtinusage(ctxt, "~ word [pattern...]");
+ if (tl argv == nil)
+ return "no match";
+ w := word(hd argv);
+
+ for (argv = tl argv; argv != nil; argv = tl argv)
+ if (filepat->match(word(hd argv), w))
+ return "";
+
+ return "no match";
+}
+
+#builtin_echo(ctxt: ref Context, argv: list of ref Listnode, nil: int): string
+#{
+# argv = tl argv;
+# nflag := 0;
+# if (argv != nil && word(hd argv) == "-n") {
+# nflag = 1;
+# argv = tl argv;
+# }
+# s: string;
+# if (argv != nil) {
+# s = word(hd argv);
+# for (argv = tl argv; argv != nil; argv = tl argv)
+# s += " " + word(hd argv);
+# }
+# e: int;
+# if (nflag)
+# e = sys->print("%s", s);
+# else
+# e = sys->print("%s\n", s);
+# if (e == -1) {
+# err := sys->sprint("%r");
+# if (ctxt.options() & ctxt.VERBOSE)
+# sys->fprint(sys->fildes(2), "echo: write error: %s\n", err);
+# return err;
+# }
+# return nil;
+#}
+
+ENOEXIST: con "file does not exist";
+TMPDIR: con "/tmp/pipes";
+sbuiltin_pipe(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ n: int;
+ if (len argv != 3 || !iscmd(hd tl tl argv))
+ builtinusage(ctxt, "pipe (from|to|fdnum) {cmd}");
+ s := (hd tl argv).word;
+ case s {
+ "from" =>
+ n = 1;
+ "to" =>
+ n = 0;
+ * =>
+ if (!isnum(s))
+ builtinusage(ctxt, "pipe (from|to|fdnum) {cmd}");
+ n = int s;
+ }
+ pipeid := ctxt.get("pipeid");
+ seq: int;
+ if (pipeid == nil)
+ seq = 0;
+ else
+ seq = int (hd pipeid).word;
+ id := "pipe." + string sys->pctl(0, nil) + "." + string seq;
+ ctxt.set("pipeid", ref Listnode(nil, string ++seq) :: nil);
+ mkdir(TMPDIR);
+ d := "/tmp/" + id + "d";
+ if (mkdir(d) == -1)
+ ctxt.fail("bad pipe", sys->sprint("pipe: cannot make %s: %r", d));
+ if (sys->bind("#|", d, Sys->MREPL) == -1) {
+ sys->remove(d);
+ ctxt.fail("bad pipe", sys->sprint("pipe: cannot bind pipe onto %s: %r", d));
+ }
+ if (rename(d + "/data", id + "x") == -1 || rename(d + "/data1", id + "y")) {
+ sys->unmount(nil, d);
+ sys->remove(d);
+ ctxt.fail("bad pipe", sys->sprint("pipe: cannot rename pipe: %r"));
+ }
+ if (sys->bind(d, TMPDIR, Sys->MBEFORE) == -1) {
+ sys->unmount(nil, d);
+ sys->remove(d);
+ ctxt.fail("bad pipe", sys->sprint("pipe: cannot bind pipe dir: %r"));
+ }
+ sys->unmount(nil, d);
+ sys->remove(d);
+ sync := chan of string;
+ spawn runpipe(sync, ctxt, n, TMPDIR + "/" + id + "x", hd tl tl argv);
+ if ((e := <-sync) != nil)
+ ctxt.fail("bad pipe", e);
+ return ref Listnode(nil, TMPDIR + "/" + id + "y") :: nil;
+}
+
+mkdir(f: string): int
+{
+ if (sys->create(f, Sys->OREAD, Sys->DMDIR | 8r777) == nil)
+ return -1;
+ return 0;
+}
+
+runpipe(sync: chan of string, ctxt: ref Context, fdno: int, p: string, cmd: ref Listnode)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ ctxt = ctxt.copy(1);
+ if ((fd := sys->open(p, Sys->ORDWR)) == nil) {
+ sync <-= sys->sprint("cannot open %s: %r", p);
+ exit;
+ }
+ sys->dup(fd.fd, fdno);
+ fd = nil;
+ sync <-= nil;
+ ctxt.run(cmd :: ctxt.get("*"), 1);
+}
+
+rename(x, y: string): int
+{
+ (ok, nil) := sys->stat(x);
+ if (ok == -1)
+ return -1;
+ inf := sys->nulldir;
+ inf.name = y;
+ if (sys->wstat(x, inf) == -1)
+ return -1;
+ return 0;
+}
+
+builtinusage(ctxt: ref Context, s: string)
+{
+ ctxt.fail("usage", "usage: " + s);
+}
+
+setstatus(ctxt: ref Context, val: string): string
+{
+ ctxt.setlocal("status", ref Listnode(nil, val) :: nil);
+ return val;
+}
+
+# same as sys->raise(), but check that length of error string is
+# acceptable, and truncate as appropriate.
+xraise(s: string)
+{
+ d := array of byte s;
+ if (len d > Sys->WAITLEN)
+ raise string d[0:Sys->WAITLEN];
+ else {
+ d = nil;
+ raise s;
+ }
+}
+
+isnum(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] > '9' || s[i] < '0')
+ return 0;
+ return 1;
+}
+
diff --git a/appl/cmd/sh/string.b b/appl/cmd/sh/string.b
new file mode 100644
index 00000000..b6d079e4
--- /dev/null
+++ b/appl/cmd/sh/string.b
@@ -0,0 +1,212 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+include "string.m";
+ str: String;
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("string: cannot load self: %r"));
+ str = load String String->PATH;
+ if (str == nil)
+ ctxt.fail("bad module",
+ sys->sprint("string: cannot load %s: %r", String->PATH));
+ ctxt.addbuiltin("prefix", myself);
+ ctxt.addbuiltin("in", myself);
+ names := array[] of {
+ "splitl", "splitr", "drop", "take", "splitstrl", "splitstrr",
+ "tolower", "toupper", "len", "alen", "slice", "fields",
+ "padl", "padr",
+ };
+ for (i := 0; i < len names; i++)
+ ctxt.addsbuiltin(names[i], myself);
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(ctxt: ref Context, nil: Sh,
+ argv: list of ref Listnode, nil: int): string
+{
+ case (hd argv).word {
+ "prefix" =>
+ (a, b) := earg2("prefix", ctxt, argv);
+ if (!str->prefix(a, b))
+ return "false";
+ "in" =>
+ (a, b) := earg2("in", ctxt, argv);
+ if (a == nil || !str->in(a[0], b))
+ return "false";
+ }
+ return nil;
+}
+
+runsbuiltin(ctxt: ref Context, nil: Sh,
+ argv: list of ref Listnode): list of ref Listnode
+{
+ name := (hd argv).word;
+ case name {
+ "splitl" =>
+ (a, b) := earg2("splitl", ctxt, argv);
+ return mk2(str->splitl(a, b));
+ "splitr" =>
+ (a, b) := earg2("splitr", ctxt, argv);
+ return mk2(str->splitr(a, b));
+ "drop" =>
+ (a, b) := earg2("drop", ctxt, argv);
+ return mk1(str->drop(a, b));
+ "take" =>
+ (a, b) := earg2("take", ctxt, argv);
+ return mk1(str->take(a, b));
+ "splitstrl" =>
+ (a, b) := earg2("splitstrl", ctxt, argv);
+ return mk2(str->splitstrl(a, b));
+ "splitstrr" =>
+ (a, b) := earg2("splitstrr", ctxt, argv);
+ return mk2(str->splitstrr(a, b));
+ "tolower" =>
+ return mk1(str->tolower(earg1("tolower", ctxt, argv)));
+ "toupper" =>
+ return mk1(str->toupper(earg1("tolower", ctxt, argv)));
+ "len" =>
+ return mk1(string len earg1("len", ctxt, argv));
+ "alen" =>
+ return mk1(string len array of byte earg1("alen", ctxt, argv));
+ "slice" =>
+ return sbuiltin_slice(ctxt, argv);
+ "fields" =>
+ return sbuiltin_fields(ctxt, argv);
+ "padl" =>
+ return sbuiltin_pad(ctxt, argv, -1);
+ "padr" =>
+ return sbuiltin_pad(ctxt, argv, 1);
+ }
+ return nil;
+}
+
+sbuiltin_pad(ctxt: ref Context, argv: list of ref Listnode, dir: int): list of ref Listnode
+{
+ if (tl argv == nil || !isnum((hd tl argv).word))
+ ctxt.fail("usage", "usage: " + (hd argv).word + " n [arg...]");
+
+ argv = tl argv;
+ n := int (hd argv).word * dir;
+ s := "";
+ for (argv = tl argv; argv != nil; argv = tl argv) {
+ s += word(hd argv);
+ if (tl argv != nil)
+ s[len s] = ' ';
+ }
+ if (n != 0)
+ s = sys->sprint("%*s", n, s);
+ return ref Listnode(nil, s) :: nil;
+}
+
+sbuiltin_fields(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ argv = tl argv;
+ if (len argv != 2)
+ ctxt.fail("usage", "usage: fields cl s");
+ cl := word(hd argv);
+ s := word(hd tl argv);
+
+ r: list of string;
+
+ n := 0;
+ for (i := 0; i < len s; i++) {
+ if (str->in(s[i], cl)) {
+ r = s[n:i] :: r;
+ n = i + 1;
+ }
+ }
+ r = s[n:i] :: r;
+ rl: list of ref Listnode;
+ for (; r != nil; r = tl r)
+ rl = ref Listnode(nil, hd r) :: rl;
+ return rl;
+}
+
+
+sbuiltin_slice(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ argv = tl argv;
+ if (len argv != 3 || !isnum((hd argv).word) ||
+ (hd tl argv).word != "end" && !isnum((hd tl argv).word))
+ ctxt.fail("usage", "usage: slice start end arg");
+ n1 := int (hd argv).word;
+ n2: int;
+ s := word(hd tl tl argv);
+ r := "";
+ if ((hd tl argv).word == "end")
+ n2 = len s;
+ else
+ n2 = int (hd tl argv).word;
+ if (n2 > len s)
+ n2 = len s;
+ if (n1 > len s)
+ n1 = len s;
+ if (n2 > n1)
+ r = s[n1:n2];
+ return mk1(r);
+}
+
+earg2(cmd: string, ctxt: ref Context, argv: list of ref Listnode): (string, string)
+{
+ argv = tl argv;
+ if (len argv != 2)
+ ctxt.fail("usage", "usage: " + cmd + " arg1 arg2");
+ return (word(hd argv), word(hd tl argv));
+}
+
+earg1(cmd: string, ctxt: ref Context, argv: list of ref Listnode): string
+{
+ if (len argv != 2)
+ ctxt.fail("usage", "usage: " + cmd + " arg");
+ return word(hd tl argv);
+}
+
+mk2(x: (string, string)): list of ref Listnode
+{
+ (a, b) := x;
+ return ref Listnode(nil, a) :: ref Listnode(nil, b) :: nil;
+}
+
+mk1(x: string): list of ref Listnode
+{
+ return ref Listnode(nil, x) :: nil;
+}
+
+isnum(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] > '9' || s[i] < '0')
+ return 0;
+ return 1;
+}
+
+word(n: ref Listnode): string
+{
+ if (n.word != nil)
+ return n.word;
+ if (n.cmd != nil)
+ n.word = sh->cmd2string(n.cmd);
+ return n.word;
+}
diff --git a/appl/cmd/sh/test.b b/appl/cmd/sh/test.b
new file mode 100644
index 00000000..d8a6b62a
--- /dev/null
+++ b/appl/cmd/sh/test.b
@@ -0,0 +1,96 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+include "itslib.m";
+ itslib: Itslib;
+ Tconfig, S_INFO, S_WARN, S_ERROR, S_FATAL: import itslib;
+
+tconf: ref Tconfig;
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ itslib = load Itslib Itslib->PATH;
+ if (itslib != nil)
+ tconf = itslib->init();
+ sh = shmod;
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("its: cannot load self: %r"));
+ ctxt.addbuiltin("report", myself);
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+
+
+runbuiltin(ctxt: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode, nil: int): string
+{
+ case (hd cmd).word {
+ "report" =>
+ if (len cmd < 4)
+ rusage(ctxt);
+ cmd = tl cmd;
+ sevstr := (hd cmd).word;
+ sev := sevtran(sevstr);
+ if (sev < 0)
+ rusage(ctxt);
+ cmd = tl cmd;
+ verb := (hd cmd).word;
+ cmd = tl cmd;
+ mtext := "";
+ i := 0;
+ while (len cmd) {
+ msg := (hd cmd).word;
+ cmd = tl cmd;
+ if (i++ > 0)
+ mtext = mtext + " ";
+ mtext = mtext + msg;
+ }
+ if (tconf != nil)
+ tconf.report(int sev, int verb, mtext);
+ else
+ sys->fprint(sys->fildes(2), "[itslib missing] %s %s\n", sevstr, mtext);
+ }
+ return nil;
+}
+
+
+runsbuiltin(nil: ref Sh->Context, nil: Sh,
+ nil: list of ref Sh->Listnode): list of ref Listnode
+{
+ return nil;
+}
+
+
+sevtran(sname: string): int
+{
+ SEVMAP := array[] of {"INF", "WRN", "ERR", "FTL"};
+ for (i:=0; i<len SEVMAP; i++)
+ if (sname == SEVMAP[i])
+ return i;
+ return -1;
+}
+
+rusage(ctxt: ref Context)
+{
+ ctxt.fail("usage", "usage: report INF|WRN|ERR|FTL verbosity message[...]");
+}
+
diff --git a/appl/cmd/sh/tk.b b/appl/cmd/sh/tk.b
new file mode 100644
index 00000000..bc6fe753
--- /dev/null
+++ b/appl/cmd/sh/tk.b
@@ -0,0 +1,426 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+
+tklock: chan of int;
+
+chans := array[23] of list of (string, chan of string);
+wins := array[16] of list of (int, ref Tk->Toplevel);
+winid := 0;
+
+badmodule(ctxt: ref Context, p: string)
+{
+ ctxt.fail("bad module", sys->sprint("tk: cannot load %s: %r", p));
+}
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+
+ myself = load Shellbuiltin "$self";
+ if (myself == nil) badmodule(ctxt, "self");
+
+ tk = load Tk Tk->PATH;
+ if (tk == nil) badmodule(ctxt, Tk->PATH);
+
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil) badmodule(ctxt, Tkclient->PATH);
+ tkclient->init();
+
+ tklock = chan[1] of int;
+
+ ctxt.addbuiltin("tk", myself);
+ ctxt.addbuiltin("chan", myself);
+ ctxt.addbuiltin("send", myself);
+
+ ctxt.addsbuiltin("tk", myself);
+ ctxt.addsbuiltin("recv", myself);
+ ctxt.addsbuiltin("alt", myself);
+ ctxt.addsbuiltin("tkquote", myself);
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(ctxt: ref Context, nil: Sh,
+ cmd: list of ref Listnode, nil: int): string
+{
+ case (hd cmd).word {
+ "tk" => return builtin_tk(ctxt, cmd);
+ "chan" => return builtin_chan(ctxt, cmd);
+ "send" => return builtin_send(ctxt, cmd);
+ }
+ return nil;
+}
+
+runsbuiltin(ctxt: ref Context, nil: Sh,
+ cmd: list of ref Listnode): list of ref Listnode
+{
+ case (hd cmd).word {
+ "tk" => return sbuiltin_tk(ctxt, cmd);
+ "recv" => return sbuiltin_recv(ctxt, cmd);
+ "alt" => return sbuiltin_alt(ctxt, cmd);
+ "tkquote" => return sbuiltin_tkquote(ctxt, cmd);
+ }
+ return nil;
+}
+
+builtin_tk(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ # usage: tk window _title_ _options_
+ # tk wintitle _winid_ _title_
+ # tk _winid_ _cmd_
+ if (tl argv == nil)
+ ctxt.fail("usage", "usage: tk (<winid>|window|onscreen|winctlwintitle|del|namechan) args...");
+ argv = tl argv;
+ w := (hd argv).word;
+ case w {
+ "window" =>
+ remark(ctxt, string makewin(ctxt, tl argv));
+ "wintitle" =>
+ argv = tl argv;
+ # change the title of a window
+ if (len argv != 2 || !isnum((hd argv).word))
+ ctxt.fail("usage", "usage: tk wintitle winid title");
+ tkclient->settitle(egetwin(ctxt, hd argv), word(hd tl argv));
+ "winctl" =>
+ argv = tl argv;
+ if (len argv != 2 || !isnum((hd argv).word))
+ ctxt.fail("usage", "usage: tk winctl winid cmd");
+ wid := (hd argv).word;
+ win := egetwin(ctxt, hd argv);
+ rq := word(hd tl argv);
+ if (rq == "exit") {
+ delwin(int wid);
+ delchan(wid);
+ }
+ tkclient->wmctl(win, rq);
+ "onscreen" =>
+ argv = tl argv;
+ if (len argv < 1 || !isnum((hd argv).word))
+ ctxt.fail("usage", "usage: tk onscreen winid [how]");
+ wid := (hd argv).word;
+ how := "";
+ if(tl argv != nil)
+ how = word(hd tl argv);
+ win := egetwin(ctxt, hd argv);
+ tkclient->startinput(win, "ptr" :: "kbd" :: nil);
+ tkclient->onscreen(win, how);
+ "namechan" =>
+ argv = tl argv;
+ n := len argv;
+ if (n < 2 || n > 3 || !isnum((hd argv).word))
+ ctxt.fail("usage", "usage: tk namechan winid chan [name]");
+ name: string;
+ if (n == 3)
+ name = word(hd tl tl argv);
+ else
+ name = word(hd tl argv);
+ tk->namechan(egetwin(ctxt, hd argv), egetchan(ctxt, hd tl argv), name);
+
+ "del" =>
+ if (len argv < 2)
+ ctxt.fail("usage", "usage: tk del id...");
+ for (argv = tl argv; argv != nil; argv = tl argv) {
+ id := (hd argv).word;
+ if (isnum(id))
+ delwin(int id);
+ delchan(id);
+ }
+ * =>
+ e := tkcmd(ctxt, argv);
+ if (e != nil)
+ remark(ctxt, e);
+ if (e != nil && e[0] == '!')
+ return e;
+ }
+ return nil;
+}
+
+remark(ctxt: ref Context, s: string)
+{
+ if (ctxt.options() & ctxt.INTERACTIVE)
+ sys->print("%s\n", s);
+}
+
+# create a new window (and its associated channel)
+makewin(ctxt: ref Context, argv: list of ref Listnode): int
+{
+ if (argv == nil)
+ ctxt.fail("usage", "usage: tk window title options");
+
+ if (ctxt.drawcontext == nil)
+ ctxt.fail("no draw context", sys->sprint("tk: no graphics context available"));
+
+ (title, options) := (word(hd argv), concat(tl argv));
+ (top, topchan) := tkclient->toplevel(ctxt.drawcontext, options, title, Tkclient->Appl);
+ newid := addwin(top);
+ addchan(string newid, topchan);
+ return newid;
+}
+
+builtin_chan(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ # create a new channel
+ argv = tl argv;
+ if (argv == nil)
+ ctxt.fail("usage", "usage: chan name....");
+ for (; argv != nil; argv = tl argv) {
+ name := (hd argv).word;
+ if (name == nil || isnum(name))
+ ctxt.fail("bad chan", "tk: bad channel name "+q(name));
+ if (addchan(name, chan of string) == nil)
+ ctxt.fail("bad chan", "tk: channel "+q(name)+" already exists");
+ }
+ return nil;
+}
+
+builtin_send(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ if (len argv != 3)
+ ctxt.fail("usage", "usage: send chan arg");
+ argv = tl argv;
+ c := egetchan(ctxt, hd argv);
+ c <-= word(hd tl argv);
+ return nil;
+}
+
+
+sbuiltin_tk(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ # usage: tk _winid_ _command_
+ # tk window _title_ _options_
+ argv = tl argv;
+ if (argv == nil)
+ ctxt.fail("usage", "tk (window|wid) args");
+ case (hd argv).word {
+ "window" =>
+ return ref Listnode(nil, string makewin(ctxt, tl argv)) :: nil;
+ "winids" =>
+ ret: list of ref Listnode;
+ for (i := 0; i < len wins; i++)
+ for (wl := wins[i]; wl != nil; wl = tl wl)
+ ret = ref Listnode(nil, string (hd wl).t0) :: ret;
+ return ret;
+ * =>
+ return ref Listnode(nil, tkcmd(ctxt, argv)) :: nil;
+ }
+}
+
+sbuiltin_alt(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ # usage: alt chan ...
+ argv = tl argv;
+ if (argv == nil)
+ ctxt.fail("usage", "usage: alt chan...");
+ ca := array[len argv] of chan of string;
+ cname := array[len ca] of string;
+ i := 0;
+ for (; argv != nil; argv = tl argv) {
+ ca[i] = egetchan(ctxt, hd argv);
+ cname[i] = (hd argv).word;
+ i++;
+ }
+ n := 0;
+ v: string;
+ if (i == 1)
+ v = <-ca[0];
+ else
+ (n, v) = <-ca;
+
+ return ref Listnode(nil, cname[n]) :: ref Listnode(nil, v) :: nil;
+}
+
+sbuiltin_recv(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ # usage: recv chan
+ if (len argv != 2)
+ ctxt.fail("usage", "usage: recv chan");
+ ch := hd tl argv;
+ c := egetchan(ctxt, ch);
+ if(!isnum(ch.word))
+ return ref Listnode(nil, <-c) :: nil;
+
+ win := egetwin(ctxt, ch);
+ for(;;)alt{
+ key := <-win.ctxt.kbd =>
+ tk->keyboard(win, key);
+ p := <-win.ctxt.ptr =>
+ tk->pointer(win, *p);
+ s := <-win.ctxt.ctl or
+ s = <-win.wreq or
+ s = <-c =>
+ return ref Listnode(nil, s) :: nil;
+ }
+}
+
+sbuiltin_tkquote(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ if (len argv != 2)
+ ctxt.fail("usage", "usage: tkquote arg");
+ return ref Listnode(nil, tk->quote(word(hd tl argv))) :: nil;
+}
+
+tkcmd(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ if (argv == nil || !isnum((hd argv).word))
+ ctxt.fail("usage", "usage: tk winid command");
+
+ return tk->cmd(egetwin(ctxt, hd argv), concat(tl argv));
+}
+
+hashfn(s: string, n: int): int
+{
+ h := 0;
+ m := len s;
+ for(i:=0; i<m; i++){
+ h = 65599*h+s[i];
+ }
+ return (h & 16r7fffffff) % n;
+}
+
+q(s: string): string
+{
+ return "'" + s + "'";
+}
+
+egetchan(ctxt: ref Context, n: ref Listnode): chan of string
+{
+ if ((c := getchan(n.word)) == nil)
+ ctxt.fail("bad chan", "tk: bad channel name "+ q(n.word));
+ return c;
+}
+
+# assumes that n.word has been checked and found to be numeric.
+egetwin(ctxt: ref Context, n: ref Listnode): ref Tk->Toplevel
+{
+ wid := int n.word;
+ if (wid < 0 || (top := getwin(wid)) == nil)
+ ctxt.fail("bad win", "tk: unknown window id " + q(n.word));
+ return top;
+}
+
+getchan(name: string): chan of string
+{
+ n := hashfn(name, len chans);
+ for (cl := chans[n]; cl != nil; cl = tl cl) {
+ (cname, c) := hd cl;
+ if (cname == name)
+ return c;
+ }
+ return nil;
+}
+
+addchan(name: string, c: chan of string): chan of string
+{
+ n := hashfn(name, len chans);
+ tklock <-= 1;
+ if (getchan(name) == nil)
+ chans[n] = (name, c) :: chans[n];
+ <-tklock;
+ return c;
+}
+
+delchan(name: string)
+{
+ n := hashfn(name, len chans);
+ tklock <-= 1;
+ ncl: list of (string, chan of string);
+ for (cl := chans[n]; cl != nil; cl = tl cl) {
+ (cname, nil) := hd cl;
+ if (cname != name)
+ ncl = hd cl :: ncl;
+ }
+ chans[n] = ncl;
+ <-tklock;
+}
+
+addwin(top: ref Tk->Toplevel): int
+{
+ tklock <-= 1;
+ id := winid++;
+ slot := id % len wins;
+ wins[slot] = (id, top) :: wins[slot];
+ <-tklock;
+ return id;
+}
+
+delwin(id: int)
+{
+ tklock <-= 1;
+ slot := id % len wins;
+ nwl: list of (int, ref Tk->Toplevel);
+ for (wl := wins[slot]; wl != nil; wl = tl wl) {
+ (wid, nil) := hd wl;
+ if (wid != id)
+ nwl = hd wl :: nwl;
+ }
+ wins[slot] = nwl;
+ <-tklock;
+}
+
+getwin(id: int): ref Tk->Toplevel
+{
+ slot := id % len wins;
+ for (wl := wins[slot]; wl != nil; wl = tl wl) {
+ (wid, top) := hd wl;
+ if (wid == id)
+ return top;
+ }
+ return nil;
+}
+
+word(n: ref Listnode): string
+{
+ if (n.word != nil)
+ return n.word;
+ if (n.cmd != nil)
+ n.word = sh->cmd2string(n.cmd);
+ return n.word;
+}
+
+isnum(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] > '9' || s[i] < '0')
+ return 0;
+ return 1;
+}
+
+concat(argv: list of ref Listnode): string
+{
+ if (argv == nil)
+ return nil;
+ s := word(hd argv);
+ for (argv = tl argv; argv != nil; argv = tl argv)
+ s += " " + word(hd argv);
+ return s;
+}
+
+lockproc(c: chan of int)
+{
+ sys->pctl(Sys->NEWFD|Sys->NEWNS, nil);
+ for(;;){
+ c <-= 1;
+ <-c;
+ }
+}
diff --git a/appl/cmd/sha1sum.b b/appl/cmd/sha1sum.b
new file mode 100644
index 00000000..0c39ac21
--- /dev/null
+++ b/appl/cmd/sha1sum.b
@@ -0,0 +1,65 @@
+implement SHA1sum;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+
+SHA1sum: module
+{
+ init: fn(nil : ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ kr = load Keyring Keyring->PATH;
+ a := tl argv;
+ err := 0;
+ if(a != nil){
+ for( ; a != nil; a = tl a) {
+ s := hd a;
+ fd := sys->open(s, Sys->OREAD);
+ if (fd == nil) {
+ sys->fprint(stderr, "sha1sum: cannot open %s: %r\n", s);
+ err = 1;
+ } else
+ err |= sha1sum(fd, s);
+ }
+ } else
+ err |= sha1sum(sys->fildes(0), "");
+ if(err)
+ raise "fail:error";
+}
+
+sha1sum(fd: ref Sys->FD, file: string): int
+{
+ err := 0;
+ buf := array[Sys->ATOMICIO] of byte;
+ state: ref Keyring->DigestState = nil;
+ nbytes := big 0;
+ while((nr := sys->read(fd, buf, len buf)) > 0){
+ state = kr->sha1(buf, nr, nil, state);
+ nbytes += big nr;
+ }
+ if(nr < 0) {
+ sys->fprint(stderr, "sha1sum: error reading %s: %r\n", file);
+ err = 1;
+ }
+ digest := array[Keyring->SHA1dlen] of byte;
+ kr->sha1(buf, 0, digest, state);
+ sum := "";
+ for(i:=0; i<len digest; i++)
+ sum += sys->sprint("%2.2ux", int digest[i]);
+ if(file != nil)
+ sys->print("%s\t%s\n", sum, file);
+ else
+ sys->print("%s\n", sum);
+ return err;
+}
diff --git a/appl/cmd/shutdown.b b/appl/cmd/shutdown.b
new file mode 100644
index 00000000..8eb7a86c
--- /dev/null
+++ b/appl/cmd/shutdown.b
@@ -0,0 +1,72 @@
+implement Shutdown;
+
+include "sys.m";
+sys: Sys;
+FD: import Sys;
+stderr: ref FD;
+
+include "draw.m";
+Context: import Draw;
+
+sysctl: con "/dev/sysctl";
+reboot: con "reboot";
+halt: con "halt";
+
+Shutdown: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+rflag: int;
+hflag: int;
+
+init(nil: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ stderr = sys->fildes(2);
+
+ argv = tl argv;
+ if(len argv < 1)
+ usage();
+
+ while(argv != nil && len hd argv && (arg := hd argv)[0] == '-' && len arg > 1){
+ case arg[1] {
+ 'r' =>
+ rflag = 1;
+ 'h' =>
+ hflag = 1;
+ }
+ argv = tl argv;
+ }
+
+ if(rflag == 0 && hflag == 0)
+ usage();
+
+ if(rflag == 1 && hflag == 1)
+ usage();
+
+ fd := sys->open(sysctl, sys->OWRITE);
+ if(fd == nil) {
+ sys->fprint(stderr, "shutdown: %r\n");
+ exit;
+ }
+
+ if(rflag == 1)
+ if (sys->write(fd, array of byte reboot, len reboot) < 0) {
+ sys->fprint(stderr, "shutdown: write failed: %r\n");
+ exit;
+ }
+
+ if(hflag == 1)
+ if (sys->write(fd, array of byte halt, len halt) < 0) {
+ sys->fprint(stderr, "shutdown: write failed: %r\n");
+ exit;
+ }
+}
+
+usage()
+{
+ sys->fprint(stderr, "usage: shutdown -r | -h\n");
+ exit;
+}
diff --git a/appl/cmd/sleep.b b/appl/cmd/sleep.b
new file mode 100644
index 00000000..4066f453
--- /dev/null
+++ b/appl/cmd/sleep.b
@@ -0,0 +1,46 @@
+implement Sleep;
+
+include "sys.m";
+sys: Sys;
+
+include "draw.m";
+
+Sleep: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if(sys == nil || argv == nil)
+ return;
+ argv = tl argv;
+ if(argv != nil && isvalid(hd argv)){
+ t := int hd argv;
+ if(t > 16r7fffffff / 1000)
+ t = 16r7fffffff / 1000;
+ sys->sleep(t * 1000);
+ } else {
+ sys->fprint(sys->fildes(2), "usage: sleep time\n");
+ raise "fail:usage";
+ }
+}
+
+isvalid(t: string): int
+{
+ l := len t;
+ if(l > 0 && (t[0] == '-' || t[0] == '+'))
+ x := 1;
+ else
+ x = 0;
+ ok := 0;
+ while(x < l) {
+ d := t[x];
+ if(d < '0' || d > '9')
+ return 0;
+ ok = 1;
+ x++;
+ }
+ return ok;
+}
diff --git a/appl/cmd/sort.b b/appl/cmd/sort.b
new file mode 100644
index 00000000..1accd583
--- /dev/null
+++ b/appl/cmd/sort.b
@@ -0,0 +1,129 @@
+implement Sort;
+
+include "sys.m";
+ sys: Sys;
+include "bufio.m";
+include "draw.m";
+include "arg.m";
+
+Sort: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: sort [-n] [file]\n");
+ raise "fail:usage";
+}
+
+Incr: con 2000; # growth quantum for record array
+
+init(nil : ref Draw->Context, args : list of string)
+{
+ bio : ref Bufio->Iobuf;
+
+ sys = load Sys Sys->PATH;
+ stderr := sys->fildes(2);
+ bufio := load Bufio Bufio->PATH;
+ if (bufio == nil) {
+ sys->fprint(stderr, "sort: cannot load %s: %r\n", Bufio->PATH);
+ raise "fail:bad module";
+ }
+ Iobuf: import bufio;
+ arg := load Arg Arg->PATH;
+ if (arg == nil) {
+ sys->fprint(stderr, "sort: cannot load %s: %r\n", Arg->PATH);
+ raise "fail:bad module";
+ }
+
+ nflag := 0;
+ rflag := 0;
+ arg->init(args);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'n' =>
+ nflag = 1;
+ 'r' =>
+ rflag = 1;
+ * =>
+ usage();
+ }
+ }
+ args = arg->argv();
+ if (len args > 1)
+ usage();
+ if (args != nil) {
+ bio = bufio->open(hd args, Bufio->OREAD);
+ if (bio == nil) {
+ sys->fprint(stderr, "sort: cannot open %s: %r\n", hd args);
+ raise "fail:open file";
+ }
+ }
+ else
+ bio = bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ a := array[Incr] of string;
+ n := 0;
+ while ((s := bio.gets('\n')) != nil) {
+ if (n >= len a) {
+ b := array[len a + Incr] of string;
+ b[0:] = a;
+ a = b;
+ }
+ a[n++] = s;
+ }
+ if (nflag)
+ mergesortnumeric(a, array[n] of string, n);
+ else
+ mergesort(a, array[n] of string, n);
+
+ stdout := bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ if (rflag) {
+ for (i := n-1; i >= 0; i--)
+ stdout.puts(a[i]);
+ } else {
+ for (i := 0; i < n; i++)
+ stdout.puts(a[i]);
+ }
+ stdout.close();
+}
+
+mergesort(a, b: array of string, r: int)
+{
+ if (r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m], m);
+ mergesort(a[m:r], b[m:r], r-m);
+ b[0:] = a[0:r];
+ for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if (b[i] > b[j])
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if (i < m)
+ a[k:] = b[i:m];
+ else if (j < r)
+ a[k:] = b[j:r];
+ }
+}
+
+mergesortnumeric(a, b: array of string, r: int)
+{
+ if (r > 1) {
+ m := (r-1)/2 + 1;
+ mergesortnumeric(a[0:m], b[0:m], m);
+ mergesortnumeric(a[m:r], b[m:r], r-m);
+ b[0:] = a[0:r];
+ for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if (int b[i] > int b[j])
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if (i < m)
+ a[k:] = b[i:m];
+ else if (j < r)
+ a[k:] = b[j:r];
+ }
+}
diff --git a/appl/cmd/spki/mkfile b/appl/cmd/spki/mkfile
new file mode 100644
index 00000000..b7edefd1
--- /dev/null
+++ b/appl/cmd/spki/mkfile
@@ -0,0 +1,22 @@
+<../../../mkconfig
+
+TARG=\
+ verify.dis\
+
+SYSMODULES=\
+ arg.m\
+ keyring.m\
+ security.m\
+ rand.m\
+ sys.m\
+ draw.m\
+ bufio.m\
+ string.m\
+ styx.m\
+ styxservers.m\
+ sexprs.m\
+ spki.m\
+
+DISBIN=$ROOT/dis/spki
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/spki/verify.b b/appl/cmd/spki/verify.b
new file mode 100644
index 00000000..9eab6b41
--- /dev/null
+++ b/appl/cmd/spki/verify.b
@@ -0,0 +1,107 @@
+implement Verify;
+
+#
+# Copyright © 2004 Vita Nuova Holdings Limited
+#
+
+# work in progress
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "sexprs.m";
+ sexprs: Sexprs;
+ Sexp: import sexprs;
+
+include "spki.m";
+ spki: SPKI;
+ Hash, Key, Cert, Name, Subject, Signature, Seqel, Toplev, Valid: import spki;
+ dump: import spki;
+
+ verifier: Verifier;
+ Speaksfor: import verifier;
+
+include "encoding.m";
+ base64: Encoding;
+
+Verify: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+debug := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ bufio = load Bufio Bufio->PATH;
+ sexprs = load Sexprs Sexprs->PATH;
+ spki = load SPKI SPKI->PATH;
+ verifier = load Verifier Verifier->PATH;
+ base64 = load Encoding Encoding->BASE64PATH;
+
+ sexprs->init();
+ spki->init();
+ verifier->init();
+
+ f := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ for(;;){
+ (e, err) := Sexp.read(f);
+ if(e == nil && err == nil)
+ break;
+ if(err != nil)
+ error(sys->sprint("invalid s-expression: %s", err));
+ (top, diag) := spki->parse(e);
+ if(diag != nil)
+ error(sys->sprint("invalid SPKI structure: %s", diag));
+ pick t := top {
+ C =>
+ if(debug)
+ sys->print("cert: %s\n", t.v.text());
+ a := spki->hashexp(e, "md5");
+ Sig =>
+ sys->print("got signature %q\n", t.v.text());
+ K =>
+ sys->print("got key %q\n", t.v.text());
+ Seq =>
+ els := t.v;
+ if(debug){
+ sys->print("(sequence");
+ for(; els != nil; els = tl els)
+ sys->print(" %s", (hd els).text());
+ sys->print(")");
+ }
+ (claim, rem, whynot) := verifier->verify(t.v);
+ if(whynot != nil){
+ if(rem == nil)
+ s := "end of sequence";
+ else
+ s = (hd rem).text();
+ sys->fprint(sys->fildes(2), "verify: failed to verify at %#q: %s\n", s, whynot);
+ }else{
+ if(claim.regarding != nil)
+ scope := sys->sprint(" regarding %q", claim.regarding.text());
+ sys->print("verified: %q speaks for %q%s\n", claim.subject.text(), claim.name.text(), scope);
+ }
+ * =>
+ sys->print("unexpected SPKI type: %q\n", e.text());
+ }
+ }
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "verify: %s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/src.b b/appl/cmd/src.b
new file mode 100644
index 00000000..70c9da65
--- /dev/null
+++ b/appl/cmd/src.b
@@ -0,0 +1,28 @@
+implement Src;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "dis.m";
+ dis: Dis;
+
+Src: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ dis = load Dis Dis->PATH;
+
+ if(dis != nil){
+ dis->init();
+ for(argv = tl argv; argv != nil; argv = tl argv){
+ src := dis->src(hd argv);
+ if(src == nil)
+ src = "?";
+ sys->print("%s: %s\n", hd argv, src);
+ }
+ }
+}
diff --git a/appl/cmd/stack.b b/appl/cmd/stack.b
new file mode 100644
index 00000000..7b90a0b5
--- /dev/null
+++ b/appl/cmd/stack.b
@@ -0,0 +1,184 @@
+implement Command;
+
+include "sys.m";
+ sys: Sys;
+ print, fprint, FD: import sys;
+ stderr: ref FD;
+
+include "draw.m";
+
+include "debug.m";
+ debug: Debug;
+ Prog, Module, Exp: import debug;
+
+include "arg.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "env.m";
+ env: Env;
+
+include "string.m";
+ str: String;
+
+include "dis.m";
+ dism: Dis;
+
+Command: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(stderr, "usage: stack [-v] pid\n");
+ raise "fail:usage";
+}
+
+badmodule(p: string)
+{
+ sys->fprint(stderr, "stack: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+sbldirs: list of (string, string);
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ badmodule(Bufio->PATH);
+ debug = load Debug Debug->PATH;
+ if(debug == nil)
+ badmodule(Debug->PATH);
+ env = load Env Env->PATH;
+ if (env != nil) {
+ str = load String String->PATH;
+ if (str == nil)
+ badmodule(String->PATH);
+ }
+ bout := bufio->fopen(sys->fildes(1), Sys->OWRITE);
+
+ arg->init(argv);
+ verbose := 0;
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'v' =>
+ verbose = 1;
+ 'p' =>
+ dispath := arg->arg();
+ sblpath := arg->arg();
+ if (dispath == nil || sblpath == nil)
+ usage();
+ sbldirs = (addslash(dispath), addslash(sblpath)) :: sbldirs;
+ * =>
+ usage();
+ }
+ }
+ if (env != nil && (pathl := env->getenv("sblpath")) != nil) {
+ toks := str->unquoted(pathl);
+ for (; toks != nil && tl toks != nil; toks = tl tl toks)
+ sbldirs = (addslash(hd toks), addslash(hd tl toks)) :: sbldirs;
+ }
+ t: list of (string, string);
+ for (; sbldirs != nil; sbldirs = tl sbldirs)
+ t = hd sbldirs :: t;
+ sbldirs = t;
+
+ argv = arg->argv();
+ if(argv == nil)
+ usage();
+
+ debug->init();
+
+ (p, err) := debug->prog(int hd argv);
+ if(err != nil){
+ fprint(stderr, "stack: %s\n", err);
+ return;
+ }
+ stk: array of ref Exp;
+ (stk, err) = p.stack();
+
+ if(err != nil){
+ fprint(stderr, "stack: %s\n", err);
+ return;
+ }
+
+ for(i := 0; i < len stk; i++){
+ stdsym(stk[i].m);
+ stk[i].m.stdsym();
+ stk[i].findsym();
+ bout.puts(stk[i].name + "(");
+ vs := stk[i].expand();
+ if(verbose && vs != nil){
+ for(j := 0; j < len vs; j++){
+ if(vs[j].name == "args"){
+ d := vs[j].expand();
+ s := "";
+ for(j = 0; j < len d; j++) {
+ bout.puts(sys->sprint("%s%s=%s", s, d[j].name, d[j].val().t0));
+ s = ", ";
+ }
+ break;
+ }
+ }
+ }
+ bout.puts(sys->sprint(") %s\n", stk[i].srcstr()));
+ if(verbose && vs != nil){
+ for(j := 0; j < len vs; j++){
+ if(vs[j].name == "locals"){
+ d := vs[j].expand();
+ for(j = 0; j < len d; j++)
+ bout.puts("\t" + d[j].name + "=" + d[j].val().t0 + "\n");
+ break;
+ }
+ }
+ }
+ }
+ bout.flush();
+}
+
+stdsym(m: ref Module)
+{
+ dis := m.dis();
+ if(dism == nil){
+ dism = load Dis Dis->PATH;
+ if(dism != nil)
+ dism->init();
+ }
+ if(dism != nil && (sp := dism->src(dis)) != nil){
+ sp = sp[0: len sp - 1] + "sbl";
+ (sym, err) := debug->sym(sp);
+ if (sym != nil) {
+ m.addsym(sym);
+ return;
+ }
+ }
+ for (sbl := sbldirs; sbl != nil; sbl = tl sbl) {
+ (dispath, sblpath) := hd sbl;
+ if (len dis > len dispath && dis[0:len dispath] == dispath) {
+ sblpath = sblpath + dis[len dispath:];
+ if (len sblpath > 4 && sblpath[len sblpath - 4:] == ".dis")
+ sblpath = sblpath[0:len sblpath - 4] + ".sbl";
+ (sym, err) := debug->sym(sblpath);
+ if (sym != nil) {
+ m.addsym(sym);
+ return;
+ }
+ }
+ }
+}
+
+addslash(p: string): string
+{
+ if (p != nil && p[len p - 1] != '/')
+ p[len p] = '/';
+ return p;
+}
diff --git a/appl/cmd/stackv.b b/appl/cmd/stackv.b
new file mode 100644
index 00000000..173f8b30
--- /dev/null
+++ b/appl/cmd/stackv.b
@@ -0,0 +1,445 @@
+implement Stackv;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "debug.m";
+ debug: Debug;
+ Prog, Module, Exp: import debug;
+ Tadt, Tarray, Tbig, Tbyte, Treal,
+ Tfn, Tint, Tlist,
+ Tref, Tstring, Tslice: import Debug;
+include "arg.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+stderr: ref Sys->FD;
+stdout: ref Iobuf;
+hasht := array[97] of list of string;
+
+Stackv: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+maxrecur := 16r7ffffffe;
+
+badmodule(p: string)
+{
+ sys->fprint(stderr, "stackv: cannot load %q: %r\n", p);
+ raise "fail:bad module";
+}
+
+currp: ref Prog;
+showtypes := 1;
+showsource := 0;
+sep := "\t";
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ debug = load Debug Debug->PATH;
+ if(debug == nil)
+ badmodule(Debug->PATH);
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ badmodule(Bufio->PATH);
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+ stdout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+
+ arg->init(argv);
+ arg->setusage("stackv [-Tl] [-i indent] [-r maxdepth] [-s dis sbl]... [pid[.sym...] ...]");
+ sblfile := "";
+ while((opt := arg->opt()) != 0){
+ case opt {
+ 's' =>
+ arg->earg(); # XXX make it a list of maps from dis to sbl later
+ sblfile = arg->earg();
+ 'l' =>
+ showsource = 1;
+ 'r' =>
+ maxrecur = int arg->earg();
+ 'T' =>
+ showtypes = 0;
+ 'i' =>
+ sep = arg->earg();
+ * =>
+ arg->usage();
+ }
+ }
+ debug->init();
+ argv = arg->argv();
+ printpids := len argv > 1;
+ if(printpids)
+ maxrecur++;
+ for(; argv != nil; argv = tl argv)
+ db(sys->tokenize(hd argv, ".").t1, printpids);
+}
+
+db(toks: list of string, printpid: int): int
+{
+ if(toks == nil){
+ sys->fprint(stderr, "stackv: bad pid\n");
+ return -1;
+ }
+ if((pid := int hd toks) <= 0){
+ sys->fprint(stderr, "stackv: bad pid %q\n", hd toks);
+ return -1;
+ }
+ err: string;
+ p: ref Prog;
+
+ # reuse process if possible
+ if(currp == nil || currp.id != pid){
+ (currp, err) = debug->prog(pid);
+ if(err != nil){
+ sys->fprint(stderr, "stackv: %s\n", err);
+ return -1;
+ }
+ if(currp == nil){
+ sys->fprint(stderr, "stackv: nil prog from pid %d\n", pid);
+ return -1;
+ }
+ }
+ p = currp;
+ stk: array of ref Exp;
+ (stk, err) = p.stack();
+ if(err != nil){
+ sys->fprint(stderr, "stackv: %s\n", err);
+ return -1;
+ }
+ for (i := 0; i < len stk; i++) {
+ stk[i].m.stdsym();
+ stk[i].findsym();
+ }
+ depth := 0;
+ if(printpid){
+ stdout.puts(sys->sprint("prog %d {\n", pid)); # }
+ depth++;
+ }
+ pexp(stk, tl toks, depth);
+ if(printpid)
+ stdout.puts("}\n");
+ stdout.flush();
+ return 0;
+}
+
+pexp(stk: array of ref Exp, toks: list of string, depth: int)
+{
+ if(toks == nil){
+ for (i := 0; i < len stk; i++)
+ pfn(stk[i], depth);
+ }else{
+ exp := stackfindsym(stk, toks, depth);
+ if(exp == nil)
+ return;
+ pname(exp, depth);
+ stdout.putc('\n');
+ }
+}
+
+stackfindsym(stk: array of ref Exp, toks: list of string, depth: int): ref Exp
+{
+ fname := hd toks;
+ toks = tl toks;
+ for(i := 0; i < len stk; i++){
+ s := stk[i].name;
+ if(s == fname)
+ break;
+ if(hasdot(s) && toks != nil && s == fname+"."+hd toks){
+ fname += "."+hd toks;
+ toks = tl toks;
+ break;
+ }
+ }
+ if(i == len stk){
+ indent(depth);
+ stdout.puts("function not found\n");
+ return nil;
+ }
+ if(toks == nil)
+ return stk[i];
+ stk = stk[i].expand();
+ if(hd toks == "module"){
+ if((e := getname(stk, "module")) == nil){
+ indent(depth);
+ stdout.puts(sys->sprint("no module declarations in function %q\n", fname));
+ }else if((e = symfindsym(e, tl toks, depth)) != nil)
+ return e;
+ return nil;
+ }
+ for(t := "locals" :: "args" :: "module" :: nil; t != nil; t = tl t){
+ if((e := getname(stk, hd t)) == nil)
+ continue;
+ if((e = symfindsym(e, toks, depth)) != nil)
+ return e;
+ }
+ indent(depth);
+ stdout.puts(sys->sprint("symbol %q not found in function %q\n", hd toks, fname));
+ return nil;
+}
+
+hasdot(s: string): int
+{
+ for(i := 0; i < len s; i++)
+ if(s[i] == '.')
+ return 1;
+ return 0;
+}
+
+symfindsym(e: ref Exp, toks: list of string, depth: int): ref Exp
+{
+ if(toks == nil)
+ return e;
+ exps := e.expand();
+ for(i := 0; i < len exps; i++)
+ if(exps[i].name == hd toks)
+ return symfindsym(exps[i], tl toks, depth);
+ return nil;
+}
+
+pfn(exp: ref Exp, depth: int)
+{
+ (v, w) := exp.val();
+ if(!w || v == nil){
+ indent(depth);
+ stdout.puts(sys->sprint("no value for fn %q\n", exp.name));
+ return;
+ }
+ exps := exp.expand();
+ indent(depth);
+ stdout.puts("["+exp.srcstr()+"]\n");
+ indent(depth);
+ stdout.puts(symname(exp)+"(");
+ if((e := getname(exps, "args")) != nil){
+ args := e.expand();
+ for(i := 0; i < len args; i++){
+ pname(args[i], depth+1);
+ if(i != len args - 1)
+ stdout.puts(", ");
+ }
+ }
+ stdout.puts(")\n");
+ indent(depth);
+ stdout.puts("{\n"); # }
+ if((e = getname(exps, "locals")) != nil){
+ locals := e.expand();
+ for(i := 0; i < len locals; i++){
+ indent(depth+1);
+ pname(locals[i], depth+1);
+ stdout.puts("\n");
+ }
+ }
+ indent(depth);
+ stdout.puts("}\n");
+}
+
+getname(exps: array of ref Exp, name: string): ref Exp
+{
+ for(i := 0; i < len exps; i++)
+ if(exps[i].name == name)
+ return exps[i];
+ return nil;
+}
+
+strval(v: string): string
+{
+ for(i := 0; i < len v; i++)
+ if(v[i] == '"')
+ break;
+ if(i < len v)
+ v = v[i:];
+ return v;
+}
+
+pname(exp: ref Exp, depth: int)
+{
+ (v, w) := exp.val();
+ if (!w && v == nil) {
+ stdout.puts(sys->sprint("%s: %s = novalue", symname(exp), exp.typename()));
+ return;
+ }
+ case exp.kind() {
+ Tfn =>
+ pfn(exp, depth);
+ Tint =>
+ stdout.puts(sys->sprint("%s := %s", symname(exp), v));
+ Tstring =>
+ stdout.puts(sys->sprint("%s := %s", symname(exp), strval(v)));
+ Tbyte or
+ Tbig or
+ Treal =>
+ stdout.puts(sys->sprint("%s := %s %s", symname(exp), exp.typename(), v));
+ * =>
+ if(showtypes)
+ stdout.puts(sys->sprint("%s: %s = ", symname(exp), exp.typename()));
+ else
+ stdout.puts(sys->sprint("%s := ", symname(exp)));
+ pval(exp, v, w, depth);
+ }
+}
+
+srcstr(src: ref Debug->Src): string
+{
+ if(src == nil)
+ return nil;
+ if(src.start.file != src.stop.file)
+ return sys->sprint("%q:%d.%d,%q:%d.%d", src.start.file, src.start.line, src.start.pos, src.stop.file, src.stop.line, src.stop.pos);
+ if(src.start.line != src.stop.line)
+ return sys->sprint("%q:%d.%d,%d.%d", src.start.file, src.start.line, src.start.pos, src.stop.line, src.stop.pos);
+ return sys->sprint("%q:%d.%d,%d", src.start.file, src.start.line, src.start.pos, src.stop.pos);
+}
+
+pval(exp: ref Exp, v: string, w: int, depth: int)
+{
+ if(depth >= maxrecur){
+ stdout.puts(v);
+ return;
+ }
+ case exp.kind() {
+ Tarray =>
+ if(pref(v)){
+ if(depth+1 >= maxrecur)
+ stdout.puts(v+"{...}");
+ else{
+ stdout.puts(v+"{\n");
+ indent(depth+1);
+ parray(exp, depth+1);
+ stdout.puts("\n");
+ indent(depth);
+ stdout.puts("}");
+ }
+ }
+ Tlist =>
+ if(v == "nil")
+ stdout.puts("nil");
+ else
+ if(depth+1 >= maxrecur)
+ stdout.puts(v+"{...}");
+ else{
+ stdout.puts("{\n");
+ indent(depth+1);
+ plist(exp, v, w, depth+1);
+ stdout.puts("\n");
+ indent(depth);
+ stdout.puts("}");
+ }
+ Tadt =>
+ pgenval(exp, nil, w, depth);
+ Tref =>
+ if(pref(v))
+ pgenval(exp, v, w, depth);
+ Tstring =>
+ stdout.puts(strval(v));
+ * =>
+ pgenval(exp, v, w, depth);
+ }
+}
+
+parray(exp: ref Exp, depth: int)
+{
+ exps := exp.expand();
+ for(i := 0; i < len exps; i++){
+ e := exps[i];
+ (v, w) := e.val();
+ if(e.kind() == Tslice)
+ parray(e, depth);
+ else{
+ pval(e, v, w, depth);
+ stdout.puts(", ");
+ }
+ }
+}
+
+plist(exp: ref Exp, v: string, w: int, depth: int)
+{
+ while(w && v != "nil"){
+ exps := exp.expand();
+ h := getname(exps, "hd");
+ (hv, vw) := h.val();
+ if(pref(v) == 0)
+ return;
+ stdout.puts(v+"(");
+ pval(h, hv, vw, depth);
+ stdout.puts(") :: ");
+ h = nil;
+ exp = getname(exps, "tl");
+ (v, w) = exp.val();
+ }
+ stdout.puts("nil");
+}
+
+pgenval(exp: ref Exp, v: string, w: int, depth: int)
+{
+ if(w){
+ exps := exp.expand();
+ if(len exps == 0)
+ stdout.puts(v);
+ else{
+ stdout.puts(v+"{\n"); # }
+ if (len exps > 0){
+ if(depth >= maxrecur){
+ indent(depth);
+ stdout.puts(sys->sprint("...[%d]\n", len exps));
+ }else{
+ for (i := 0; i < len exps; i++){
+ indent(depth+1);
+ pname(exps[i], depth+1);
+ stdout.puts("\n");
+ }
+ }
+ }
+ indent(depth); # {
+ stdout.puts("}");
+ }
+ }else
+ stdout.puts(v);
+}
+
+symname(exp: ref Exp): string
+{
+ if(showsource == 0)
+ return exp.name;
+ return exp.name+"["+srcstr(exp.src())+"]";
+}
+
+indent(n: int)
+{
+ while(n-- > 0)
+ stdout.puts(sep);
+}
+
+pref(v: string): int
+{
+ if(addref(v) == 0){
+ stdout.puts(v);
+ if(v != "nil")
+ stdout.puts("(qv)");
+ return 0;
+ }
+ return 1;
+}
+
+addref(v: string): int
+{
+ slot := hashfn(v, len hasht);
+ for(l := hasht[slot]; l != nil; l = tl l)
+ if((hd l) == v)
+ return 0;
+ hasht[slot] = v :: hasht[slot];
+ return 1;
+}
+
+hashfn(s: string, n: int): int
+{
+ h := 0;
+ m := len s;
+ for(i:=0; i<m; i++){
+ h = 65599*h+s[i];
+ }
+ return (h & 16r7fffffff) % n;
+}
diff --git a/appl/cmd/stream.b b/appl/cmd/stream.b
new file mode 100644
index 00000000..4dd2cda3
--- /dev/null
+++ b/appl/cmd/stream.b
@@ -0,0 +1,98 @@
+#
+# stream data from files
+#
+# Copyright © 2000 Vita Nuova Limited. All rights reserved.
+#
+
+implement Stream;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+Stream: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+usage()
+{
+ sys->fprint(stderr, "Usage: stream [-a] [-b bufsize] file1 [file2]\n");
+ fail("usage");
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ bsize := 0;
+ sync := chan of int;
+ if(argv != nil)
+ argv = tl argv;
+ for(; argv != nil && len hd argv && (s := hd argv)[0] == '-' && len s > 1; argv = tl argv)
+ case s[1] {
+ 'b' =>
+ if(len s > 2)
+ bsize = int s[2:];
+ else if((argv = tl argv) != nil)
+ bsize = int hd argv;
+ else
+ usage();
+ 'a' =>
+ sync = nil;
+ * =>
+ usage();
+ }
+ if(bsize <= 0 || bsize > 2*1024*1024)
+ bsize = Sys->ATOMICIO;
+ argc := len argv;
+ if(argc < 1)
+ usage();
+
+ if(argc > 1){
+ f1 := eopen(hd argv, Sys->ORDWR);
+ f2 := eopen(hd tl argv, Sys->ORDWR);
+ spawn stream(f1, f2, bsize, sync);
+ spawn stream(f2, f1, bsize, sync);
+ }else{
+ f2 := sys->fildes(1);
+ if(f2 == nil) {
+ sys->fprint(stderr, "stream: can't access standard output: %r\n");
+ fail("stdout");
+ }
+ f1 := eopen(hd argv, Sys->OREAD);
+ spawn stream(f1, f2, bsize, sync);
+ }
+ if(sync != nil){ # count them back in
+ <-sync;
+ if(argc > 1)
+ <-sync;
+ }
+}
+
+stream(source: ref Sys->FD, sink: ref Sys->FD, bufsize: int, sync: chan of int)
+{
+ if(sys->stream(source, sink, bufsize) < 0)
+ sys->fprint(stderr, "stream: error streaming data: %r\n");
+ if(sync != nil)
+ sync <-= 1;
+}
+
+eopen(name: string, mode: int): ref Sys->FD
+{
+ fd := sys->open(name, mode);
+ if(fd == nil){
+ sys->fprint(stderr, "stream: can't open %s: %r\n", name);
+ fail("open");
+ }
+ return fd;
+}
+
+fail(s: string)
+{
+ raise s;
+ exit;
+}
diff --git a/appl/cmd/strings.b b/appl/cmd/strings.b
new file mode 100644
index 00000000..9f806fa5
--- /dev/null
+++ b/appl/cmd/strings.b
@@ -0,0 +1,87 @@
+#
+# initially generated by c2l
+#
+
+implement Strings;
+
+include "draw.m";
+
+Strings: module
+{
+ init: fn(nil: ref Draw->Context, argl: list of string);
+};
+
+include "sys.m";
+ sys: Sys;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+MINSPAN: con 6;
+BUFSIZE: con 70;
+
+init(nil: ref Draw->Context, argl: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ argc := len argl;
+ if(argc < 2){
+ stringit("");
+ exit;
+ }
+ argl = tl argl;
+ for(i := 1; i < argc; i++){
+ if(argc > 2)
+ sys->print("%s:\n", hd argl);
+ stringit(hd argl);
+ argl = tl argl;
+ }
+}
+
+stringit(str: string)
+{
+ cnt := 0;
+ c: int;
+ buf := string array[BUFSIZE] of { * => byte 'z' };
+
+ if(str == nil)
+ fin := bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ else
+ fin = bufio->open(str, Bufio->OREAD);
+ if(fin == nil){
+ sys->fprint(sys->fildes(2), "cannot open %s\n", str);
+ return;
+ }
+ start := big -1;
+ posn := fin.offset();
+ while((c = fin.getc()) >= 0){
+ if(isprint(c)){
+ if(start == big -1)
+ start = posn;
+ buf[cnt++] = c;
+ if(cnt == BUFSIZE){
+ sys->print("%8bd: %s ...\n", start, buf[0: cnt]);
+ start = big -1;
+ cnt = 0;
+ }
+ }
+ else{
+ if(cnt >= MINSPAN)
+ sys->print("%8bd: %s\n", start, buf[0: cnt]);
+ start = big -1;
+ cnt = 0;
+ }
+ posn = fin.offset();
+ }
+ if(cnt >= MINSPAN)
+ sys->print("%8bd: %s\n", start, buf[0: cnt]);
+ fin = nil;
+}
+
+isprint(r: int): int
+{
+ if(r >= ' ' && r < 16r7f || r > 16ra0)
+ return 1;
+ else
+ return 0;
+}
diff --git a/appl/cmd/styxchat.b b/appl/cmd/styxchat.b
new file mode 100644
index 00000000..f0b1f2c5
--- /dev/null
+++ b/appl/cmd/styxchat.b
@@ -0,0 +1,557 @@
+implement Styxchat;
+
+#
+# Copyright © 2002,2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+
+include "string.m";
+ str: String;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+
+Styxchat: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+msgsize := 64*1024;
+nexttag := 1;
+verbose := 0;
+
+stdin: ref Sys->FD;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ styx = load Styx Styx->PATH;
+ str = load String String->PATH;
+ bufio = load Bufio Bufio->PATH;
+ styx->init();
+
+ client := 1;
+ addr := 0;
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("styxchat [-nsv] [-m messagesize] [dest]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'm' =>
+ msgsize = atoi(arg->earg());
+ 's' =>
+ client = 0;
+ 'n' =>
+ addr = 1;
+ 'v' =>
+ verbose++;
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+ fd: ref Sys->FD;
+ if(args == nil){
+ fd = sys->fildes(0);
+ stdin = sys->open("/dev/cons", Sys->ORDWR);
+ if (stdin == nil)
+ err(sys->sprint("can't open /dev/cons: %r"));
+ sys->dup(stdin.fd, 1);
+ }else{
+ if(tl args != nil)
+ arg->usage();
+ stdin = sys->fildes(0);
+ dest := hd args;
+ if(addr){
+ dest = netmkaddr(dest, "net", "styx");
+ if (client){
+ (rc, c) := sys->dial(dest, nil);
+ if(rc < 0)
+ err(sys->sprint("can't dial %s: %r", dest));
+ fd = c.dfd;
+ }else{
+ (rlc, lc) := sys->announce(dest);
+ if (rlc < 0)
+ err(sys->sprint("can't announce %s: %r", dest));
+ (rc, c) := sys->listen(lc);
+ if (rc < 0)
+ err(sys->sprint("can't listen on %s: %r", dest));
+ fd = sys->open(c.dir + "/data", Sys->ORDWR);
+ if (fd == nil)
+ err(sys->sprint("can't open %s/data: %r", c.dir));
+ }
+ }else{
+ fd = sys->open(dest, Sys->ORDWR);
+ if(fd == nil)
+ err(sys->sprint("can't open %s: %r", dest));
+ }
+ }
+ sys->pctl(Sys->NEWPGRP, nil);
+ if(client){
+ spawn Rreader(fd);
+ Twriter(fd);
+ }else{
+ spawn Treader(fd);
+ Rwriter(fd);
+ }
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
+
+quit(e: int)
+{
+ fd := sys->open("/prog/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+ if(e)
+ raise "fail:error";
+ exit;
+}
+
+Rreader(fd: ref Sys->FD)
+{
+ while((m := Rmsg.read(fd, msgsize)) != nil){
+ sys->print("<- %s\n%s", m.text(), Rdump(m));
+ if(tagof m == tagof Rmsg.Readerror)
+ quit(1);
+ }
+ sys->print("styxchat: server hungup\n");
+}
+
+Twriter(fd: ref Sys->FD)
+{
+ in := bufio->fopen(stdin, Sys->OREAD);
+ while((l := in.gets('\n')) != nil){
+ if(l != nil && l[0] == '#')
+ continue;
+ (t, err) := Tparse(l);
+ if(t == nil){
+ if(err != nil)
+ sys->print("?%s\n", err);
+ }else{
+ if(t.tag == 0)
+ t.tag = nexttag;
+ a := t.pack();
+ if(a != nil){
+ sys->print("-> %s\n%s", t.text(), Tdump(t));
+ n := len a;
+ if(n <= msgsize){
+ if(sys->write(fd, a, len a) != len a)
+ sys->print("?write error to server: %r\n");
+ if(t.tag != Styx->NOTAG && t.tag != ~0)
+ nexttag++;
+ }else
+ sys->print("?message bigger than agreed: %d bytes\n", n);
+ }else
+ sys->fprint(sys->fildes(2), "styxchat: T-message conversion failed\n");
+ }
+ }
+}
+
+Rdump(m: ref Rmsg): string
+{
+ if(!verbose)
+ return "";
+ pick r :=m {
+ Read =>
+ return dump(r.data, len r.data, verbose>1);
+ * =>
+ return "";
+ }
+}
+
+Tdump(m: ref Tmsg): string
+{
+ if(!verbose)
+ return "";
+ pick t := m {
+ Write =>
+ return dump(t.data, len t.data, verbose>1);
+ * =>
+ return "";
+ }
+}
+
+isprint(c: int): int
+{
+ return c >= 16r20 && c < 16r7F || c == '\n' || c == '\t' || c == '\r';
+}
+
+textdump(a: array of byte, lim: int): string
+{
+ s := "\ttext(\"";
+ for(i := 0; i < lim; i++)
+ case c := int a[i] {
+ '\t' =>
+ s += "\\t";
+ '\n' =>
+ s += "\\n";
+ '\r' =>
+ s += "\\r";
+ '"' =>
+ s += "\\\"";
+ * =>
+ if(isprint(c))
+ s[len s] = c;
+ else
+ s += sys->sprint("\\u%4.4ux", c);
+ }
+ s += "\")\n";
+ return s;
+}
+
+dump(a: array of byte, lim: int, text: int): string
+{
+ if(a == nil)
+ return "";
+ if(len a < lim)
+ lim = len a;
+ printable := 1;
+ for(i := 0; i < lim; i++)
+ if(!isprint(int a[i])){
+ printable = 0;
+ break;
+ }
+ if(printable)
+ return textdump(a, lim);
+ s := "\tdump(";
+ for(i = 0; i < lim; i++)
+ s += sys->sprint("%2.2ux", int a[i]);
+ s += ")\n";
+ if(text)
+ s += textdump(a, lim);
+ return s;
+}
+
+val(s: string): int
+{
+ if(s == "~0")
+ return ~0;
+ return atoi(s);
+}
+
+bigval(s: string): big
+{
+ if(s == "~0")
+ return ~ big 0;
+ return atob(s);
+}
+
+fid(s: string): int
+{
+ if(s == "nofid" || s == "NOFID")
+ return Styx->NOFID;
+ return val(s);
+}
+
+tag(s: string): int
+{
+ if(s == "~0" || s == "notag" || s == "NOTAG")
+ return Styx->NOTAG;
+ return atoi(s);
+}
+
+dir(name: string, uid: string, gid: string, mode: int, mtime: int, length: big): Sys->Dir
+{
+ d := sys->zerodir;
+ d.name = name;
+ d.uid = uid;
+ d.gid = gid;
+ d.mode = mode;
+ d.mtime = mtime;
+ d.length = length;
+ return d;
+}
+
+Tparse(s: string): (ref Tmsg, string)
+{
+ args := str->unquoted(s);
+ if(args == nil)
+ return (nil, nil);
+ argc := len args;
+ av := array[argc] of string;
+ for(i:=0; args != nil; args = tl args)
+ av[i++] = hd args;
+ case av[0] {
+ "Tversion" =>
+ if(argc != 3)
+ return (nil, "usage: Tversion messagesize version");
+ return (ref Tmsg.Version(Styx->NOTAG, atoi(av[1]), av[2]), nil);
+ "Tauth" =>
+ if(argc != 4)
+ return (nil, "usage: Tauth afid uname aname");
+ return (ref Tmsg.Auth(0, fid(av[1]), av[2], av[3]), nil);
+ "Tflush" =>
+ if(argc != 2)
+ return (nil, "usage: Tflush oldtag");
+ return (ref Tmsg.Flush(0, tag(av[1])), nil);
+ "Tattach" =>
+ if(argc != 5)
+ return (nil, "usage: Tattach fid afid uname aname");
+ return (ref Tmsg.Attach(0, fid(av[1]), fid(av[2]), av[3], av[4]), nil);
+ "Twalk" =>
+ if(argc < 3)
+ return (nil, "usage: Twalk fid newfid [name...]");
+ names: array of string;
+ if(argc > 3)
+ names = av[3:];
+ return (ref Tmsg.Walk(0, fid(av[1]), fid(av[2]), names), nil);
+ "Topen" =>
+ if(argc != 3)
+ return (nil, "usage: Topen fid mode");
+ return (ref Tmsg.Open(0, fid(av[1]), atoi(av[2])), nil);
+ "Tcreate" =>
+ if(argc != 5)
+ return (nil, "usage: Tcreate fid name perm mode");
+ return (ref Tmsg.Create(0, fid(av[1]), av[2], atoi(av[3]), atoi(av[4])), nil);
+ "Tread" =>
+ if(argc != 4)
+ return (nil, "usage: Tread fid offset count");
+ return (ref Tmsg.Read(0, fid(av[1]), atob(av[2]), atoi(av[3])), nil);
+ "Twrite" =>
+ if(argc != 4)
+ return (nil, "usage: Twrite fid offset data");
+ return (ref Tmsg.Write(0, fid(av[1]), atob(av[2]), array of byte av[3]), nil);
+ "Tclunk" =>
+ if(argc != 2)
+ return (nil, "usage: Tclunk fid");
+ return (ref Tmsg.Clunk(0, fid(av[1])), nil);
+ "Tremove" =>
+ if(argc != 2)
+ return (nil, "usage: Tremove fid");
+ return (ref Tmsg.Remove(0, fid(av[1])), nil);
+ "Tstat" =>
+ if(argc != 2)
+ return (nil, "usage: Tstat fid");
+ return (ref Tmsg.Stat(0, fid(av[1])), nil);
+ "Twstat" =>
+ if(argc != 8)
+ return (nil, "usage: Twstat fid name uid gid mode mtime length");
+ return (ref Tmsg.Wstat(0, fid(av[1]), dir(av[2], av[3], av[4], val(av[5]), val(av[6]), bigval(av[7]))), nil);
+ "nexttag" =>
+ if(argc < 2)
+ return (nil, sys->sprint("next tag is %d", nexttag));
+ nexttag = tag(av[1]);
+ return (nil, nil);
+ "dump" =>
+ verbose++;
+ return (nil, nil);
+ * =>
+ return (nil, "unknown message type");
+ }
+}
+
+#
+# server side
+#
+
+Treader(fd: ref Sys->FD)
+{
+ while((m := Tmsg.read(fd, msgsize)) != nil){
+ sys->print("<- %s\n", m.text());
+ if(tagof m == tagof Tmsg.Readerror)
+ quit(1);
+ }
+ sys->print("styxchat: clients hungup\n");
+}
+
+Rwriter(fd: ref Sys->FD)
+{
+ in := bufio->fopen(stdin, Sys->OREAD);
+ while((l := in.gets('\n')) != nil){
+ if(l != nil && l[0] == '#')
+ continue;
+ (r, err) := Rparse(l);
+ if(r == nil){
+ if(err != nil)
+ sys->print("?%s\n", err);
+ }else{
+ a := r.pack();
+ if(a != nil){
+ sys->print("-> %s\n", r.text());
+ n := len a;
+ if(n <= msgsize){
+ if(sys->write(fd, a, len a) != len a)
+ sys->print("?write error to clients: %r\n");
+ }else
+ sys->print("?message bigger than agreed: %d bytes\n", n);
+ }else
+ sys->fprint(sys->fildes(2), "styxchat: R-message conversion failed\n");
+ }
+ }
+}
+
+qid(s: string): Sys->Qid
+{
+ (nf, flds) := sys->tokenize(s, ".");
+ q := Sys->Qid(big 0, 0, 0);
+ if(nf < 1)
+ return q;
+ q.path = atob(hd flds);
+ if(nf < 2)
+ return q;
+ q.vers = atoi(hd tl flds);
+ if(nf < 3)
+ return q;
+ q.qtype = mode(hd tl tl flds);
+ return q;
+}
+
+mode(s: string): int
+{
+ if(len s > 0 && s[0] >= '0' && s[0] <= '9')
+ return atoi(s);
+ mode := 0;
+ for(i := 0; i < len s; i++){
+ case s[i] {
+ 'd' =>
+ mode |= Sys->QTDIR;
+ 'a' =>
+ mode |= Sys->QTAPPEND;
+ 'u' =>
+ mode |= Sys->QTAUTH;
+ 'l' =>
+ mode |= Sys->QTEXCL;
+ 'f' =>
+ ;
+ * =>
+ sys->fprint(sys->fildes(2), "styxchat: unknown mode character %c, ignoring\n", s[i]);
+ }
+ }
+ return mode;
+}
+
+rdir(a: array of string): Sys->Dir
+{
+ d := sys->zerodir;
+ d.qid = qid(a[0]);
+ d.mode = atoi(a[1]) | (d.qid.qtype<<24);
+ d.atime = atoi(a[2]);
+ d.mtime = atoi(a[3]);
+ d.length = atob(a[4]);
+ d.name = a[5];
+ d.uid = a[6];
+ d.gid = a[7];
+ d.muid = a[8];
+ return d;
+}
+
+Rparse(s: string): (ref Rmsg, string)
+{
+ args := str->unquoted(s);
+ if(args == nil)
+ return (nil, nil);
+ argc := len args;
+ av := array[argc] of string;
+ for(i:=0; args != nil; args = tl args)
+ av[i++] = hd args;
+ case av[0] {
+ "Rversion" =>
+ if(argc != 4)
+ return (nil, "usage: Rversion tag messagesize version");
+ return (ref Rmsg.Version(tag(av[1]), atoi(av[2]), av[3]), nil);
+ "Rauth" =>
+ if(argc != 3)
+ return (nil, "usage: Rauth tag aqid");
+ return (ref Rmsg.Auth(tag(av[1]), qid(av[2])), nil);
+ "Rflush" =>
+ if(argc != 2)
+ return (nil, "usage: Rflush tag");
+ return (ref Rmsg.Flush(tag(av[1])), nil);
+ "Rattach" =>
+ if(argc != 3)
+ return (nil, "usage: Rattach tag qid");
+ return (ref Rmsg.Attach(tag(av[1]), qid(av[2])), nil);
+ "Rwalk" =>
+ if(argc < 2)
+ return (nil, "usage: Rwalk tag [qid ...]");
+ qids := array[argc-2] of Sys->Qid;
+ for(i = 0; i < len qids; i++)
+ qids[i] = qid(av[i+2]);
+ return (ref Rmsg.Walk(tag(av[1]), qids), nil);
+ "Ropen" =>
+ if(argc != 4)
+ return (nil, "usage: Ropen tag qid iounit");
+ return (ref Rmsg.Open(tag(av[1]), qid(av[2]), atoi(av[3])), nil);
+ "Rcreate" =>
+ if(argc != 4)
+ return (nil, "usage: Rcreate tag qid iounit");
+ return (ref Rmsg.Create(tag(av[1]), qid(av[2]), atoi(av[3])), nil);
+ "Rread" =>
+ if(argc != 3)
+ return (nil, "usage: Rread tag data");
+ return (ref Rmsg.Read(tag(av[1]), array of byte av[2]), nil);
+ "Rwrite" =>
+ if(argc != 3)
+ return (nil, "usage: Rwrite tag count");
+ return (ref Rmsg.Write(tag(av[1]), atoi(av[2])), nil);
+ "Rclunk" =>
+ if(argc != 2)
+ return (nil, "usage: Rclunk tag");
+ return (ref Rmsg.Clunk(tag(av[1])), nil);
+ "Rremove" =>
+ if(argc != 2)
+ return (nil, "usage: Rremove tag");
+ return (ref Rmsg.Remove(tag(av[1])), nil);
+ "Rstat" =>
+ if(argc != 11)
+ return (nil, "usage: Rstat tag qid mode atime mtime length name uid gid muid");
+ return (ref Rmsg.Stat(tag(av[1]), rdir(av[2:])), nil);
+ "Rwstat" =>
+ if(argc != 8)
+ return (nil, "usage: Rwstat tag");
+ return (ref Rmsg.Wstat(tag(av[1])), nil);
+ "Rerror" =>
+ if(argc != 3)
+ return (nil, "usage: Rerror tag ename");
+ return (ref Rmsg.Error(tag(av[1]), av[2]), nil);
+ "dump" =>
+ verbose++;
+ return (nil, nil);
+ * =>
+ return (nil, "unknown message type");
+ }
+}
+
+atoi(s: string): int
+{
+ (i, nil) := str->toint(s, 0);
+ return i;
+}
+
+# atoi with traditional unix semantics for octal and hex.
+atob(s: string): big
+{
+ (b, nil) := str->tobig(s, 0);
+ return b;
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "styxchat: %s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/styxlisten.b b/appl/cmd/styxlisten.b
new file mode 100644
index 00000000..2147e619
--- /dev/null
+++ b/appl/cmd/styxlisten.b
@@ -0,0 +1,262 @@
+implement Styxlisten;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "keyring.m";
+ keyring: Keyring;
+include "security.m";
+ auth: Auth;
+include "arg.m";
+include "sh.m";
+
+Styxlisten: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+badmodule(p: string)
+{
+ sys->fprint(stderr(), "styxlisten: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+verbose := 0;
+passhostnames := 0;
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ auth = load Auth Auth->PATH;
+ if (auth == nil)
+ badmodule(Auth->PATH);
+ if ((e := auth->init()) != nil)
+ error("auth init failed: " + e);
+ keyring = load Keyring Keyring->PATH;
+ if (keyring == nil)
+ badmodule(Keyring->PATH);
+
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+
+ arg->init(argv);
+ arg->setusage("styxlisten [-a alg]... [-Atsv] [-k keyfile] address cmd [arg...]");
+
+ algs: list of string;
+ doauth := 1;
+ synchronous := 0;
+ trusted := 0;
+ keyfile := "";
+
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'v' =>
+ verbose = 1;
+ 'a' =>
+ algs = arg->earg() :: algs;
+ 'f' or
+ 'k' =>
+ keyfile = arg->earg();
+ if (! (keyfile[0] == '/' || (len keyfile > 2 && keyfile[0:2] == "./")))
+ keyfile = "/usr/" + user() + "/keyring/" + keyfile;
+ 'h' =>
+ passhostnames = 1;
+ 't' =>
+ trusted = 1;
+ 's' =>
+ synchronous = 1;
+ 'A' =>
+ doauth = 0;
+ * =>
+ arg->usage();
+ }
+ }
+ argv = arg->argv();
+ if (len argv < 2)
+ arg->usage();
+ arg = nil;
+ if (doauth && algs == nil)
+ algs = getalgs();
+ addr := netmkaddr(hd argv, "tcp", "styx");
+ cmd := tl argv;
+
+ authinfo: ref Keyring->Authinfo;
+ if (doauth) {
+ if (keyfile == nil)
+ keyfile = "/usr/" + user() + "/keyring/default";
+ authinfo = keyring->readauthinfo(keyfile);
+ if (authinfo == nil)
+ error(sys->sprint("cannot read %s: %r", keyfile));
+ }
+
+ (ok, c) := sys->announce(addr);
+ if (ok == -1)
+ error(sys->sprint("cannot announce on %s: %r", addr));
+ if(!trusted){
+ sys->unmount(nil, "/mnt/keys"); # should do for now
+ # become none?
+ }
+
+ lsync := chan[1] of int;
+ if(synchronous)
+ listener(c, popen(ctxt, cmd, lsync), authinfo, algs, lsync);
+ else
+ spawn listener(c, popen(ctxt, cmd, lsync), authinfo, algs, lsync);
+}
+
+listener(c: Sys->Connection, mfd: ref Sys->FD, authinfo: ref Keyring->Authinfo, algs: list of string, lsync: chan of int)
+{
+ lsync <-= sys->pctl(0, nil);
+ for (;;) {
+ (n, nc) := sys->listen(c);
+ if (n == -1)
+ error(sys->sprint("listen failed: %r"));
+ if (verbose)
+ sys->fprint(stderr(), "styxlisten: got connection from %s",
+ readfile(nc.dir + "/remote"));
+ dfd := sys->open(nc.dir + "/data", Sys->ORDWR);
+ if (dfd != nil) {
+ if(nc.cfd != nil)
+ sys->fprint(nc.cfd, "keepalive");
+ hostname: string;
+ if(passhostnames){
+ hostname = readfile(nc.dir + "/remote");
+ if(hostname != nil)
+ hostname = hostname[0:len hostname - 1];
+ }
+ if (algs == nil) {
+ sync := chan of int;
+ spawn exportproc(sync, mfd, nil, hostname, dfd);
+ <-sync;
+ } else
+ spawn authenticator(dfd, authinfo, mfd, algs, hostname);
+ }
+ }
+}
+
+# authenticate a connection and set the user id.
+authenticator(dfd: ref Sys->FD, authinfo: ref Keyring->Authinfo, mfd: ref Sys->FD,
+ algs: list of string, hostname: string)
+{
+ # authenticate and change user id appropriately
+ (fd, err) := auth->server(algs, authinfo, dfd, 1);
+ if (fd == nil) {
+ if (verbose)
+ sys->fprint(stderr(), "styxlisten: authentication failed: %s\n", err);
+ return;
+ }
+ if (verbose)
+ sys->fprint(stderr(), "styxlisten: client authenticated as %s\n", err);
+ sync := chan of int;
+ spawn exportproc(sync, mfd, err, hostname, fd);
+ <-sync;
+}
+
+exportproc(sync: chan of int, fd: ref Sys->FD, uname, hostname: string, dfd: ref Sys->FD)
+{
+ sys->pctl(Sys->NEWFD | Sys->NEWNS, 2 :: fd.fd :: dfd.fd :: nil);
+ fd = sys->fildes(fd.fd);
+ dfd = sys->fildes(dfd.fd);
+ sync <-= 1;
+
+ # XXX unfortunately we cannot pass through the aname from
+ # the original attach, an inherent shortcoming of this scheme.
+ if (sys->mount(fd, nil, "/", Sys->MREPL|Sys->MCREATE, hostname) == -1)
+ error(sys->sprint("cannot mount for user '%s': %r\n", uname));
+
+ sys->export(dfd, "/", Sys->EXPWAIT);
+}
+
+error(e: string)
+{
+ sys->fprint(stderr(), "styxlisten: %s\n", e);
+ raise "fail:error";
+}
+
+
+popen(ctxt: ref Draw->Context, argv: list of string, lsync: chan of int): ref Sys->FD
+{
+ sync := chan of int;
+ fds := array[2] of ref Sys->FD;
+ sys->pipe(fds);
+ spawn runcmd(ctxt, argv, fds[0], sync, lsync);
+ <-sync;
+ return fds[1];
+}
+
+runcmd(ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD,
+ sync: chan of int, lsync: chan of int)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ sys->dup(stdin.fd, 0);
+ stdin = nil;
+ sync <-= 0;
+ sh := load Sh Sh->PATH;
+ e := sh->run(ctxt, argv);
+ kill(<-lsync, "kill"); # kill listener, as command has exited
+ if(verbose){
+ if(e != nil)
+ sys->fprint(stderr(), "styxlisten: command exited with error: %s\n", e);
+ else
+ sys->fprint(stderr(), "styxlisten: command exited\n");
+ }
+}
+
+kill(pid: int, how: string)
+{
+ sys->fprint(sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE), "%s", how);
+}
+
+user(): string
+{
+ if ((s := readfile("/dev/user")) == nil)
+ return "none";
+ return s;
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, sys->OREAD);
+ if(fd == nil)
+ return nil;
+
+ buf := array[1024] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return nil;
+
+ return string buf[0:n];
+}
+
+getalgs(): list of string
+{
+ sslctl := readfile("#D/clone");
+ if (sslctl == nil) {
+ sslctl = readfile("#D/ssl/clone");
+ if (sslctl == nil)
+ return nil;
+ sslctl = "#D/ssl/" + sslctl;
+ } else
+ sslctl = "#D/" + sslctl;
+ (nil, algs) := sys->tokenize(readfile(sslctl + "/encalgs") + " " + readfile(sslctl + "/hashalgs"), " \t\n");
+ return "none" :: algs;
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/cmd/styxmon.b b/appl/cmd/styxmon.b
new file mode 100644
index 00000000..0e5cb412
--- /dev/null
+++ b/appl/cmd/styxmon.b
@@ -0,0 +1,110 @@
+implement Styxmon;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+include "sh.m";
+include "arg.m";
+
+Styxmon: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "styxmon: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+showdata := 0;
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ styx = load Styx Styx->PATH;
+ if(styx == nil)
+ badmod(Styx->PATH);
+ styx->init();
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ badmod(Arg->PATH);
+ arg->init(argv);
+ arg->setusage("usage: styxmon [-d] cmd [arg...]");
+ while((opt := arg->opt()) != 0){
+ case opt{
+ 'd' =>
+ showdata = 1;
+ * =>
+ arg->usage();
+ }
+ }
+ argv = arg->argv();
+ if(argv == nil)
+ arg->usage();
+ fd0 := sys->fildes(0);
+ fd1 := popen(ctxt, argv);
+ sync := chan of int;
+ spawn msgtx(fd0, fd1, sync, "tmsg");
+ <-sync;
+ spawn msgtx(fd1, fd0, sync, "rmsg");
+ <-sync;
+}
+
+msgtx(f0, f1: ref Sys->FD, sync: chan of int, what: string)
+{
+ sys->pctl(Sys->NEWFD|Sys->NEWNS, 2 :: f0.fd :: f1.fd :: nil);
+ sync <-= 1;
+ f0 = sys->fildes(f0.fd);
+ f1 = sys->fildes(f1.fd);
+ stderr := sys->fildes(2);
+ for (;;) {
+ (d, err) := styx->readmsg(f0, 0);
+ if(d == nil){
+ if(err != nil)
+ sys->fprint(stderr, "styxmon: error from %s: %s\n", what, err);
+ else
+ sys->fprint(stderr, "styxmon: eof from %s\n", what);
+ exit;
+ }
+ if(styx->istmsg(d)){
+ (n, m) := Tmsg.unpack(d);
+ if(n != len d){
+ sys->fprint(stderr, "styxmon: %s message error (%d/%d)\n", what, n, len d);
+ }else{
+ sys->fprint(stderr, "%s\n", m.text());
+ }
+ }else{
+ (n, m) := Rmsg.unpack(d);
+ if(n != len d){
+ sys->fprint(stderr, "styxmon: %s message error (%d/%d)\n", what, n, len d);
+ if(m != nil)
+ sys->fprint(stderr, "err: %s\n", m.text());
+ }else{
+ sys->fprint(stderr, "%s\n", m.text());
+ }
+ }
+ sys->write(f1, d, len d);
+ }
+}
+
+popen(ctxt: ref Draw->Context, argv: list of string): ref Sys->FD
+{
+ sync := chan of int;
+ fds := array[2] of ref Sys->FD;
+ sys->pipe(fds);
+ spawn runcmd(ctxt, argv, fds[0], sync);
+ <-sync;
+ return fds[1];
+}
+
+runcmd(ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD, sync: chan of int)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ sys->dup(stdin.fd, 0);
+ stdin = nil;
+ sync <-= 0;
+ sh := load Sh Sh->PATH;
+ sh->run(ctxt, argv);
+}
diff --git a/appl/cmd/sum.b b/appl/cmd/sum.b
new file mode 100644
index 00000000..7e7a1335
--- /dev/null
+++ b/appl/cmd/sum.b
@@ -0,0 +1,59 @@
+implement Sum;
+
+include "sys.m";
+include "draw.m";
+include "crc.m";
+
+Sum : module
+{
+ init : fn(nil : ref Draw->Context, argv : list of string);
+};
+
+init(nil : ref Draw->Context, argv : list of string)
+{
+ sys := load Sys Sys->PATH;
+ stderr := sys->fildes(2);
+ crcm := load Crc Crc->PATH;
+ crcs := crcm->init(0, 0);
+ a := tl argv;
+ buf := array[Sys->ATOMICIO] of byte;
+ err := 0;
+ for ( ; a != nil; a = tl a) {
+ s := hd a;
+ (ok, d) := sys->stat(s);
+ if (ok < 0) {
+ sys->fprint(stderr, "sum: cannot get status of %s: %r\n", s);
+ err = 1;
+ continue;
+ }
+ if (d.mode & Sys->DMDIR)
+ continue;
+ fd := sys->open(s, Sys->OREAD);
+ if (fd == nil) {
+ sys->fprint(stderr, "sum: cannot open %s: %r\n", s);
+ err = 1;
+ continue;
+ }
+ crc := 0;
+ nbytes := big 0;
+ while((nr := sys->read(fd, buf, len buf)) > 0){
+ crc = crcm->crc(crcs, buf, nr);
+ nbytes += big nr;
+ }
+ if(nr < 0) {
+ sys->fprint(stderr, "sum: error reading %s: %r\n", s);
+ err = 1;
+ }
+ # encode the length but make n==0 not 0
+ l := int (nbytes & big 16rFFFFFFFF);
+ buf[0] = byte((l>>24)^16rCC);
+ buf[1] = byte((l>>16)^16r55);
+ buf[2] = byte((l>>8)^16rCC);
+ buf[3] = byte(l^16r55);
+ crc = crcm->crc(crcs, buf, 4);
+ sys->print("%.8ux %6bd %s\n", crc, nbytes, s);
+ crcm->reset(crcs);
+ }
+ if(err)
+ raise "fail:error";
+}
diff --git a/appl/cmd/tail.b b/appl/cmd/tail.b
new file mode 100644
index 00000000..07d900d1
--- /dev/null
+++ b/appl/cmd/tail.b
@@ -0,0 +1,379 @@
+implement Tail;
+
+include "sys.m";
+sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+bufmod : Bufio;
+Iobuf : import bufmod;
+
+include "string.m";
+ str : String;
+
+count, anycount, follow : int;
+file : ref sys->FD;
+bout : ref Iobuf;
+BSize : con 8*1024;
+
+BEG, END, CHARS, LINES , FWD, REV : con iota;
+
+origin := END;
+units := LINES;
+dir := FWD;
+
+
+Tail: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ bufmod = load Bufio Bufio->PATH;
+ seekable : int;
+ bout = bufmod->fopen(sys->fildes(1),bufmod->OWRITE);
+ argv=parse(tl argv);
+ if(dir==REV && (units==CHARS || follow || origin==BEG))
+ fail("incompatible options");
+ if(!anycount){
+ if (dir==REV)
+ count= 16r7fffffff;
+ else
+ count = 10;
+ }
+ if(origin==BEG && units==LINES && count>0)
+ count--;
+ if(len argv > 1)
+ usage();
+ if(argv == nil || hd argv == "-") {
+ file = sys->fildes(0);
+ seekable = 0;
+ }
+ else {
+ if((file=sys->open(hd argv,sys->OREAD)) == nil )
+ fatal(hd argv);
+ (ok, stat) := sys->fstat(file);
+ seekable = sys->seek(file,big 0,sys->SEEKSTART) == big 0 && stat.length > big 0;
+ }
+
+ if(!seekable && origin==END)
+ keep();
+ else if(!seekable && origin==BEG)
+ skip();
+ else if(units==CHARS && origin==END){
+ tseek(big -count, Sys->SEEKEND);
+ copy();
+ }
+ else if(units==CHARS && origin==BEG){
+ tseek(big count, Sys->SEEKSTART);
+ copy();
+ }
+ else if(units==LINES && origin==END)
+ reverse();
+ else if(units==LINES && origin==BEG)
+ skip();
+ if(follow){
+ if(seekable){
+ d : sys->Dir;
+ d.length=big -1;
+ for(;;){
+ d=trunc(d.length);
+ copy();
+ sys->sleep(5000);
+ }
+ }else{
+ for(;;){
+ copy();
+ sys->sleep(5000);
+ }
+ }
+ }
+ exit;
+}
+
+
+trunc(length : big) : sys->Dir
+{
+ (i,d):=sys->fstat(file);
+ if(d.length < length)
+ d.length = tseek(big 0, sys->SEEKSTART);
+ return d;
+}
+
+
+skip() # read past head of the file to find tail
+{
+ n : int;
+ buf := array[BSize] of byte;
+ if(units == CHARS) {
+ for( ; count>0; count -=n) {
+ if (count<BSize)
+ n=count;
+ else
+ n=BSize;
+ n = tread(buf, n);
+ if(n == 0)
+ return;
+ }
+ } else { # units == LINES
+ i:=0;
+ n=0;
+ while(count > 0) {
+ n = tread(buf, BSize);
+ if(n == 0)
+ return;
+ for(i=0; i<n && count>0; i++)
+ if(buf[i]==byte '\n')
+ count--;
+ }
+ twrite(buf[i:n]);
+ }
+ copy();
+}
+
+
+copy()
+{
+ buf := array[BSize] of byte;
+ while((n := tread(buf, BSize)) > 0){
+ twrite(buf[0:n]);
+ }
+ bout.flush();
+}
+
+
+keep() # read whole file, keeping the tail
+{ # complexity=length(file)*length(tail). could be linear
+ j, k : int;
+ length:=0;
+ buf : array of byte;
+ tbuf : array of byte;
+ bufsize := 0;
+ for(n:=1; n;) {
+ if(length+BSize > bufsize ) {
+ bufsize += 2*BSize;
+ tbuf = array[bufsize+1] of byte;
+ tbuf[0:]=buf[0:];
+ buf = tbuf;
+ }
+ for( ; n && length<bufsize; length+=n)
+ n = tread(buf[length:], bufsize-length);
+ if(count >= length)
+ continue;
+ if(units == CHARS)
+ j = length - count;
+ else{ # units == LINES
+ if (int buf[length-1]=='\n')
+ j = length-1;
+ else
+ j=length;
+ for(k=0; j>0; j--)
+ if(int buf[j-1] == '\n')
+ if(++k >= count)
+ break;
+ }
+ length-=j;
+ buf[0:]=buf[j:j+length];
+ }
+ if(dir == REV) {
+ if(length>0 && buf[length-1]!= byte '\n')
+ buf[length++] = byte '\n';
+ for(j=length-1 ; j>0; j--)
+ if(buf[j-1] == byte '\n') {
+ twrite(buf[j:length]);
+ if(--count <= 0)
+ return;
+ length = j;
+ }
+ }
+ if(count > 0 && length > 0)
+ twrite(buf[0:length]);
+ bout.flush();
+}
+
+reverse() # count backward and print tail of file
+{
+ length := 0;
+ n := 0;
+ buf : array of byte;
+ pos := tseek(big 0, sys->SEEKEND);
+ bufsize := 0;
+ for(first:=1; pos>big 0 && count>0; first=0) {
+ if (pos>big BSize)
+ n = BSize;
+ else
+ n = int pos;
+ pos -= big n;
+ if(length+2*n > bufsize) {
+ bufsize += BSize*((length+2*n-bufsize+BSize-1)/BSize);
+ tbuf := array[bufsize+1] of byte;
+ tbuf[0:] = buf;
+ buf = tbuf;
+ }
+ length += n;
+ abuf := array[length] of byte;
+ abuf[0:] = buf[0:length];
+ buf[n:] = abuf;
+ tseek(pos, sys->SEEKSTART);
+ if(tread(buf, n) != n)
+ fatal("length error");
+ if(first && buf[length-1]!= byte '\n')
+ buf[length++] = byte '\n';
+ for(n=length-1 ; n>0 && count>0; n--)
+ if(buf[n-1] == byte '\n') {
+ count--;
+ if(dir == REV){
+ twrite(buf[n:length]);
+ bout.flush();
+ }
+ length = n;
+ }
+ }
+ if(dir == FWD) {
+ if (n==0)
+ tseek(big 0 , sys->SEEKSTART);
+ else
+ tseek(pos+big n+big 1, sys->SEEKSTART);
+
+ copy();
+ } else if(count > 0)
+ twrite(buf[0:length]);
+ bout.flush();
+}
+
+
+tseek(o : big, p: int) : big
+{
+ o = sys->seek(file, o, p);
+ if(o == big -1)
+ fatal("");
+ return o;
+}
+
+
+tread(buf: array of byte, n: int): int
+{
+ r := sys->read(file, buf, n);
+ if(r == -1)
+ fatal("");
+ return r;
+}
+
+
+twrite(buf:array of byte)
+{
+ str1:= string buf;
+ if(bout.puts(str1)!=len str1)
+ fatal("");
+}
+
+
+
+fatal(s : string)
+{
+ sys->fprint(sys->fildes(2), "tail: %s: %r\n", s);
+ exit;
+}
+
+fail(s : string)
+{
+ sys->fprint(sys->fildes(2), "tail: %s\n", s);
+ exit;
+}
+
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc][fr]] [file]\n");
+ exit;
+}
+
+
+getnumber(s: string) : int
+{
+ i:=0;
+ if (len s == 0) return 0;
+ if(s[i]=='-' || s[i]=='+') {
+ if (len s == 1)
+ return 0;
+ i++;
+ }
+ if(!(s[i]>='0' && s[i]<='9'))
+ return 0;
+ if(s[0] == '+')
+ origin = BEG;
+ if(anycount++)
+ fail("excess option");
+ if (s[0]=='-')
+ s=s[1:];
+ (count,nil) = str->toint(s,10);
+ if(count < 0){ # protect int args (read, fwrite)
+ fail("too big");
+ }
+ return 1;
+}
+
+parse(args : list of string) : list of string
+{
+ for(; args!=nil ; args = tl args ) {
+ hdarg := hd args;
+ if(getnumber(hdarg))
+ suffix(hdarg);
+ else if(len hdarg > 1 && hdarg[0] == '-')
+ case (hdarg[1]) {
+ 'c' or 'n'=>
+ if (hdarg[1]=='c')
+ units = CHARS;
+ if(len hdarg>2 && getnumber(hdarg[2:]))
+ ;
+ else if(tl args != nil && getnumber(hd tl args)) {
+ args = tl args;
+ } else
+ usage();
+ 'r' =>
+ dir = REV;
+ 'f' =>
+ follow++;
+ '-' =>
+ args = tl args;
+ }
+ else
+ break;
+ }
+ return args;
+}
+
+
+suffix(s : string)
+{
+ i:=0;
+ while(i < len s && str->in(s[i],"0123456789+-"))
+ i++;
+ if (i==len s)
+ return;
+ if (s[i]=='b')
+ if((count*=1024) < 0)
+ fail("too big");
+ if (s[i]=='c' || s[i]=='b')
+ units = CHARS;
+ if (s[i]=='l' || s[i]=='c' || s[i]=='b')
+ i++;
+ if (i<len s){
+ case s[i] {
+ 'r'=>
+ dir = REV;
+ return;
+ 'f'=>
+ follow++;
+ return;
+ }
+ }
+ i++;
+ if (i<len s)
+ usage();
+}
diff --git a/appl/cmd/tarfs.b b/appl/cmd/tarfs.b
new file mode 100644
index 00000000..2e0b6473
--- /dev/null
+++ b/appl/cmd/tarfs.b
@@ -0,0 +1,411 @@
+implement Tarfs;
+
+#
+# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+ Qid: import Sys;
+
+include "draw.m";
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "arg.m";
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+
+include "styxservers.m";
+ styxservers: Styxservers;
+ Fid, Styxserver, Navigator, Navop: import styxservers;
+ Enotfound: import styxservers;
+
+Tarfs: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+File: adt {
+ x: int;
+ name: string;
+ mode: int;
+ uid: int;
+ gid: int;
+ mtime: int;
+ length: big;
+ offset: big;
+ parent: cyclic ref File;
+ children: cyclic list of ref File;
+
+ find: fn(f: self ref File, name: string): ref File;
+ enter: fn(d: self ref File, f: ref File);
+ stat: fn(d: self ref File): ref Sys->Dir;
+};
+
+tarfd: ref Sys->FD;
+root: ref File;
+files: array of ref File;
+pathgen: int;
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "tarfs: %s\n", s);
+ raise "fail:error";
+}
+
+checkload[T](m: T, path: string)
+{
+ if(m == nil)
+ error(sys->sprint("can't load %s: %r", path));
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ styx = load Styx Styx->PATH;
+ checkload(styx, Styx->PATH);
+ styx->init();
+ styxservers = load Styxservers Styxservers->PATH;
+ checkload(styxservers, Styxservers->PATH);
+ styxservers->init(styx);
+ daytime = load Daytime Daytime->PATH;
+ checkload(daytime, Daytime->PATH);
+
+ arg := load Arg Arg->PATH;
+ checkload(arg, Arg->PATH);
+ arg->setusage("tarfs [-a|-b|-ac|-bc] [-D] file mountpoint");
+ arg->init(args);
+ flags := Sys->MREPL;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' => flags = Sys->MAFTER;
+ 'b' => flags = Sys->MBEFORE;
+ 'D' => styxservers->traceset(1);
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(len args != 2)
+ arg->usage();
+ arg = nil;
+
+ file := hd args;
+ args = tl args;
+ mountpt := hd args;
+
+ sys->pctl(Sys->FORKFD, nil);
+
+ files = array[100] of ref File;
+ root = files[0] = ref File;
+ root.x = 0;
+ root.name = "/";
+ root.mode = Sys->DMDIR | 8r555;
+ root.uid = 0;
+ root.gid = 0;
+ root.length = big 0;
+ root.offset = big 0;
+ root.mtime = 0;
+ pathgen = 1;
+
+ tarfd = sys->open(file, Sys->OREAD);
+ if(tarfd == nil)
+ error(sys->sprint("can't open %s: %r", file));
+ if(readtar(tarfd) < 0)
+ error(sys->sprint("error reading %s: %r", file));
+
+ fds := array[2] of ref Sys->FD;
+ if(sys->pipe(fds) < 0)
+ error(sys->sprint("can't create pipe: %r"));
+
+ navops := chan of ref Navop;
+ spawn navigator(navops);
+
+ (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big 0);
+ fds[0] = nil;
+
+ pidc := chan of int;
+ spawn server(tchan, srv, pidc, navops);
+ <-pidc;
+
+ if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0)
+ error(sys->sprint("can't mount tarfs: %r"));
+}
+
+server(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop)
+{
+ pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::tarfd.fd::nil);
+Serve:
+ while((gm := <-tchan) != nil){
+ root.mtime = daytime->now();
+ pick m := gm {
+ Readerror =>
+ sys->fprint(sys->fildes(2), "tarfs: mount read error: %s\n", m.error);
+ break Serve;
+ Read =>
+ (c, err) := srv.canread(m);
+ if(c == nil){
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ if(c.qtype & Sys->QTDIR){
+ srv.default(m); # does readdir
+ break;
+ }
+ f := files[int c.path];
+ n := m.count;
+ if(m.offset + big n > f.length)
+ n = int (f.length - m.offset);
+ if(n <= 0){
+ srv.reply(ref Rmsg.Read(m.tag, nil));
+ break;
+ }
+ a := array[n] of byte;
+ sys->seek(tarfd, f.offset+m.offset, 0);
+ n = sys->read(tarfd, a, len a);
+ if(n < 0)
+ srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ else
+ srv.reply(ref Rmsg.Read(m.tag, a[0:n]));
+ * =>
+ srv.default(gm);
+ }
+ }
+ navops <-= nil; # shut down navigator
+}
+
+File.enter(dir: self ref File, f: ref File)
+{
+ if(pathgen >= len files){
+ t := array[pathgen+50] of ref File;
+ t[0:] = files;
+ files = t;
+ }
+ if(0)
+ sys->print("enter %s, %s [#%ux %bd]\n", dir.name, f.name, f.mode, f.length);
+ f.x = pathgen;
+ f.parent = dir;
+ dir.children = f :: dir.children;
+ files[pathgen++] = f;
+}
+
+File.find(f: self ref File, name: string): ref File
+{
+ for(g := f.children; g != nil; g = tl g)
+ if((hd g).name == name)
+ return hd g;
+ return nil;
+}
+
+File.stat(f: self ref File): ref Sys->Dir
+{
+ d := ref sys->zerodir;
+ d.mode = f.mode;
+ d.qid.path = big f.x;
+ d.qid.qtype = f.mode>>24;
+ d.name = f.name;
+ d.uid = string f.uid;
+ d.gid = string f.gid;
+ d.muid = d.uid;
+ d.length = f.length;
+ d.mtime = f.mtime;
+ d.atime = root.mtime;
+ return d;
+}
+
+split(s: string): (string, string)
+{
+ for(i := 0; i < len s; i++)
+ if(s[i] == '/'){
+ for(j := i+1; j < len s && s[j] == '/';)
+ j++;
+ return (s[0:i], s[j:]);
+ }
+ return (nil, s);
+}
+
+putfile(f: ref File)
+{
+ n := f.name;
+ df := root;
+ for(;;){
+ (d, rest) := split(n);
+ if(d == nil || rest == nil){
+ f.name = n;
+ break;
+ }
+ g := df.find(d);
+ if(g == nil){
+ g = ref *f;
+ g.name = d;
+ g.mode |= Sys->DMDIR;
+ df.enter(g);
+ }
+ n = rest;
+ df = g;
+ }
+ df.enter(f);
+}
+
+navigator(navops: chan of ref Navop)
+{
+ while((m := <-navops) != nil){
+ pick n := m {
+ Stat =>
+ n.reply <-= (files[int n.path].stat(), nil);
+ Walk =>
+ f := files[int n.path];
+ if((f.mode & Sys->DMDIR) == 0){
+ n.reply <-= (nil, "not a directory");
+ break;
+ }
+ case n.name {
+ ".." =>
+ if(f.parent != nil)
+ f = f.parent;
+ n.reply <-= (f.stat(), nil);
+ * =>
+ f = f.find(n.name);
+ if(f != nil)
+ n.reply <-= (f.stat(), nil);
+ else
+ n.reply <-= (nil, Enotfound);
+ }
+ Readdir =>
+ f := files[int n.path];
+ if((f.mode & Sys->DMDIR) == 0){
+ n.reply <-= (nil, "not a directory");
+ break;
+ }
+ g := f.children;
+ for(i := n.offset; i > 0 && g != nil; i--)
+ g = tl g;
+ for(; --n.count >= 0 && g != nil; g = tl g)
+ n.reply <-= ((hd g).stat(), nil);
+ n.reply <-= (nil, nil);
+ }
+ }
+}
+
+Blocksize: con 512;
+Namelen: con 100;
+Userlen: con 32;
+
+Oname: con 0;
+Omode: con Namelen;
+Ouid: con Omode+8;
+Ogid: con Ouid+8;
+Osize: con Ogid+8;
+Omtime: con Osize+12;
+Ochksum: con Omtime+12;
+Olinkflag: con Ochksum+8;
+Olinkname: con Olinkflag+1;
+# POSIX extensions follow
+Omagic: con Olinkname+Namelen; # ustar
+Ouname: con Omagic+8;
+Ogname: con Ouname+Userlen;
+Omajor: con Ogname+Userlen;
+Ominor: con Omajor+8;
+Oend: con Ominor+8;
+
+readtar(fd: ref Sys->FD): int
+{
+ buf := array[Blocksize] of byte;
+ offset := big 0;
+ for(;;){
+ sys->seek(fd, offset, 0);
+ n := sys->read(fd, buf, len buf);
+ if(n == 0)
+ break;
+ if(n < 0)
+ return -1;
+ if(n < len buf){
+ sys->werrstr(sys->sprint("short read: expected %d, got %d", len buf, n));
+ return -1;
+ }
+ if(buf[0] == byte 0)
+ break;
+ offset += big Blocksize;
+ mode := octal(buf[Omode:Ouid]);
+ linkflag := int buf[Olinkflag];
+ # don't use linkname
+ if((mode & 8r170000) == 8r40000)
+ linkflag = '5';
+ mode &= 8r777;
+ case linkflag {
+ '1' or '2' or 's' => # ignore links and symbolic links
+ continue;
+ '3' or '4' or '6' => # special file or fifo (leave them, but empty)
+ ;
+ '5' =>
+ mode |= Sys->DMDIR;
+ }
+ f := ref File;
+ f.name = ascii(buf[Oname:Omode]);
+ while(len f.name > 0 && f.name[0] == '/')
+ f.name = f.name[1:];
+ while(len f.name > 0 && f.name[len f.name-1] == '/'){
+ mode |= Sys->DMDIR;
+ f.name = f.name[:len f.name-1];
+ }
+ f.mode = mode;
+ f.uid = octal(buf[Ouid:Ogid]);
+ f.gid = octal(buf[Ogid:Osize]);
+ f.length = big octal(buf[Osize:Omtime]);
+ if(f.length < big 0)
+ error(sys->sprint("tar file size is negative: %s", f.name));
+ if(mode & Sys->DMDIR)
+ f.length = big 0;
+ f.mtime = octal(buf[Omtime:Ochksum]);
+ sum := octal(buf[Ochksum:Olinkflag]);
+ if(sum != checksum(buf))
+ error(sys->sprint("checksum error on %s", f.name));
+ f.offset = offset;
+ offset += f.length;
+ v := int (f.length % big Blocksize);
+ if(v != 0)
+ offset += big (Blocksize-v);
+ putfile(f);
+ }
+ return 0;
+}
+
+ascii(b: array of byte): string
+{
+ top := 0;
+ for(i := 0; i < len b && b[i] != byte 0; i++)
+ if(int b[i] >= 16r80)
+ top = 1;
+ if(top)
+ ; # TO DO: do it by hand if not utf-8
+ return string b[0:i];
+}
+
+octal(b: array of byte): int
+{
+ v := 0;
+ for(i := 0; i < len b && b[i] == byte ' '; i++)
+ ;
+ for(; i < len b && b[i] != byte 0 && b[i] != byte ' '; i++){
+ c := int b[i];
+ if(!(c >= '0' && c <= '7'))
+ error(sys->sprint("bad octal value in tar header: %s (%c)", string b, c));
+ v = (v<<3) | (c-'0');
+ }
+ return v;
+}
+
+checksum(b: array of byte): int
+{
+ c := 0;
+ for(i := 0; i < Ochksum; i++)
+ c += int b[i];
+ for(; i < Olinkflag; i++)
+ c += ' ';
+ for(; i < len b; i++)
+ c += int b[i];
+ return c;
+}
diff --git a/appl/cmd/tclsh.b b/appl/cmd/tclsh.b
new file mode 100644
index 00000000..9a2664a9
--- /dev/null
+++ b/appl/cmd/tclsh.b
@@ -0,0 +1,48 @@
+implement Tclsh;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufmod : Bufio;
+Iobuf : import bufmod;
+
+include "tk.m";
+
+include "../lib/tcl.m";
+ tcl : Tcl_Core;
+
+Tclsh: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+init(ctxt: ref Draw->Context, argv : list of string){
+ sys=load Sys Sys->PATH;
+ tcl=load Tcl_Core Tcl_Core->PATH;
+ if (tcl==nil){
+ sys->print("Cannot load Tcl (%r)\n");
+ exit;
+ }
+ bufmod=load Bufio Bufio->PATH;
+ if (bufmod==nil){
+ sys->print("Cannot load Bufio (%r)\n");
+ exit;
+ }
+ lines:=chan of string;
+ tcl->init(ctxt,argv);
+ new_inp := "tcl%";
+ spawn tcl->grab_lines(nil,nil,lines);
+ for(;;){
+ alt{
+ line := <-lines =>
+ line = tcl->prepass(line);
+ msg:= tcl->evalcmd(line,0);
+ if (msg!=nil)
+ sys->print("%s\n",msg);
+ sys->print("%s ", new_inp);
+ tcl->clear_error();
+ }
+ }
+}
diff --git a/appl/cmd/tcs.b b/appl/cmd/tcs.b
new file mode 100644
index 00000000..4ad70167
--- /dev/null
+++ b/appl/cmd/tcs.b
@@ -0,0 +1,184 @@
+implement Tcs;
+
+include "sys.m";
+include "draw.m";
+include "arg.m";
+include "bufio.m";
+include "convcs.m";
+
+Tcs : module {
+ init : fn (nil : ref Draw->Context, args : list of string);
+};
+
+sys : Sys;
+convcs : Convcs;
+bufio : Bufio;
+
+Iobuf : import bufio;
+
+stderr : ref Sys->FD;
+
+usage()
+{
+ sys->fprint(stderr, "tcs [-C configfile] [-l] [-f ics] [-t ocs] file ...\n");
+ raise "fail:usage";
+}
+
+init(nil : ref Draw->Context, args : list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ if ((arg := load Arg Arg->PATH) == nil)
+ badmodule(Arg->PATH);
+ if ((bufio = load Bufio Bufio->PATH) == nil)
+ badmodule(Bufio->PATH);
+ if ((convcs = load Convcs Convcs->PATH) == nil)
+ badmodule(Convcs->PATH);
+
+ arg->init(args);
+ lflag, vflag : int = 0;
+ ics, ocs : string = "utf8";
+ csfile := "";
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'C' =>
+ csfile = arg->arg();
+ 'f' =>
+ ics = arg->arg();
+ 'l' =>
+ lflag = 1;
+ 't' =>
+ ocs = arg->arg();
+ 'v' =>
+ vflag = 1;
+ * =>
+ usage();
+ }
+ }
+ file := arg->arg();
+
+ out := bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ err := convcs->init(csfile);
+ if (err != nil) {
+ sys->fprint(stderr, "convcs: %s\n", err);
+ raise "fail:init";
+ }
+
+ if (lflag) {
+ if (file != nil)
+ dumpaliases(out, file, vflag);
+ else
+ dumpconvs(out, vflag);
+ return;
+ }
+
+ stob : Stob;
+ btos : Btos;
+ (stob, err) = convcs->getstob(ocs);
+ if (err != nil) {
+ sys->fprint(stderr, "%s: %s\n", ocs, err);
+ raise "fail:badarg";
+ }
+ (btos, err) = convcs->getbtos(ics);
+ if (err != nil) {
+ sys->fprint(stderr, "%s: %s\n", ics, err);
+ raise "fail:badarg";
+ }
+
+ fd := sys->fildes(0);
+ if (file != nil)
+ fd = open(file);
+
+ inbuf := array [Sys->ATOMICIO] of byte;
+ start := 0;
+ while (fd != nil) {
+ btoss : Convcs->State = nil;
+ stobs : Convcs->State = nil;
+
+ while ((n := sys->read(fd, inbuf[start:], len inbuf - start)) > 0) {
+ s := "";
+ nc := 0;
+ outbuf : array of byte = nil;
+ (btoss, s, nc) = btos->btos(btoss, inbuf[0:n], -1);
+ if (s != nil)
+ (stobs, outbuf) = stob->stob(stobs, s);
+ if (outbuf != nil) {
+ out.write(outbuf, len outbuf);
+ }
+ # copy down unconverted part of buffer
+ start = n - nc;
+ if (start && nc)
+ inbuf[:] = inbuf[nc:n];
+ }
+
+ out.flush();
+ file = arg->arg();
+ if (file == nil)
+ break;
+ fd = open(file);
+ }
+}
+
+badmodule(s : string)
+{
+ sys->fprint(stderr, "cannot load module %s: %r\n", s);
+ raise "fail:init";
+}
+
+dumpconvs(out : ref Iobuf, verbose : int)
+{
+ first := 1;
+ for (csl := convcs->enumcs(); csl != nil; csl = tl csl) {
+ (name, desc, mode) := hd csl;
+ if (!verbose) {
+ if (!first)
+ out.putc(' ');
+ out.puts(name);
+ } else {
+ ms := "";
+ case mode {
+ Convcs->BTOS =>
+ ms = "(from)";
+ Convcs->STOB =>
+ ms = "(to)";
+ }
+ out.puts(sys->sprint("%s%s\t%s\n", name, ms, desc));
+ }
+ first = 0;
+ }
+ if (!verbose)
+ out.putc('\n');
+ out.flush();
+}
+
+dumpaliases(out : ref Iobuf, cs : string, verbose : int)
+{
+ (desc, asl) := convcs->aliases(cs);
+ if (asl == nil) {
+ sys->fprint(stderr, "%s\n", desc);
+ return;
+ }
+
+ if (verbose) {
+ out.puts(desc);
+ out.putc('\n');
+ }
+ first := 1;
+ for (; asl != nil; asl = tl asl) {
+ a := hd asl;
+ if (!first)
+ out.putc(' ');
+ out.puts(a);
+ first = 0;
+ }
+ out.putc('\n');
+ out.flush();
+}
+
+open(path : string) : ref Sys->FD
+{
+ fd := sys->open(path, Bufio->OREAD);
+ if (fd == nil)
+ sys->fprint(stderr, "cannot open %s: %r\n", path);
+ return fd;
+}
diff --git a/appl/cmd/tee.b b/appl/cmd/tee.b
new file mode 100644
index 00000000..a555487c
--- /dev/null
+++ b/appl/cmd/tee.b
@@ -0,0 +1,79 @@
+implement Tee;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+Tee: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+File: adt
+{
+ fd: ref Sys->FD;
+ name: string;
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "Usage: tee [-a] [file ...]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ err(sys->sprint("can't load %s: %r", Arg->PATH));
+
+ append := 0;
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'a' => append = 1;
+ * => usage();
+ }
+ names := arg->argv();
+ arg = nil;
+
+ fd0 := sys->fildes(0);
+ if(fd0 == nil)
+ err("no standard input");
+ nf := 0;
+ files := array[len names + 1] of ref File;
+ for(; names != nil; names = tl names){
+ f := hd names;
+ fd: ref Sys->FD;
+ if(append){
+ fd = sys->open(f, Sys->OWRITE);
+ if(fd != nil)
+ sys->seek(fd, big 0, 2);
+ else
+ fd = sys->create(f, Sys->OWRITE, 8r666);
+ }else
+ fd = sys->create(f, Sys->OWRITE, 8r666 );
+ if(fd == nil)
+ err(sys->sprint("cannot open %s: %r", f));
+ files[nf++] = ref File(fd, f);
+ }
+ files[nf++] = ref File(sys->fildes(1), "standard output");
+ buf := array[Sys->ATOMICIO] of byte;
+ while((n := sys->read(fd0, buf, len buf)) > 0){
+ for(i := 0; i < nf; i++)
+ if(sys->write(files[i].fd, buf, n) != n)
+ err(sys->sprint("error writing %s: %r", files[i].name));
+ }
+ if(n < 0)
+ err(sys->sprint("read error: %r"));
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "tee: %s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/telnet.b b/appl/cmd/telnet.b
new file mode 100644
index 00000000..0a30f3f9
--- /dev/null
+++ b/appl/cmd/telnet.b
@@ -0,0 +1,482 @@
+implement Telnet;
+
+include "sys.m";
+ sys: Sys;
+ Connection: import sys;
+
+include "draw.m";
+ draw: Draw;
+ Context: import draw;
+
+Telnet: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+Debug: con 0;
+
+Inbuf: adt {
+ fd: ref Sys->FD;
+ out: ref Outbuf;
+ buf: array of byte;
+ ptr: int;
+ nbyte: int;
+};
+
+Outbuf: adt {
+ buf: array of byte;
+ ptr: int;
+};
+
+BS: con 8; # ^h backspace character
+BSW: con 23; # ^w bacspace word
+BSL: con 21; # ^u backspace line
+EOT: con 4; # ^d end of file
+ESC: con 27; # hold mode
+
+net: Connection;
+stdin, stdout, stderr: ref Sys->FD;
+
+# control characters
+Se: con 240; # end subnegotiation
+NOP: con 241;
+Mark: con 242; # data mark
+Break: con 243;
+Interrupt: con 244;
+Abort: con 245; # TENEX ^O
+AreYouThere: con 246;
+Erasechar: con 247; # erase last character
+Eraseline: con 248; # erase line
+GoAhead: con 249; # half duplex clear to send
+Sb: con 250; # start subnegotiation
+Will: con 251;
+Wont: con 252;
+Do: con 253;
+Dont: con 254;
+Iac: con 255;
+
+# options
+Binary, Echo, SGA, Stat, Timing,
+Det, Term, EOR, Uid, Outmark,
+Ttyloc, M3270, Padx3, Window, Speed,
+Flow, Line, Xloc, Extend: con iota;
+
+Opt: adt
+{
+ name: string;
+ code: int;
+ noway: int;
+ remote: int; # remote value
+ local: int; # local value
+};
+
+opt := array[] of
+{
+ Binary => Opt("binary", 0, 0, 0, 0),
+ Echo => Opt("echo", 1, 0, 0, 0),
+ SGA => Opt("suppress go ahead", 3, 0, 0, 0),
+ Stat => Opt("status", 5, 1, 0, 0),
+ Timing => Opt("timing", 6, 1, 0, 0),
+ Det=> Opt("det", 20, 1, 0, 0),
+ Term => Opt("terminal", 24, 0, 0, 0),
+ EOR => Opt("end of record", 25, 1, 0, 0),
+ Uid => Opt("uid", 26, 1, 0, 0),
+ Outmark => Opt("outmark", 27, 1, 0, 0),
+ Ttyloc => Opt("ttyloc", 28, 1, 0, 0),
+ M3270 => Opt("3270 mode", 29, 1, 0, 0),
+ Padx3 => Opt("pad x.3", 30, 1, 0, 0),
+ Window => Opt("window size", 31, 1, 0, 0),
+ Speed => Opt("speed", 32, 1, 0, 0),
+ Flow => Opt("flow control", 33, 1, 0, 0),
+ Line => Opt("line mode", 34, 1, 0, 0),
+ Xloc => Opt("X display loc", 35, 1, 0, 0),
+ Extend => Opt("Extended", 255, 1, 0, 0),
+};
+
+usage()
+{
+ sys->fprint(stderr, "usage: telnet host [port]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ stdout = sys->fildes(1);
+ stdin = sys->fildes(0);
+
+ if (len argv < 2)
+ usage();
+ argv = tl argv;
+ host := hd argv;
+ argv = tl argv;
+ port := "23";
+ if(argv != nil)
+ port = hd argv;
+ connect(host, port);
+}
+
+ccfd: ref Sys->FD;
+connect(addr: string, port: string)
+{
+ ok: int;
+ (ok, net) = sys->dial(netmkaddr(addr, "tcp", port), nil);
+ if(ok < 0) {
+ sys->fprint(stderr, "telnet: %r\n");
+ return;
+ }
+ sys->fprint(stderr, "telnet: connected to %s\n", addr);
+
+ raw(1);
+ pidch := chan of int;
+ finished := chan of int;
+ spawn fromnet(pidch, finished);
+ spawn fromuser(pidch, finished);
+ pids := array[2] of {* => <-pidch};
+ kill(pids[<-finished == pids[0]]);
+ raw(0);
+}
+
+
+fromuser(pidch, finished: chan of int)
+{
+ pidch <-= sys->pctl(0, nil);
+ b := array[1024] of byte;
+ while((n := sys->read(stdin, b, len b)) > 0) {
+ if (opt[Echo].remote == 0)
+ sys->write(stdout, b, n);
+ sys->write(net.dfd, b, n);
+ }
+ sys->fprint(stderr, "telnet: error reading stdin: %r\n");
+ finished <-= sys->pctl(0, nil);
+}
+
+getc(b: ref Inbuf): int
+{
+ if(b.nbyte == 0) {
+ if(b.out != nil)
+ flushout(b.out);
+ b.nbyte = sys->read(b.fd, b.buf, len b.buf);
+ if(b.nbyte <= 0)
+ return -1;
+ b.ptr = 0;
+ }
+ b.nbyte--;
+ return int b.buf[b.ptr++];
+}
+
+putc(b: ref Outbuf, c: int)
+{
+ b.buf[b.ptr++] = byte c;
+ if(b.ptr == len b.buf)
+ flushout(b);
+}
+
+flushout(b: ref Outbuf)
+{
+ sys->write(stdout, b.buf, b.ptr);
+ b.ptr = 0;
+}
+
+BUFSIZE: con 2048;
+fromnet(pidch, finished: chan of int)
+{
+ pidch <-= sys->pctl(0, nil);
+ conout := ref Outbuf(array[BUFSIZE] of byte, 0);
+ netinp := ref Inbuf(net.dfd, conout, array[BUFSIZE] of byte, 0, 0);
+
+loop: for(;;) {
+ c := getc(netinp);
+ case c {
+ -1 =>
+ break loop;
+ Iac =>
+ c = getc(netinp);
+ if(c != Iac) {
+ flushout(conout);
+ if(control(netinp, c) < 0)
+ break loop;
+ } else
+ putc(conout, c);
+ * =>
+ putc(conout, c);
+ }
+ }
+ sys->fprint(stderr, "telnet: remote host closed connection\n");
+ finished <-= sys->pctl(0, nil);
+}
+
+control(bp: ref Inbuf, c: int): int
+{
+ r := 0;
+ case c {
+ AreYouThere =>
+ sys->fprint(net.dfd, "Inferno telnet\r\n");
+ Sb =>
+ r = sub(bp);
+ Will =>
+ r = will(bp);
+ Wont =>
+ r = wont(bp);
+ Do =>
+ r = doit(bp);
+ Dont =>
+ r = dont(bp);
+ Se =>
+ sys->fprint(stderr, "telnet: SE without an SB\n");
+ -1 =>
+ r = -1;
+ }
+
+ return r;
+}
+
+sub(bp: ref Inbuf): int
+{
+ subneg: string;
+ i := 0;
+ for(;;){
+ c := getc(bp);
+ if(c == Iac) {
+ c = getc(bp);
+ if(c == Se)
+ break;
+ subneg[i++] = Iac;
+ }
+ if(c < 0)
+ return -1;
+ subneg[i++] = c;
+ }
+ if(i == 0)
+ return 0;
+
+ if (Debug)
+ sys->fprint(stderr, "telnet: sub(%s, %d, n = %d)\n", optname(subneg[0]), subneg[1], i);
+
+ for(i = 0; i < len opt; i++)
+ if(opt[i].code == subneg[0])
+ break;
+
+ if(i >= len opt)
+ return 0;
+
+ case i {
+ Term =>
+ sbsend(opt[Term].code, array of byte "network");
+ }
+
+ return 0;
+}
+
+sbsend(code: int, data: array of byte): int
+{
+ buf := array[4+len data+2] of byte;
+ o := 4+len data;
+
+ buf[0] = byte Iac;
+ buf[1] = byte Sb;
+ buf[2] = byte code;
+ buf[3] = byte 0;
+ buf[4:] = data;
+ buf[o] = byte Iac;
+ o++;
+ buf[o] = byte Se;
+
+ return sys->write(net.dfd, buf, len buf);
+}
+
+will(bp: ref Inbuf): int
+{
+ c := getc(bp);
+ if(c < 0)
+ return -1;
+
+ if (Debug)
+ sys->fprint(stderr, "telnet: will(%s)\n", optname(c));
+
+ for(i := 0; i < len opt; i++)
+ if(opt[i].code == c)
+ break;
+
+ if(i >= len opt) {
+ send3(bp, Iac, Dont, c);
+ return 0;
+ }
+
+ rv := 0;
+ if(opt[i].noway)
+ send3(bp, Iac, Dont, c);
+ else
+ if(opt[i].remote == 0)
+ rv |= send3(bp, Iac, Do, c);
+
+ if(opt[i].remote == 0)
+ rv |= change(bp, i, Will);
+ opt[i].remote = 1;
+ return rv;
+}
+
+wont(bp: ref Inbuf): int
+{
+ c := getc(bp);
+ if(c < 0)
+ return -1;
+
+ if (Debug)
+ sys->fprint(stderr, "telnet: wont(%s)\n", optname(c));
+
+ for(i := 0; i < len opt; i++)
+ if(opt[i].code == c)
+ break;
+
+ if(i >= len opt)
+ return 0;
+
+ rv := 0;
+ if(opt[i].remote) {
+ rv |= change(bp, i, Wont);
+ rv |= send3(bp, Iac, Dont, c);
+ }
+ opt[i].remote = 0;
+ return rv;
+}
+
+doit(bp: ref Inbuf): int
+{
+ c := getc(bp);
+ if(c < 0)
+ return -1;
+
+ if (Debug)
+ sys->fprint(stderr, "telnet: do(%s)\n", optname(c));
+
+ for(i := 0; i < len opt; i++)
+ if(opt[i].code == c)
+ break;
+
+ if(i >= len opt || opt[i].noway) {
+ send3(bp, Iac, Wont, c);
+ return 0;
+ }
+ rv := 0;
+ if(opt[i].local == 0) {
+ rv |= change(bp, i, Do);
+ rv |= send3(bp, Iac, Will, c);
+ }
+ opt[i].local = 1;
+ return rv;
+}
+
+dont(bp: ref Inbuf): int
+{
+ c := getc(bp);
+ if(c < 0)
+ return -1;
+
+ if (Debug)
+ sys->fprint(stderr, "telnet: dont(%s)\n", optname(c));
+
+ for(i := 0; i < len opt; i++)
+ if(opt[i].code == c)
+ break;
+
+ if(i >= len opt || opt[i].noway)
+ return 0;
+
+ rv := 0;
+ if(opt[i].local){
+ opt[i].local = 0;
+ rv |= change(bp, i, Dont);
+ rv |= send3(bp, Iac, Wont, c);
+ }
+ opt[i].local = 0;
+ return rv;
+}
+
+change(bp: ref Inbuf, o: int, what: int): int
+{
+ if(bp != nil)
+ {}
+ if(o != 0)
+ {}
+ if(what != 0)
+ {}
+ return 0;
+}
+
+send3(bp: ref Inbuf, c0: int, c1: int, c2: int): int
+{
+ if (Debug)
+ sys->fprint(stderr, "telnet: reply(%s(%s))\n", negname(c1), optname(c2));
+
+ buf := array[3] of byte;
+
+ buf[0] = byte c0;
+ buf[1] = byte c1;
+ buf[2] = byte c2;
+
+ if (sys->write(bp.fd, buf, 3) != 3)
+ return -1;
+ return 0;
+}
+
+kill(pid: int): int
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if (fd == nil)
+ return -1;
+ if (sys->write(fd, array of byte "kill", 4) != 4)
+ return -1;
+ return 0;
+}
+
+negname(c: int): string
+{
+ t := "Unknown";
+ case c {
+ Will => t = "will";
+ Wont => t = "wont";
+ Do => t = "do";
+ Dont => t = "dont";
+ }
+ return t;
+}
+
+optname(c: int): string
+{
+ for (i := 0; i < len opt; i++)
+ if (opt[i].code == c)
+ return opt[i].name;
+ return "unknown";
+}
+
+raw(on: int)
+{
+ if(ccfd == nil) {
+ ccfd = sys->open("/dev/consctl", Sys->OWRITE);
+ if(ccfd == nil) {
+ sys->fprint(stderr, "telnet: cannot open /dev/consctl: %r\n");
+ return;
+ }
+ }
+ if(on)
+ sys->fprint(ccfd, "rawon");
+ else
+ sys->fprint(ccfd, "rawoff");
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/cmd/test.b b/appl/cmd/test.b
new file mode 100644
index 00000000..eb7bf46f
--- /dev/null
+++ b/appl/cmd/test.b
@@ -0,0 +1,278 @@
+implement Test;
+#
+# POSIX standard
+# test expression
+# [ expression ]
+#
+# translated Brazil /sys/src/cmd/test.c
+
+#
+# print "true" on stdout iff the expression evaluates to true
+#
+
+include "sys.m";
+sys: Sys;
+stderr: ref Sys->FD;
+
+include "draw.m";
+
+Test: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+ap: int;
+ac: int;
+av: array of string;
+
+init(nil: ref Draw->Context, argl: list of string)
+{
+ if(argl == nil)
+ return;
+
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ ac = len argl;
+ av = array [ac] of string;
+ for(i := 0; argl != nil; argl = tl argl)
+ av[i++] = hd argl;
+
+ if(av[0] == "[") {
+ if(av[--ac] != "]")
+ synbad("] missing");
+ }
+
+ ap = 1;
+ if(ap<ac && e())
+ sys->print("true");
+# exit;
+# sys->raise "fail: false";
+}
+
+nxtarg(mt: int): string
+{
+ if(ap >= ac){
+ if(mt){
+ ap++;
+ return nil;
+ }
+ synbad("argument expected");
+ }
+ return av[ap++];
+}
+
+nxtintarg(): (int, int)
+{
+ if(ap<ac && isint(av[ap]))
+ return (1, int av[ap++]);
+ return (0, 0);
+}
+
+e(): int
+{
+ p1 := e1();
+ if(nxtarg(1) == "-o")
+ return p1 || e();
+ ap--;
+ return p1;
+}
+
+e1(): int
+{
+ p1 := e2();
+ if(nxtarg(1) == "-a")
+ return p1 && e1();
+ ap--;
+ return p1;
+}
+
+e2(): int
+{
+ if(nxtarg(0) == "!")
+ return !e2();
+ ap--;
+ return e3();
+}
+
+e3(): int
+{
+ a := nxtarg(0);
+ if(a == "(") {
+ p1 := e();
+ if(nxtarg(0) != ")")
+ synbad(") expected");
+ return p1;
+ }
+
+ if(a == "-f")
+ return filck(nxtarg(0), Topf);
+
+ if(a == "-d")
+ return filck(nxtarg(0), Topd);
+
+ if(a == "-r")
+ return filck(nxtarg(0), Topr);
+
+ if(a == "-w")
+ return filck(nxtarg(0), Topw);
+
+ if(a == "-x")
+ return filck(nxtarg(0), Topx);
+
+ if(a == "-e")
+ return filck(nxtarg(0), Tope);
+
+ if(a == "-c")
+ return 0;
+
+ if(a == "-b")
+ return 0;
+
+ if(a == "-u")
+ return 0;
+
+ if(a == "-g")
+ return 0;
+
+ if(a == "-s")
+ return filck(nxtarg(0), Tops);
+
+ if(a == "-t") {
+ (ok, int1) := nxtintarg();
+ if(!ok)
+ return isatty(1);
+ else
+ return isatty(int1);
+ }
+
+ if(a == "-n")
+ return nxtarg(0) != "";
+ if(a == "-z")
+ return nxtarg(0) == "";
+
+ p2 := nxtarg(1);
+ if (p2 == nil)
+ return a != nil;
+ if(p2 == "=")
+ return nxtarg(0) == a;
+
+ if(p2 == "!=")
+ return nxtarg(0) != a;
+
+ if(!isint(a))
+ return a != nil;
+ int1 := int a;
+
+ (ok, int2) := nxtintarg();
+ if(ok){
+ if(p2 == "-eq")
+ return int1 == int2;
+ if(p2 == "-ne")
+ return int1 != int2;
+ if(p2 == "-gt")
+ return int1 > int2;
+ if(p2 == "-lt")
+ return int1 < int2;
+ if(p2 == "-ge")
+ return int1 >= int2;
+ if(p2 == "-le")
+ return int1 <= int2;
+ }
+
+ synbad("unknown operator " + p2);
+ return 0; # to shut ken up
+}
+
+synbad(s: string)
+{
+ sys->fprint(stderr, "test: bad syntax: %s\n", s);
+ exit;
+}
+
+isint(s: string): int
+{
+ if(s == nil)
+ return 0;
+ for(i := 0; i < len s; i++)
+ if(s[i] < '0' || s[i] > '9')
+ return 0;
+ return 1;
+}
+
+Topr,
+Topw,
+Topx,
+Tope,
+Topf,
+Topd,
+Tops: con iota;
+
+filck(fname: string, Top: int): int
+{
+ (ok, dir) := sys->stat(fname);
+
+ if(ok >= 0) {
+ ok = 0;
+ case Top {
+ Topr => # readable
+ ok = permck(dir, 8r004);
+ Topw => # writable
+ ok = permck(dir, 8r002);
+ Topx => # executable
+ ok = permck(dir, 8r001);
+ Tope => # exists
+ ok = 1;
+ Topf => # is a regular file
+ ok = (dir.mode & Sys->DMDIR) == 0;
+ Topd => # is a directory
+ ok = (dir.mode & Sys->DMDIR) != 0;
+ Tops => # has length > 0
+ ok = dir.length > big 0;
+ }
+ }
+
+ return ok > 0;
+}
+
+uid,
+gid: string;
+
+permck(dir: Sys->Dir, mask: int): int
+{
+ if(uid == nil) {
+ fd := sys->open("/dev/user", Sys->OREAD);
+ if(fd != nil) {
+ buf := array [28] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n > 0)
+ uid = string buf[:n];
+ }
+ gid = nil; # how do I find out what my group is?
+ }
+
+ ok: int = 0;
+
+ ok = dir.mode & mask<<0;
+ if(!ok && dir.gid == gid)
+ ok = dir.mode & mask<<3;
+ if(!ok && dir.uid == uid)
+ ok = dir.mode & mask<<6;
+
+ return ok > 0;
+}
+
+isatty(fd: int): int
+{
+ d1, d2: Sys->Dir;
+
+ ok: int;
+ (ok, d1) = sys->fstat(sys->fildes(fd));
+ if(ok < 0)
+ return 0;
+ (ok, d2) = sys->stat("/dev/cons");
+ if(ok < 0)
+ return 0;
+
+ return d1.dtype==d2.dtype && d1.dev==d2.dev && d1.qid.path==d2.qid.path;
+}
diff --git a/appl/cmd/time.b b/appl/cmd/time.b
new file mode 100644
index 00000000..b4fba159
--- /dev/null
+++ b/appl/cmd/time.b
@@ -0,0 +1,97 @@
+implement Time;
+
+include "sys.m";
+include "draw.m";
+include "sh.m";
+
+FD: import Sys;
+Context: import Draw;
+
+Time: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+sys: Sys;
+stderr, waitfd: ref FD;
+
+init(ctxt: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ stderr = sys->fildes(2);
+
+ waitfd = sys->open("#p/"+string sys->pctl(0, nil)+"/wait", sys->OREAD);
+ if(waitfd == nil){
+ sys->fprint(stderr, "time: open wait: %r\n");
+ return;
+ }
+
+ argv = tl argv;
+
+ if(argv == nil) {
+ sys->fprint(stderr, "usage: time cmd ...\n");
+ return;
+ }
+
+ file := hd argv;
+
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+
+ t0 := sys->millisec();
+
+ c := load Command file;
+ if(c == nil) {
+ err := sys->sprint("%r");
+ if(1){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ if(c == nil) {
+ sys->fprint(stderr, "time: %s: %s\n", hd argv, err);
+ return;
+ }
+ }
+
+ t1 := sys->millisec();
+
+ pidc := chan of int;
+
+ spawn cmd(ctxt, c, pidc, argv);
+ waitfor(<-pidc);
+
+ t2 := sys->millisec();
+
+ f1 := real (t1 - t0) /1000.;
+ f2 := real (t2 - t1) /1000.;
+ sys->fprint(stderr, "%.4gl %.4gr %.4gt\n", f1, f2, f1+f2);
+}
+
+cmd(ctxt: ref Context, c: Command, pidc: chan of int, argv: list of string)
+{
+ pidc <-= sys->pctl(0, nil);
+ c->init(ctxt, argv);
+}
+
+waitfor(pid: int)
+{
+ buf := array[sys->WAITLEN] of byte;
+ status := "";
+ for(;;){
+ n := sys->read(waitfd, buf, len buf);
+ if(n < 0) {
+ sys->fprint(stderr, "sh: read wait: %r\n");
+ return;
+ }
+ status = string buf[0:n];
+ if(status[len status-1] != ':')
+ sys->fprint(stderr, "%s\n", status);
+ who := int status;
+ if(who != 0) {
+ if(who == pid)
+ return;
+ }
+ }
+}
diff --git a/appl/cmd/timestamp.b b/appl/cmd/timestamp.b
new file mode 100644
index 00000000..8f8554f8
--- /dev/null
+++ b/appl/cmd/timestamp.b
@@ -0,0 +1,42 @@
+implement Timestamp;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Timestamp: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+timefd: ref Sys->FD;
+starttime: big;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ note: string;
+ if(len argv > 1)
+ note = hd tl argv + " ";
+
+ timefd = sys->open("/dev/time", Sys->OREAD);
+ starttime = now();
+
+ sys->print("%.10bd %sstart %bd\n", now(), note, starttime);
+
+ iob := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ while((s := iob.gets('\n')) != nil)
+ sys->print("%.10bd %s%s", now(), note, s);
+}
+
+now(): big
+{
+ buf := array[24] of byte;
+ n := sys->pread(timefd, buf, len buf, big 0);
+ if(n <= 0)
+ return big 0;
+ return big string buf[0:n] / big 1000 - starttime;
+}
diff --git a/appl/cmd/tkcmd.b b/appl/cmd/tkcmd.b
new file mode 100644
index 00000000..4dd607b1
--- /dev/null
+++ b/appl/cmd/tkcmd.b
@@ -0,0 +1,190 @@
+implement Tkcmd;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+ draw: Draw;
+ Display, Image, Point: import draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "bufio.m";
+include "arg.m";
+
+Tkcmd : module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->print("usage: tkcmd [-iu] [toplevelarg]\n");
+ raise "fail:usage";
+}
+
+badmodule(m: string)
+{
+ sys->fprint(stderr, "tkcmd: cannot load %s: %r\n", m);
+ raise "fail:bad module";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ if (tk == nil)
+ badmodule(Tk->PATH);
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient==nil)
+ badmodule(Tkclient->PATH);
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+
+ arg->init(argv);
+ update := 1;
+ interactive := isconsole(sys->fildes(0));
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'i' =>
+ interactive = 1;
+ 'u' =>
+ update = 0;
+ * =>
+ usage();
+ }
+ }
+ argv = arg->argv();
+ arg = nil;
+ tkarg := "";
+ if (argv != nil) {
+ if (tl argv != nil)
+ usage();
+ tkarg = hd argv;
+ }
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ tkclient->init();
+ shellit(ctxt, tkarg, interactive, update);
+}
+
+isconsole(fd: ref Sys->FD): int
+{
+ (ok1, d1) := sys->fstat(fd);
+ (ok2, d2) := sys->stat("/dev/cons");
+ if (ok1 < 0 || ok2 < 0)
+ return 0;
+ return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path;
+}
+
+shellit(ctxt: ref Draw->Context, arg: string, interactive, update: int)
+{
+ (Wwsh, winctl) := tkclient->toplevel(ctxt, arg, "Tk", Tkclient->Appl);
+ tkclient->onscreen(Wwsh, nil);
+ tkclient->startinput(Wwsh, "ptr" :: "kbd" :: nil);
+ wm := Wwsh.ctxt;
+ if(update)
+ tk->cmd(Wwsh, "update");
+ ps1 := "";
+ ps2 := "";
+ if (!interactive)
+ ps1 = ps2 = "";
+
+ lines := chan of string;
+ sync := chan of int;
+ spawn grab_lines(ps1, ps2, lines, sync);
+ output := chan of string;
+ tk->namechan(Wwsh, output, "stdout");
+ pid := <-sync;
+Loop:
+ for(;;) alt {
+ c := <-wm.kbd =>
+ tk->keyboard(Wwsh, c);
+ m := <-wm.ptr =>
+ tk->pointer(Wwsh, *m);
+ c := <-wm.ctl or
+ c = <-Wwsh.wreq =>
+ tkclient->wmctl(Wwsh, c);
+ line := <-lines =>
+ if (line == nil)
+ break Loop;
+ if (line[0] == '#')
+ break;
+ line = line[0:len line - 1];
+ result := tk->cmd(Wwsh, line);
+ if (result != nil)
+ sys->print("#%s\n", result);
+ if (update)
+ tk->cmd(Wwsh, "update");
+ sys->print("%s", ps1);
+ menu := <-winctl =>
+ tkclient->wmctl(Wwsh, menu);
+ s := <-output =>
+ sys->print("#<stdout>%s\n", s);
+ sys->print("%s", ps1);
+ }
+}
+
+grab_lines(new_inp, unfin: string, lines: chan of string, sync: chan of int)
+{
+ sync <-= sys->pctl(0, nil);
+ {
+ bufmod := load Bufio Bufio->PATH;
+ Iobuf: import bufmod;
+ if (bufmod == nil) {
+ lines <-= nil;
+ return;
+ }
+ sys->print("%s", new_inp);
+ iob := bufmod->fopen(sys->fildes(0),bufmod->OREAD);
+ if (iob==nil){
+ sys->fprint(stderr, "tkcmd: cannot open stdin for reading.\n");
+ lines <-= nil;
+ return;
+ }
+ line := "";
+ while((input := iob.gets('\n')) != nil) {
+ line+=input;
+ if (!finished(line,0))
+ sys->print("%s", unfin);
+ else{
+ lines <-= line;
+ line=nil;
+ }
+ }
+ lines <-= nil;
+ }exception e{
+ "*" =>
+ sys->fprint(stderr, "tkcmd: fail: %s\n", e);
+ lines <-= nil;
+ }
+}
+
+# returns 1 if the line has matching braces, brackets and
+# double-quotes and does not end in "\\\n"
+finished(s : string, termchar : int) : int {
+ cb:=0;
+ dq:=0;
+ sb:=0;
+ if (s==nil) return 1;
+ if (termchar=='}') cb++;
+ if (termchar==']') sb++;
+ if (len s > 1 && s[len s -2]=='\\')
+ return 0;
+ if (s[0]=='{') cb++;
+ if (s[0]=='}' && cb>0) cb--;
+ if (s[0]=='[') sb++;
+ if (s[0]==']' && sb>0) sb--;
+ if (s[0]=='"') dq=1-dq;
+ for(i:=1;i<len s;i++){
+ if (s[i]=='{' && s[i-1]!='\\') cb++;
+ if (s[i]=='}' && s[i-1]!='\\' && cb>0) cb--;
+ if (s[i]=='[' && s[i-1]!='\\') sb++;
+ if (s[i]==']' && s[i-1]!='\\' && sb>0) sb--;
+ if (s[i]=='"' && s[i-1]!='\\') dq=1-dq;
+ }
+ return (cb==0 && sb==0 && dq==0);
+}
diff --git a/appl/cmd/tokenize.b b/appl/cmd/tokenize.b
new file mode 100644
index 00000000..e5bcf416
--- /dev/null
+++ b/appl/cmd/tokenize.b
@@ -0,0 +1,33 @@
+implement Tokenize;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+Tokenize: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+stderr: ref Sys->FD;
+
+usage()
+{
+ sys->fprint(stderr, "Usage: tokenize string delimiters\n");
+ raise "fail: usage";
+}
+
+init(nil: ref Draw->Context, args : list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ if(args != nil)
+ args = tl args;
+ if(len args != 2)
+ usage();
+ (nil, l) := sys->tokenize(hd args, hd tl args);
+ for(; l != nil; l = tl l)
+ sys->print("%s\n", hd l);
+}
diff --git a/appl/cmd/touch.b b/appl/cmd/touch.b
new file mode 100644
index 00000000..9ff2dcc5
--- /dev/null
+++ b/appl/cmd/touch.b
@@ -0,0 +1,77 @@
+implement Touch;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "arg.m";
+
+stderr: ref Sys->FD;
+
+Touch: module
+{
+ init: fn(ctxt: ref Draw->Context, argl: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ force := 1;
+ status := 0;
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ cantload(Daytime->PATH);
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ cantload(Arg->PATH);
+ arg->init(args);
+ arg->setusage("touch [-c] [-t time] file ...");
+ now := daytime->now();
+ while((c := arg->opt()) != 0)
+ case c {
+ 't' => now = int arg->earg();
+ 'c' => force = 0;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+ if(args == nil)
+ arg->usage();
+ for(; args != nil; args = tl args)
+ status += touch(force, hd args, now);
+ if(status)
+ raise "fail:touch";
+}
+
+cantload(s: string)
+{
+ sys->fprint(stderr, "touch: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+touch(force: int, name: string, now: int): int
+{
+ dir := sys->nulldir;
+ dir.mtime = now;
+ (rc, nil) := sys->stat(name);
+ if(rc >= 0){
+ if(sys->wstat(name, dir) >= 0)
+ return 0;
+ force = 0; # we don't want to create it: it's there, we just can't wstat it
+ }
+ if(force == 0) {
+ sys->fprint(stderr, "touch: %s: cannot change time: %r\n", name);
+ return 1;
+ }
+ if((fd := sys->create(name, Sys->OREAD|Sys->OEXCL, 8r666)) == nil) {
+ sys->fprint(stderr, "touch: %s: cannot create: %r\n", name);
+ return 1;
+ }
+ sys->fwstat(fd, dir);
+ return 0;
+}
diff --git a/appl/cmd/touchcal.b b/appl/cmd/touchcal.b
new file mode 100644
index 00000000..5557e324
--- /dev/null
+++ b/appl/cmd/touchcal.b
@@ -0,0 +1,278 @@
+implement Touchcal;
+
+#
+# calibrate a touch screen
+#
+# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Display, Font, Image, Point, Pointer, Rect: import draw;
+
+include "tk.m";
+
+include "wmclient.m";
+ wmclient: Wmclient;
+ Window: import wmclient;
+
+include "translate.m";
+ translate: Translate;
+ Dict: import translate;
+
+Touchcal: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+
+Margin: con 20;
+
+prompt:= "Please tap the centre\nof the cross\nwith the stylus";
+
+mousepid := 0;
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ r: Rect;
+ disp: ref Image;
+
+ if(args != nil)
+ args = tl args;
+ debug := args != nil && hd args == "-d";
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ if(draw == nil)
+ err(sys->sprint("no Draw module: %r"));
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+ translate = load Translate Translate->PATH;
+ if(translate != nil){
+ translate->init();
+ (dict, nil) := translate->opendict(translate->mkdictname("", "touchcal"));
+ if(dict != nil)
+ prompt = dict.xlate(prompt);
+ dict = nil;
+ translate = nil;
+ }
+
+ display: ref Display;
+ win: ref Window;
+ ptr: chan of ref Pointer;
+ if(ctxt != nil){
+ display = ctxt.display;
+ wmclient = load Wmclient Wmclient->PATH;
+ if(wmclient == nil)
+ err(sys->sprint("cannot load %s: %r", Wmclient->PATH));
+ wmclient->init();
+ win = wmclient->window(ctxt, "Touchcal", Wmclient->Plain);
+ win.reshape(ctxt.display.image.r);
+ ptr = chan of ref Pointer;
+ win.onscreen("exact");
+ win.startinput("ptr"::nil);
+ pidc := chan of int;
+ ptr = win.ctxt.ptr;
+ display = ctxt.display;
+ disp = win.image;
+ r = disp.r;
+ }else{
+ # standalone, catch them ourselves
+ display = draw->Display.allocate(nil);
+ disp = display.image;
+ r = disp.r;
+ mfd := sys->open("/dev/pointer", Sys->OREAD);
+ if(mfd == nil)
+ err(sys->sprint("can't open /dev/pointer: %r"));
+ pidc := chan of int;
+ ptr = chan of ref Pointer;
+ spawn rawmouse(mfd, ptr, pidc);
+ mousepid = <-pidc;
+ }
+ white := display.white;
+ black := display.black;
+ red := display.color(Draw->Red);
+ disp.draw(r, white, nil, r.min);
+ samples := array[4] of Point;
+ points := array[4] of Point;
+ points[0] = (r.min.x+Margin, r.min.y+Margin);
+ points[1] = (r.max.x-Margin, r.min.y+Margin);
+ points[2] = (r.max.x-Margin, r.max.y-Margin);
+ points[3] = (r.min.x+Margin, r.max.y-Margin);
+ midpoint := Point((r.min.x+r.max.x)/2, (r.min.y+r.max.y)/2);
+ refx := FX((points[1].x - points[0].x) + (points[2].x - points[3].x), 1);
+ refy := FX((points[3].y - points[0].y) + (points[2].y - points[1].y), 1);
+ ctl := sys->open("/dev/touchctl", Sys->ORDWR);
+ if(ctl == nil)
+ ctl = sys->open("/dev/null", Sys->ORDWR);
+ if(ctl == nil)
+ err(sys->sprint("can't open /dev/touchctl: %r"));
+ #oldvalues := array[128] of byte;
+ #nr := sys->read(ctl, oldvalues, len oldvalues);
+ #if(nr < 0)
+ # err(sys->sprint("can't read old values from /dev/touchctl: %r"));
+ #oldvalues = oldvalues[0:nr];
+ sys->fprint(ctl, "X %d %d %d\nY %d %d %d\n", FX(1,1), 0, 0, 0, FX(1,1), 0); # identity
+ font := Font.open(display, sys->sprint("/fonts/lucida/unicode.%d.font", 6+(r.dx()/512)));
+ if(font == nil)
+ font = Font.open(display, "*default*");
+ if(font != nil){
+ drawtext(disp, midpoint, black, font, prompt);
+ font = nil;
+ }
+ for(;;) {
+ tm := array[] of {0 to 2 =>array[] of {0, 0, 0}};
+ for(i := 0; i < 4; i++){
+ cross(disp, points[i], red);
+ samples[i] = getpoint(ptr);
+ cross(disp, points[i], white);
+ }
+ # first, rotate if necessary
+ rotate := 0;
+ if(abs(samples[1].x-samples[2].x) > 80 && abs(samples[2].y-samples[3].y) > 80){
+ rotate = 1;
+ for(i = 0; i < len samples; i++)
+ samples[i] = (samples[i].y, samples[i].x);
+ }
+ # calculate scaling and offset transformations
+ actx := (samples[1].x-samples[0].x)+(samples[2].x-samples[3].x);
+ acty := (samples[3].y-samples[0].y)+(samples[2].y-samples[1].y);
+ if(actx == 0 || acty == 0)
+ continue; # either the user or device is not trying
+ tm[0][rotate] = refx/actx;
+ tm[0][2] = FX(points[0].x - XF(tm[0][rotate]*samples[0].x), 1);
+ tm[1][1-rotate] = refy/acty;
+ tm[1][2] = FX(points[0].y - XF(tm[1][1-rotate]*samples[0].y), 1);
+ cross(disp, midpoint, red);
+ m := getpoint(ptr);
+ cross(disp, midpoint, white);
+ p := Point(ptmap(tm[0], m.x, m.y), ptmap(tm[1], m.x, m.y));
+ if(debug){
+ for(k:=0; k<4; k++)
+ sys->print("%d %d,%d %d,%d\n", k, points[k].x,points[k].y, samples[k].x, samples[k].y);
+ if(rotate)
+ sys->print("rotated\n");
+ sys->print("rx=%d ax=%d ry=%d ay=%d tm[0][0]=%d\n", refx, actx, refy, acty, tm[0][0]);
+ sys->print("%g %g %g\n%g %g %g\n",
+ G(tm[0][0]), G(tm[0][1]), G(tm[0][2]),
+ G(tm[1][0]), G(tm[1][1]), G(tm[1][2]));
+ sys->print("%d %d -> %d %d (%d %d)\n", m.x, m.y, p.x, p.y, midpoint.x, midpoint.y);
+ }
+ if(abs(p.x-midpoint.x) > 5 || abs(p.y-midpoint.y) > 5)
+ continue;
+ printmat(sys->fildes(1), tm);
+ if(debug || printmat(ctl, tm) >= 0){
+ disp.draw(r, white, nil, r.min);
+ break;
+ }
+ sys->fprint(sys->fildes(2), "touchcal: can't set calibration: %r\n");
+ }
+ if(mousepid > 0)
+ kill(mousepid);
+}
+
+printmat(fd: ref Sys->FD, tm: array of array of int): int
+{
+ return sys->fprint(fd, "X %d %d %d\nY %d %d %d\n",
+ tm[0][0], tm[0][1], tm[0][2],
+ tm[1][0], tm[1][1], tm[1][2]);
+}
+
+FX(a, b: int): int
+{
+ return (a << 16)/b;
+}
+
+XF(v: int): int
+{
+ return v>>16;
+}
+
+G(v: int): real
+{
+ return real v / 65536.0;
+}
+
+ptmap(m: array of int, x, y: int): int
+{
+ return XF(m[0]*x + m[1]*y + m[2]);
+}
+
+rawmouse(fd: ref Sys->FD, mc: chan of ref Pointer, pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil);
+ buf := array[64] of byte;
+ for(;;){
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ err(sys->sprint("can't read /dev/pointer: %r"));
+
+ if(int buf[0] != 'm' || n < 1+3*12)
+ continue;
+
+ x := int string buf[ 1:13];
+ y := int string buf[12:25];
+ b := int string buf[24:37];
+ mc <-= ref Pointer(b, (x,y), 0);
+ }
+}
+
+getpoint(mousec: chan of ref Pointer): Point
+{
+ p := Point(0,0);
+ while((m := <-mousec).buttons == 0)
+ p = m.xy;
+ n := 0;
+ do{
+ if(abs(p.x-m.xy.x) > 10 || abs(p.y-m.xy.y) > 10){
+ n = 0;
+ p = m.xy;
+ }else{
+ p = p.mul(n).add(m.xy).div(n+1);
+ n++;
+ }
+ }while((m = <-mousec).buttons & 7);
+ return p;
+}
+
+cross(im: ref Image, p: Point, col: ref Image)
+{
+ im.line(p.sub((0,10)), p.add((0,10)), Draw->Endsquare, Draw->Endsquare, 0, col, col.r.min);
+ im.line(p.sub((10,0)), p.add((10,0)), Draw->Endsquare, Draw->Endsquare, 0, col, col.r.min);
+ im.flush(Draw->Flushnow);
+}
+
+drawtext(im: ref Image, p: Point, col: ref Image, font: ref Font, text: string)
+{
+ (n, lines) := sys->tokenize(text, "\n");
+ p = p.sub((0, (n+1)*font.height));
+ for(; lines != nil; lines = tl lines){
+ s := hd lines;
+ w := font.width(s);
+ im.text(p.sub((w/2, 0)), col, col.r.min, font, s);
+ p = p.add((0, font.height));
+ }
+}
+
+abs(x: int): int
+{
+ if(x < 0)
+ return -x;
+ return x;
+}
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "touchcal: %s\n", s);
+ if(mousepid > 0)
+ kill(mousepid);
+ raise "fail:touch";
+}
diff --git a/appl/cmd/tr.b b/appl/cmd/tr.b
new file mode 100644
index 00000000..202ace53
--- /dev/null
+++ b/appl/cmd/tr.b
@@ -0,0 +1,319 @@
+implement Tr;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+ arg: Arg;
+
+Tr: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Pcb: adt { # Control block controlling specification parse
+ spec: string; # specification string
+ end: int; # its length
+ current: int; # current parse point
+ last: int; # last Rune returned
+ final: int; # final Rune in a span
+
+ new: fn(nil: string): ref Pcb;
+ rewind: fn(nil: self ref Pcb);
+ getc: fn(nil: self ref Pcb): int;
+ canon: fn(nil: self ref Pcb): int;
+};
+
+bits := array [] of { byte 1, byte 2, byte 4, byte 8, byte 16, byte 32, byte 64, byte 128 };
+
+SETBIT(a: array of byte, c: int)
+{
+ a[c>>3] |= bits[c & 7];
+}
+
+CLEARBIT(a: array of byte, c: int)
+{
+ a[c>>3] &= ~bits[c & 7];
+}
+
+BITSET(a: array of byte, c: int): int
+{
+ return int (a[c>>3] & bits[c&7]);
+}
+
+MAXRUNE: con 16rFFFF;
+
+f := array[(MAXRUNE+1)/8] of byte;
+t := array[(MAXRUNE+1)/8] of byte;
+
+pto, pfrom: ref Pcb;
+
+cflag := 0;
+dflag := 0;
+sflag := 0;
+stderr: ref Sys->FD;
+
+ib: ref Iobuf;
+ob: ref Iobuf;
+
+usage()
+{
+ sys->fprint(stderr, "Usage: tr [-sdc] [from-set [to-set]]\n");
+ raise "fail: usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ arg = load Arg Arg->PATH;
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 's' => sflag = 1;
+ 'd' => dflag = 1;
+ 'c' => cflag = 1;
+ * => usage();
+ }
+ args = arg->argv();
+ argc := len args;
+ if(args != nil){
+ pfrom = Pcb.new(hd args);
+ args = tl args;
+ }
+ if(args != nil){
+ pto = Pcb.new(hd args);
+ args = tl args;
+ }
+ if(args != nil)
+ usage();
+ ib = bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ ob = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ if(dflag) {
+ if(sflag && argc != 2 || !sflag && argc != 1)
+ usage();
+ delete();
+ } else {
+ if(argc != 2)
+ usage();
+ if(cflag)
+ complement();
+ else
+ translit();
+ }
+ if(ob.flush() == Bufio->ERROR)
+ error(sys->sprint("write error: %r"));
+}
+
+delete()
+{
+ if (cflag) {
+ for(i := 0; i < len f; i++)
+ f[i] = byte 16rFF;
+ while ((c := pfrom.canon()) >= 0)
+ CLEARBIT(f, c);
+ } else {
+ while ((c := pfrom.canon()) >= 0)
+ SETBIT(f, c);
+ }
+ if (sflag) {
+ while ((c := pto.canon()) >= 0)
+ SETBIT(t, c);
+ }
+
+ last := MAXRUNE+1;
+ while ((c := ib.getc()) >= 0) {
+ if(!BITSET(f, c) && (c != last || !BITSET(t,c))) {
+ last = c;
+ ob.putc(c);
+ }
+ }
+}
+
+complement()
+{
+ lastc := 0;
+ high := 0;
+ while ((from := pfrom.canon()) >= 0) {
+ if (from > high)
+ high = from;
+ SETBIT(f, from);
+ }
+ while ((cto := pto.canon()) >= 0) {
+ if (cto > high)
+ high = cto;
+ SETBIT(t,cto);
+ }
+ pto.rewind();
+ p := array[high+1] of int;
+ for (i := 0; i <= high; i++){
+ if (!BITSET(f,i)) {
+ if ((cto = pto.canon()) < 0)
+ cto = lastc;
+ else
+ lastc = cto;
+ p[i] = cto;
+ } else
+ p[i] = i;
+ }
+ if (sflag){
+ lastc = MAXRUNE+1;
+ while ((from = ib.getc()) >= 0) {
+ if (from > high)
+ from = cto;
+ else
+ from = p[from];
+ if (from != lastc || !BITSET(t,from)) {
+ lastc = from;
+ ob.putc(from);
+ }
+ }
+ } else {
+ while ((from = ib.getc()) >= 0){
+ if (from > high)
+ from = cto;
+ else
+ from = p[from];
+ ob.putc(from);
+ }
+ }
+}
+
+translit()
+{
+ lastc := 0;
+ high := 0;
+ while ((from := pfrom.canon()) >= 0)
+ if (from > high)
+ high = from;
+ pfrom.rewind();
+ p := array[high+1] of int;
+ for (i := 0; i <= high; i++)
+ p[i] = i;
+ while ((from = pfrom.canon()) >= 0) {
+ if ((cto := pto.canon()) < 0)
+ cto = lastc;
+ else
+ lastc = cto;
+ if (BITSET(f,from) && p[from] != cto)
+ error("ambiguous translation");
+ SETBIT(f,from);
+ p[from] = cto;
+ SETBIT(t,cto);
+ }
+ while ((cto := pto.canon()) >= 0)
+ SETBIT(t,cto);
+ if (sflag){
+ lastc = MAXRUNE+1;
+ while ((from = ib.getc()) >= 0) {
+ if (from <= high)
+ from = p[from];
+ if (from != lastc || !BITSET(t,from)) {
+ lastc = from;
+ ob.putc(from);
+ }
+ }
+
+ } else {
+ while ((from = ib.getc()) >= 0) {
+ if (from <= high)
+ from = p[from];
+ ob.putc(from);
+ }
+ }
+}
+
+Pcb.new(s: string): ref Pcb
+{
+ return ref Pcb(s, len s, 0, -1, -1);
+}
+
+Pcb.rewind(p: self ref Pcb)
+{
+ p.current = 0;
+ p.last = p.final = -1;
+}
+
+Pcb.getc(p: self ref Pcb): int
+{
+ if(p.current >= p.end)
+ return -1;
+ s := p.current;
+ r := p.spec[s++];
+ if(r == '\\' && s < p.end){
+ n := 0;
+ if ((r = p.spec[s]) == 'x') {
+ s++;
+ for (i := 0; i < 4 && s < p.end; i++) {
+ p.current = s;
+ r = p.spec[s++];
+ if ('0' <= r && r <= '9')
+ n = 16*n + r - '0';
+ else if ('a' <= r && r <= 'f')
+ n = 16*n + r - 'a' + 10;
+ else if ('A' <= r && r <= 'F')
+ n = 16*n + r - 'A' + 10;
+ else {
+ if (i == 0)
+ return 'x';
+ return n;
+ }
+ }
+ r = n;
+ } else {
+ for(i := 0; i < 3 && s < p.end; i++) {
+ p.current = s;
+ r = p.spec[s++];
+ if('0' <= r && r <= '7')
+ n = 8*n + r - '0';
+ else {
+ if (i == 0)
+ return r;
+ return n;
+ }
+ }
+ if(n > 0377)
+ error("char>0377");
+ r = n;
+ }
+ }
+ p.current = s;
+ return r;
+}
+
+Pcb.canon(p: self ref Pcb): int
+{
+ if (p.final >= 0) {
+ if (p.last < p.final)
+ return ++p.last;
+ p.final = -1;
+ }
+ if (p.current >= p.end)
+ return -1;
+ if(p.spec[p.current] == '-' && p.last >= 0 && p.current+1 < p.end){
+ p.current++;
+ r := p.getc();
+ if (r < p.last)
+ error ("Invalid range specification");
+ if (r > p.last) {
+ p.final = r;
+ return ++p.last;
+ }
+ }
+ r := p.getc();
+ p.last = r;
+ return p.last;
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "tr: %s\n", s);
+ raise "fail: error";
+}
diff --git a/appl/cmd/tsort.b b/appl/cmd/tsort.b
new file mode 100644
index 00000000..5993fa31
--- /dev/null
+++ b/appl/cmd/tsort.b
@@ -0,0 +1,133 @@
+implement Tsort;
+
+#
+# tsort -- topological sort
+#
+# convert a partial ordering into a linear ordering
+#
+# Copyright © 2004 Vita Nuova Holdings Limited
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Tsort: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Item: adt {
+ name: string;
+ mark: int;
+ succ: cyclic list of ref Item; # node's successors
+
+ precede: fn(a: self ref Item, b: ref Item);
+};
+
+Q: adt {
+ item: ref Item;
+ next: cyclic ref Q;
+};
+
+items, itemt: ref Q; # use a Q not a list only to keep input order
+nitem := 0;
+bout: ref Iobuf;
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ input();
+ output();
+ bout.flush();
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "tsort: %s\n", s);
+ raise "fail:error";
+}
+
+input()
+{
+ b := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ while((line := b.gets('\n')) != nil){
+ (nf, fld) := sys->tokenize(line, " \t\n");
+ if(fld != nil){
+ a := finditem(hd fld);
+ while((fld = tl fld) != nil)
+ a.precede(finditem(hd fld));
+ }
+ }
+}
+
+Item.precede(a: self ref Item, b: ref Item)
+{
+ if(a != b){
+ for(l := a.succ; l != nil; l = tl l)
+ if((hd l) == b)
+ return;
+ a.succ = b :: a.succ;
+ }
+}
+
+finditem(s: string): ref Item
+{
+ # would use a hash table for large sets
+ for(il := items; il != nil; il = il.next)
+ if(il.item.name == s)
+ return il.item;
+ i := ref Item;
+ i.name = s;
+ i.mark = 0;
+ if(items != nil)
+ itemt = itemt.next = ref Q(i, nil);
+ else
+ itemt = items = ref Q(i, nil);
+ nitem++;
+ return i;
+}
+
+dep: list of ref Item;
+
+output()
+{
+ for(k := items; k != nil; k = k.next)
+ if((q := k.item).mark == 0)
+ visit(q, nil);
+ for(; dep != nil; dep = tl dep)
+ bout.puts((hd dep).name+"\n");
+}
+
+# visit q's successors depth first
+# parents is only used to print any cycles, and since it matches
+# the stack, the recursion could be eliminated
+visit(q: ref Item, parents: list of ref Item)
+{
+ q.mark = 2;
+ parents = q :: parents;
+ for(sl := q.succ; sl != nil; sl = tl sl)
+ if((s := hd sl).mark == 0)
+ visit(s, parents);
+ else if(s.mark == 2){
+ sys->fprint(sys->fildes(2), "tsort: cycle in input\n");
+ rl: list of ref Item;
+ for(l := parents;; l = tl l){ # reverse to be closer to input order
+ rl = hd l :: rl;
+ if(hd l == s)
+ break;
+ }
+ for(l = rl; l != nil; l = tl l)
+ sys->fprint(sys->fildes(2), "tsort: %s\n", (hd l).name);
+ }
+ q.mark = 1;
+ dep = q :: dep;
+}
diff --git a/appl/cmd/unicode.b b/appl/cmd/unicode.b
new file mode 100644
index 00000000..9f1ae3d1
--- /dev/null
+++ b/appl/cmd/unicode.b
@@ -0,0 +1,162 @@
+implement Unicode;
+
+include "sys.m";
+sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+
+Unicode: module
+{
+ init: fn(c: ref Draw->Context, v: list of string);
+};
+
+usage: con "unicode { [-t] hex hex ... | hexmin-hexmax ... | [-n] char ... }";
+hex: con "0123456789abcdefABCDEF";
+numout:= 0;
+text:= 0;
+out: ref Bufio->Iobuf;
+stderr: ref sys->FD;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ stderr = sys->fildes(2);
+
+ if(str==nil || bufio==nil){
+ sys->fprint(stderr, "unicode: can't load String or Bufio module: %r\n");
+ return;
+ }
+
+ if(argv == nil){
+ sys->fprint(stderr, "usage: %s\n", usage);
+ return;
+ }
+ argv = tl argv;
+ while(argv != nil) {
+ s := hd argv;
+ if(s != nil && s[0] != '-')
+ break;
+ case s{
+ "-n" =>
+ numout = 1;
+ "-t" =>
+ text = 1;
+ }
+ argv = tl argv;
+ }
+ if(argv == nil){
+ sys->fprint(stderr, "usage: %s\n", usage);
+ return;
+ }
+
+ out = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+
+ if(!numout && oneof(hd argv, '-'))
+ range(argv);
+ else if(numout || oneof(hex, (hd argv)[0]) == 0)
+ nums(argv);
+ else
+ chars(argv);
+ out.flush();
+}
+
+oneof(s: string, c: int): int
+{
+ for(i:=0; i<len s; i++)
+ if(s[i] == c)
+ return 1;
+ return 0;
+}
+
+badrange(q: string)
+{
+ sys->fprint(stderr, "unicode: bad range %s\n", q);
+}
+
+range(argv: list of string)
+{
+ min, max: int;
+
+ while(argv != nil){
+ q := hd argv;
+ if(oneof(hex, q[0]) == 0){
+ badrange(q);
+ return;
+ }
+ (min, q) = str->toint(q,16);
+ if(min<0 || min>16rFFFF || len q==0 || q[0]!='-'){
+ badrange(hd argv);
+ return;
+ }
+ q = q[1:];
+ if(oneof(hex, q[0]) == 0){
+ badrange(hd argv);
+ return;
+ }
+ (max, q) = str->toint(q,16);
+ if(max<0 || max>16rFFFF || max<min || len q>0){
+ badrange(hd argv);
+ return;
+ }
+ i := 0;
+ do{
+ out.puts(sys->sprint("%.4x %c", min, min));
+ i++;
+ if(min==max || (i&7)==0)
+ out.puts("\n");
+ else
+ out.puts("\t");
+ min++;
+ }while(min<=max);
+ argv = tl argv;
+ }
+}
+
+
+nums(argv: list of string)
+{
+ while(argv != nil){
+ q := hd argv;
+ for(i:=0; i<len q; i++)
+ out.puts(sys->sprint("%.4x\n", q[i]));
+ argv = tl argv;
+ }
+}
+
+badvalue(s: string)
+{
+ sys->fprint(stderr, "unicode: bad unicode value %s\n", s);
+}
+
+chars(argv: list of string)
+{
+ m: int;
+
+ while(argv != nil){
+ q := hd argv;
+ if(oneof(hex, q[0]) == 0){
+ badvalue(hd argv);
+ return;
+ }
+ (m, q) = str->toint(q, 16);
+ if(m<0 || m>16rFFFF || len q>0){
+ badvalue(hd argv);
+ return;
+ }
+ out.puts(sys->sprint("%c", m));
+ if(!text)
+ out.puts("\n");
+ argv = tl argv;
+ }
+}
diff --git a/appl/cmd/uniq.b b/appl/cmd/uniq.b
new file mode 100644
index 00000000..4442c22f
--- /dev/null
+++ b/appl/cmd/uniq.b
@@ -0,0 +1,79 @@
+implement Uniq;
+
+include "sys.m";
+ sys: Sys;
+include "bufio.m";
+include "draw.m";
+include "arg.m";
+
+Uniq: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+usage()
+{
+ fail("usage", sys->sprint("usage: uniq [-ud] [file]"));
+}
+
+init(nil : ref Draw->Context, args : list of string)
+{
+ bio : ref Bufio->Iobuf;
+
+ sys = load Sys Sys->PATH;
+ bufio := load Bufio Bufio->PATH;
+ if (bufio == nil)
+ fail("bad module", sys->sprint("uniq: cannot load %s: %r", Bufio->PATH));
+ Iobuf: import bufio;
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ fail("bad module", sys->sprint("uniq: cannot load %s: %r", Arg->PATH));
+
+ uflag := 0;
+ dflag := 0;
+ arg->init(args);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'u' =>
+ uflag = 1;
+ 'd' =>
+ dflag = 1;
+ * =>
+ usage();
+ }
+ }
+ args = arg->argv();
+ if (len args > 1)
+ usage();
+ if (args != nil) {
+ bio = bufio->open(hd args, Bufio->OREAD);
+ if (bio == nil)
+ fail("open file", sys->sprint("uniq: cannot open %s: %r\n", hd args));
+ } else
+ bio = bufio->fopen(sys->fildes(0), Bufio->OREAD);
+
+ stdout := bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ if (!(uflag || dflag))
+ uflag = dflag = 1;
+ prev := "";
+ n := 0;
+ while ((s := bio.gets('\n')) != nil) {
+ if (s == prev)
+ n++;
+ else {
+ if ((uflag && n == 1) || (dflag && n > 1))
+ stdout.puts(prev);
+ n = 1;
+ prev = s;
+ }
+ }
+ if ((uflag && n == 1) || (dflag && n > 1))
+ stdout.puts(prev);
+ stdout.close();
+}
+
+fail(ex, msg: string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", msg);
+ raise "fail:"+ex;
+}
diff --git a/appl/cmd/units.b b/appl/cmd/units.b
new file mode 100644
index 00000000..b1dffc60
--- /dev/null
+++ b/appl/cmd/units.b
@@ -0,0 +1,1061 @@
+implement Units;
+
+#line 2 "units.y"
+#
+# subject to the Lucent Public License 1.02
+#
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "math.m";
+ math: Math;
+
+include "arg.m";
+
+Ndim: con 15; # number of dimensions
+Nvar: con 203; # hash table size
+Maxe: con 695.0; # log of largest number
+
+Node: adt
+{
+ val: real;
+ dim: array of int; # [Ndim] schar
+
+ mk: fn(v: real): Node;
+ text: fn(n: self Node): string;
+ add: fn(a: self Node, b: Node): Node;
+ sub: fn(a: self Node, b: Node): Node;
+ mul: fn(a: self Node, b: Node): Node;
+ div: fn(a: self Node, b: Node): Node;
+ xpn: fn(a: self Node, b: int): Node;
+ copy: fn(a: self Node): Node;
+};
+Var: adt
+{
+ name: string;
+ node: Node;
+};
+Prefix: adt
+{
+ val: real;
+ pname: string;
+};
+
+digval := 0;
+fi: ref Iobuf;
+fund := array[Ndim] of ref Var;
+line: string;
+lineno := 0;
+linep := 0;
+nerrors := 0;
+peekrune := 0;
+retnode1: Node;
+retnode2: Node;
+retnode: Node;
+sym: string;
+vars := array[Nvar] of list of ref Var;
+vflag := 0;
+
+YYSTYPE: adt {
+ node: Node;
+ var: ref Var;
+ numb: int;
+ val: real;
+};
+
+YYLEX: adt {
+ lval: YYSTYPE;
+ lex: fn(l: self ref YYLEX): int;
+ error: fn(l: self ref YYLEX, msg: string);
+};
+
+Units: module {
+
+ init: fn(nil: ref Draw->Context, args: list of string);
+VAL: con 57346;
+VAR: con 57347;
+SUP: con 57348;
+
+};
+YYEOFCODE: con 1;
+YYERRCODE: con 2;
+YYMAXDEPTH: con 200;
+
+#line 203 "units.y"
+
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ math = load Math Math->PATH;
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("units [-v] [file]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'v' => vflag = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ file := "/lib/units";
+ if(args != nil)
+ file = hd args;
+ fi = bufio->open(file, Sys->OREAD);
+ if(fi == nil) {
+ sys->fprint(sys->fildes(2), "units: cannot open %s: %r\n", file);
+ raise "fail:open";
+ }
+ lex := ref YYLEX;
+
+ #
+ # read the 'units' file to
+ # develop a database
+ #
+ lineno = 0;
+ for(;;) {
+ lineno++;
+ if(readline())
+ break;
+ if(len line == 0 || line[0] == '/')
+ continue;
+ peekrune = ':';
+ yyparse(lex);
+ }
+
+ #
+ # read the console to
+ # print ratio of pairs
+ #
+ fi = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ lineno = 0;
+ for(;;) {
+ if(lineno & 1)
+ sys->print("you want: ");
+ else
+ sys->print("you have: ");
+ if(readline())
+ break;
+ peekrune = '?';
+ nerrors = 0;
+ yyparse(lex);
+ if(nerrors)
+ continue;
+ if(lineno & 1) {
+ isspcl: int;
+ (isspcl, retnode) = specialcase(retnode2, retnode1);
+ if(isspcl)
+ sys->print("\tis %s\n", retnode.text());
+ else {
+ retnode = retnode2.div(retnode1);
+ sys->print("\t* %s\n", retnode.text());
+ retnode = retnode1.div(retnode2);
+ sys->print("\t/ %s\n", retnode.text());
+ }
+ } else
+ retnode2 = retnode1.copy();
+ lineno++;
+ }
+ sys->print("\n");
+}
+
+YYLEX.lex(lex: self ref YYLEX): int
+{
+ c := peekrune;
+ peekrune = ' ';
+
+ while(c == ' ' || c == '\t'){
+ if(linep >= len line)
+ return 0; # -1?
+ c = line[linep++];
+ }
+ case c {
+ '0' to '9' or '.' =>
+ digval = c;
+ (lex.lval.val, peekrune) = readreal(gdigit, lex);
+ return VAL;
+ '×' =>
+ return '*';
+ '÷' =>
+ return '/';
+ '¹' or
+ 'ⁱ' =>
+ lex.lval.numb = 1;
+ return SUP;
+ '²' or
+ '⁲' =>
+ lex.lval.numb = 2;
+ return SUP;
+ '³' or
+ '⁳' =>
+ lex.lval.numb = 3;
+ return SUP;
+ * =>
+ if(ralpha(c)){
+ sym = "";
+ for(i:=0;; i++) {
+ sym[i] = c;
+ if(linep >= len line){
+ c = ' ';
+ break;
+ }
+ c = line[linep++];
+ if(!ralpha(c))
+ break;
+ }
+ peekrune = c;
+ lex.lval.var = lookup(0);
+ return VAR;
+ }
+ }
+ return c;
+}
+
+#
+# all characters that have some
+# meaning. rest are usable as names
+#
+ralpha(c: int): int
+{
+ case c {
+ 0 or
+ '+' or
+ '-' or
+ '*' or
+ '/' or
+ '[' or
+ ']' or
+ '(' or
+ ')' or
+ '^' or
+ ':' or
+ '?' or
+ ' ' or
+ '\t' or
+ '.' or
+ '|' or
+ '#' or
+ '¹' or
+ 'ⁱ' or
+ '²' or
+ '⁲' or
+ '³' or
+ '⁳' or
+ '×' or
+ '÷' =>
+ return 0;
+ }
+ return 1;
+}
+
+gdigit(nil: ref YYLEX): int
+{
+ c := digval;
+ if(c) {
+ digval = 0;
+ return c;
+ }
+ if(linep >= len line)
+ return 0;
+ return line[linep++];
+}
+
+YYLEX.error(lex: self ref YYLEX, s: string)
+{
+ #
+ # hack to intercept message from yaccpar
+ #
+ if(s == "syntax error") {
+ lex.error(sys->sprint("syntax error, last name: %s", sym));
+ return;
+ }
+ sys->print("%d: %s\n\t%s\n", lineno, line, s);
+ nerrors++;
+ if(nerrors > 5) {
+ sys->print("too many errors\n");
+ raise "fail:errors";
+ }
+}
+
+yyerror(s: string)
+{
+ l := ref YYLEX;
+ l.error(s);
+}
+
+Node.mk(v: real): Node
+{
+ return (v, array[Ndim] of {* => 0});
+}
+
+Node.add(a: self Node, b: Node): Node
+{
+ c := Node.mk(fadd(a.val, b.val));
+ for(i:=0; i<Ndim; i++) {
+ d := a.dim[i];
+ c.dim[i] = d;
+ if(d != b.dim[i])
+ yyerror("add must be like units");
+ }
+ return c;
+}
+
+Node.sub(a: self Node, b: Node): Node
+{
+ c := Node.mk(fadd(a.val, -b.val));
+ for(i:=0; i<Ndim; i++) {
+ d := a.dim[i];
+ c.dim[i] = d;
+ if(d != b.dim[i])
+ yyerror("sub must be like units");
+ }
+ return c;
+}
+
+Node.mul(a: self Node, b: Node): Node
+{
+ c := Node.mk(fmul(a.val, b.val));
+ for(i:=0; i<Ndim; i++)
+ c.dim[i] = a.dim[i] + b.dim[i];
+ return c;
+}
+
+Node.div(a: self Node, b: Node): Node
+{
+ c := Node.mk(fdiv(a.val, b.val));
+ for(i:=0; i<Ndim; i++)
+ c.dim[i] = a.dim[i] - b.dim[i];
+ return c;
+}
+
+Node.xpn(a: self Node, b: int): Node
+{
+ c := Node.mk(1.0);
+ if(b < 0) {
+ b = -b;
+ for(i:=0; i<b; i++)
+ c = c.div(a);
+ } else
+ for(i:=0; i<b; i++)
+ c = c.mul(a);
+ return c;
+}
+
+Node.copy(a: self Node): Node
+{
+ c := Node.mk(a.val);
+ c.dim[0:] = a.dim;
+ return c;
+}
+
+specialcase(a, b: Node): (int, Node)
+{
+ c := Node.mk(0.0);
+ d1 := 0;
+ d2 := 0;
+ for(i:=1; i<Ndim; i++) {
+ d := a.dim[i];
+ if(d) {
+ if(d != 1 || d1)
+ return (0, c);
+ d1 = i;
+ }
+ d = b.dim[i];
+ if(d) {
+ if(d != 1 || d2)
+ return (0, c);
+ d2 = i;
+ }
+ }
+ if(d1 == 0 || d2 == 0)
+ return (0, c);
+
+ if(fund[d1].name == "°C" &&
+ fund[d2].name == "°F" &&
+ b.val == 1.0) {
+ c = b.copy();
+ c.val = a.val * 9. / 5. + 32.;
+ return (1, c);
+ }
+
+ if(fund[d1].name == "°F" &&
+ fund[d2].name == "°C" &&
+ b.val == 1.0) {
+ c = b.copy();
+ c.val = (a.val - 32.) * 5. / 9.;
+ return (1, c);
+ }
+ return (0, c);
+}
+
+printdim(d: int, n: int): string
+{
+ s := "";
+ if(n) {
+ v := fund[d];
+ if(v != nil)
+ s += " "+v.name;
+ else
+ s += sys->sprint(" [%d]", d);
+ case n {
+ 1 =>
+ ;
+ 2 =>
+ s += "²";
+ 3 =>
+ s += "³";
+ 4 =>
+ s += "⁴";
+ * =>
+ s += sys->sprint("^%d", n);
+ }
+ }
+ return s;
+}
+
+Node.text(n: self Node): string
+{
+ str := sys->sprint("%.7g", n.val);
+ f := 0;
+ for(i:=1; i<len n.dim; i++) {
+ d := n.dim[i];
+ if(d > 0)
+ str += printdim(i, d);
+ else if(d < 0)
+ f = 1;
+ }
+
+ if(f) {
+ str += " /";
+ for(i=1; i<len n.dim; i++) {
+ d := n.dim[i];
+ if(d < 0)
+ str += printdim(i, -d);
+ }
+ }
+
+ return str;
+}
+
+readline(): int
+{
+ linep = 0;
+ line = "";
+ for(i:=0;; i++) {
+ c := fi.getc();
+ if(c < 0)
+ return 1;
+ if(c == '\n')
+ return 0;
+ line[i] = c;
+ }
+}
+
+lookup(f: int): ref Var
+{
+ h := 0;
+ for(i:=0; i < len sym; i++)
+ h = h*13 + sym[i];
+ if(h < 0)
+ h ^= int 16r80000000;
+ h %= len vars;
+
+ for(vl:=vars[h]; vl != nil; vl = tl vl)
+ if((hd vl).name == sym)
+ return hd vl;
+ if(f)
+ return nil;
+ v := ref Var(sym, Node.mk(0.0));
+ vars[h] = v :: vars[h];
+
+ p := 1.0;
+ for(;;) {
+ p = fmul(p, pname());
+ if(p == 0.0)
+ break;
+ w := lookup(1);
+ if(w != nil) {
+ v.node = w.node.copy();
+ v.node.val = fmul(v.node.val, p);
+ break;
+ }
+ }
+ return v;
+}
+
+prefix: array of Prefix = array[] of {
+ (1e-24, "yocto"),
+ (1e-21, "zepto"),
+ (1e-18, "atto"),
+ (1e-15, "femto"),
+ (1e-12, "pico"),
+ (1e-9, "nano"),
+ (1e-6, "micro"),
+ (1e-6, "μ"),
+ (1e-3, "milli"),
+ (1e-2, "centi"),
+ (1e-1, "deci"),
+ (1e1, "deka"),
+ (1e2, "hecta"),
+ (1e2, "hecto"),
+ (1e3, "kilo"),
+ (1e6, "mega"),
+ (1e6, "meg"),
+ (1e9, "giga"),
+ (1e12, "tera"),
+ (1e15, "peta"),
+ (1e18, "exa"),
+ (1e21, "zetta"),
+ (1e24, "yotta")
+};
+
+pname(): real
+{
+ #
+ # rip off normal prefices
+ #
+Pref:
+ for(i:=0; i < len prefix; i++) {
+ p := prefix[i].pname;
+ for(j:=0; j < len p; j++)
+ if(j >= len sym || p[j] != sym[j])
+ continue Pref;
+ sym = sym[j:];
+ return prefix[i].val;
+ }
+
+ #
+ # rip off 's' suffixes
+ #
+ for(j:=0; j < len sym; j++)
+ ;
+ j--;
+ # j>1 is special hack to disallow ms finding m
+ if(j > 1 && sym[j] == 's') {
+ sym = sym[0:j];
+ return 1.0;
+ }
+ return 0.0;
+}
+
+#
+# reads a floating-point number
+#
+
+readreal[T](f: ref fn(t: T): int, vp: T): (real, int)
+{
+ s := "";
+ c := f(vp);
+ while(c == ' ' || c == '\t')
+ c = f(vp);
+ if(c == '-' || c == '+'){
+ s[len s] = c;
+ c = f(vp);
+ }
+ start := len s;
+ while(c >= '0' && c <= '9'){
+ s[len s] = c;
+ c = f(vp);
+ }
+ if(c == '.'){
+ s[len s] = c;
+ c = f(vp);
+ while(c >= '0' && c <= '9'){
+ s[len s] = c;
+ c = f(vp);
+ }
+ }
+ if(len s > start && (c == 'e' || c == 'E')){
+ s[len s] = c;
+ c = f(vp);
+ if(c == '-' || c == '+'){
+ s[len s] = c;
+ c = f(vp);
+ }
+ while(c >= '0' && c <= '9'){
+ s[len s] = c;
+ c = f(vp);
+ }
+ }
+ return (real s, c);
+}
+
+#
+# careful floating point
+#
+
+fmul(a, b: real): real
+{
+ l: real;
+
+ if(a <= 0.0) {
+ if(a == 0.0)
+ return 0.0;
+ l = math->log(-a);
+ } else
+ l = math->log(a);
+
+ if(b <= 0.0) {
+ if(b == 0.0)
+ return 0.0;
+ l += math->log(-b);
+ } else
+ l += math->log(b);
+
+ if(l > Maxe) {
+ yyerror("overflow in multiply");
+ return 1.0;
+ }
+ if(l < -Maxe) {
+ yyerror("underflow in multiply");
+ return 0.0;
+ }
+ return a*b;
+}
+
+fdiv(a, b: real): real
+{
+ l: real;
+
+ if(a <= 0.0) {
+ if(a == 0.0)
+ return 0.0;
+ l = math->log(-a);
+ } else
+ l = math->log(a);
+
+ if(b <= 0.0) {
+ if(b == 0.0) {
+ yyerror("division by zero");
+ return 1.0;
+ }
+ l -= math->log(-b);
+ } else
+ l -= math->log(b);
+
+ if(l > Maxe) {
+ yyerror("overflow in divide");
+ return 1.0;
+ }
+ if(l < -Maxe) {
+ yyerror("underflow in divide");
+ return 0.0;
+ }
+ return a/b;
+}
+
+fadd(a, b: real): real
+{
+ return a + b;
+}
+yyexca := array[] of {-1, 1,
+ 1, -1,
+ -2, 0,
+};
+YYNPROD: con 21;
+YYPRIVATE: con 57344;
+yytoknames: array of string;
+yystates: array of string;
+yydebug: con 0;
+YYLAST: con 41;
+yyact := array[] of {
+ 8, 10, 7, 9, 16, 17, 12, 11, 20, 21,
+ 15, 31, 23, 6, 4, 12, 11, 22, 13, 5,
+ 1, 27, 28, 0, 14, 30, 29, 13, 20, 20,
+ 25, 26, 0, 24, 18, 19, 16, 17, 2, 0,
+ 3,
+};
+yypact := array[] of {
+ 31,-1000, 9, 11, 2, 26, 22, 11, 3, -3,
+-1000,-1000,-1000, 11, 26,-1000, 11, 11, 11, 11,
+ 3,-1000, 11, 11, -6, 22, 22, 11, 11, -3,
+-1000,-1000,
+};
+yypgo := array[] of {
+ 0, 20, 19, 1, 3, 0, 2, 13,
+};
+yyr1 := array[] of {
+ 0, 1, 1, 1, 1, 2, 2, 2, 7, 7,
+ 7, 6, 6, 5, 5, 5, 4, 4, 3, 3,
+ 3,
+};
+yyr2 := array[] of {
+ 0, 3, 3, 2, 1, 1, 3, 3, 1, 3,
+ 3, 1, 2, 1, 2, 3, 1, 3, 1, 1,
+ 3,
+};
+yychk := array[] of {
+-1000, -1, 7, 9, 5, -2, -7, -6, -5, -4,
+ -3, 5, 4, 16, -2, 8, 10, 11, 12, 13,
+ -5, 6, 14, 15, -2, -7, -7, -6, -6, -4,
+ -3, 17,
+};
+yydef := array[] of {
+ 0, -2, 0, 4, 0, 3, 5, 8, 11, 13,
+ 16, 18, 19, 0, 1, 2, 0, 0, 0, 0,
+ 12, 14, 0, 0, 0, 6, 7, 9, 10, 15,
+ 17, 20,
+};
+yytok1 := array[] of {
+ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 8, 3, 3, 3, 3,
+ 16, 17, 12, 10, 3, 11, 3, 13, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 7, 3,
+ 3, 3, 3, 9, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 14, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 15,
+};
+yytok2 := array[] of {
+ 2, 3, 4, 5, 6,
+};
+yytok3 := array[] of {
+ 0
+};
+
+YYSys: module
+{
+ FD: adt
+ {
+ fd: int;
+ };
+ fildes: fn(fd: int): ref FD;
+ fprint: fn(fd: ref FD, s: string, *): int;
+};
+
+yysys: YYSys;
+yystderr: ref YYSys->FD;
+
+YYFLAG: con -1000;
+
+# parser for yacc output
+
+yytokname(yyc: int): string
+{
+ if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil)
+ return yytoknames[yyc-1];
+ return "<"+string yyc+">";
+}
+
+yystatname(yys: int): string
+{
+ if(yys >= 0 && yys < len yystates && yystates[yys] != nil)
+ return yystates[yys];
+ return "<"+string yys+">\n";
+}
+
+yylex1(yylex: ref YYLEX): int
+{
+ c : int;
+ yychar := yylex.lex();
+ if(yychar <= 0)
+ c = yytok1[0];
+ else if(yychar < len yytok1)
+ c = yytok1[yychar];
+ else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2)
+ c = yytok2[yychar-YYPRIVATE];
+ else{
+ n := len yytok3;
+ c = 0;
+ for(i := 0; i < n; i+=2) {
+ if(yytok3[i+0] == yychar) {
+ c = yytok3[i+1];
+ break;
+ }
+ }
+ if(c == 0)
+ c = yytok2[1]; # unknown char
+ }
+ if(yydebug >= 3)
+ yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c));
+ return c;
+}
+
+YYS: adt
+{
+ yyv: YYSTYPE;
+ yys: int;
+};
+
+yyparse(yylex: ref YYLEX): int
+{
+ if(yydebug >= 1 && yysys == nil) {
+ yysys = load YYSys "$Sys";
+ yystderr = yysys->fildes(2);
+ }
+
+ yys := array[YYMAXDEPTH] of YYS;
+
+ yyval: YYSTYPE;
+ yystate := 0;
+ yychar := -1;
+ yynerrs := 0; # number of errors
+ yyerrflag := 0; # error recovery flag
+ yyp := -1;
+ yyn := 0;
+
+yystack:
+ for(;;){
+ # put a state and value onto the stack
+ if(yydebug >= 4)
+ yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+ yyp++;
+ if(yyp >= len yys)
+ yys = (array[len yys * 2] of YYS)[0:] = yys;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = yyval;
+
+ for(;;){
+ yyn = yypact[yystate];
+ if(yyn > YYFLAG) { # simple state
+ if(yychar < 0)
+ yychar = yylex1(yylex);
+ yyn += yychar;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yyn = yyact[yyn];
+ if(yychk[yyn] == yychar) { # valid shift
+ yychar = -1;
+ yyp++;
+ if(yyp >= len yys)
+ yys = (array[len yys * 2] of YYS)[0:] = yys;
+ yystate = yyn;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = yylex.lval;
+ if(yyerrflag > 0)
+ yyerrflag--;
+ if(yydebug >= 4)
+ yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+ continue;
+ }
+ }
+ }
+
+ # default state action
+ yyn = yydef[yystate];
+ if(yyn == -2) {
+ if(yychar < 0)
+ yychar = yylex1(yylex);
+
+ # look through exception table
+ for(yyxi:=0;; yyxi+=2)
+ if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate)
+ break;
+ for(yyxi += 2;; yyxi += 2) {
+ yyn = yyexca[yyxi];
+ if(yyn < 0 || yyn == yychar)
+ break;
+ }
+ yyn = yyexca[yyxi+1];
+ if(yyn < 0){
+ yyn = 0;
+ break yystack;
+ }
+ }
+
+ if(yyn != 0)
+ break;
+
+ # error ... attempt to resume parsing
+ if(yyerrflag == 0) { # brand new error
+ yylex.error("syntax error");
+ yynerrs++;
+ if(yydebug >= 1) {
+ yysys->fprint(yystderr, "%s", yystatname(yystate));
+ yysys->fprint(yystderr, "saw %s\n", yytokname(yychar));
+ }
+ }
+
+ if(yyerrflag != 3) { # incompletely recovered error ... try again
+ yyerrflag = 3;
+
+ # find a state where "error" is a legal shift action
+ while(yyp >= 0) {
+ yyn = yypact[yys[yyp].yys] + YYERRCODE;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yystate = yyact[yyn]; # simulate a shift of "error"
+ if(yychk[yystate] == YYERRCODE)
+ continue yystack;
+ }
+
+ # the current yyp has no shift onn "error", pop stack
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n",
+ yys[yyp].yys, yys[yyp-1].yys );
+ yyp--;
+ }
+ # there is no state on the stack with an error shift ... abort
+ yyn = 1;
+ break yystack;
+ }
+
+ # no shift yet; clobber input char
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar));
+ if(yychar == YYEOFCODE) {
+ yyn = 1;
+ break yystack;
+ }
+ yychar = -1;
+ # try again in the same state
+ }
+
+ # reduction by production yyn
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+ yypt := yyp;
+ yyp -= yyr2[yyn];
+# yyval = yys[yyp+1].yyv;
+ yym := yyn;
+
+ # consult goto table to find next state
+ yyn = yyr1[yyn];
+ yyg := yypgo[yyn];
+ yyj := yyg + yys[yyp].yys + 1;
+
+ if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+ yystate = yyact[yyg];
+ case yym {
+
+1=>
+#line 90 "units.y"
+{
+ f := yys[yypt-1].yyv.var.node.dim[0];
+ yys[yypt-1].yyv.var.node = yys[yypt-0].yyv.node.copy();
+ yys[yypt-1].yyv.var.node.dim[0] = 1;
+ if(f)
+ yyerror(sys->sprint("redefinition of %s", yys[yypt-1].yyv.var.name));
+ else if(vflag)
+ sys->print("%s\t%s\n", yys[yypt-1].yyv.var.name, yys[yypt-1].yyv.var.node.text());
+ }
+2=>
+#line 100 "units.y"
+{
+ for(i:=1; i<Ndim; i++)
+ if(fund[i] == nil)
+ break;
+ if(i >= Ndim) {
+ yyerror("too many dimensions");
+ i = Ndim-1;
+ }
+ fund[i] = yys[yypt-1].yyv.var;
+
+ f := yys[yypt-1].yyv.var.node.dim[0];
+ yys[yypt-1].yyv.var.node = Node.mk(1.0);
+ yys[yypt-1].yyv.var.node.dim[0] = 1;
+ yys[yypt-1].yyv.var.node.dim[i] = 1;
+ if(f)
+ yyerror(sys->sprint("redefinition of %s", yys[yypt-1].yyv.var.name));
+ else if(vflag)
+ sys->print("%s\t#\n", yys[yypt-1].yyv.var.name);
+ }
+3=>
+#line 120 "units.y"
+{
+ retnode1 = yys[yypt-0].yyv.node.copy();
+ }
+4=>
+#line 124 "units.y"
+{
+ retnode1 = Node.mk(1.0);
+ }
+5=>
+yyval.node = yys[yyp+1].yyv.node;
+6=>
+#line 131 "units.y"
+{
+ yyval.node = yys[yypt-2].yyv.node.add(yys[yypt-0].yyv.node);
+ }
+7=>
+#line 135 "units.y"
+{
+ yyval.node = yys[yypt-2].yyv.node.sub(yys[yypt-0].yyv.node);
+ }
+8=>
+yyval.node = yys[yyp+1].yyv.node;
+9=>
+#line 142 "units.y"
+{
+ yyval.node = yys[yypt-2].yyv.node.mul(yys[yypt-0].yyv.node);
+ }
+10=>
+#line 146 "units.y"
+{
+ yyval.node = yys[yypt-2].yyv.node.div(yys[yypt-0].yyv.node);
+ }
+11=>
+yyval.node = yys[yyp+1].yyv.node;
+12=>
+#line 153 "units.y"
+{
+ yyval.node = yys[yypt-1].yyv.node.mul(yys[yypt-0].yyv.node);
+ }
+13=>
+yyval.node = yys[yyp+1].yyv.node;
+14=>
+#line 160 "units.y"
+{
+ yyval.node = yys[yypt-1].yyv.node.xpn(yys[yypt-0].yyv.numb);
+ }
+15=>
+#line 164 "units.y"
+{
+ for(i:=1; i<Ndim; i++)
+ if(yys[yypt-0].yyv.node.dim[i]) {
+ yyerror("exponent has units");
+ yyval.node = yys[yypt-2].yyv.node;
+ break;
+ }
+ if(i >= Ndim) {
+ i = int yys[yypt-0].yyv.node.val;
+ if(real i != yys[yypt-0].yyv.node.val)
+ yyerror("exponent not integral");
+ yyval.node = yys[yypt-2].yyv.node.xpn(i);
+ }
+ }
+16=>
+yyval.node = yys[yyp+1].yyv.node;
+17=>
+#line 182 "units.y"
+{
+ yyval.node = yys[yypt-2].yyv.node.div(yys[yypt-0].yyv.node);
+ }
+18=>
+#line 188 "units.y"
+{
+ if(yys[yypt-0].yyv.var.node.dim[0] == 0) {
+ yyerror(sys->sprint("undefined %s", yys[yypt-0].yyv.var.name));
+ yyval.node = Node.mk(1.0);
+ } else
+ yyval.node = yys[yypt-0].yyv.var.node.copy();
+ }
+19=>
+#line 196 "units.y"
+{
+ yyval.node = Node.mk(yys[yypt-0].yyv.val);
+ }
+20=>
+#line 200 "units.y"
+{
+ yyval.node = yys[yypt-1].yyv.node;
+ }
+ }
+ }
+
+ return yyn;
+}
diff --git a/appl/cmd/units.y b/appl/cmd/units.y
new file mode 100644
index 00000000..70284868
--- /dev/null
+++ b/appl/cmd/units.y
@@ -0,0 +1,771 @@
+%{
+#
+# subject to the Lucent Public License 1.02
+#
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "math.m";
+ math: Math;
+
+include "arg.m";
+
+Ndim: con 15; # number of dimensions
+Nvar: con 203; # hash table size
+Maxe: con 695.0; # log of largest number
+
+Node: adt
+{
+ val: real;
+ dim: array of int; # [Ndim] schar
+
+ mk: fn(v: real): Node;
+ text: fn(n: self Node): string;
+ add: fn(a: self Node, b: Node): Node;
+ sub: fn(a: self Node, b: Node): Node;
+ mul: fn(a: self Node, b: Node): Node;
+ div: fn(a: self Node, b: Node): Node;
+ xpn: fn(a: self Node, b: int): Node;
+ copy: fn(a: self Node): Node;
+};
+Var: adt
+{
+ name: string;
+ node: Node;
+};
+Prefix: adt
+{
+ val: real;
+ pname: string;
+};
+
+digval := 0;
+fi: ref Iobuf;
+fund := array[Ndim] of ref Var;
+line: string;
+lineno := 0;
+linep := 0;
+nerrors := 0;
+peekrune := 0;
+retnode1: Node;
+retnode2: Node;
+retnode: Node;
+sym: string;
+vars := array[Nvar] of list of ref Var;
+vflag := 0;
+
+YYSTYPE: adt {
+ node: Node;
+ var: ref Var;
+ numb: int;
+ val: real;
+};
+
+YYLEX: adt {
+ lval: YYSTYPE;
+ lex: fn(l: self ref YYLEX): int;
+ error: fn(l: self ref YYLEX, msg: string);
+};
+
+%}
+%module Units
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+}
+
+%type <node> prog expr expr0 expr1 expr2 expr3 expr4
+
+%token <val> VAL
+%token <var> VAR
+%token <numb> SUP
+%%
+prog:
+ ':' VAR expr
+ {
+ f := $2.node.dim[0];
+ $2.node = $3.copy();
+ $2.node.dim[0] = 1;
+ if(f)
+ yyerror(sys->sprint("redefinition of %s", $2.name));
+ else if(vflag)
+ sys->print("%s\t%s\n", $2.name, $2.node.text());
+ }
+| ':' VAR '#'
+ {
+ for(i:=1; i<Ndim; i++)
+ if(fund[i] == nil)
+ break;
+ if(i >= Ndim) {
+ yyerror("too many dimensions");
+ i = Ndim-1;
+ }
+ fund[i] = $2;
+
+ f := $2.node.dim[0];
+ $2.node = Node.mk(1.0);
+ $2.node.dim[0] = 1;
+ $2.node.dim[i] = 1;
+ if(f)
+ yyerror(sys->sprint("redefinition of %s", $2.name));
+ else if(vflag)
+ sys->print("%s\t#\n", $2.name);
+ }
+| '?' expr
+ {
+ retnode1 = $2.copy();
+ }
+| '?'
+ {
+ retnode1 = Node.mk(1.0);
+ }
+
+expr:
+ expr4
+| expr '+' expr4
+ {
+ $$ = $1.add($3);
+ }
+| expr '-' expr4
+ {
+ $$ = $1.sub($3);
+ }
+
+expr4:
+ expr3
+| expr4 '*' expr3
+ {
+ $$ = $1.mul($3);
+ }
+| expr4 '/' expr3
+ {
+ $$ = $1.div($3);
+ }
+
+expr3:
+ expr2
+| expr3 expr2
+ {
+ $$ = $1.mul($2);
+ }
+
+expr2:
+ expr1
+| expr2 SUP
+ {
+ $$ = $1.xpn($2);
+ }
+| expr2 '^' expr1
+ {
+ for(i:=1; i<Ndim; i++)
+ if($3.dim[i]) {
+ yyerror("exponent has units");
+ $$ = $1;
+ break;
+ }
+ if(i >= Ndim) {
+ i = int $3.val;
+ if(real i != $3.val)
+ yyerror("exponent not integral");
+ $$ = $1.xpn(i);
+ }
+ }
+
+expr1:
+ expr0
+| expr1 '|' expr0
+ {
+ $$ = $1.div($3);
+ }
+
+expr0:
+ VAR
+ {
+ if($1.node.dim[0] == 0) {
+ yyerror(sys->sprint("undefined %s", $1.name));
+ $$ = Node.mk(1.0);
+ } else
+ $$ = $1.node.copy();
+ }
+| VAL
+ {
+ $$ = Node.mk($1);
+ }
+| '(' expr ')'
+ {
+ $$ = $2;
+ }
+%%
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ math = load Math Math->PATH;
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("units [-v] [file]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'v' => vflag = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ file := "/lib/units";
+ if(args != nil)
+ file = hd args;
+ fi = bufio->open(file, Sys->OREAD);
+ if(fi == nil) {
+ sys->fprint(sys->fildes(2), "units: cannot open %s: %r\n", file);
+ raise "fail:open";
+ }
+ lex := ref YYLEX;
+
+ #
+ # read the 'units' file to
+ # develop a database
+ #
+ lineno = 0;
+ for(;;) {
+ lineno++;
+ if(readline())
+ break;
+ if(len line == 0 || line[0] == '/')
+ continue;
+ peekrune = ':';
+ yyparse(lex);
+ }
+
+ #
+ # read the console to
+ # print ratio of pairs
+ #
+ fi = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ lineno = 0;
+ for(;;) {
+ if(lineno & 1)
+ sys->print("you want: ");
+ else
+ sys->print("you have: ");
+ if(readline())
+ break;
+ peekrune = '?';
+ nerrors = 0;
+ yyparse(lex);
+ if(nerrors)
+ continue;
+ if(lineno & 1) {
+ isspcl: int;
+ (isspcl, retnode) = specialcase(retnode2, retnode1);
+ if(isspcl)
+ sys->print("\tis %s\n", retnode.text());
+ else {
+ retnode = retnode2.div(retnode1);
+ sys->print("\t* %s\n", retnode.text());
+ retnode = retnode1.div(retnode2);
+ sys->print("\t/ %s\n", retnode.text());
+ }
+ } else
+ retnode2 = retnode1.copy();
+ lineno++;
+ }
+ sys->print("\n");
+}
+
+YYLEX.lex(lex: self ref YYLEX): int
+{
+ c := peekrune;
+ peekrune = ' ';
+
+ while(c == ' ' || c == '\t'){
+ if(linep >= len line)
+ return 0; # -1?
+ c = line[linep++];
+ }
+ case c {
+ '0' to '9' or '.' =>
+ digval = c;
+ (lex.lval.val, peekrune) = readreal(gdigit, lex);
+ return VAL;
+ '×' =>
+ return '*';
+ '÷' =>
+ return '/';
+ '¹' or
+ 'ⁱ' =>
+ lex.lval.numb = 1;
+ return SUP;
+ '²' or
+ '⁲' =>
+ lex.lval.numb = 2;
+ return SUP;
+ '³' or
+ '⁳' =>
+ lex.lval.numb = 3;
+ return SUP;
+ * =>
+ if(ralpha(c)){
+ sym = "";
+ for(i:=0;; i++) {
+ sym[i] = c;
+ if(linep >= len line){
+ c = ' ';
+ break;
+ }
+ c = line[linep++];
+ if(!ralpha(c))
+ break;
+ }
+ peekrune = c;
+ lex.lval.var = lookup(0);
+ return VAR;
+ }
+ }
+ return c;
+}
+
+#
+# all characters that have some
+# meaning. rest are usable as names
+#
+ralpha(c: int): int
+{
+ case c {
+ 0 or
+ '+' or
+ '-' or
+ '*' or
+ '/' or
+ '[' or
+ ']' or
+ '(' or
+ ')' or
+ '^' or
+ ':' or
+ '?' or
+ ' ' or
+ '\t' or
+ '.' or
+ '|' or
+ '#' or
+ '¹' or
+ 'ⁱ' or
+ '²' or
+ '⁲' or
+ '³' or
+ '⁳' or
+ '×' or
+ '÷' =>
+ return 0;
+ }
+ return 1;
+}
+
+gdigit(nil: ref YYLEX): int
+{
+ c := digval;
+ if(c) {
+ digval = 0;
+ return c;
+ }
+ if(linep >= len line)
+ return 0;
+ return line[linep++];
+}
+
+YYLEX.error(lex: self ref YYLEX, s: string)
+{
+ #
+ # hack to intercept message from yaccpar
+ #
+ if(s == "syntax error") {
+ lex.error(sys->sprint("syntax error, last name: %s", sym));
+ return;
+ }
+ sys->print("%d: %s\n\t%s\n", lineno, line, s);
+ nerrors++;
+ if(nerrors > 5) {
+ sys->print("too many errors\n");
+ raise "fail:errors";
+ }
+}
+
+yyerror(s: string)
+{
+ l := ref YYLEX;
+ l.error(s);
+}
+
+Node.mk(v: real): Node
+{
+ return (v, array[Ndim] of {* => 0});
+}
+
+Node.add(a: self Node, b: Node): Node
+{
+ c := Node.mk(fadd(a.val, b.val));
+ for(i:=0; i<Ndim; i++) {
+ d := a.dim[i];
+ c.dim[i] = d;
+ if(d != b.dim[i])
+ yyerror("add must be like units");
+ }
+ return c;
+}
+
+Node.sub(a: self Node, b: Node): Node
+{
+ c := Node.mk(fadd(a.val, -b.val));
+ for(i:=0; i<Ndim; i++) {
+ d := a.dim[i];
+ c.dim[i] = d;
+ if(d != b.dim[i])
+ yyerror("sub must be like units");
+ }
+ return c;
+}
+
+Node.mul(a: self Node, b: Node): Node
+{
+ c := Node.mk(fmul(a.val, b.val));
+ for(i:=0; i<Ndim; i++)
+ c.dim[i] = a.dim[i] + b.dim[i];
+ return c;
+}
+
+Node.div(a: self Node, b: Node): Node
+{
+ c := Node.mk(fdiv(a.val, b.val));
+ for(i:=0; i<Ndim; i++)
+ c.dim[i] = a.dim[i] - b.dim[i];
+ return c;
+}
+
+Node.xpn(a: self Node, b: int): Node
+{
+ c := Node.mk(1.0);
+ if(b < 0) {
+ b = -b;
+ for(i:=0; i<b; i++)
+ c = c.div(a);
+ } else
+ for(i:=0; i<b; i++)
+ c = c.mul(a);
+ return c;
+}
+
+Node.copy(a: self Node): Node
+{
+ c := Node.mk(a.val);
+ c.dim[0:] = a.dim;
+ return c;
+}
+
+specialcase(a, b: Node): (int, Node)
+{
+ c := Node.mk(0.0);
+ d1 := 0;
+ d2 := 0;
+ for(i:=1; i<Ndim; i++) {
+ d := a.dim[i];
+ if(d) {
+ if(d != 1 || d1)
+ return (0, c);
+ d1 = i;
+ }
+ d = b.dim[i];
+ if(d) {
+ if(d != 1 || d2)
+ return (0, c);
+ d2 = i;
+ }
+ }
+ if(d1 == 0 || d2 == 0)
+ return (0, c);
+
+ if(fund[d1].name == "°C" &&
+ fund[d2].name == "°F" &&
+ b.val == 1.0) {
+ c = b.copy();
+ c.val = a.val * 9. / 5. + 32.;
+ return (1, c);
+ }
+
+ if(fund[d1].name == "°F" &&
+ fund[d2].name == "°C" &&
+ b.val == 1.0) {
+ c = b.copy();
+ c.val = (a.val - 32.) * 5. / 9.;
+ return (1, c);
+ }
+ return (0, c);
+}
+
+printdim(d: int, n: int): string
+{
+ s := "";
+ if(n) {
+ v := fund[d];
+ if(v != nil)
+ s += " "+v.name;
+ else
+ s += sys->sprint(" [%d]", d);
+ case n {
+ 1 =>
+ ;
+ 2 =>
+ s += "²";
+ 3 =>
+ s += "³";
+ 4 =>
+ s += "⁴";
+ * =>
+ s += sys->sprint("^%d", n);
+ }
+ }
+ return s;
+}
+
+Node.text(n: self Node): string
+{
+ str := sys->sprint("%.7g", n.val);
+ f := 0;
+ for(i:=1; i<len n.dim; i++) {
+ d := n.dim[i];
+ if(d > 0)
+ str += printdim(i, d);
+ else if(d < 0)
+ f = 1;
+ }
+
+ if(f) {
+ str += " /";
+ for(i=1; i<len n.dim; i++) {
+ d := n.dim[i];
+ if(d < 0)
+ str += printdim(i, -d);
+ }
+ }
+
+ return str;
+}
+
+readline(): int
+{
+ linep = 0;
+ line = "";
+ for(i:=0;; i++) {
+ c := fi.getc();
+ if(c < 0)
+ return 1;
+ if(c == '\n')
+ return 0;
+ line[i] = c;
+ }
+}
+
+lookup(f: int): ref Var
+{
+ h := 0;
+ for(i:=0; i < len sym; i++)
+ h = h*13 + sym[i];
+ if(h < 0)
+ h ^= int 16r80000000;
+ h %= len vars;
+
+ for(vl:=vars[h]; vl != nil; vl = tl vl)
+ if((hd vl).name == sym)
+ return hd vl;
+ if(f)
+ return nil;
+ v := ref Var(sym, Node.mk(0.0));
+ vars[h] = v :: vars[h];
+
+ p := 1.0;
+ for(;;) {
+ p = fmul(p, pname());
+ if(p == 0.0)
+ break;
+ w := lookup(1);
+ if(w != nil) {
+ v.node = w.node.copy();
+ v.node.val = fmul(v.node.val, p);
+ break;
+ }
+ }
+ return v;
+}
+
+prefix: array of Prefix = array[] of {
+ (1e-24, "yocto"),
+ (1e-21, "zepto"),
+ (1e-18, "atto"),
+ (1e-15, "femto"),
+ (1e-12, "pico"),
+ (1e-9, "nano"),
+ (1e-6, "micro"),
+ (1e-6, "μ"),
+ (1e-3, "milli"),
+ (1e-2, "centi"),
+ (1e-1, "deci"),
+ (1e1, "deka"),
+ (1e2, "hecta"),
+ (1e2, "hecto"),
+ (1e3, "kilo"),
+ (1e6, "mega"),
+ (1e6, "meg"),
+ (1e9, "giga"),
+ (1e12, "tera"),
+ (1e15, "peta"),
+ (1e18, "exa"),
+ (1e21, "zetta"),
+ (1e24, "yotta")
+};
+
+pname(): real
+{
+ #
+ # rip off normal prefices
+ #
+Pref:
+ for(i:=0; i < len prefix; i++) {
+ p := prefix[i].pname;
+ for(j:=0; j < len p; j++)
+ if(j >= len sym || p[j] != sym[j])
+ continue Pref;
+ sym = sym[j:];
+ return prefix[i].val;
+ }
+
+ #
+ # rip off 's' suffixes
+ #
+ for(j:=0; j < len sym; j++)
+ ;
+ j--;
+ # j>1 is special hack to disallow ms finding m
+ if(j > 1 && sym[j] == 's') {
+ sym = sym[0:j];
+ return 1.0;
+ }
+ return 0.0;
+}
+
+#
+# reads a floating-point number
+#
+
+readreal[T](f: ref fn(t: T): int, vp: T): (real, int)
+{
+ s := "";
+ c := f(vp);
+ while(c == ' ' || c == '\t')
+ c = f(vp);
+ if(c == '-' || c == '+'){
+ s[len s] = c;
+ c = f(vp);
+ }
+ start := len s;
+ while(c >= '0' && c <= '9'){
+ s[len s] = c;
+ c = f(vp);
+ }
+ if(c == '.'){
+ s[len s] = c;
+ c = f(vp);
+ while(c >= '0' && c <= '9'){
+ s[len s] = c;
+ c = f(vp);
+ }
+ }
+ if(len s > start && (c == 'e' || c == 'E')){
+ s[len s] = c;
+ c = f(vp);
+ if(c == '-' || c == '+'){
+ s[len s] = c;
+ c = f(vp);
+ }
+ while(c >= '0' && c <= '9'){
+ s[len s] = c;
+ c = f(vp);
+ }
+ }
+ return (real s, c);
+}
+
+#
+# careful floating point
+#
+
+fmul(a, b: real): real
+{
+ l: real;
+
+ if(a <= 0.0) {
+ if(a == 0.0)
+ return 0.0;
+ l = math->log(-a);
+ } else
+ l = math->log(a);
+
+ if(b <= 0.0) {
+ if(b == 0.0)
+ return 0.0;
+ l += math->log(-b);
+ } else
+ l += math->log(b);
+
+ if(l > Maxe) {
+ yyerror("overflow in multiply");
+ return 1.0;
+ }
+ if(l < -Maxe) {
+ yyerror("underflow in multiply");
+ return 0.0;
+ }
+ return a*b;
+}
+
+fdiv(a, b: real): real
+{
+ l: real;
+
+ if(a <= 0.0) {
+ if(a == 0.0)
+ return 0.0;
+ l = math->log(-a);
+ } else
+ l = math->log(a);
+
+ if(b <= 0.0) {
+ if(b == 0.0) {
+ yyerror("division by zero");
+ return 1.0;
+ }
+ l -= math->log(-b);
+ } else
+ l -= math->log(b);
+
+ if(l > Maxe) {
+ yyerror("overflow in divide");
+ return 1.0;
+ }
+ if(l < -Maxe) {
+ yyerror("underflow in divide");
+ return 0.0;
+ }
+ return a/b;
+}
+
+fadd(a, b: real): real
+{
+ return a + b;
+}
diff --git a/appl/cmd/unmount.b b/appl/cmd/unmount.b
new file mode 100644
index 00000000..7be037c4
--- /dev/null
+++ b/appl/cmd/unmount.b
@@ -0,0 +1,44 @@
+implement Unmount;
+
+include "sys.m";
+include "draw.m";
+
+FD: import Sys;
+Context: import Draw;
+
+Unmount: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+sys: Sys;
+stderr: ref FD;
+
+usage()
+{
+ sys->fprint(stderr, "Usage: unmount [source] target\n");
+}
+
+init(nil: ref Context, argv: list of string)
+{
+ r: int;
+
+ sys = load Sys Sys->PATH;
+
+ stderr = sys->fildes(2);
+
+ argv = tl argv;
+
+ case len argv {
+ * =>
+ usage();
+ return;
+ 1 =>
+ r = sys->unmount(nil, hd argv);
+ 2 =>
+ r = sys->unmount(hd argv, hd tl argv);
+ };
+
+ if(r < 0)
+ sys->fprint(stderr, "unmount: %r\n");
+}
diff --git a/appl/cmd/usb/mkfile b/appl/cmd/usb/mkfile
new file mode 100644
index 00000000..9a6ea4fa
--- /dev/null
+++ b/appl/cmd/usb/mkfile
@@ -0,0 +1,11 @@
+<../../../mkconfig
+
+TARG=\
+ usbd.dis\
+
+SYSMODULES=\
+ usb.m\
+
+DISBIN=$ROOT/dis/usb
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/usb/usbd.b b/appl/cmd/usb/usbd.b
new file mode 100644
index 00000000..1594da08
--- /dev/null
+++ b/appl/cmd/usb/usbd.b
@@ -0,0 +1,835 @@
+implement Usbd;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "string.m";
+ str: String;
+include "lock.m";
+ lock: Lock;
+ Semaphore: import lock;
+include "arg.m";
+ arg: Arg;
+
+include "usb.m";
+ usb: Usb;
+ Device, Configuration, Endpt: import Usb;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Detached, Attached, Enabled, Assigned, Configured: con (iota);
+
+Usbd: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+Hub: adt {
+ nport, pwrmode, compound, pwrms, maxcurrent, removable, pwrctl: int;
+ ports: cyclic ref DDevice;
+};
+
+DDevice: adt {
+ port: int;
+ pids: list of int;
+ parent: cyclic ref DDevice;
+ next: cyclic ref DDevice;
+ cfd, setupfd, rawfd: ref Sys->FD;
+ id: int;
+ ls: int;
+ state: int;
+ ep: array of ref Endpt;
+ config: array of ref Usb->Configuration;
+ hub: Hub;
+ mod: UsbDriver;
+ d: ref Device;
+};
+
+Line: adt {
+ level: int;
+ command: string;
+ value: int;
+ svalue: string;
+};
+
+ENUMERATE_POLL_INTERVAL: con 1000;
+FAILED_ENUMERATE_RETRY_INTERVAL: con 10000;
+
+verbose: int;
+debug: int;
+stderr: ref Sys->FD;
+
+usbportfd: ref Sys->FD;
+usbctlfd: ref Sys->FD;
+usbctl0: ref Sys->FD;
+usbsetup0: ref Sys->FD;
+
+usbbase: string;
+
+configsema, setupsema, treesema: ref Semaphore;
+
+
+# UHCI style status which is returned by the driver.
+UHCIstatus_Suspend: con 1 << 12;
+UHCIstatus_PortReset: con 1 << 9;
+UHCIstatus_SlowDevice: con 1 << 8;
+UHCIstatus_ResumeDetect: con 1 << 6;
+UHCIstatus_PortEnableChange: con 1 << 3;
+UHCIstatus_PortEnable: con 1 << 2;
+UHCIstatus_ConnectStatusChange: con 1 << 1;
+UHCIstatus_DevicePresent: con 1 << 0;
+
+obt()
+{
+# sys->fprint(stderr, "%d waiting\n", sys->pctl(0, nil));
+ setupsema.obtain();
+# sys->fprint(stderr, "%d got\n", sys->pctl(0, nil));
+}
+
+rel()
+{
+# sys->fprint(stderr, "%d releasing\n", sys->pctl(0, nil));
+ setupsema.release();
+}
+
+hubid(hub: ref DDevice): int
+{
+ if (hub == nil)
+ return 0;
+ return hub.id;
+}
+
+hubfeature(d: ref DDevice, p: int, feature: int, on: int): int
+{
+ rtyp: int;
+ if (p == 0)
+ rtyp = Usb->Rclass;
+ else
+ rtyp = Usb->Rclass | Usb->Rother;
+ obt();
+ rv := usb->setclear_feature(d.setupfd, rtyp, feature, p, on);
+ rel();
+ return rv;
+}
+
+portpower(hub: ref DDevice, port: int, on: int)
+{
+ if (verbose)
+ sys->fprint(stderr, "portpower %d/%d %d\n", hubid(hub), port, on);
+ if (hub == nil)
+ return;
+ if (port)
+ hubfeature(hub, port, Usb->PORT_POWER, on);
+}
+
+countrootports(): int
+{
+ sys->seek(usbportfd, big 0, Sys->SEEKSTART);
+ buf := array [256] of byte;
+ n := sys->read(usbportfd, buf, len buf);
+ if (n <= 0) {
+ sys->fprint(stderr, "usbd: countrootports: error reading root port status\n");
+ exit;
+ }
+ (nv, nil) := sys->tokenize(string buf[0: n], "\n");
+ if (nv < 1) {
+ sys->fprint(stderr, "usbd: countrootports: strange root port status\n");
+ exit;
+ }
+ return nv;
+}
+
+portstatus(hub: ref DDevice, port: int): int
+{
+ rv: int;
+# setupsema.obtain();
+ obt();
+ if (hub == nil) {
+ sys->seek(usbportfd, big 0, Sys->SEEKSTART);
+ buf := array [256] of byte;
+ n := sys->read(usbportfd, buf, len buf);
+ if (n < 1) {
+ sys->fprint(stderr, "usbd: portstatus: read error\n");
+ rel();
+ return 0;
+ }
+ (nil, l) := sys->tokenize(string buf[0: n], "\n");
+ for(; l != nil; l = tl l){
+ (nv, f) := sys->tokenize(hd l, " ");
+ if(nv < 2){
+ sys->fprint(stderr, "usbd: portstatus: odd status line\n");
+ rel();
+ return 0;
+ }
+ if(int hd f == port){
+ (rv, nil) = usb->strtol(hd tl f, 16);
+ # the status change bits are not used so mask them off
+ rv &= 16rffff;
+ break;
+ }
+ }
+ if (l == nil) {
+ sys->fprint(stderr, "usbd: portstatus: no status for port %d\n", port);
+ rel();
+ return 0;
+ }
+ }
+ else
+ rv = usb->get_status(hub.setupfd, port);
+# setupsema.release();
+ rel();
+ if (rv < 0)
+ return 0;
+ return rv;
+}
+
+portenable(hub: ref DDevice, port: int, enable: int)
+{
+ if (verbose)
+ sys->fprint(stderr, "portenable %d/%d %d\n", hubid(hub), port, enable);
+ if (hub == nil) {
+ if (enable)
+ sys->fprint(usbctlfd, "enable %d", port);
+ else
+ sys->fprint(usbctlfd, "disable %d", port);
+ return;
+ }
+ if (port)
+ hubfeature(hub, port, Usb->PORT_ENABLE, enable);
+}
+
+portreset(hub: ref DDevice, port: int)
+{
+ if (verbose)
+ sys->fprint(stderr, "portreset %d/%d\n", hubid(hub), port);
+ if (hub == nil) {
+ if(0)sys->fprint(usbctlfd, "reset %d", port);
+ for (i := 0; i < 4; ++i) {
+ sys->sleep(20); # min 10 milli second reset recovery.
+ s := portstatus(hub, port);
+ if ((s & UHCIstatus_PortReset) == 0) # only leave when reset is finished.
+ break;
+ }
+ return;
+ }
+ if (port)
+ hubfeature(hub, port, Usb->PORT_RESET, 1);
+ return;
+}
+
+devspeed(d: ref DDevice)
+{
+ sys->fprint(d.cfd, "speed %d", !d.ls);
+ if (debug) {
+ s: string;
+ if (d.ls)
+ s = "low";
+ else
+ s = "high";
+ sys->fprint(stderr, "%d: set speed %s\n", d.id, s);
+ }
+}
+
+devmaxpkt0(d: ref DDevice, size: int)
+{
+ sys->fprint(d.cfd, "maxpkt 0 %d", size);
+ if (debug)
+ sys->fprint(stderr, "%d: set maxpkt0 %d\n", d.id, size);
+}
+
+closedev(d: ref DDevice)
+{
+ d.cfd = usbctl0;
+ d.rawfd = nil;
+ d.setupfd = usbsetup0;
+}
+
+openusb(f: string, mode: int): ref Sys->FD
+{
+ fd := sys->open(usbbase + f, mode);
+ if (fd == nil) {
+ sys->fprint(stderr, "usbd: can't open %s: %r\n", usbbase + f);
+ raise "fail:open";
+ }
+ return fd;
+}
+
+opendevf(id: int, f: string, mode: int): ref Sys->FD
+{
+ fd := sys->open(usbbase + string id + "/" + f, mode);
+ if (fd == nil) {
+ sys->fprint(stderr, "usbd: can't open %s: %r\n", usbbase + string id + "/" + f);
+ exit;
+ }
+ return fd;
+}
+
+kill(pid: int): int
+{
+ if (debug)
+ sys->print("killing %d\n", pid);
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if (fd == nil) {
+ sys->print("kill: open failed\n");
+ return -1;
+ }
+ if (sys->write(fd, array of byte "kill", 4) != 4) {
+ sys->print("kill: write failed\n");
+ return -1;
+ }
+ return 0;
+}
+
+rdetach(d: ref DDevice)
+{
+ if (d.mod != nil) {
+ d.mod->shutdown();
+ d.mod = nil;
+ }
+ while (d.pids != nil) {
+ if (verbose)
+ sys->fprint(stderr, "kill %d\n", hd d.pids);
+ kill(hd d.pids);
+ d.pids = tl d.pids;
+ }
+ if (d.parent != nil) {
+ last, hp: ref DDevice;
+ last = nil;
+ hp = d.parent.hub.ports;
+ while (hp != nil && hp != d)
+ hp = hp.next;
+ if (last != nil)
+ last.next = d.next;
+ else
+ d.parent.hub.ports = d.next;
+ }
+ if (d.hub.ports != nil) {
+ for (c := d.hub.ports; c != nil; c = c.next) {
+ c.parent = nil;
+ rdetach(c);
+ }
+ }
+ d.state = Detached;
+ if (sys->fprint(d.cfd, "detach") < 0)
+ sys->fprint(stderr, "detach failed\n");
+ d.cfd = nil;
+ d.rawfd = nil;
+ d.setupfd = nil;
+}
+
+detach(d: ref DDevice)
+{
+ configsema.obtain();
+ treesema.obtain();
+ obt();
+# setupsema.obtain();
+
+ if (verbose)
+ sys->fprint(stderr, "detach %d\n", d.id);
+ rdetach(d);
+ if (verbose)
+ sys->fprint(stderr, "detach %d done\n", d.id);
+# setupsema.release();
+ rel();
+ treesema.release();
+ configsema.release();
+}
+
+readnum(fd: ref Sys->FD): int
+{
+ buf := array [16] of byte;
+ n := sys->read(fd, buf, len buf);
+ if (n <= 0)
+ return -1;
+ (rv , nil) := usb->strtol(string buf[0: n], 0);
+ return rv;
+}
+
+setaddress(d: ref DDevice): int
+{
+ if (d.state == Assigned)
+ return d.id;
+ closedev(d);
+ d.id = 0;
+ d.cfd = openusb("new", Sys->ORDWR);
+ id := readnum(d.cfd);
+ if (id <= 0) {
+ if (debug)
+ sys->fprint(stderr, "usbd: usb/new ID: %r\n");
+ d.cfd = nil;
+ return -1;
+ }
+# setupsema.obtain();
+ obt();
+ if (usb->set_address(d.setupfd, id) < 0) {
+# setupsema.release();
+ rel();
+ return -1;
+ }
+# setupsema.release();
+ rel();
+ d.id = id;
+ d.state = Assigned;
+ return id;
+}
+
+#optstring(d: ref DDevice, langids: list of int, desc: string, index: int)
+#{
+# if (index) {
+# buf := array [256] of byte;
+# while (langids != nil) {
+# nr := usb->get_descriptor(d.setupfd, Usb->Rstandard, Usb->STRING, index, hd langids, buf);
+# if (nr > 2) {
+# sys->fprint(stderr, "%s: ", desc);
+# usbdump->desc(d, -1, buf[0: nr]);
+# }
+# langids = tl langids;
+# }
+# }
+#}
+
+langid(d: ref DDevice): (list of int)
+{
+ l: list of int;
+ buf := array [256] of byte;
+ nr := usb->get_standard_descriptor(d.setupfd, Usb->STRING, 0, buf);
+ if (nr < 4)
+ return nil;
+ if (nr & 1)
+ nr--;
+ l = nil;
+ for (i := nr - 2; i >= 2; i -= 2)
+ l = usb->get2(buf[i:]) :: l;
+ return l;
+}
+
+describedevice(d: ref DDevice): int
+{
+ obt();
+ devmaxpkt0(d, 64); # guess 64 byte max packet to avoid overrun on read
+ for (x := 0; x < 3; x++) { # retry 3 times
+ d.d = usb->get_parsed_device_descriptor(d.setupfd);
+ if (d.d != nil)
+ break;
+ sys->sleep(200); # tolerate out of spec. devices
+ }
+
+ if (d.d == nil) {
+ rel();
+ return -1;
+ }
+
+ if (d.d.maxpkt0 != 64) {
+ devmaxpkt0(d, d.d.maxpkt0);
+ d.d = usb->get_parsed_device_descriptor(d.setupfd);
+ if (d.d == nil) {
+ rel();
+ return -1;
+ }
+ }
+
+ rel();
+
+ if (verbose) {
+ sys->fprint(stderr, "usb %x.%x", d.d.usbmajor, d.d.usbminor);
+ sys->fprint(stderr, " class %d subclass %d proto %d [%s] max0 %d",
+ d.d.class, d.d.subclass, d.d.proto,
+ usb->sclass(d.d.class, d.d.subclass, d.d.proto), d.d.maxpkt0);
+ sys->fprint(stderr, " vendor 0x%.4x product 0x%.4x rel %x.%x",
+ d.d.vid, d.d.did, d.d.relmajor, d.d.relminor);
+ sys->fprint(stderr, " nconf %d", d.d.nconf);
+ sys->fprint(stderr, "\n");
+ obt();
+ l := langid(d);
+ if (l != nil) {
+ l2 := l;
+ sys->fprint(stderr, "langids [");
+ while (l2 != nil) {
+ sys->fprint(stderr, " %d", hd l2);
+ l2 = tl l2;
+ }
+ sys->fprint(stderr, "]\n");
+ }
+# optstring(d, l, "manufacturer", int buf[14]);
+# optstring(d, l, "product", int buf[15]);
+# optstring(d, l, "serial number", int buf[16]);
+ rel();
+ }
+ return 0;
+}
+
+describehub(d: ref DDevice): int
+{
+ b := array [256] of byte;
+# setupsema.obtain();
+ obt();
+ nr := usb->get_class_descriptor(d.setupfd, 0, 0, b);
+ if (nr < Usb->DHUBLEN) {
+# setupsema.release();
+ rel();
+ sys->fprint(stderr, "usbd: error reading hub descriptor: got %d of %d\n", nr, Usb->DHUBLEN);
+ return -1;
+ }
+# setupsema.release();
+ rel();
+ if (verbose)
+ sys->fprint(stderr, "nport %d charac 0x%.4ux pwr %dms current %dmA remov 0x%.2ux pwrctl 0x%.2ux",
+ int b[2], usb->get2(b[3:]), int b[5] * 2, int b[6] * 2, int b[7], int b[8]);
+ d.hub.nport = int b[2];
+ d.hub.pwrms = int b[5] * 2;
+ d.hub.maxcurrent = int b[6] * 2;
+ char := usb->get2(b[3:]);
+ d.hub.pwrmode = char & 3;
+ d.hub.compound = (char & 4) != 0;
+ d.hub.removable = int b[7];
+ d.hub.pwrctl = int b[8];
+ return 0;
+}
+
+loadconfig(d: ref DDevice, n: int): int
+{
+ obt();
+ d.config[n] = usb->get_parsed_configuration_descriptor(d.setupfd, n);
+ if (d.config[n] == nil) {
+ rel();
+ sys->fprint(stderr, "usbd: error reading configuration descriptor\n");
+ return -1;
+ }
+ rel();
+ if (verbose)
+ usb->dump_configuration(stderr, d.config[n]);
+ return 0;
+}
+
+#setdevclass(d: ref DDevice, n: int)
+#{
+# dd := d.config[n];
+# if (dd != nil)
+# sys->fprint(d.cfd, "class %d %d %d %d %d", d.d.nconf, n, dd.class, dd.subclass, dd.proto);
+#}
+
+setconfig(d: ref DDevice, n: int): int
+{
+ obt();
+ rv := usb->set_configuration(d.setupfd, n);
+ rel();
+ if (rv < 0)
+ return -1;
+ d.state = Configured;
+ return 0;
+}
+
+configure(hub: ref DDevice, port: int): ref DDevice
+{
+ configsema.obtain();
+ portreset(hub, port);
+ sys->sleep(300); # long sleep necessary for strange hardware....
+# sys->sleep(20);
+ s := portstatus(hub, port);
+ s = portstatus(hub, port);
+
+ if (debug)
+ sys->fprint(stderr, "port %d status 0x%ux\n", port, s);
+
+ if ((s & UHCIstatus_DevicePresent) == 0) {
+ configsema.release();
+ return nil;
+ }
+
+ if ((s & UHCIstatus_PortEnable) == 0) {
+ if (debug)
+ sys->fprint(stderr, "hack: re-enabling port %d\n", port);
+ portenable(hub, port, 1);
+ s = portstatus(hub, port);
+ if (debug)
+ sys->fprint(stderr, "port %d status now 0x%.ux\n", port, s);
+ }
+
+ d := ref DDevice;
+ d.port = port;
+ d.cfd = usbctl0;
+ d.setupfd = usbsetup0;
+ d.id = 0;
+ if (hub == nil)
+ d.ls = (s & UHCIstatus_SlowDevice) != 0;
+ else
+ d.ls = (s & (1 << 9)) != 0;
+ d.state = Enabled;
+ devspeed(d);
+ if (describedevice(d) < 0) {
+ portenable(hub, port, 0);
+ configsema.release();
+ return nil;
+ }
+ if (setaddress(d) < 0) {
+ portenable(hub, port, 0);
+ configsema.release();
+ return nil;
+ }
+ d.setupfd = opendevf(d.id, "setup", Sys->ORDWR);
+ d.cfd = opendevf(d.id, "ctl", Sys->ORDWR);
+ devspeed(d);
+ devmaxpkt0(d, d.d.maxpkt0);
+ d.config = array [d.d.nconf] of ref Configuration;
+ for (i := 0; i < d.d.nconf; i++) {
+ loadconfig(d, i);
+# setdevclass(d, i);
+ }
+ if (hub != nil) {
+ treesema.obtain();
+ d.parent = hub;
+ d.next = hub.hub.ports;
+ hub.hub.ports = d;
+ treesema.release();
+ }
+ configsema.release();
+ return d;
+}
+
+enumerate(hub: ref DDevice, port: int)
+{
+ if (hub != nil)
+ hub.pids = sys->pctl(0, nil) :: hub.pids;
+ reenumerate := 0;
+ for (;;) {
+ if (verbose)
+ sys->fprint(stderr, "enumerate: starting\n");
+ if ((portstatus(hub, port) & UHCIstatus_DevicePresent) == 0) {
+ if (verbose)
+ sys->fprint(stderr, "%d: port %d empty\n", hubid(hub), port);
+ do {
+ sys->sleep(ENUMERATE_POLL_INTERVAL);
+ } while ((portstatus(hub, port) & UHCIstatus_DevicePresent) == 0);
+ }
+ if (verbose)
+ sys->fprint(stderr, "%d: port %d attached\n", hubid(hub), port);
+ # Δt3 (TATTDB) guarantee 100ms after attach detected
+ sys->sleep(200);
+ d := configure(hub, port);
+ if (d == nil) {
+ if (verbose)
+ sys->fprint(stderr, "%d: can't configure port %d\n", hubid(hub), port);
+ }
+ else if (d.d.class == Usb->CL_HUB) {
+ i: int;
+ if (setconfig(d, 1) < 0) {
+ if (verbose)
+ sys->fprint(stderr, "%d: can't set configuration for hub on port %d\n", hubid(hub), port);
+ detach(d);
+ d = nil;
+ }
+ else if (describehub(d) < 0) {
+ if (verbose)
+ sys->fprint(stderr, "%d: failed to describe hub on port %d\n", hubid(hub), port);
+ detach(d);
+ d = nil;
+ }
+ else {
+ for (i = 1; i <= d.hub.nport; i++)
+ portpower(d, i, 1);
+ sys->sleep(d.hub.pwrms);
+ for (i = 1; i <= d.hub.nport; i++)
+ spawn enumerate(d, i);
+ }
+ }
+ else if (d.d.nconf >= 1 && (path := searchdriverdatabase(d.d, d.config[0])) != nil) {
+ d.mod = load UsbDriver path;
+ if (d.mod == nil)
+ sys->fprint(stderr, "usbd: failed to load %s\n", path);
+ else {
+ rv := d.mod->init(usb, d.setupfd, d.cfd, d.d, d.config, usbbase + string d.id + "/");
+ if (rv == -11) {
+ sys->fprint(stderr, "usbd: %s: reenumerate\n", path);
+ d.mod = nil;
+ reenumerate = 1;
+ }
+ else if (rv < 0) {
+ sys->fprint(stderr, "usbd: %s:init failed\n", path);
+ d.mod = nil;
+ }
+ else if (verbose)
+ sys->fprint(stderr, "%s running\n", path);
+ }
+ }
+ else if (setconfig(d, 1) < 0) {
+ if (verbose)
+ sys->fprint(stderr, "%d: can't set configuration for port %d\n", hubid(hub), port);
+ detach(d);
+ d = nil;
+ }
+ if (!reenumerate) {
+ if (d != nil) {
+ # wait for it to be unplugged
+ while (portstatus(hub, port) & UHCIstatus_DevicePresent)
+ sys->sleep(ENUMERATE_POLL_INTERVAL);
+ }
+ else {
+ # wait a bit and prod it again
+ if (portstatus(hub, port) & UHCIstatus_DevicePresent)
+ sys->sleep(FAILED_ENUMERATE_RETRY_INTERVAL);
+ }
+ }
+ if (d != nil) {
+ detach(d);
+ d = nil;
+ }
+ reenumerate = 0;
+ }
+}
+
+lines: array of Line;
+
+searchdriverdatabase(d: ref Device, conf: ref Configuration): string
+{
+ backtracking := 0;
+ level := 0;
+ for (i := 0; i < len lines; i++) {
+ if (verbose > 1)
+ sys->fprint(stderr, "search line %d: lvl %d cmd %s val %d (back %d lvl %d)\n",
+ i, lines[i].level, lines[i].command, lines[i].value, backtracking, level);
+ if (backtracking) {
+ if (lines[i].level > level)
+ continue;
+ backtracking = 0;
+ }
+ if (lines[i].level != level) {
+ level = 0;
+ backtracking = 1;
+ }
+ case lines[i].command {
+ "class" =>
+ if (d.class != 0) {
+ if (lines[i].value != d.class)
+ backtracking = 1;
+ }
+ else if (lines[i].value != (hd conf.iface[0].altiface).class)
+ backtracking = 1;
+ "subclass" =>
+ if (d.class != 0) {
+ if (lines[i].value != d.subclass)
+ backtracking = 1;
+ }
+ else if (lines[i].value != (hd conf.iface[0].altiface).subclass)
+ backtracking = 1;
+ "proto" =>
+ if (d.class != 0) {
+ if (lines[i].value != d.proto)
+ backtracking = 1;
+ }
+ else if (lines[i].value != (hd conf.iface[0].altiface).proto)
+ backtracking = 1;
+ "vendor" =>
+ if (lines[i].value != d.vid)
+ backtracking =1;
+ "product" =>
+ if (lines[i].value != d.did)
+ backtracking =1;
+ "load" =>
+ return lines[i].svalue;
+ * =>
+ continue;
+ }
+ if (!backtracking)
+ level++;
+ }
+ return nil;
+}
+
+loaddriverdatabase()
+{
+ newlines: array of Line;
+
+ if (bufio == nil)
+ bufio = load Bufio Bufio->PATH;
+
+ iob := bufio->open(Usb->DATABASEPATH, Sys->OREAD);
+ if (iob == nil) {
+ sys->fprint(stderr, "usbd: couldn't open %s: %r\n", Usb->DATABASEPATH);
+ return;
+ }
+ lines = array[100] of Line;
+ lc := 0;
+ while ((line := iob.gets('\n')) != nil) {
+ if (line[0] == '#')
+ continue;
+ level := 0;
+ while (line[0] == '\t') {
+ level++;
+ line = line[1:];
+ }
+ (n, l) := sys->tokenize(line[0: len line - 1], "\t ");
+ if (n != 2)
+ continue;
+ if (lc >= len lines) {
+ newlines = array [len lines * 2] of Line;
+ newlines[0:] = lines[0: len lines];
+ lines = newlines;
+ }
+ lines[lc].level = level;
+ lines[lc].command = hd l;
+ case hd l {
+ "class" or "subclass" or "proto" or "vendor" or "product" =>
+ (lines[lc].value, nil) = usb->strtol(hd tl l, 0);
+ "load" =>
+ lines[lc].svalue = hd tl l;
+ * =>
+ continue;
+ }
+ lc++;
+ }
+ if (verbose)
+ sys->fprint(stderr, "usbd: loaded %d lines\n", lc);
+ newlines = array [lc] of Line;
+ newlines[0:] = lines[0 : lc];
+ lines = newlines;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ usbbase = "/dev/usbh/";
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+
+ lock = load Lock Lock->PATH;
+ lock->init();
+
+ usb = load Usb Usb->PATH;
+ usb->init();
+
+ arg = load Arg Arg->PATH;
+
+ stderr = sys->fildes(2);
+
+ verbose = 0;
+ debug = 0;
+
+ arg->init(args);
+ arg->setusage("usbd [-dv] [-i interface]");
+ while ((c := arg->opt()) != 0)
+ case c {
+ 'v' => verbose = 1;
+ 'd' => debug = 1;
+ 'i' => usbbase = arg->earg() + "/";
+ * => arg->usage();
+ }
+ args = arg->argv();
+
+ usbportfd = openusb("port", Sys->OREAD);
+ usbctlfd = sys->open(usbbase + "ctl", Sys->OWRITE);
+ if(usbctlfd == nil)
+ usbctlfd = openusb("port", Sys->OWRITE);
+ usbctl0 = opendevf(0, "ctl", Sys->ORDWR);
+ usbsetup0 = opendevf(0, "setup", Sys->ORDWR);
+ setupsema = Semaphore.new();
+ configsema = Semaphore.new();
+ treesema = Semaphore.new();
+ loaddriverdatabase();
+ ports := countrootports();
+ if (verbose)
+ sys->print("%d root ports found\n", ports);
+ for (p := 2; p <= ports; p++)
+ spawn enumerate(nil, p);
+ if (p >= 1)
+ enumerate(nil, 1);
+}
diff --git a/appl/cmd/uudecode.b b/appl/cmd/uudecode.b
new file mode 100644
index 00000000..12894aa3
--- /dev/null
+++ b/appl/cmd/uudecode.b
@@ -0,0 +1,132 @@
+implement Uudecode;
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+include "string.m";
+ str : String;
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+
+Uudecode : module
+{
+ init : fn(nil : ref Draw->Context, argv : list of string);
+};
+
+fatal(s : string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", s);
+ exit;
+}
+
+usage()
+{
+ fatal("usage: uudecode [ -p ] [ encodedfile... ]");
+}
+
+init(nil : ref Draw->Context, argv : list of string)
+{
+ fd : ref Sys->FD;
+
+ tostdout := 0;
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ bufio = load Bufio Bufio->PATH;
+ argv = tl argv;
+ if (argv != nil && hd argv == "-p") {
+ tostdout = 1;
+ argv = tl argv;
+ }
+ if (argv != nil) {
+ for (; argv != nil; argv = tl argv) {
+ fd = sys->open(hd argv, Sys->OREAD);
+ if (fd == nil)
+ fatal(sys->sprint("cannot open %s", hd argv));
+ decode(fd, tostdout);
+ }
+ }
+ else
+ decode(sys->fildes(0), tostdout);
+}
+
+code(c : byte) : int
+{
+ return (int c - ' ')&16r3f;
+}
+
+LEN : con 45;
+
+decode(ifd : ref Sys->FD, tostdout : int)
+{
+ mode : int;
+ ofile : string;
+
+ bio := bufio->fopen(ifd, Bufio->OREAD);
+ if (bio == nil)
+ fatal("cannot open input for buffered io: %r");
+ while ((s := bio.gets('\n')) != nil) {
+ if (len s >= 6 && s[0:6] == "begin ") {
+ (n, l) := sys->tokenize(s, " \n");
+ if (n < 3)
+ fatal("bad begin line");
+ (mode, nil) = str->toint(hd tl l, 8);
+ ofile = hd tl tl l;
+ break;
+ }
+ }
+ if (ofile == nil)
+ fatal("no begin line");
+ if (tostdout)
+ ofd := sys->fildes(1);
+ else {
+ if (ofile[0] == '~') # ~user/file
+ ofile = "/usr/" + ofile[1:];
+ ofd = sys->create(ofile, Sys->OWRITE, 8r666);
+ if (ofd == nil)
+ fatal(sys->sprint("cannot create %s: %r", ofile));
+ }
+ ob := array[LEN] of byte;
+ while ((s = bio.gets('\n')) != nil) {
+ b := array of byte s;
+ n := code(b[0]);
+ if (n == 0 && (len b != 2 || b[1] != byte '\n'))
+ fatal("bad 0 count line");
+ if (n <= 0)
+ break;
+ if (n > LEN)
+ fatal("too many bytes on line");
+ e := 0; f := 0;
+ if (n%3 == 1) {
+ e = 2; f = 4;
+ }
+ else if (n%3 == 2) {
+ e = 3; f = 4;
+ }
+ if (len b < 4*(n/3)+e+2 || len b > 4*(n/3)+f+2)
+ fatal("bad uuencode count");
+ b = b[1:];
+ i := 0;
+ nl := n;
+ for (j := 0; nl > 0; j += 4) {
+ if (nl >= 1)
+ ob[i++] = byte (code(b[j+0])<<2 | code(b[j+1])>>4);
+ if (nl >= 2)
+ ob[i++] = byte (code(b[j+1])<<4 | code(b[j+2])>>2);
+ if (nl >= 3)
+ ob[i++] = byte (code(b[j+2])<<6 | code(b[j+3])>>0);
+ nl -= 3;
+ }
+ if (sys->write(ofd, ob, i) != i)
+ fatal("bad write to output: %r");
+ }
+ s = bio.gets('\n');
+ if (s == nil || len s < 4 || s[0:4] != "end\n")
+ fatal("missing end line");
+ if (!tostdout) {
+ d := sys->nulldir;
+ d.mode = mode;
+ if (sys->fwstat(ofd, d) < 0)
+ fatal(sys->sprint("cannot wstat %s: %r", ofile));
+ }
+}
diff --git a/appl/cmd/uuencode.b b/appl/cmd/uuencode.b
new file mode 100644
index 00000000..54dedfdf
--- /dev/null
+++ b/appl/cmd/uuencode.b
@@ -0,0 +1,101 @@
+implement Uuencode;
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+
+Uuencode : module
+{
+ init : fn(nil : ref Draw->Context, argv : list of string);
+};
+
+fatal(s : string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", s);
+ exit;
+}
+
+usage()
+{
+ fatal("usage: uuencode [ sourcefile ] remotefile");
+}
+
+init(nil : ref Draw->Context, argv : list of string)
+{
+ fd : ref Sys->FD;
+ mode : int;
+
+ sys = load Sys Sys->PATH;
+ argv = tl argv;
+ if (argv == nil)
+ usage();
+ if (tl argv != nil) {
+ fd = sys->open(hd argv, Sys->OREAD);
+ if (fd == nil)
+ fatal(sys->sprint("cannot open %s", hd argv));
+ (ok, d) := sys->fstat(fd);
+ if (ok < 0)
+ fatal(sys->sprint("cannot stat %s: %r", hd argv));
+ if (d.mode & Sys->DMDIR)
+ fatal("cannot uuencode a directory");
+ mode = d.mode;
+ argv = tl argv;
+ }
+ else {
+ fd = sys->fildes(0);
+ mode = 8r666;
+ }
+ if (tl argv != nil)
+ usage();
+ sys->print("begin %o %s\n", mode, hd argv);
+ encode(fd);
+ sys->print("end\n");
+}
+
+LEN : con 45;
+
+code(c : int) : byte
+{
+ return byte ((c&16r3f) + ' ');
+}
+
+encode(ifd : ref Sys->FD)
+{
+ c, d, e : int;
+
+ ofd := sys->fildes(1);
+ ib := array[LEN] of byte;
+ ob := array[4*LEN/3 + 2] of byte;
+ for (;;) {
+ n := sys->read(ifd, ib, LEN);
+ if (n < 0)
+ fatal("cannot read input file: %r");
+ if (n == 0)
+ break;
+ i := 0;
+ ob[i++] = code(n);
+ for (j := 0; j < n; j += 3) {
+ c = int ib[j];
+ ob[i++] = code((0<<6)&16r00 | (c>>2)&16r3f);
+ if (j+1 < n)
+ d = int ib[j+1];
+ else
+ d = 0;
+ ob[i++] = code((c<<4)&16r30 | (d>>4)&16r0f);
+ if (j+2 < n)
+ e = int ib[j+2];
+ else
+ e = 0;
+ ob[i++] = code((d<<2)&16r3c | (e>>6)&16r03);
+ ob[i++] = code((e<<0)&16r3f | (0>>8)&16r00);
+ }
+ ob[i++] = byte '\n';
+ if (sys->write(ofd, ob, i) != i)
+ fatal("bad write to output: %r");
+ }
+ ob[0] = code(0);
+ ob[1] = byte '\n';
+ if (sys->write(ofd, ob, 2) != 2)
+ fatal("bad write to output: %r");
+}
+
diff --git a/appl/cmd/wav2iaf.b b/appl/cmd/wav2iaf.b
new file mode 100644
index 00000000..d00dc69e
--- /dev/null
+++ b/appl/cmd/wav2iaf.b
@@ -0,0 +1,171 @@
+implement Wav2Iaf;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+sys: Sys;
+FD: import sys;
+bufio: Bufio;
+Iobuf: import bufio;
+
+stderr: ref FD;
+inf: ref Iobuf;
+prog: string;
+buff4: array of byte;
+
+pad := array[] of { " ", " ", "", " " };
+
+Wav2Iaf: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+ioerror()
+{
+ sys->fprint(stderr, "%s: read error: %r\n", prog);
+ exit;
+}
+
+shortfile(diag: string)
+{
+ sys->fprint(stderr, "%s: short read: %s\n", prog, diag);
+ exit;
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "%s: bad wave file: %s\n", prog, s);
+ exit;
+}
+
+get(c: int, s: string)
+{
+ n := inf.read(buff4, c);
+ if (n < 0)
+ ioerror();
+ if (n != c)
+ shortfile("expected " + s);
+}
+
+gets(c: int, s: string) : string
+{
+ get(c, s);
+ return string buff4[0:c];
+}
+
+need(s: string)
+{
+ get(4, s);
+ if (string buff4 != s) {
+ sys->fprint(stderr, "%s: not a wave file\n", prog);
+ exit;
+ }
+}
+
+getl(s: string) : int
+{
+ get(4, s);
+ return int buff4[0] + (int buff4[1] << 8) + (int buff4[2] << 16) + (int buff4[3] << 24);
+}
+
+getw(s: string) : int
+{
+ get(2, s);
+ return int buff4[0] + (int buff4[1] << 8);
+}
+
+skip(n: int)
+{
+ while (n > 0) {
+ inf.getc();
+ n--;
+ }
+}
+
+bufcp(s, d: ref Iobuf, n: int)
+{
+ while (n > 0) {
+ b := s.getb();
+ if (b < 0) {
+ if (b == Bufio->EOF)
+ sys->fprint(stderr, "%s: short input file\n", prog);
+ else
+ sys->fprint(stderr, "%s: read error: %r\n", prog);
+ exit;
+ }
+ d.putb(byte b);
+ n--;
+ }
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ l: int;
+ a: string;
+
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ prog = hd argv;
+ argv = tl argv;
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ sys->fprint(stderr, "%s: could not load %s: %r\n", prog, Bufio->PATH);
+ if (argv == nil) {
+ inf = bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ if (inf == nil) {
+ sys->fprint(stderr, "%s: could not fopen stdin: %r\n", prog);
+ exit;
+ }
+ }
+ else if (tl argv != nil) {
+ sys->fprint(stderr, "usage: %s [infile]\n", prog);
+ exit;
+ }
+ else {
+ inf = bufio->open(hd argv, Sys->OREAD);
+ if (inf == nil) {
+ sys->fprint(stderr, "%s: could not open %s: %r\n", prog, hd argv);
+ exit;
+ }
+ }
+ buff4 = array[4] of byte;
+ need("RIFF");
+ getl("length");
+ need("WAVE");
+ for (;;) {
+ a = gets(4, "tag");
+ l = getl("length");
+ if (a == "fmt ")
+ break;
+ skip(l);
+ }
+ if (getw("format") != 1)
+ error("not PCM");
+ chans := getw("channels");
+ rate := getl("rate");
+ getl("AvgBytesPerSec");
+ getw("BlockAlign");
+ bits := getw("bits");
+ l -= 16;
+ do {
+ skip(l);
+ a = gets(4, "tag");
+ l = getl("length");
+ }
+ while (a != "data");
+ outf := bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if (outf == nil) {
+ sys->fprint(stderr, "%s: could not fopen stdout: %r\n", prog);
+ exit;
+ }
+ s := "rate\t" + string rate + "\n"
+ + "chans\t" + string chans + "\n"
+ + "bits\t" + string bits + "\n"
+ + "enc\tpcm";
+ outf.puts(s);
+ outf.puts(pad[len s % 4]);
+ outf.puts("\n\n");
+ bufcp(inf, outf, l);
+ outf.flush();
+}
diff --git a/appl/cmd/wc.b b/appl/cmd/wc.b
new file mode 100644
index 00000000..c0a57d35
--- /dev/null
+++ b/appl/cmd/wc.b
@@ -0,0 +1,303 @@
+implement Wc;
+
+#
+# wc -- count things in utf-encoded text files
+# Bugs:
+# The only white space characters recognized are ' ', '\t' and '\n', even though
+# ISO 10646 has many more blanks scattered through it.
+# Should count characters that cannot occur in any rune (hex f0-ff) separately.
+# Should count non-canonical runes (e.g. hex c1,80 instead of hex 40).
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+Wc: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+NBUF: con 8*1024;
+
+stderr: ref Sys->FD;
+nline, tnline, pline: int;
+nword, tnword, pword: int;
+nchar, tnchar, pchar: int;
+nbadr, tnbadr, pbadr: int;
+nbyte, tnbyte, pbyte: int;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ for(argv = tl argv; argv != nil; argv = tl argv){
+ arg := hd argv;
+ if(len arg < 2 || arg[0] != '-' || arg[1] == '-')
+ break;
+ for(i := 1; i < len arg; i++){
+ case arg[i]{
+ 'l' => pline++;
+ 'w' => pword++;
+ 'c' => pchar++;
+ 'e' => pbadr++;
+ 'b' => pbyte++;
+ * =>
+ sys->fprint(stderr, "usage: wc [-lwcbe] [file ...]\n");
+ raise "fail:usage";
+ }
+ }
+ }
+ if(pline+pword+pchar+pbadr+pbyte == 0)
+ pline = pword = pchar = 1;
+ argc := len argv;
+ if(argc == 0)
+ count(sys->fildes(0), "");
+ else{
+ for(; argv != nil; argv = tl argv){
+ name := hd argv;
+ f := sys->open(name, sys->OREAD);
+ if(f == nil)
+ sys->fprint(stderr, "wc: can't open %s: %r\n", name);
+ else{
+ count(f, name);
+ tnline += nline;
+ tnword += nword;
+ tnchar += nchar;
+ tnbadr += nbadr;
+ tnbyte += nbyte;
+ f = nil;
+ }
+ }
+ if(argc > 1)
+ report(tnline, tnword, tnchar, tnbadr, tnbyte, "total");
+ }
+ exit;
+}
+report(nline, nword, nchar, nbadr, nbyte: int, fname: string)
+{
+ line := "";
+ if(pline)
+ line += sys->sprint(" %7d", nline);
+ if(pword)
+ line += sys->sprint(" %7d", nword);
+ if(pchar)
+ line += sys->sprint(" %7d", nchar);
+ if(pbadr)
+ line += sys->sprint(" %7d", nbadr);
+ if(pbyte)
+ line += sys->sprint(" %7d", nbyte);
+ if(fname != nil)
+ line += sys->sprint(" %s", fname);
+ sys->print("%s\n", line[1:]);
+}
+#
+# How it works. Start in statesp. Each time we read a character,
+# increment various counts, and do state transitions according to the
+# following table. If we're not in statesp or statewd when done, the
+# file ends with a partial rune.
+# | character
+# state |09,20| 0a |00-7f|80-bf|c0-df|e0-ef|f0-ff
+# -------+-----+-----+-----+-----+-----+-----+-----
+# statesp|ASP |ASPN |AWDW |AWDWX|AC2W |AC3W |AWDWX
+# statewd|ASP |ASPN |AWD |AWDX |AC2 |AC3 |AWDX
+# statec2|ASPX |ASPNX|AWDX |AWDR |AC2X |AC3X |AWDX
+# statec3|ASPX |ASPNX|AWDX |AC2R |AC2X |AC3X |AWDX
+#
+ # actions
+ AC2, # enter statec2
+ AC2R, # enter statec2, don't count a rune
+ AC2W, # enter statec2, count a word
+ AC2X, # enter statec2, count a bad rune
+ AC3, # enter statec3
+ AC3W, # enter statec3, count a word
+ AC3X, # enter statec3, count a bad rune
+ ASP, # enter statesp
+ ASPN, # enter statesp, count a newline
+ ASPNX, # enter statesp, count a newline, count a bad rune
+ ASPX, # enter statesp, count a bad rune
+ AWD, # enter statewd
+ AWDR, # enter statewd, don't count a rune
+ AWDW, # enter statewd, count a word
+ AWDWX, # enter statewd, count a word, count a bad rune
+ AWDX: # enter statewd, count a bad rune
+ con byte iota;
+
+statesp := array[256] of{ # looking for the start of a word
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 00-07
+AWDW, ASP, ASPN, AWDW, AWDW, AWDW, AWDW, AWDW, # 08-0f
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 10-17
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 18-1f
+ASP, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 20-27
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 28-2f
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 30-37
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 38-3f
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 40-47
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 48-4f
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 50-57
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 58-5f
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 60-67
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 68-6f
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 70-77
+AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 78-7f
+AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 80-87
+AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 88-8f
+AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 90-97
+AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 98-9f
+AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# a0-a7
+AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# a8-af
+AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# b0-b7
+AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# b8-bf
+AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # c0-c7
+AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # c8-cf
+AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # d0-d7
+AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # d8-df
+AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, # e0-e7
+AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, # e8-ef
+AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# f0-f7
+AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# f8-ff
+};
+statewd := array[256] of { # looking for the next character in a word
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 00-07
+AWD, ASP, ASPN, AWD, AWD, AWD, AWD, AWD, # 08-0f
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 10-17
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 18-1f
+ASP, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 20-27
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 28-2f
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 30-37
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 38-3f
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 40-47
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 48-4f
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 50-57
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 58-5f
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 60-67
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 68-6f
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 70-77
+AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 78-7f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 80-87
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 88-8f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 90-97
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 98-9f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # a0-a7
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # a8-af
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # b0-b7
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # b8-bf
+AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # c0-c7
+AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # c8-cf
+AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # d0-d7
+AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # d8-df
+AC3, AC3, AC3, AC3, AC3, AC3, AC3, AC3, # e0-e7
+AC3, AC3, AC3, AC3, AC3, AC3, AC3, AC3, # e8-ef
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f0-f7
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f8-ff
+};
+statec2 := array[256] of { # looking for 10xxxxxx to complete a rune
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 00-07
+AWDX, ASPX, ASPNX,AWDX, AWDX, AWDX, AWDX, AWDX, # 08-0f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 10-17
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 18-1f
+ASPX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 20-27
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 28-2f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 30-37
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 38-3f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 40-47
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 48-4f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 50-57
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 58-5f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 60-67
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 68-6f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 70-77
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 78-7f
+AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 80-87
+AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 88-8f
+AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 90-97
+AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 98-9f
+AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # a0-a7
+AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # a8-af
+AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # b0-b7
+AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # b8-bf
+AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c0-c7
+AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c8-cf
+AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d0-d7
+AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d8-df
+AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e0-e7
+AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e8-ef
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f0-f7
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f8-ff
+};
+statec3 := array[256] of { # looking for 10xxxxxx,10xxxxxx to complete a rune
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 00-07
+AWDX, ASPX, ASPNX,AWDX, AWDX, AWDX, AWDX, AWDX, # 08-0f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 10-17
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 18-1f
+ASPX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 20-27
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 28-2f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 30-37
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 38-3f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 40-47
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 48-4f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 50-57
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 58-5f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 60-67
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 68-6f
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 70-77
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 78-7f
+AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 80-87
+AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 88-8f
+AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 90-97
+AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 98-9f
+AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # a0-a7
+AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # a8-af
+AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # b0-b7
+AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # b8-bf
+AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c0-c7
+AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c8-cf
+AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d0-d7
+AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d8-df
+AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e0-e7
+AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e8-ef
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f0-f7
+AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f8-ff
+};
+buf := array[NBUF] of byte;
+count(f: ref Sys->FD, name: string)
+{
+ state := statesp;
+ nline = nword = nchar = nbadr = nbyte = 0;
+ n := 0;
+ for(;;){
+ n = sys->read(f, buf, NBUF);
+ if(n <= 0)
+ break;
+ nbyte += n;
+ nchar += n; # might be too large, gets decreased later
+ i := 0;
+ do{
+ case int state[int buf[i++]]{
+ int AC2 => state = statec2;
+ int AC2R => state = statec2; nchar--;
+ int AC2W => state = statec2; nword++;
+ int AC2X => state = statec2; nbadr++;
+ int AC3 => state = statec3;
+ int AC3W => state = statec3; nword++;
+ int AC3X => state = statec3; nbadr++;
+ int ASP => state = statesp;
+ int ASPN => state = statesp; nline++;
+ int ASPNX => state = statesp; nline++; nbadr++;
+ int ASPX => state = statesp; nbadr++;
+ int AWD => state = statewd;
+ int AWDR => state = statewd; nchar--;
+ int AWDW => state = statewd; nword++;
+ int AWDWX => state = statewd; nword++; nbadr++;
+ int AWDX => state = statewd; nbadr++;
+ }
+ }while(i < n);
+ }
+ if(state!=statesp && state!=statewd)
+ nbadr++;
+ if(n < 0)
+ sys->fprint(stderr, "wc: error reading %s: %r\n", name);
+ report(nline, nword, nchar, nbadr, nbyte, name);
+}
diff --git a/appl/cmd/webgrab.b b/appl/cmd/webgrab.b
new file mode 100644
index 00000000..0659f398
--- /dev/null
+++ b/appl/cmd/webgrab.b
@@ -0,0 +1,532 @@
+# Webgrab -- for getting html pages and the subordinate files (images, frame children)
+# they refer to (using "src=..." in a tag) into the local file space.
+# Assume http: scheme if none specified.
+# Usage:
+# webgrab [-r] [-v] [-o stem] url
+# If stem is specified, file will be saved in stem.html and images will
+# go in stem_1.jpg (or .gif, ...), stem_2.jpg, etc.
+# If stem is not specified, derive it from url (see getstem comment, below).
+# If -r is specified, get "raw", i.e., no image fetching/html munging.
+# If -v is specified (verbose), print some progress information,
+# with more if -vv is given.
+
+implement Webgrab;
+
+include "sys.m";
+ sys: Sys;
+ FD: import sys;
+
+include "draw.m";
+
+include "string.m";
+ S: String;
+
+include "url.m";
+ U: Url;
+ ParsedUrl: import U;
+
+include "daytime.m";
+ DT: Daytime;
+
+include "bufio.m";
+ B: Bufio;
+
+include "arg.m";
+
+Webgrab: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+stderr: ref FD;
+verbose := 0;
+
+httpproxy: ref Url->ParsedUrl;
+noproxydoms: list of string; # domains that don't require proxy
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ S = load String String->PATH;
+ U = load Url Url->PATH;
+ DT = load Daytime Daytime->PATH;
+ B = load Bufio Bufio->PATH;
+ arg := load Arg Arg->PATH;
+ if(S == nil || U == nil || DT == nil || B == nil || arg == nil)
+ error_exit("can't load a module");
+ U->init();
+ stem := "";
+ rawflag := 0;
+ arg->init(args);
+ arg->setusage("webgrab [-r] [-v[v]] [-o stem] url");
+ url := "";
+ while((o := arg->opt()) != 0)
+ case o {
+ 'r' =>
+ rawflag = 1;
+ 'v' =>
+ verbose++;
+ 'o' =>
+ stem = arg->earg();
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(len args != 1)
+ arg->usage();
+ url = hd args;
+ arg = nil;
+ (nil,xr) := S->splitstrl(url,"//");
+ (nil,yr) := S->splitl(url,":");
+ if(xr == "" && yr == "")
+ url = "http://" + url;
+ u := U->makeurl(url);
+ if(stem == "")
+ stem = getstem(u);
+ readconfig();
+ grab(u, stem, rawflag);
+}
+
+readconfig()
+{
+ cfgio := B->open("/services/webget/config", sys->OREAD);
+ if(cfgio != nil) {
+ for(;;) {
+ line := B->cfgio.gets('\n');
+ if(line == "") {
+ B->cfgio.close();
+ break;
+ }
+ if(line[0]=='#')
+ continue;
+ (key, val) := S->splitl(line, " \t=");
+ val = S->take(S->drop(val, " \t="), "^\r\n");
+ if(val == "")
+ continue;
+ case key {
+ "httpproxy" =>
+ if(val == "none")
+ continue;
+ # val should be host or host:port
+ httpproxy = U->makeurl("http://" + val);
+ if(verbose)
+ sys->fprint(stderr, "Using http proxy %s\n", httpproxy.tostring());
+ "noproxy" or
+ "noproxydoms" =>
+ (nil, noproxydoms) = sys->tokenize(val, ";, \t");
+ }
+ }
+ }
+}
+
+# Make up a stem for forming save-file-names, based on url u.
+# Use the last non-nil component of u.path, without a final extension,
+# else use the host. Then, if the stem still contains a '.' (e.g., www.lucent)
+# use the part after the final '.'.
+# Finally, if all else fails, use use "grabout".
+getstem(u: ref ParsedUrl) : string
+{
+ stem := "";
+ if(u.path != "") {
+ (l, r) := S->splitr(u.path, "/");
+ if(r == "") {
+ # path ended with '/'; try next to last component
+ if(l != "")
+ (l, r) = S->splitr(l[0:len l - 1], "/");
+ }
+ if(r != "")
+ stem = r;
+ }
+ if(stem == "")
+ stem = u.host;
+ if(stem != "") {
+ ext: string;
+ (stem, ext) = S->splitr(stem, ".");
+ if(stem == "")
+ stem = ext;
+ else
+ stem = stem[0:len stem - 1];
+ (nil, stem) = S->splitr(stem, ".");
+ }
+ if(stem == "")
+ stem = "grabout";
+ return stem;
+}
+
+grab(u: ref ParsedUrl, stem: string, rawflag: int)
+{
+ (err, contents, fd, actual) := httpget(u);
+ if(err != "")
+ error_exit(err);
+ ish := is_html(contents);
+ if(ish)
+ contents = addfetchcomment(contents, u, actual);
+ if(rawflag || !ish) {
+ writebytes(stem, contents, fd);
+ return;
+ }
+ # get subordinates, modify contents
+ subs : list of (string, string);
+ (contents, subs) = subfix(contents, stem);
+ writebytes(stem + ".html", contents, fd);
+ for(l := subs; l != nil; l = tl l) {
+ (fname, suburl) := hd l;
+ subu := U->makeurl(suburl);
+ subu.makeabsolute(actual);
+ (suberr, subcontents, subfd, subactual) := httpget(subu);
+ if(suberr != "") {
+ sys->fprint(stderr, "webgrab: can't fetch subordinate %s from %s: %s\n", fname, subu.tostring(), suberr);
+ continue;
+ }
+ writebytes(fname, subcontents, subfd);
+ }
+}
+
+# Fix the html in array a so that referenced subordinate files (SRC= or BACKGROUND= fields of tags)
+# are replaced with local names (stem_1.xxx, stem_2.xxx, etc.),
+# and return the fixed array along with a list of (local name, subordinate url)
+# of images to be fetched.
+subfix(a: array of byte, stem: string) : (array of byte, list of (string, string))
+{
+ alen := len a;
+ if(alen == 0)
+ return (a, nil);
+ nsubs := 0;
+ newa := array[alen + 1000] of byte;
+ newai := 0;
+ j := 0;
+ intag := 0;
+ incom := 0;
+ quote := 0;
+ subs : list of (string, string) = nil;
+ for(i := 0; i < alen; i++) {
+ c := int a[i];
+ if(incom) {
+ if(amatch(a, i, alen, "-->")) {
+ incom = 0;
+ i = i+2;
+ }
+ }
+ else if(intag) {
+ if(quote==0 && (amatch(a, i, alen, "src") || amatch(a, i, alen, "background"))) {
+ v := "";
+ eqi := 0;
+ if(amatch(a, i, alen, "src"))
+ k := i+3;
+ else
+ k = i+10;
+ for(; k < alen; k++)
+ if(!iswhite(int a[k]))
+ break;
+ if(k < alen && int a[k] == '=') {
+ eqi = k;
+ k++;
+ while(k<alen && iswhite(int a[k]))
+ k++;
+ if(k<alen) {
+ kstart := k;
+ c = int a[k];
+ if(c == '\'' || c== '"') {
+ quote = int a[k++];
+ while(k<alen && (int a[k])!=quote)
+ k++;
+ v = string a[kstart+1:k];
+ k++;
+ }
+ else {
+ while(k<alen && !iswhite(int a[k]) && int a[k] != '>')
+ k++;
+ v = string a[kstart:k];
+ }
+ }
+ }
+ if(v != "") {
+ f := "";
+ for(l := subs; l != nil; l = tl l) {
+ (ff,uu) := hd l;
+ if(v == uu) {
+ f = ff;
+ break;
+ }
+ }
+ if(f == "") {
+ nsubs++;
+ f = stem + "_" + string nsubs + getsuff(v);
+ subs = (f, v) :: subs;
+ }
+ # should check for newa too small
+ newa[newai:] = a[j:eqi+1];
+ newai += eqi+1-j;
+ xa := array of byte f;
+ newa[newai:] = xa;
+ newai += len xa;
+ j = k;
+ }
+ i = k-1;
+ }
+ if(c == '>' && quote == 0)
+ intag = 0;
+ if(quote) {
+ if(quote == c)
+ quote = 0;
+ else if(c == '"' || c == '\'')
+ quote = c;
+ }
+ }
+ else if(c == '<')
+ intag = 1;
+ }
+ if(nsubs == 0)
+ return (a, nil);
+ if(i > j) {
+ newa[newai:] = a[j:i];
+ newai += i-j;
+ }
+ ans := array[newai] of byte;
+ ans[0:] = newa[0:newai];
+ anssubs : list of (string, string) = nil;
+ for(ll := subs; ll != nil; ll = tl ll)
+ anssubs = hd ll :: anssubs;
+ return (ans, anssubs);
+}
+
+# add c after all f's in a
+fixnames(a: array of byte, f: string, c: byte)
+{
+ alen := len a;
+ n := alen - len f;
+ for(i := 0; i < n; i++) {
+ if(amatch(a, i, alen, f)) {
+ a[i+len f] = c;
+ }
+ }
+}
+
+amatch(a: array of byte, i, alen: int, s: string) : int
+{
+ slen := len s;
+ for(k := 0; i+k < alen && k < slen; k++) {
+ c := int a[i+k];
+ if(c >= 'A' && c <= 'Z')
+ c = c + (int 'a' - int 'A');
+ if(c != s[k])
+ break;
+ }
+ if(k == slen) {
+ return 1;
+ }
+ return 0;
+}
+
+getsuff(ustr: string) : string
+{
+ u := U->makeurl(ustr);
+ if(u.path != "") {
+ for(i := len u.path - 1; i >= 0; i--) {
+ c := u.path[i];
+ if(c == '.')
+ return u.path[i:];
+ if(c == '/')
+ break;
+ }
+ }
+ return "";
+}
+
+iswhite(c: int) : int
+{
+ return (c==' ' || c=='\t' || c=='\n' || c=='\r');
+}
+
+# Add a comment to end of a giving date and source of fetch
+addfetchcomment(a: array of byte, u, actu: ref ParsedUrl) : array of byte
+{
+ now := DT->text(DT->local(DT->now()));
+ ustr := u.tostring();
+ actustr := actu.tostring();
+ comment := "\n<!-- Fetched " + now + " from " + ustr;
+ if(ustr != actustr)
+ comment += ", redirected to " + actustr;
+ comment += " -->\n";
+ acom := array of byte comment;
+ newa := array[len a + len acom] of byte;
+ newa[0:] = a;
+ newa[len a:] = acom;
+ return newa;
+}
+
+# Get u, return (error string, body, actual url of source, after redirection)
+httpget(u: ref ParsedUrl) : (string, array of byte, ref Sys->FD, ref ParsedUrl)
+{
+ ans, body : array of byte;
+ restfd: ref Sys->FD;
+ for(redir := 0; redir < 10; redir++) {
+ if(u.port == "")
+ u.port = "80"; # default IP port for HTTP
+ if(verbose)
+ sys->fprint(stderr, "connecting to %s\n", u.host);
+ dialhost, port: string;
+ req := "GET ";
+ if(httpproxy != nil && need_proxy(u.host)) {
+ dialhost = httpproxy.host;
+ port = httpproxy.port;
+ req += "http://" + u.host;
+ }
+ else {
+ dialhost = u.host;
+ port = u.port;
+ }
+ (ok, net) := sys->dial("tcp!" + dialhost + "!" + port, nil);
+ if(ok < 0)
+ return (sys->sprint("can't dial %s: %r", dialhost), nil, nil, nil);
+ req += "/" + u.path;
+ if(u.query != "")
+ req += "?" + u.query;
+ req += " HTTP/1.0\r\nHost: "+u.host+"\r\nUser-agent: Inferno/webgrab\r\n\r\n";
+ if(verbose)
+ sys->fprint(stderr, "writing request: %s\n", req);
+ areq := array of byte req;
+ n := sys->write(net.dfd, areq, len areq);
+ if(n != len areq)
+ return (sys->sprint("write problem: %r"), nil, nil, nil);
+ (ans, restfd) = readbytes(net.dfd);
+ (status, rest) := stripline(ans);
+ if(verbose)
+ sys->fprint(stderr, "response: %s\n", status);
+ (vers, statusrest) := S->splitl(status, " ");
+ if(!S->prefix("HTTP/", vers))
+ return ("bad reply status: " + status, rest, restfd, nil);
+ code := int statusrest;
+ location := "";
+ body = rest;
+ for(;;) {
+ hline: string;
+ (hline, body) = stripline(body);
+ if(hline == "")
+ break;
+ if(verbose > 1)
+ sys->fprint(stderr, "%s\n", hline);
+ if(!iswhite(hline[0])) {
+ (hname, hrest) := S->splitl(hline, ":");
+ if(hrest != "") {
+ hname = S->tolower(hname);
+ hval := S->drop(hrest, ": \t");
+ hval = S->take(hval, "^ \t");
+ if(hname == "location")
+ location = hval;
+ }
+ }
+ }
+ if(code != 200) {
+ if((code == 300 || code == 301 || code == 302) && location != "") {
+ # MultipleChoices, MovedPerm, or MovedTemp
+ if(verbose)
+ sys->fprint(stderr, "redirect to %s\n", location);
+ u = U->makeurl(location);
+ continue;
+ }
+ return ("status not ok: " + status, rest, restfd, u);
+ }
+ break;
+ }
+ return ("", body, restfd, u);
+}
+
+need_proxy(h: string) : int
+{
+ doml := noproxydoms;
+ if(doml == nil)
+ return 1; # all domains need proxy
+
+ lh := len h;
+ for(dom := hd doml; doml != nil; doml = tl doml) {
+ ld := len dom;
+ if(lh >= ld && h[lh-ld:] == dom)
+ return 0; # domain is on the noproxy list
+ }
+
+ return 1;
+}
+
+# Simple guess test for HTML: first non-white byte is '<'
+is_html(a: array of byte) : int
+{
+ for(i := 0; i < len a; i++)
+ if(!iswhite(int a[i]))
+ break;
+ if(i < len a && a[i] == byte '<')
+ return 1;
+ return 0;
+}
+
+readbytes(fd: ref Sys->FD) : (array of byte, ref Sys->FD)
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ i := 0;
+ avail := len buf;
+ while (avail > 0) {
+ n := sys->read(fd, buf[i:], avail);
+ if(n <= 0) {
+ fd = nil;
+ break;
+ }
+ i += n;
+ avail -= n;
+ }
+ return (buf[0:i], fd);
+}
+
+writebytes(f: string, a: array of byte, fd: ref Sys->FD)
+{
+ ofd: ref Sys->FD;
+ if (f == "-")
+ ofd = sys->fildes(1);
+ else
+ ofd = sys->create(f, Sys->OWRITE, 8r666);
+ if(ofd == nil) {
+ sys->fprint(stderr, "webgrab: can't create %s: %r\n", f);
+ return;
+ }
+ i := 0;
+ clen := len a;
+ while(i < clen) {
+ n := sys->write(ofd, a[i:], clen-i);
+ if(n < 0) {
+ sys->fprint(stderr, "webgrab: write error: %r\n");
+ return;
+ }
+ i += n;
+ }
+ if(fd != nil) {
+ buf := array[Sys->ATOMICIO] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0) {
+ if(sys->write(ofd, buf, n) != n) {
+ sys->fprint(stderr, "webgrab: write error: %r\n");
+ return;
+ }
+ }
+ if(n < 0) {
+ sys->fprint(stderr, "webgrab: read error: %r\n");
+ return;
+ }
+ clen += n;
+ }
+ if (f != "-")
+ sys->fprint(stderr, "created %s, %d bytes\n", f, clen);
+}
+
+stripline(b: array of byte) : (string, array of byte)
+{
+ n := len b - 1;
+ for(i := 0; i < n; i++)
+ if(b[i] == byte '\r' && b[i+1] == byte '\n')
+ return (string b[0:i], b[i+2:]);
+ return ("", b);
+}
+
+error_exit(msg: string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", msg);
+ raise "fail:error";
+}
diff --git a/appl/cmd/wish.b b/appl/cmd/wish.b
new file mode 100644
index 00000000..39be49a0
--- /dev/null
+++ b/appl/cmd/wish.b
@@ -0,0 +1,191 @@
+implement Test;
+
+include "sys.m";
+include "draw.m";
+draw: Draw;
+Screen, Display, Image: import draw;
+include "tk.m";
+
+Test: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+tk: Tk;
+sys: Sys;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ cmd: string;
+
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+
+ display := Display.allocate(nil);
+ if(display == nil) {
+ sys->print("can't initialize display: %r\n");
+ return;
+ }
+
+ disp := display.image;
+ screen := Screen.allocate(disp, display.rgb(161, 195, 209), 1);
+ if(screen == nil) {
+ sys->print("can't allocate screen: %r\n");
+ return;
+ }
+ fd := sys->open("/dev/pointer", sys->OREAD);
+ if(fd == nil) {
+ sys->print("open: %s: %r\n", "/dev/pointer");
+ sys->print("run wm/wish instead\n");
+ return;
+ }
+
+ t := tk->toplevel(display, "");
+ spawn mouse(t, fd);
+ spawn keyboard(t);
+ disp.draw(disp.r, screen.fill, nil, disp.r.min);
+
+ input := array[8192] of byte;
+ stdin := sys->fildes(0);
+
+ if(argv != nil)
+ argv = tl argv;
+ while(argv != nil) {
+ exec(t, hd argv);
+ argv = tl argv;
+ }
+
+ for(;;) {
+ tk->cmd(t, "update");
+
+ prompt := '%';
+ if(cmd != nil)
+ prompt = '>';
+ sys->print("%c ", prompt);
+
+ n := sys->read(stdin, input, len input);
+ if(n <= 0)
+ break;
+ if(n == 1)
+ continue;
+ cmd += string input[0:n-1];
+ if(cmd[len cmd-1] != '\\') {
+ cmd = esc(cmd);
+ s := tk->cmd(t, cmd);
+ if(len s != 0)
+ sys->print("%s\n", s);
+ cmd = nil;
+ continue;
+ }
+ cmd = cmd[0:len cmd-1];
+ }
+}
+
+esc(s: string): string
+{
+ c: int;
+
+ for(i := 0; i < len s; i++) {
+ if(s[i] != '\\')
+ continue;
+ case s[i+1] {
+ 'n'=> c = '\n';
+ 't'=> c = '\t';
+ 'b'=> c = '\b';
+ '\\'=> c = '\\';
+ * => c = 0;
+ }
+ if(c != 0) {
+ s[i] = c;
+ s = s[0:i+1]+s[i+2:len s];
+ }
+ }
+ return s;
+}
+
+exec(t: ref Tk->Toplevel, path: string)
+{
+ fd := sys->open(path, sys->OREAD);
+ if(fd == nil) {
+ sys->print("open: %s: %r\n", path);
+ return;
+ }
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0) {
+ sys->print("fstat: %s: %r\n", path);
+ return;
+ }
+ buf := array[int d.length] of byte;
+ if(sys->read(fd, buf, len buf) < 0) {
+ sys->print("read: %s: %r\n", path);
+ return;
+ }
+ (n, l) := sys->tokenize(string buf, "\n");
+ buf = nil;
+ n = -1;
+ for(; l != nil; l = tl l) {
+ n++;
+ s := hd l;
+ if(len s == 0 || s[0] == '#')
+ continue;
+
+ while(s[len s-1] == '\\') {
+ s = s[0:len s-1];
+ if(tl l != nil) {
+ l = tl l;
+ s = s + hd l;
+ }
+ else
+ break;
+ }
+
+ s = tk->cmd(t, esc(s));
+
+ if(len s != 0 && s[0] == '!') {
+ sys->print("%s:%d %s\n", path, n, s);
+ sys->print("%s:%d %s\n", path, n, hd l);
+ }
+ }
+}
+
+mouse(t: ref Tk->Toplevel, fd: ref Sys->FD)
+{
+ n := 0;
+ buf := array[100] of byte;
+ for(;;) {
+ n = sys->read(fd, buf, len buf);
+ if(n <= 0)
+ break;
+
+ if(int buf[0] == 'm' && n >= 1+3*12) {
+ x := int(string buf[ 1:13]);
+ y := int(string buf[12:25]);
+ b := int(string buf[24:37]);
+ tk->pointer(t, Draw->Pointer(b, Draw->Point(x, y), sys->millisec()));
+ }
+ }
+}
+
+keyboard(t: ref Tk->Toplevel)
+{
+ dfd := sys->open("/dev/keyboard", sys->OREAD);
+ if(dfd == nil)
+ return;
+
+ b:= array[1] of byte;
+ buf := array[10] of byte;
+ i := 0;
+ for(;;) {
+ n := sys->read(dfd, buf[i:], len buf - i);
+ if(n < 1)
+ break;
+ i += n;
+ while(i >0 && (nutf := sys->utfbytes(buf, i)) > 0){
+ s := string buf[0:nutf];
+ tk->keyboard(t, int s[0]);
+ buf[0:] = buf[nutf:i];
+ i -= nutf;
+ }
+ }
+}
diff --git a/appl/cmd/wmexport.b b/appl/cmd/wmexport.b
new file mode 100644
index 00000000..204337cd
--- /dev/null
+++ b/appl/cmd/wmexport.b
@@ -0,0 +1,557 @@
+implement Wmexport;
+
+#
+# Copyright © 2003 Vita Nuova Holdings Limited.
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Wmcontext, Image: import draw;
+include "wmlib.m";
+ wmlib: Wmlib;
+include "styx.m";
+ styx: Styx;
+ Rmsg, Tmsg: import styx;
+include "styxservers.m";
+ styxservers: Styxservers;
+ Styxserver, Fid, Navigator, Navop: import styxservers;
+ Enotdir, Enotfound: import Styxservers;
+ nametree: Nametree;
+
+Wmexport: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+# filesystem looks like:
+# clone
+# 1
+# wmctl
+# keyboard
+# pointer
+# winname
+
+badmodule(p: string)
+{
+ sys->fprint(sys->fildes(2), "wmexport: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+user := "me";
+qidseq := 1;
+imgseq := 0;
+
+pidregister: chan of (int, int);
+flush: chan of (int, int, chan of int);
+
+makeconn: chan of chan of (ref Conn, string);
+delconn: chan of ref Conn;
+reqpool: list of chan of (ref Tmsg, ref Conn, ref Fid);
+reqidle: int;
+reqdone: chan of chan of (ref Tmsg, ref Conn, ref Fid);
+
+srv: ref Styxserver;
+ctxt: ref Draw->Context;
+
+conns: array of ref Conn;
+nconns := 0;
+
+Qerror, Qroot, Qdir, Qclone, Qwmctl, Qptr, Qkbd, Qwinname: con iota;
+Shift: con 4;
+Mask: con 16rf;
+
+Maxreqidle: con 3;
+Maxreplyidle: con 3;
+
+Conn: adt {
+ wm: ref Wmcontext;
+ iname: string; # name of image
+ n: int;
+ nreads: int;
+};
+
+# initial connection provides base-name (fid?) for images.
+# full name could be:
+# window.fid.tag
+
+init(drawctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ ctxt = drawctxt;
+ if(ctxt == nil || ctxt.wm == nil){
+ sys->fprint(sys->fildes(2), "wmexport: no window manager context\n");
+ raise "fail:no wm";
+ }
+ draw = load Draw Draw->PATH;
+ styx = load Styx Styx->PATH;
+ if (styx == nil)
+ badmodule(Styx->PATH);
+ styx->init();
+ styxservers = load Styxservers Styxservers->PATH;
+ if (styxservers == nil)
+ badmodule(Styxservers->PATH);
+ styxservers->init(styx);
+
+ wmlib = load Wmlib Wmlib->PATH;
+ if(wmlib == nil)
+ badmodule(Wmlib->PATH);
+ wmlib->init();
+
+ sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil); # fork pgrp?
+
+ ctxt = drawctxt;
+ navops := chan of ref Navop;
+ spawn navigator(navops);
+ tchan: chan of ref Tmsg;
+ (tchan, srv) = Styxserver.new(sys->fildes(0), Navigator.new(navops), big Qroot);
+ srv.replychan = chan of ref Styx->Rmsg;
+ spawn replymarshal(srv.replychan);
+ spawn serve(tchan, navops);
+}
+
+serve(tchan: chan of ref Tmsg, navops: chan of ref Navop)
+{
+ pidregister = chan of (int, int);
+ makeconn = chan of chan of (ref Conn, string);
+ delconn = chan of ref Conn;
+ flush = chan of (int, int, chan of int);
+ reqdone = chan of chan of (ref Tmsg, ref Conn, ref Fid);
+ spawn flushproc(flush);
+
+Serve:
+ for(;;)alt{
+ gm := <-tchan =>
+ if(gm == nil)
+ break Serve;
+ pick m := gm {
+ Readerror =>
+ sys->fprint(sys->fildes(2), "wmexport: fatal read error: %s\n", m.error);
+ break Serve;
+ Open =>
+ (fid, mode, d, err) := srv.canopen(m);
+ if(err != nil)
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ else if(fid.qtype & Sys->QTDIR)
+ srv.default(m);
+ else
+ request(ctxt, m, fid);
+ Read =>
+ (fid, err) := srv.canread(m);
+ if(err != nil)
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ else if(fid.qtype & Sys->QTDIR)
+ srv.read(m);
+ else
+ request(ctxt, m, fid);
+ Write =>
+ (fid, err) := srv.canwrite(m);
+ if(err != nil)
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ else
+ request(ctxt, m, fid);
+ Flush =>
+ done := chan of int;
+ flush <-= (m.tag, m.oldtag, done);
+ <-done;
+ Clunk =>
+ request(ctxt, m, srv.clunk(m));
+ * =>
+ srv.default(gm);
+ }
+ rc := <-makeconn =>
+ if(nconns >= len conns)
+ conns = (array[len conns + 5] of ref Conn)[0:] = conns;
+ wm := wmlib->connect(ctxt);
+ if(wm == nil) # XXX this can't happen - give wmlib->connect an error return
+ rc <-= (nil, "cannot connect");
+ else{
+ c := ref Conn(wm, nil, qidseq++, 0);
+ conns[nconns++] = c;
+ rc <-= (c, nil);
+ }
+ c := <-delconn =>
+ for(i := 0; i < nconns; i++)
+ if(conns[i] == c)
+ break;
+ nconns--;
+ if(i < nconns)
+ conns[i] = conns[nconns];
+ conns[nconns] = nil;
+ reqpool = <-reqdone :: reqpool =>
+ if(reqidle++ > Maxreqidle){
+ hd reqpool <-= (nil, nil, nil);
+ reqpool = tl reqpool;
+ reqidle--;
+ }
+ }
+ navops <-= nil;
+ kill(sys->pctl(0, nil), "killgrp");
+}
+
+nameimage(nil: ref Conn, img: ref Draw->Image): string
+{
+ if(img.iname != nil)
+ return img.iname;
+ for(i := 0; i < 100; i++){
+ s := "inferno." + string imgseq++;
+ if(img.name(s, 1) > 0)
+ return s;
+ if(img.iname != nil)
+ return img.iname; # a competing process has done it for us.
+ }
+sys->print("wmexport: no image names: %r\n");
+raise "panic";
+}
+
+request(nil: ref Draw->Context, m: ref Styx->Tmsg, fid: ref Fid)
+{
+ n := int fid.path >> Shift;
+ conn: ref Conn;
+ for(i := 0; i < nconns; i++){
+ if(conns[i].n == n){
+ conn = conns[i];
+ break;
+ }
+ }
+ c: chan of (ref Tmsg, ref Conn, ref Fid);
+ if(reqpool == nil){
+ c = chan of (ref Tmsg, ref Conn, ref Fid);
+ spawn requestproc(c);
+ }else{
+ (c, reqpool) = (hd reqpool, tl reqpool);
+ reqidle--;
+ }
+ c <-= (m, conn, fid);
+}
+
+requestproc(req: chan of (ref Tmsg, ref Conn, ref Fid))
+{
+ pid := sys->pctl(0, nil);
+ for(;;){
+ (gm, c, fid) := <-req;
+ if(gm == nil)
+ break;
+ pidregister <-= (pid, gm.tag);
+ path := int fid.path;
+ pick m := gm {
+ Read =>
+ if(c == nil)
+ srv.replydirect(ref Rmsg.Error(m.tag, "connection is dead"));
+ case path & Mask {
+ Qwmctl =>
+ # first read gets number of connection.
+ m.offset = big 0;
+ if(c.nreads++ == 0)
+ srv.replydirect(styxservers->readstr(m, string c.n));
+ else
+ srv.replydirect(styxservers->readstr(m, <-c.wm.ctl));
+ Qptr =>
+ m.offset = big 0;
+ p := <-c.wm.ptr;
+ srv.replydirect(styxservers->readbytes(m,
+ sys->aprint("m%11d %11d %11d %11ud ", p.xy.x, p.xy.y, p.buttons, p.msec)));
+ Qkbd =>
+ m.offset = big 0;
+ s := "";
+ s[0] = <-c.wm.kbd;
+ srv.replydirect(styxservers->readstr(m, s));
+ Qwinname =>
+ m.offset = big 0;
+ srv.replydirect(styxservers->readstr(m, c.iname));
+ * =>
+ srv.replydirect(ref Rmsg.Error(m.tag, "what was i thinking1?"));
+ }
+ Write =>
+ if(c == nil)
+ srv.replydirect(ref Rmsg.Error(m.tag, "connection is dead"));
+ case path & Mask {
+ Qwmctl =>
+ if(sys->write(c.wm.connfd, m.data, len m.data) == -1){
+ srv.replydirect(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ break;
+ }
+ if(len m.data > 0 && int m.data[0] == '!'){
+ i := <-c.wm.images;
+ if(i == nil)
+ i = <-c.wm.images;
+ c.iname = nameimage(c, i);
+ }
+ srv.replydirect(ref Rmsg.Write(m.tag, len m.data));
+ * =>
+ srv.replydirect(ref Rmsg.Error(m.tag, "what was i thinking2?"));
+ }
+ Open =>
+ if(c == nil && path != Qclone)
+ srv.replydirect(ref Rmsg.Error(m.tag, "connection is dead"));
+ err: string;
+ q := qid(path);
+ case path & Mask {
+ Qclone =>
+ cch := chan of (ref Conn, string);
+ makeconn <-= cch;
+ (c, err) = <-cch;
+ if(c != nil)
+ q = qid(Qwmctl | (c.n << Shift));
+ Qptr =>
+ if(sys->fprint(c.wm.connfd, "start ptr") == -1)
+ err = sys->sprint("%r");
+ Qkbd =>
+ if(sys->fprint(c.wm.connfd, "start kbd") == -1)
+ err = sys->sprint("%r");
+ Qwmctl =>
+ ;
+ Qwinname =>
+ ;
+ * =>
+ err = "what was i thinking3?";
+ }
+ if(err != nil)
+ srv.replydirect(ref Rmsg.Error(m.tag, err));
+ else{
+ srv.replydirect(ref Rmsg.Open(m.tag, q, 0));
+ fid.open(m.mode, q);
+ }
+ Clunk =>
+ case path & Mask {
+ Qwmctl =>
+ if(c != nil)
+ delconn <-= c;
+ }
+ * =>
+ srv.replydirect(ref Rmsg.Error(gm.tag, "oh dear"));
+ }
+ pidregister <-= (pid, -1);
+ reqdone <-= req;
+ }
+}
+
+qid(path: int): Sys->Qid
+{
+ return dirgen(path).t0.qid;
+}
+
+replyproc(c: chan of ref Rmsg, replydone: chan of chan of ref Rmsg)
+{
+ # hmm, this could still send a reply out-of-order with a flush
+ while((m := <-c) != nil){
+ srv.replydirect(m);
+ replydone <-= c;
+ }
+}
+
+# deal with reply messages coming from styxservers.
+replymarshal(c: chan of ref Styx->Rmsg)
+{
+ replypool: list of chan of ref Rmsg;
+ n := 0;
+ replydone := chan of chan of ref Rmsg;
+ for(;;) alt{
+ m := <-c =>
+ c: chan of ref Rmsg;
+ if(replypool == nil){
+ c = chan of ref Rmsg;
+ spawn replyproc(c, replydone);
+ }else{
+ (c, replypool) = (hd replypool, tl replypool);
+ n--;
+ }
+ c <-= m;
+ replypool = <-replydone :: replypool =>
+ if(++n > Maxreplyidle){
+ hd replypool <-= nil;
+ replypool = tl replypool;
+ n--;
+ }
+ }
+}
+
+navigator(navops: chan of ref Navop)
+{
+ while((m := <-navops) != nil){
+ path := int m.path;
+ pick n := m {
+ Stat =>
+ n.reply <-= dirgen(int n.path);
+ Walk =>
+ name := n.name;
+ case path & Mask {
+ Qdir =>
+ dp := path & ~Mask;
+ case name {
+ ".." =>
+ path = Qroot;
+ "wmctl" =>
+ path = Qwmctl | dp;
+ "pointer" =>
+ path = Qptr | dp;
+ "keyboard" =>
+ path = Qkbd | dp;
+ "winname" =>
+ path = Qwinname | dp;
+ * =>
+ path = Qerror;
+ }
+ Qroot =>
+ case name{
+ "clone" =>
+ path = Qclone;
+ * =>
+ x := int name;
+ path = Qerror;
+ if(string x == name){
+ for(i := 0; i < nconns; i++)
+ if(conns[i].n == x){
+ path = (x << Shift) | Qdir;
+ break;
+ }
+ }
+ }
+ }
+ n.reply <-= dirgen(path);
+ Readdir =>
+ err := "";
+ d: array of int;
+ case path & Mask {
+ Qdir =>
+ d = array[] of {Qwmctl, Qptr, Qkbd, Qwinname};
+ for(i := 0; i < len d; i++)
+ d[i] |= path & ~Mask;
+ Qroot =>
+ d = array[nconns + 1] of int;
+ d[0] = Qclone;
+ for(i := 0; i < nconns; i++)
+ d[i + 1] = (conns[i].n<<Shift) | Qdir;
+ }
+ if(d == nil){
+ n.reply <-= (nil, Enotdir);
+ break;
+ }
+ for (i := n.offset; i < len d; i++)
+ n.reply <-= dirgen(d[i]);
+ n.reply <-= (nil, nil);
+ }
+ }
+}
+
+dirgen(path: int): (ref Sys->Dir, string)
+{
+ name: string;
+ perm: int;
+ case path & Mask {
+ Qroot =>
+ name = ".";
+ perm = 8r555|Sys->DMDIR;
+ Qdir =>
+ name = string (path >> Shift);
+ perm = 8r555|Sys->DMDIR;
+ Qclone =>
+ name = "clone";
+ perm = 8r666;
+ Qwmctl =>
+ name = "wmctl";
+ perm = 8r666;
+ Qptr =>
+ name = "pointer";
+ perm = 8r444;
+ Qkbd =>
+ name = "keyboard";
+ perm = 8r444;
+ Qwinname =>
+ name = "winname";
+ perm = 8r444;
+ * =>
+ return (nil, Enotfound);
+ }
+ return (dir(path, name, perm), nil);
+}
+
+dir(path: int, name: string, perm: int): ref Sys->Dir
+{
+ d := ref sys->zerodir;
+ d.qid.path = big path;
+ if(perm & Sys->DMDIR)
+ d.qid.qtype = Sys->QTDIR;
+ d.mode = perm;
+ d.name = name;
+ d.uid = user;
+ d.gid = user;
+ return d;
+}
+
+flushproc(flush: chan of (int, int, chan of int))
+{
+ a: array of (int, int); # (pid, tag)
+ n := 0;
+ for(;;)alt{
+ (pid, tag) := <-pidregister =>
+ if(tag == -1){
+ for(i := 0; i < n; i++)
+ if(a[i].t0 == pid)
+ break;
+ n--;
+ if(i < n)
+ a[i] = a[n];
+ }else{
+ if(n >= len a){
+ na := array[n + 5] of (int, int);
+ na[0:] = a;
+ a = na;
+ }
+ a[n++] = (pid, tag);
+ }
+ (tag, oldtag, done) := <-flush =>
+ for(i := 0; i < n; i++)
+ if(a[i].t1 == oldtag){
+ spawn doflush(tag, a[i].t0, done);
+ break;
+ }
+ if(i == n)
+ spawn doflush(tag, -1, done);
+ }
+}
+
+doflush(tag: int, pid: int, done: chan of int)
+{
+ if(pid != -1){
+ kill(pid, "kill");
+ pidregister <-= (pid, -1);
+ }
+ srv.replydirect(ref Rmsg.Flush(tag));
+ done <-= 1;
+}
+
+# return number of characters from s that will fit into
+# max bytes when encoded as utf-8.
+fullutf(s: string, max: int): int
+{
+ Bit1: con 7;
+ Bitx: con 6;
+ Bit2: con 5;
+ Bit3: con 4;
+ Bit4: con 3;
+ Rune1: con (1<<(Bit1+0*Bitx))-1; # 0000 0000 0111 1111
+ Rune2: con (1<<(Bit2+1*Bitx))-1; # 0000 0111 1111 1111
+ Rune3: con (1<<(Bit3+2*Bitx))-1; # 1111 1111 1111 1111
+ nb := 0;
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ if(c <= Rune1)
+ nb += 1;
+ else if(c <= Rune2)
+ nb += 2;
+ else
+ nb += 3;
+ if(nb > max)
+ break;
+ }
+ return i;
+}
+
+kill(pid: int, note: string): int
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "%s", note) < 0)
+ return -1;
+ return 0;
+}
diff --git a/appl/cmd/wmimport.b b/appl/cmd/wmimport.b
new file mode 100644
index 00000000..2a0d3f3b
--- /dev/null
+++ b/appl/cmd/wmimport.b
@@ -0,0 +1,64 @@
+implement Wmimport;
+
+#
+# Copyright © 2003 Vita Nuova Holdings Limited.
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+include "arg.m";
+include "wmlib.m";
+include "sh.m";
+
+# turn wmexport namespace into a Draw->Context.
+# usage: wmimport [-d /dev/draw] [-w /mnt/wm] cmd [arg...]
+
+Wmimport: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ wmlib := load Wmlib Wmlib->PATH;
+ wmlib->init();
+ sh := load Sh Sh->PATH;
+ arg := load Arg Arg->PATH;
+
+ devdraw := "/dev";
+ mntwm := "/mnt/wm";
+ arg->init(argv);
+ arg->setusage("wmimport [-d /dev] [-w /mnt/wm] cmd [arg...]");
+ while((opt := arg->opt()) != 0){
+ case opt{
+ 'd' =>
+ devdraw = arg->earg();
+ 'w' =>
+ mntwm = arg->earg();
+ * =>
+ arg->usage();
+ }
+ }
+ argv = arg->argv();
+ if(argv == nil)
+ arg->usage();
+ arg = nil;
+ (ok, nil) := sys->stat(mntwm + "/clone");
+ if(ok == -1){
+ sys->fprint(sys->fildes(2), "wmimport: no wm at %s\n", mntwm);
+ raise "fail:no wm";
+ }
+ (ctxt, err) := wmlib->importdrawcontext(devdraw, mntwm);
+ if(ctxt == nil){
+ sys->fprint(sys->fildes(2), "wmimport: remote connect failed; %s\n", err);
+ raise "fail:error";
+ }
+
+ e := sh->run(ctxt, argv);
+ if(e != nil)
+ raise "fail:" + e;
+}
+
diff --git a/appl/cmd/xargs.b b/appl/cmd/xargs.b
new file mode 100644
index 00000000..abfe8cf1
--- /dev/null
+++ b/appl/cmd/xargs.b
@@ -0,0 +1,86 @@
+# apply cmd to args list read from stdin
+# obc
+implement Xargs;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Xargs: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+usage()
+{
+ sys->fprint(stderr, "Usage: xargs command [command args] <[list of last command arg]\n");
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil){
+ sys->fprint(stderr, "xargs: can't load Bufio: %r\n");
+ exit;
+ }
+ if(args != nil)
+ args = tl args;
+ if (args == nil) {
+ usage();
+ return;
+ }
+ cmd := hd args;
+ args = tl args;
+ if(len cmd < 4 || cmd[len cmd -4:]!=".dis")
+ cmd += ".dis";
+ sh := load Command cmd;
+ if (sh == nil){
+ cmd = "/dis/"+cmd;
+ sh = load Command cmd;
+ }
+ if (sh == nil){
+ sys->fprint(stderr, "xargs: can't load %s: %r\n", cmd);
+ exit;
+ }
+
+ stdin := sys->fildes(0);
+ if(stdin == nil){
+ sys->fprint(stderr, "xargs: no standard input\n");
+ exit;
+ }
+ b := bufio->fopen(stdin, Bufio->OREAD);
+ while((t := b.gets('\n')) != nil){
+ (nil, rargs) := sys->tokenize(t, " \t\n");
+ if (rargs == nil)
+ continue;
+ if (args == nil)
+ rargs = cmd :: rargs;
+ else
+ rargs = append(cmd :: args, rargs);
+ sh->init(ctxt, rargs); # BUG: process environment?
+ }
+}
+
+reverse[T](l: list of T): list of T
+{
+ t: list of T;
+ for(; l != nil; l = tl l)
+ t = hd l :: t;
+ return t;
+}
+
+append(h, t: list of string) : list of string
+{
+ r := reverse(h);
+ for(; r != nil; r = tl r)
+ t = hd r :: t;
+ return t;
+}
diff --git a/appl/cmd/xd.b b/appl/cmd/xd.b
new file mode 100644
index 00000000..fa032550
--- /dev/null
+++ b/appl/cmd/xd.b
@@ -0,0 +1,316 @@
+implement Xd;
+
+#
+# based on Plan9 xd
+#
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+Xd: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+sys : Sys;
+bufio : Bufio;
+Iobuf : import bufio;
+stdin, stdout, stderr : ref Sys->FD;
+
+wbytes := array [] of {
+ 1,
+ 2,
+ 4,
+ 8,
+};
+fmtchars : con "odx";
+fmtbases := array [] of {
+ 8,
+ 10,
+ 16,
+};
+fwidths := array [] of {
+ 3, # 1o
+ 3, # 1d
+ 2, # 1x
+ 6, # 2o
+ 5, # 2d
+ 4, # 2x
+ 11, # 4o
+ 10, # 4d
+ 8, # 4x
+ 22, # 8o
+ 20, # 8d
+ 16, # 8x
+};
+
+bytepos := array [16] of { * => 0 };
+
+formats := array [10] of (int, int, int); # (nbytes, base, fieldwidth)
+nformats := 0;
+addrbase := 16;
+repeats := 0;
+swab := 0;
+flush := 0;
+addr := big 0;
+output : ref Iobuf;
+pad : string;
+
+
+init(nil : ref Draw->Context, argv : list of string)
+{
+ sys = load Sys Sys->PATH;
+ stdin = sys->fildes(0);
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) {
+ sys->fprint(stderr, "cannot load bufio: %r\n");
+ raise "fail:init";
+ }
+ output = bufio->fopen(stdout, Sys->OWRITE);
+ if (argv == nil)
+ raise "fail:bad argv";
+
+ pad = string array [32] of { * => byte ' ' };
+
+ for (argv = tl argv; argv != nil; argv = tl argv) {
+ arg := hd argv;
+ if (arg == nil)
+ continue;
+ if (arg[0] != '-')
+ break;
+
+ if (len arg == 2) {
+ case arg[1] {
+ 'c' =>
+ addformat(0, 256);
+ 'r' =>
+ repeats = 1;
+ 's' =>
+ swab = 1;
+ 'u' =>
+ flush = 1;
+ * =>
+ usage();
+ }
+ continue;
+ }
+ # XXX should allow -x1, -x
+ if (len arg == 3) {
+ n := 0;
+ baseix := strchr(fmtchars,arg[2]);
+ if (baseix == -1)
+ usage();
+ case arg[1] {
+ 'a' =>
+ addrbase = fmtbases[baseix];
+ continue;
+ 'b' or '1' => n = 0;
+ 'w' or '2' => n = 1;
+ 'l' or '4' => n = 2;
+ 'v' or '8' => n = 3;
+ * =>
+ usage();
+ }
+ addformat(n, baseix);
+ continue;
+ }
+ usage();
+ }
+ if (nformats == 0)
+ addformat(2, 2); # "4x"
+
+ if (argv == nil)
+ dump(nil, 0);
+ else if (tl argv == nil)
+ dump(hd argv, 0);
+ else {
+ for (; argv != nil; argv = tl argv) {
+ dump(hd argv, 1);
+ }
+ }
+}
+
+usage()
+{
+ sys->fprint(stderr, "usage: xd [-u] [-r] [-s] [-a{odx}] [-c|{b1w2l4v8}{odx}] ... file ...\n");
+ raise "fail:usage";
+}
+
+strchr(s : string, ch : int) : int
+{
+ for (ix := 0; ix < len s; ix++)
+ if (s[ix] == ch)
+ return ix;
+ return -1;
+}
+
+addformat(widix, baseix : int)
+{
+ nbytes := wbytes[widix];
+ if (nformats >= len formats) {
+ sys->fprint(stderr, "xd: too many formats\n");
+ raise "fail:error";
+ }
+ fw : int;
+ if (baseix == 256) {
+ # special -c case
+ formats[nformats++] = (nbytes, 256, 2);
+ fw = 2;
+ } else {
+ fw = fwidths[baseix + (widix *len fmtbases)];
+ formats[nformats++] = (nbytes, fmtbases[baseix], fw);
+ }
+ bpos := 0;
+ for (ix := 0; ix < 16; ix += nbytes) {
+ if (bytepos[ix] >= bpos)
+ bpos = bytepos[ix];
+ else {
+ d := bpos - bytepos[ix];
+ for (dix := ix; dix < 16; dix++)
+ bytepos[dix] += d;
+ }
+ bpos += fw + 1;
+ }
+}
+
+dump(path : string, title : int)
+{
+ input := bufio->fopen(stdin, Sys->OREAD);
+ zeros := array [16] of {* => byte 0};
+
+ if (path != nil) {
+ input = bufio->open(path, Sys->OREAD);
+ if (input == nil) {
+ sys->fprint(stderr, "xd: cannot open %s: %r\n", path);
+ raise "fail:cannot open";
+ }
+ }
+
+ if (title) {
+ output.puts(path);
+ output.putc('\n');
+ }
+
+ addr = big 0;
+ star := 0;
+ obuf: array of byte;
+
+ for (;;) {
+ n := 0;
+ buf := array [16] of byte;
+ while (n < 16 && (r := input.read(buf[n:], 16 - n)) > 0)
+ n += r;
+ if (n < 16)
+ buf[n:] = zeros[n:];
+ if (swab)
+ doswab(buf);
+ if (n == 16 && repeats) {
+ if (obuf != nil && buf[0]==obuf[0]) {
+ for (i := 0; i < 16; i++)
+ if (obuf[i] != buf[i])
+ break;
+ if (i == 16) {
+ addr += big 16;
+ if (star == 0) {
+ star++;
+ output.puts("*\n");
+ }
+ continue;
+ }
+ }
+ obuf = buf;
+ star = 0;
+ }
+ for (fmt := 0; fmt < nformats; fmt++) {
+ if (fmt == 0)
+ output.puts(big2str(addr, 7, addrbase, '0'));
+ else
+ output.puts(big2str(addr, 7, addrbase, ' '));
+ output.putc(' ');
+ (w, b, fw) := formats[fmt];
+ pdata(fw, w, b, n, buf);
+ output.putc('\n');
+ if (flush)
+ output.flush();
+ }
+ addr += big n;
+ if (n < 16) {
+ output.puts(big2str(addr, 7, addrbase, '0'));
+ output.putc('\n');
+ if (flush)
+ output.flush();
+ break;
+ }
+ }
+ output.flush();
+}
+
+hexchars : con "0123456789abcdef";
+
+big2str(b : big, minw, base, padc : int) : string
+{
+ s := "";
+ do {
+ d := int (b % big base);
+ s[len s] = hexchars[d];
+ b /= big base;
+ } while (b > big 0);
+ t := "";
+ if (len s < minw)
+ t = string array [minw] of { * => byte padc };
+ else
+ t = s;
+ for (i := len s - 1; i >= 0; i--)
+ t[len t - 1 - i] = s[i];
+ return t;
+}
+
+pdata(fw, n, base, dlen : int, data : array of byte)
+{
+ nout := 0;
+ text := "";
+
+ for (i := 0; i < dlen; i += n) {
+ if (i != 0) {
+ padlen := bytepos[i] - nout;
+ output.puts(pad[0:padlen]);
+ nout += padlen;
+ }
+ if (base == 256) {
+ # special -c case
+ ch := int data[i];
+ case ch {
+ '\t' => text = "\\t";
+ '\r' => text = "\\r";
+ '\n' => text = "\\n";
+ '\b' => text = "\\b";
+ * =>
+ if (ch >= 16r7f || ' ' > ch)
+ text = sys->sprint("%.2x", ch);
+ else
+ text = sys->sprint("%c", ch);
+ }
+ } else {
+ v := big data[i];
+ for (ix := 1; ix < n; ix++)
+ v = (v << 8) + big data[i+ix];
+ text = big2str(v, fw, base, '0');
+ }
+ output.puts(text);
+ nout += len text;
+ }
+}
+
+doswab(b : array of byte)
+{
+ ix := 0;
+ for (i := 0; i < 4; i++) {
+ (b[ix], b[ix+3]) = (b[ix+3], b[ix]);
+ (b[ix+1], b[ix+2]) = (b[ix+2], b[ix+1]);
+ ix += 4;
+ }
+}
diff --git a/appl/cmd/xmount.b b/appl/cmd/xmount.b
new file mode 100644
index 00000000..6d86c939
--- /dev/null
+++ b/appl/cmd/xmount.b
@@ -0,0 +1,231 @@
+implement Mount;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "keyring.m";
+include "security.m";
+include "arg.m";
+include "sh.m";
+include "styxconv.m";
+ styxconv: Styxconv;
+
+Mount: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+vflag := 0;
+doauth := 1;
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "Usage: mount [-a|-b] [-cA] [-C cryptoalg] [-f keyfile] net!addr|file|{command} mountpoint [spec]\n");
+ raise "fail:usage";
+}
+
+fail(status, msg: string)
+{
+ sys->fprint(sys->fildes(2), "mount: %s\n", msg);
+ raise "fail:"+status;
+}
+
+nomod(mod: string)
+{
+ fail("load", sys->sprint("can't load %s: %r", mod));
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ vflag = 0;
+ unauth := 0;
+ alg := "none";
+ keyfile: string;
+ spec: string;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ styxconv = load Styxconv Styxconv->PATH;
+ if(styxconv == nil)
+ nomod(Styxconv->PATH);
+
+ arg->init(args);
+ styxconv->init();
+
+ flags := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' =>
+ flags |= Sys->MAFTER;
+ 'b' =>
+ flags |= Sys->MBEFORE;
+ 'c' =>
+ flags |= Sys->MCREATE;
+ 'C' =>
+ alg = arg->arg();
+ if(alg == nil)
+ usage();
+ 'f' =>
+ keyfile = arg->arg();
+ if(keyfile == nil)
+ usage();
+ 'A' =>
+ doauth = 0;
+ 'v' =>
+ vflag = 1;
+ 'u' =>
+ unauth = 1; # temporary, undocumented option for testing
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+ if(len args != 2){
+ if(len args != 3)
+ usage();
+ spec = hd tl tl args;
+ }
+ addr := hd args;
+ mountpoint := hd tl args;
+
+ # open stream
+ fd := do_connect(ctxt, addr);
+
+ # authenticate if necessary
+ if (doauth)
+ fd = do_auth(keyfile, alg, fd, addr, unauth);
+
+ p := array[2] of ref Sys->FD;
+ if(sys->pipe(p) < 0)
+ fail("can't create pipe", sys->sprint("can't create pipe: %r"));
+ pidch := chan of int;
+ spawn styxconv->styxconv(p[1], fd, pidch);
+ p[1] = nil;
+ <- pidch;
+ ok := sys->mount(p[0], nil, mountpoint, flags, spec);
+ p[0] = nil;
+ if(ok < 0)
+ fail("mount failed", sys->sprint("mount failed: %r"));
+
+}
+
+# either make network connection or open file
+do_connect(ctxt: ref Draw->Context, dest: string): ref Sys->FD
+{
+ if(dest != nil && dest[0] == '{' && dest[len dest - 1] == '}'){
+ doauth = 0;
+ return popen(ctxt, dest :: nil);
+ }
+ (n, nil) := sys->tokenize(dest, "!");
+ if(n == 1){
+ fd := sys->open(dest, Sys->ORDWR);
+ if(fd != nil)
+ return fd;
+ if(dest[0] == '/')
+ fail("open failed", sys->sprint("can't open %s: %r", dest));
+ }
+ (ok, c) := sys->dial(netmkaddr(dest, "net", "styx"), nil);
+ if(ok < 0)
+ fail("dial failed", sys->sprint("can't dial %s: %r", dest));
+ return c.dfd;
+}
+
+popen(ctxt: ref Draw->Context, argv: list of string): ref Sys->FD
+{
+ sh := load Sh Sh->PATH;
+ if(sh == nil)
+ nomod(Sh->PATH);
+ sync := chan of int;
+ fds := array[2] of ref Sys->FD;
+ sys->pipe(fds);
+ spawn runcmd(sh, ctxt, argv, fds[0], sync);
+ <-sync;
+ return fds[1];
+}
+
+runcmd(sh: Sh, ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD,
+ sync: chan of int)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ sys->dup(stdin.fd, 0);
+ stdin = nil;
+ sync <-= 0;
+ sh->run(ctxt, argv);
+}
+
+# authenticate if necessary
+do_auth(keyfile, alg: string, dfd: ref Sys->FD, addr: string, unauth: int): ref Sys->FD
+{
+ cert : string;
+
+ kr := load Keyring Keyring->PATH;
+ if(kr == nil)
+ nomod(Keyring->PATH);
+
+ kd := "/usr/" + user() + "/keyring/";
+ if (keyfile == nil) {
+ cert = kd + netmkaddr(addr, "tcp", "");
+ (ok, nil) := sys->stat(cert);
+ if (ok < 0)
+ cert = kd + "default";
+ }
+ else if (len keyfile > 0 && keyfile[0] != '/')
+ cert = kd + keyfile;
+ else
+ cert = keyfile;
+ ai := kr->readauthinfo(cert);
+ if (ai == nil){
+ if(!unauth)
+ fail("readauthinfo failed", sys->sprint("cannot read %s: %r", cert));
+ sys->fprint(sys->fildes(2), "mount: can't read %s (%r): trying mount as `nobody'\n", cert);
+ }
+
+ au := load Auth Auth->PATH;
+ if(au == nil)
+ nomod(Auth->PATH);
+
+ err := au->init();
+ if(err != nil)
+ fail("auth init failed", sys->sprint("cannot init Auth: %s", err));
+
+ fd: ref Sys->FD;
+ (fd, err) = au->client(alg, ai, dfd);
+ if(fd == nil)
+ fail("auth failed", sys->sprint("authentication failed: %s", err));
+ if(vflag)
+ sys->print("remote username is %s\n", err);
+
+ return fd;
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil)
+ return "";
+
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+
+ return string buf[0:n];
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/cmd/yacc.b b/appl/cmd/yacc.b
new file mode 100644
index 00000000..97ef87cf
--- /dev/null
+++ b/appl/cmd/yacc.b
@@ -0,0 +1,2810 @@
+implement Yacc;
+
+include "sys.m";
+ sys: Sys;
+ print, fprint, sprint: import sys;
+ UTFmax: import Sys;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "draw.m";
+
+Yacc: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Arg: adt
+{
+ argv: list of string;
+ c: int;
+ opts: string;
+
+ init: fn(argv: list of string): ref Arg;
+ opt: fn(arg: self ref Arg): int;
+ arg: fn(arg: self ref Arg): string;
+};
+
+PARSER: con "/lib/yaccpar";
+OFILE: con "tab.b";
+FILEU: con "output";
+FILED: con "tab.m";
+FILEDEBUG: con "debug";
+
+# the following are adjustable
+# according to memory size
+ACTSIZE: con 30000;
+NSTATES: con 2000;
+TEMPSIZE: con 2000;
+
+SYMINC: con 50; # increase for non-term or term
+RULEINC: con 50; # increase for max rule length prodptr[i]
+PRODINC: con 100; # increase for productions prodptr
+WSETINC: con 50; # increase for working sets wsets
+STATEINC: con 200; # increase for states statemem
+
+NAMESIZE: con 50;
+NTYPES: con 63;
+ISIZE: con 400;
+
+PRIVATE: con 16rE000; # unicode private use
+
+# relationships which must hold:
+# TEMPSIZE >= NTERMS + NNONTERM + 1
+# TEMPSIZE >= NSTATES
+#
+
+NTBASE: con 8r10000;
+ERRCODE: con 8190;
+ACCEPTCODE: con 8191;
+YYLEXUNK: con 3;
+TOKSTART: con 4; #index of first defined token
+
+# no, left, right, binary assoc.
+NOASC, LASC, RASC, BASC: con iota;
+
+# flags for state generation
+DONE, MUSTDO, MUSTLOOKAHEAD: con iota;
+
+# flags for a rule having an action, and being reduced
+ACTFLAG: con 16r4;
+REDFLAG: con 16r8;
+
+# output parser flags
+YYFLAG1: con -1000;
+
+# parse tokens
+IDENTIFIER, MARK, TERM, LEFT, RIGHT, BINARY, PREC, LCURLY, IDENTCOLON, NUMBER, START, TYPEDEF, TYPENAME, MODULE: con PRIVATE+iota;
+
+ENDFILE: con 0;
+
+EMPTY: con 1;
+WHOKNOWS: con 0;
+OK: con 1;
+NOMORE: con -1000;
+
+# macros for getting associativity and precedence levels
+ASSOC(i: int): int
+{
+ return i & 3;
+}
+
+PLEVEL(i: int): int
+{
+ return (i >> 4) & 16r3f;
+}
+
+TYPE(i: int): int
+{
+ return (i >> 10) & 16r3f;
+}
+
+# macros for setting associativity and precedence levels
+SETASC(i, j: int): int
+{
+ return i | j;
+}
+
+SETPLEV(i, j: int): int
+{
+ return i | (j << 4);
+}
+
+SETTYPE(i, j: int): int
+{
+ return i | (j << 10);
+}
+
+# I/O descriptors
+stderr: ref Sys->FD;
+fdefine: ref Iobuf; # file for module definition
+fdebug: ref Iobuf; # y.debug for strings for debugging
+ftable: ref Iobuf; # y.tab.c file
+finput: ref Iobuf; # input file
+foutput: ref Iobuf; # y.output file
+
+CodeData, CodeMod, CodeAct: con iota;
+NCode: con 8192;
+
+Code: adt
+{
+ kind: int;
+ data: array of byte;
+ ndata: int;
+ next: cyclic ref Code;
+};
+
+codehead: ref Code;
+codetail: ref Code;
+
+modname: string; # name of module
+suppressmod: int; # suppress module definition
+stacksize := 200;
+
+# communication variables between various I/O routines
+infile: string; # input file name
+numbval: int; # value of an input number
+tokname: string; # input token name, slop for runes and 0
+
+# structure declarations
+Lkset: type array of int;
+
+Pitem: adt
+{
+ prod: array of int;
+ off: int; # offset within the production
+ first: int; # first term or non-term in item
+ prodno: int; # production number for sorting
+};
+
+Item: adt
+{
+ pitem: Pitem;
+ look: Lkset;
+};
+
+Symb: adt
+{
+ name: string;
+ value: int;
+};
+
+Wset: adt
+{
+ pitem: Pitem;
+ flag: int;
+ ws: Lkset;
+};
+
+ # storage of names
+
+parser := PARSER;
+yydebug: string;
+
+ # storage of types
+ntypes: int; # number of types defined
+typeset := array[NTYPES] of string; # pointers to type tags
+
+ # token information
+
+ntokens := 0; # number of tokens
+tokset: array of Symb;
+toklev: array of int; # vector with the precedence of the terminals
+
+ # nonterminal information
+
+nnonter := -1; # the number of nonterminals
+nontrst: array of Symb;
+start: int; # start symbol
+
+ # state information
+
+nstate := 0; # number of states
+pstate := array[NSTATES+2] of int; # index into statemem to the descriptions of the states
+statemem : array of Item;
+tystate := array[NSTATES] of int; # contains type information about the states
+tstates : array of int; # states generated by terminal gotos
+ntstates : array of int; # states generated by nonterminal gotos
+mstates := array[NSTATES] of {* => 0}; # chain of overflows of term/nonterm generation lists
+lastred: int; # number of last reduction of a state
+defact := array[NSTATES] of int; # default actions of states
+
+ # lookahead set information
+
+lkst: array of Lkset;
+nolook := 0; # flag to turn off lookahead computations
+tbitset := 0; # size of lookahead sets
+clset: Lkset; # temporary storage for lookahead computations
+
+ # working set information
+
+wsets: array of Wset;
+cwp: int;
+
+ # storage for action table
+
+amem: array of int; # action table storage
+memp: int; # next free action table position
+indgo := array[NSTATES] of int; # index to the stored goto table
+
+ # temporary vector, indexable by states, terms, or ntokens
+
+temp1 := array[TEMPSIZE] of int; # temporary storage, indexed by terms + ntokens or states
+lineno := 1; # current input line number
+fatfl := 1; # if on, error is fatal
+nerrors := 0; # number of errors
+
+ # assigned token type values
+extval := 0;
+
+ytabc := OFILE; # name of y.tab.c
+
+ # grammar rule information
+
+nprod := 1; # number of productions
+prdptr: array of array of int; # pointers to descriptions of productions
+levprd: array of int; # precedence levels for the productions
+rlines: array of int; # line number for this rule
+
+
+ # statistics collection variables
+
+zzgoent := 0;
+zzgobest := 0;
+zzacent := 0;
+zzexcp := 0;
+zzclose := 0;
+zzrrconf := 0;
+zzsrconf := 0;
+zzstate := 0;
+
+ # optimizer arrays
+yypgo: array of array of int;
+optst: array of array of int;
+ggreed: array of int;
+pgo: array of int;
+
+maxspr: int; # maximum spread of any entry
+maxoff: int; # maximum offset into a array
+maxa: int;
+
+ # storage for information about the nonterminals
+
+pres: array of array of array of int; # vector of pointers to productions yielding each nonterminal
+pfirst: array of Lkset;
+pempty: array of int; # vector of nonterminals nontrivially deriving e
+ # random stuff picked out from between functions
+
+indebug := 0; # debugging flag for cpfir
+pidebug := 0; # debugging flag for putitem
+gsdebug := 0; # debugging flag for stagen
+cldebug := 0; # debugging flag for closure
+pkdebug := 0; # debugging flag for apack
+g2debug := 0; # debugging for go2gen
+adb := 0; # debugging for callopt
+
+Resrv : adt
+{
+ name: string;
+ value: int;
+};
+
+resrv := array[] of {
+ Resrv("binary", BINARY),
+ Resrv("module", MODULE),
+ Resrv("left", LEFT),
+ Resrv("nonassoc", BINARY),
+ Resrv("prec", PREC),
+ Resrv("right", RIGHT),
+ Resrv("start", START),
+ Resrv("term", TERM),
+ Resrv("token", TERM),
+ Resrv("type", TYPEDEF),};
+
+zznewstate := 0;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ stderr = sys->fildes(2);
+
+ setup(argv); # initialize and read productions
+
+ tbitset = (ntokens+32)/32;
+ cpres(); # make table of which productions yield a given nonterminal
+ cempty(); # make a table of which nonterminals can match the empty string
+ cpfir(); # make a table of firsts of nonterminals
+
+ stagen(); # generate the states
+
+ yypgo = array[nnonter+1] of array of int;
+ optst = array[nstate] of array of int;
+ output(); # write the states and the tables
+ go2out();
+
+ hideprod();
+ summary();
+
+ callopt();
+
+ others();
+
+ if(fdefine != nil)
+ fdefine.close();
+ if(fdebug != nil)
+ fdebug.close();
+ if(ftable != nil)
+ ftable.close();
+ if(foutput != nil)
+ foutput.close();
+}
+
+setup(argv: list of string)
+{
+ j, ty: int;
+
+ ytab := 0;
+ vflag := 0;
+ dflag := 0;
+ stem := 0;
+ stemc := "y";
+ foutput = nil;
+ fdefine = nil;
+ fdebug = nil;
+ arg := Arg.init(argv);
+ while(c := arg.opt()){
+ case c{
+ 'v' or 'V' =>
+ vflag++;
+ 'D' =>
+ yydebug = arg.arg();
+ 'd' =>
+ dflag++;
+ 'n' =>
+ stacksize = int arg.arg();
+ 'o' =>
+ ytab++;
+ ytabc = arg.arg();
+ 's' =>
+ stem++;
+ stemc = arg.arg();
+ 'm' =>
+ suppressmod++;
+ * =>
+ usage();
+ }
+ }
+ argv = arg.argv;
+ if(len argv != 1)
+ usage();
+ if (suppressmod && dflag) {
+ sys->fprint(stderr, "yacc: -m and -d are exclusive\n");
+ usage();
+ }
+ if (stacksize < 1) {
+ sys->fprint(stderr, "yacc: stack size too small\n");
+ usage();
+ }
+ infile = hd argv;
+ finput = bufio->open(infile, Bufio->OREAD);
+ if(finput == nil)
+ error("cannot open '"+infile+"'");
+
+ openup(stemc, dflag, vflag, ytab, ytabc);
+
+ defin(0, "$end");
+ extval = PRIVATE; # tokens start in unicode 'private use'
+ defin(0, "error");
+ defin(1, "$accept");
+ defin(0, "$unk");
+ i := 0;
+
+ for(t := gettok(); t != MARK && t != ENDFILE; )
+ case t {
+ ';' =>
+ t = gettok();
+
+ START =>
+ if(gettok() != IDENTIFIER)
+ error("bad %%start construction");
+ start = chfind(1, tokname);
+ t = gettok();
+
+ TYPEDEF =>
+ if(gettok() != TYPENAME)
+ error("bad syntax in %%type");
+ ty = numbval;
+ for(;;) {
+ t = gettok();
+ case t {
+ IDENTIFIER =>
+ if((t=chfind(1, tokname)) < NTBASE) {
+ j = TYPE(toklev[t]);
+ if(j != 0 && j != ty)
+ error("type redeclaration of token "+
+ tokset[t].name);
+ else
+ toklev[t] = SETTYPE(toklev[t], ty);
+ } else {
+ j = nontrst[t-NTBASE].value;
+ if(j != 0 && j != ty)
+ error("type redeclaration of nonterminal "+
+ nontrst[t-NTBASE].name);
+ else
+ nontrst[t-NTBASE].value = ty;
+ }
+ continue;
+ ',' =>
+ continue;
+ ';' =>
+ t = gettok();
+ }
+ break;
+ }
+
+ MODULE =>
+ cpymodule();
+ t = gettok();
+
+ LEFT or BINARY or RIGHT or TERM =>
+ # nonzero means new prec. and assoc.
+ lev := t-TERM;
+ if(lev)
+ i++;
+ ty = 0;
+
+ # get identifiers so defined
+ t = gettok();
+
+ # there is a type defined
+ if(t == TYPENAME) {
+ ty = numbval;
+ t = gettok();
+ }
+ for(;;) {
+ case t {
+ ',' =>
+ t = gettok();
+ continue;
+
+ ';' =>
+ break;
+
+ IDENTIFIER =>
+ j = chfind(0, tokname);
+ if(j >= NTBASE)
+ error(tokname+" defined earlier as nonterminal");
+ if(lev) {
+ if(ASSOC(toklev[j]))
+ error("redeclaration of precedence of "+tokname);
+ toklev[j] = SETASC(toklev[j], lev);
+ toklev[j] = SETPLEV(toklev[j], i);
+ }
+ if(ty) {
+ if(TYPE(toklev[j]))
+ error("redeclaration of type of "+tokname);
+ toklev[j] = SETTYPE(toklev[j],ty);
+ }
+ t = gettok();
+ if(t == NUMBER) {
+ tokset[j].value = numbval;
+ t = gettok();
+ }
+ continue;
+ }
+ break;
+ }
+
+ LCURLY =>
+ cpycode();
+ t = gettok();
+
+ * =>
+ error("syntax error");
+ }
+ if(t == ENDFILE)
+ error("unexpected EOF before %%");
+ if(modname == nil)
+ error("missing %module specification");
+
+ moreprod();
+ prdptr[0] = array[4] of {
+ NTBASE, # added production
+ start, # if start is 0, we will overwrite with the lhs of the first rule
+ 1,
+ 0
+ };
+ nprod = 1;
+ curprod := array[RULEINC] of int;
+ t = gettok();
+ if(t != IDENTCOLON)
+ error("bad syntax on first rule");
+
+ if(!start)
+ prdptr[0][1] = chfind(1, tokname);
+
+ # read rules
+ # put into prdptr array in the format
+ # target
+ # followed by id's of terminals and non-terminals
+ # followd by -nprod
+ while(t != MARK && t != ENDFILE) {
+ mem := 0;
+ # process a rule
+ rlines[nprod] = lineno;
+ if(t == '|')
+ curprod[mem++] = prdptr[nprod-1][0];
+ else if(t == IDENTCOLON) {
+ curprod[mem] = chfind(1, tokname);
+ if(curprod[mem] < NTBASE)
+ error("token illegal on LHS of grammar rule");
+ mem++;
+ } else
+ error("illegal rule: missing semicolon or | ?");
+
+ # read rule body
+ t = gettok();
+
+ for(;;){
+ while(t == IDENTIFIER) {
+ curprod[mem] = chfind(1, tokname);
+ if(curprod[mem] < NTBASE)
+ levprd[nprod] = toklev[curprod[mem]];
+ mem++;
+ if(mem >= len curprod){
+ ncurprod := array[mem+RULEINC] of int;
+ ncurprod[0:] = curprod;
+ curprod = ncurprod;
+ }
+ t = gettok();
+ }
+ if(t == PREC) {
+ if(gettok() != IDENTIFIER)
+ error("illegal %%prec syntax");
+ j = chfind(2, tokname);
+ if(j >= NTBASE)
+ error("nonterminal "+nontrst[j-NTBASE].name+" illegal after %%prec");
+ levprd[nprod] = toklev[j];
+ t = gettok();
+ }
+ if(t != '=')
+ break;
+ levprd[nprod] |= ACTFLAG;
+ addcode(CodeAct, "\n"+string nprod+"=>");
+ cpyact(curprod, mem);
+
+ # action within rule...
+ if((t=gettok()) == IDENTIFIER) {
+ # make it a nonterminal
+ j = chfind(1, "$$"+string nprod);
+
+ #
+ # the current rule will become rule number nprod+1
+ # enter null production for action
+ #
+ prdptr[nprod] = array[2] of {j, -nprod};
+
+ # update the production information
+ nprod++;
+ moreprod();
+ levprd[nprod] = levprd[nprod-1] & ~ACTFLAG;
+ levprd[nprod-1] = ACTFLAG;
+ rlines[nprod] = lineno;
+
+ # make the action appear in the original rule
+ curprod[mem++] = j;
+ if(mem >= len curprod){
+ ncurprod := array[mem+RULEINC] of int;
+ ncurprod[0:] = curprod;
+ curprod = ncurprod;
+ }
+ }
+ }
+
+ while(t == ';')
+ t = gettok();
+ curprod[mem++] = -nprod;
+
+ # check that default action is reasonable
+ if(ntypes && !(levprd[nprod]&ACTFLAG) && nontrst[curprod[0]-NTBASE].value) {
+ # no explicit action, LHS has value
+
+ tempty := curprod[1];
+ if(tempty < 0)
+ error("must return a value, since LHS has a type");
+ else
+ if(tempty >= NTBASE)
+ tempty = nontrst[tempty-NTBASE].value;
+ else
+ tempty = TYPE(toklev[tempty]);
+ if(tempty != nontrst[curprod[0]-NTBASE].value)
+ error("default action causes potential type clash");
+ else{
+ addcodec(CodeAct, '\n');
+ addcode(CodeAct, string nprod);
+ addcode(CodeAct, "=>\nyyval.");
+ addcode(CodeAct, typeset[tempty]);
+ addcode(CodeAct, " = yys[yyp+1].yyv.");
+ addcode(CodeAct, typeset[tempty]);
+ addcodec(CodeAct, ';');
+ }
+ }
+ moreprod();
+ prdptr[nprod] = array[mem] of int;
+ prdptr[nprod][0:] = curprod[:mem];
+ nprod++;
+ moreprod();
+ levprd[nprod] = 0;
+ }
+
+ #
+ # end of all rules
+ # dump out the prefix code
+ #
+ ftable.puts("implement ");
+ ftable.puts(modname);
+ ftable.puts(";\n");
+
+ dumpcode(CodeMod);
+ dumpmod();
+ dumpcode(CodeAct);
+
+ ftable.puts("YYEOFCODE: con 1;\n");
+ ftable.puts("YYERRCODE: con 2;\n");
+ ftable.puts("YYMAXDEPTH: con " + string stacksize + ";\n"); # was 150
+ #ftable.puts("yyval: YYSTYPE;\n");
+
+ #
+ # copy any postfix code
+ #
+ if(t == MARK) {
+ ftable.puts("\n#line\t");
+ ftable.puts(string lineno);
+ ftable.puts("\t\"");
+ ftable.puts(infile);
+ ftable.puts("\"\n");
+ while((c=finput.getc()) != Bufio->EOF)
+ ftable.putc(c);
+ }
+ finput.close();
+}
+
+#
+# allocate enough room to hold another production
+#
+moreprod()
+{
+ n := len prdptr;
+ if(nprod < n)
+ return;
+ n += PRODINC;
+ aprod := array[n] of array of int;
+ aprod[0:] = prdptr;
+ prdptr = aprod;
+
+ alevprd := array[n] of int;
+ alevprd[0:] = levprd;
+ levprd = alevprd;
+
+ arlines := array[n] of int;
+ arlines[0:] = rlines;
+ rlines = arlines;
+}
+
+#
+# define s to be a terminal if t=0
+# or a nonterminal if t=1
+#
+defin(nt: int, s: string): int
+{
+ val := 0;
+ if(nt) {
+ nnonter++;
+ if(nnonter >= len nontrst){
+ anontrst := array[nnonter + SYMINC] of Symb;
+ anontrst[0:] = nontrst;
+ nontrst = anontrst;
+ }
+ nontrst[nnonter] = Symb(s, 0);
+ return NTBASE + nnonter;
+ }
+
+ # must be a token
+ ntokens++;
+ if(ntokens >= len tokset){
+ atokset := array[ntokens + SYMINC] of Symb;
+ atokset[0:] = tokset;
+ tokset = atokset;
+
+ atoklev := array[ntokens + SYMINC] of int;
+ atoklev[0:] = toklev;
+ toklev = atoklev;
+ }
+ tokset[ntokens].name = s;
+ toklev[ntokens] = 0;
+
+ # establish value for token
+ # single character literal
+ if(s[0] == ' ' && len s == 1+1){
+ val = s[1];
+ }else if(s[0] == ' ' && s[1] == '\\') { # escape sequence
+ if(len s == 2+1) {
+ # single character escape sequence
+ case s[2] {
+ '\'' => val = '\'';
+ '"' => val = '"';
+ '\\' => val = '\\';
+ 'a' => val = '\a';
+ 'b' => val = '\b';
+ 'n' => val = '\n';
+ 'r' => val = '\r';
+ 't' => val = '\t';
+ 'v' => val = '\v';
+ * =>
+ error("invalid escape "+s[1:3]);
+ }
+ }else if(s[2] == 'u' && len s == 2+1+4) { # \unnnn sequence
+ val = 0;
+ s = s[3:];
+ while(s != ""){
+ c := s[0];
+ if(c >= '0' && c <= '9')
+ c -= '0';
+ else if(c >= 'a' && c <= 'f')
+ c -= 'a' - 10;
+ else if(c >= 'A' && c <= 'F')
+ c -= 'A' - 10;
+ else
+ error("illegal \\unnnn construction");
+ val = val * 16 + c;
+ s = s[1:];
+ }
+ if(val == 0)
+ error("'\\u0000' is illegal");
+ }else
+ error("unknown escape");
+ }else
+ val = extval++;
+
+ tokset[ntokens].value = val;
+ return ntokens;
+}
+
+peekline := 0;
+gettok(): int
+{
+ i, match, c: int;
+
+ tokname = "";
+ for(;;){
+ reserve := 0;
+ lineno += peekline;
+ peekline = 0;
+ c = finput.getc();
+ while(c == ' ' || c == '\n' || c == '\t' || c == '\v' || c == '\r') {
+ if(c == '\n')
+ lineno++;
+ c = finput.getc();
+ }
+
+ # skip comment
+ if(c != '#')
+ break;
+ lineno += skipcom();
+ }
+ case c {
+ Bufio->EOF =>
+ return ENDFILE;
+
+ '{' =>
+ finput.ungetc();
+ return '=';
+
+ '<' =>
+ # get, and look up, a type name (union member name)
+ i = 0;
+ while((c=finput.getc()) != '>' && c != Bufio->EOF && c != '\n')
+ tokname[i++] = c;
+ if(c != '>')
+ error("unterminated < ... > clause");
+ for(i=1; i<=ntypes; i++)
+ if(typeset[i] == tokname) {
+ numbval = i;
+ return TYPENAME;
+ }
+ ntypes++;
+ numbval = ntypes;
+ typeset[numbval] = tokname;
+ return TYPENAME;
+
+ '"' or '\'' =>
+ match = c;
+ tokname[0] = ' ';
+ i = 1;
+ for(;;) {
+ c = finput.getc();
+ if(c == '\n' || c == Bufio->EOF)
+ error("illegal or missing ' or \"" );
+ if(c == '\\') {
+ tokname[i++] = '\\';
+ c = finput.getc();
+ } else if(c == match)
+ return IDENTIFIER;
+ tokname[i++] = c;
+ }
+
+ '%' =>
+ case c = finput.getc(){
+ '%' => return MARK;
+ '=' => return PREC;
+ '{' => return LCURLY;
+ }
+
+ getword(c);
+ # find a reserved word
+ for(c=0; c < len resrv; c++)
+ if(tokname == resrv[c].name)
+ return resrv[c].value;
+ error("invalid escape, or illegal reserved word: "+tokname);
+
+ '0' to '9' =>
+ numbval = c - '0';
+ while(isdigit(c = finput.getc()))
+ numbval = numbval*10 + c-'0';
+ finput.ungetc();
+ return NUMBER;
+
+ * =>
+ if(isword(c) || c=='.' || c=='$')
+ getword(c);
+ else
+ return c;
+ }
+
+ # look ahead to distinguish IDENTIFIER from IDENTCOLON
+ c = finput.getc();
+ while(c == ' ' || c == '\t'|| c == '\n' || c == '\v' || c == '\r' || c == '#') {
+ if(c == '\n')
+ peekline++;
+ # look for comments
+ if(c == '#')
+ peekline += skipcom();
+ c = finput.getc();
+ }
+ if(c == ':')
+ return IDENTCOLON;
+ finput.ungetc();
+ return IDENTIFIER;
+}
+
+getword(c: int)
+{
+ i := 0;
+ while(isword(c) || isdigit(c) || c == '_' || c=='.' || c=='$') {
+ tokname[i++] = c;
+ c = finput.getc();
+ }
+ finput.ungetc();
+}
+
+#
+# determine the type of a symbol
+#
+fdtype(t: int): int
+{
+ v : int;
+ s: string;
+
+ if(t >= NTBASE) {
+ v = nontrst[t-NTBASE].value;
+ s = nontrst[t-NTBASE].name;
+ } else {
+ v = TYPE(toklev[t]);
+ s = tokset[t].name;
+ }
+ if(v <= 0)
+ error("must specify type for "+s);
+ return v;
+}
+
+chfind(t: int, s: string): int
+{
+ if(s[0] == ' ')
+ t = 0;
+ for(i:=0; i<=ntokens; i++)
+ if(s == tokset[i].name)
+ return i;
+ for(i=0; i<=nnonter; i++)
+ if(s == nontrst[i].name)
+ return NTBASE+i;
+
+ # cannot find name
+ if(t > 1)
+ error(s+" should have been defined earlier");
+ return defin(t, s);
+}
+
+#
+# saves module definition in Code
+#
+cpymodule()
+{
+ if(gettok() != IDENTIFIER)
+ error("bad %%module construction");
+ if(modname != nil)
+ error("duplicate %%module construction");
+ modname = tokname;
+
+ level := 0;
+ for(;;) {
+ if((c:=finput.getc()) == Bufio->EOF)
+ error("EOF encountered while processing %%module");
+ case c {
+ '\n' =>
+ lineno++;
+ '{' =>
+ level++;
+ if(level == 1)
+ continue;
+ '}' =>
+ level--;
+
+ # we are finished copying
+ if(level == 0)
+ return;
+ }
+ addcodec(CodeMod, c);
+ }
+ if(codehead == nil || codetail.kind != CodeMod)
+ addcodec(CodeMod, '\n'); # ensure we add something
+}
+
+#
+# saves code between %{ and %}
+#
+cpycode()
+{
+ c := finput.getc();
+ if(c == '\n') {
+ c = finput.getc();
+ lineno++;
+ }
+ addcode(CodeData, "\n#line\t" + string lineno + "\t\"" + infile + "\"\n");
+ while(c != Bufio->EOF) {
+ if(c == '%') {
+ if((c=finput.getc()) == '}')
+ return;
+ addcodec(CodeData, '%');
+ }
+ addcodec(CodeData, c);
+ if(c == '\n')
+ lineno++;
+ c = finput.getc();
+ }
+ error("eof before %%}");
+}
+
+addcode(k: int, s: string)
+{
+ for(i := 0; i < len s; i++)
+ addcodec(k, s[i]);
+}
+
+addcodec(k, c: int)
+{
+ if(codehead == nil
+ || k != codetail.kind
+ || codetail.ndata >= NCode){
+ cd := ref Code(k, array[NCode+UTFmax] of byte, 0, nil);
+ if(codehead == nil)
+ codehead = cd;
+ else
+ codetail.next = cd;
+ codetail = cd;
+ }
+
+ codetail.ndata += sys->char2byte(c, codetail.data, codetail.ndata);
+}
+
+dumpcode(til: int)
+{
+ for(; codehead != nil; codehead = codehead.next){
+ if(codehead.kind == til)
+ return;
+ if(ftable.write(codehead.data, codehead.ndata) != codehead.ndata)
+ error("can't write output file");
+ }
+}
+
+#
+# write out the module declaration and any token info
+#
+dumpmod()
+{
+ if(fdefine != nil) {
+ fdefine.puts(modname);
+ fdefine.puts(": module {\n");
+ }
+ if (!suppressmod) {
+ ftable.puts(modname);
+ ftable.puts(": module {\n");
+ }
+
+ for(; codehead != nil; codehead = codehead.next){
+ if(codehead.kind != CodeMod)
+ break;
+ if(ftable.write(codehead.data, codehead.ndata) != codehead.ndata)
+ error("can't write output file");
+ if(fdefine != nil && fdefine.write(codehead.data, codehead.ndata) != codehead.ndata)
+ error("can't write define file");
+ }
+
+ for(i:=TOKSTART; i<=ntokens; i++) {
+ # non-literals
+ c := tokset[i].name[0];
+ if(c != ' ' && c != '$') {
+ s := tokset[i].name+": con "+string tokset[i].value+";\n";
+ ftable.puts(s);
+ if(fdefine != nil)
+ fdefine.puts(s);
+ }
+ }
+
+ if(fdefine != nil)
+ fdefine.puts("};\n");
+ if (!suppressmod)
+ ftable.puts("\n};\n");
+
+ if(fdebug != nil) {
+ fdebug.puts("yytoknames = array[] of {\n");
+ for(i=1; i<=ntokens; i++) {
+ if(tokset[i].name != nil)
+ fdebug.puts("\t\""+chcopy(tokset[i].name)+"\",\n");
+ else
+ fdebug.puts("\t\"\",\n");
+ }
+ fdebug.puts("};\n");
+ }
+}
+
+#
+# skip over comments
+# skipcom is called after reading a '#'
+#
+skipcom(): int
+{
+ c := finput.getc();
+ while(c != Bufio->EOF) {
+ if(c == '\n')
+ return 1;
+ c = finput.getc();
+ }
+ error("EOF inside comment");
+ return 0;
+}
+
+#
+# copy limbo action to the next ; or closing }
+#
+cpyact(curprod: array of int, max: int)
+{
+ addcode(CodeAct, "\n#line\t");
+ addcode(CodeAct, string lineno);
+ addcode(CodeAct, "\t\"");
+ addcode(CodeAct, infile);
+ addcode(CodeAct, "\"\n");
+
+ brac := 0;
+
+loop: for(;;){
+ c := finput.getc();
+ swt: case c {
+ ';' =>
+ if(brac == 0) {
+ addcodec(CodeAct, c);
+ return;
+ }
+
+ '{' =>
+ brac++;
+
+ '$' =>
+ s := 1;
+ tok := -1;
+ c = finput.getc();
+
+ # type description
+ if(c == '<') {
+ finput.ungetc();
+ if(gettok() != TYPENAME)
+ error("bad syntax on $<ident> clause");
+ tok = numbval;
+ c = finput.getc();
+ }
+ if(c == '$') {
+ addcode(CodeAct, "yyval");
+
+ # put out the proper tag...
+ if(ntypes) {
+ if(tok < 0)
+ tok = fdtype(curprod[0]);
+ addcode(CodeAct, "."+typeset[tok]);
+ }
+ continue loop;
+ }
+ if(c == '-') {
+ s = -s;
+ c = finput.getc();
+ }
+ j := 0;
+ if(isdigit(c)) {
+ while(isdigit(c)) {
+ j = j*10 + c-'0';
+ c = finput.getc();
+ }
+ finput.ungetc();
+ j = j*s;
+ if(j >= max)
+ error("Illegal use of $" + string j);
+ }else if(isword(c) || c == '_' || c == '.') {
+ # look for $name
+ finput.ungetc();
+ if(gettok() != IDENTIFIER)
+ error("$ must be followed by an identifier");
+ tokn := chfind(2, tokname);
+ fnd := -1;
+ if((c = finput.getc()) != '@')
+ finput.ungetc();
+ else if(gettok() != NUMBER)
+ error("@ must be followed by number");
+ else
+ fnd = numbval;
+ for(j=1; j<max; j++){
+ if(tokn == curprod[j]) {
+ fnd--;
+ if(fnd <= 0)
+ break;
+ }
+ }
+ if(j >= max)
+ error("$name or $name@number not found");
+ }else{
+ addcodec(CodeAct, '$');
+ if(s < 0)
+ addcodec(CodeAct, '-');
+ finput.ungetc();
+ continue loop;
+ }
+ addcode(CodeAct, "yys[yypt-" + string(max-j-1) + "].yyv");
+
+ # put out the proper tag
+ if(ntypes) {
+ if(j <= 0 && tok < 0)
+ error("must specify type of $" + string j);
+ if(tok < 0)
+ tok = fdtype(curprod[j]);
+ addcodec(CodeAct, '.');
+ addcode(CodeAct, typeset[tok]);
+ }
+ continue loop;
+
+ '}' =>
+ brac--;
+ if(brac)
+ break;
+ addcodec(CodeAct, c);
+ return;
+
+ '#' =>
+ # a comment
+ addcodec(CodeAct, c);
+ c = finput.getc();
+ while(c != Bufio->EOF) {
+ if(c == '\n') {
+ lineno++;
+ break swt;
+ }
+ addcodec(CodeAct, c);
+ c = finput.getc();
+ }
+ error("EOF inside comment");
+
+ '\''or '"' =>
+ # character string or constant
+ match := c;
+ addcodec(CodeAct, c);
+ while(c = finput.getc()) {
+ if(c == '\\') {
+ addcodec(CodeAct, c);
+ c = finput.getc();
+ if(c == '\n')
+ lineno++;
+ } else if(c == match)
+ break swt;
+ if(c == '\n')
+ error("newline in string or char const.");
+ addcodec(CodeAct, c);
+ }
+ error("EOF in string or character constant");
+
+ Bufio->EOF =>
+ error("action does not terminate");
+
+ '\n' =>
+ lineno++;
+ }
+
+ addcodec(CodeAct, c);
+ }
+}
+
+openup(stem: string, dflag, vflag, ytab: int, ytabc: string)
+{
+ buf: string;
+ if(vflag) {
+ buf = stem + "." + FILEU;
+ foutput = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(foutput == nil)
+ error("can't create " + buf);
+ }
+ if(yydebug != nil) {
+ buf = stem + "." + FILEDEBUG;
+ fdebug = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(fdebug == nil)
+ error("can't create " + buf);
+ }
+ if(dflag) {
+ buf = stem + "." + FILED;
+ fdefine = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(fdefine == nil)
+ error("can't create " + buf);
+ }
+ if(ytab == 0)
+ buf = stem + "." + OFILE;
+ else
+ buf = ytabc;
+ ftable = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(ftable == nil)
+ error("can't create file " + buf);
+}
+
+#
+# return a pointer to the name of symbol i
+#
+symnam(i: int): string
+{
+ s: string;
+ if(i >= NTBASE)
+ s = nontrst[i-NTBASE].name;
+ else
+ s = tokset[i].name;
+ if(s[0] == ' ')
+ s = s[1:];
+ return s;
+}
+
+#
+# write out error comment
+#
+error(s: string)
+{
+ nerrors++;
+ fprint(stderr, "yacc: fatal error: %s, %s:%d\n", s, infile, lineno);
+ if(!fatfl)
+ return;
+ summary();
+ raise "fail:error";
+}
+
+#
+# set elements 0 through n-1 to c
+#
+aryfil(v: array of int, n, c: int)
+{
+ for(i:=0; i<n; i++)
+ v[i] = c;
+}
+
+#
+# compute an array with the beginnings of productions yielding given nonterminals
+# The array pres points to these lists
+# the array pyield has the lists: the total size is only NPROD+1
+#
+cpres()
+{
+ pres = array[nnonter+1] of array of array of int;
+ curres := array[nprod] of array of int;
+ for(i:=0; i<=nnonter; i++) {
+ n := 0;
+ c := i+NTBASE;
+ fatfl = 0; # make undefined symbols nonfatal
+ for(j:=0; j<nprod; j++)
+ if(prdptr[j][0] == c)
+ curres[n++] = prdptr[j][1:];
+ if(n == 0)
+ error("nonterminal " + nontrst[i].name + " not defined!");
+ else{
+ pres[i] = array[n] of array of int;
+ pres[i][0:] = curres[:n];
+ }
+ }
+ fatfl = 1;
+ if(nerrors) {
+ summary();
+ raise "fail:error";
+ }
+}
+
+dumppres()
+{
+ for(i := 0; i <= nnonter; i++){
+ print("nonterm %d\n", i);
+ curres := pres[i];
+ for(j := 0; j < len curres; j++){
+ print("\tproduction %d:", j);
+ prd := curres[j];
+ for(k := 0; k < len prd; k++)
+ print(" %d", prd[k]);
+ print("\n");
+ }
+ }
+}
+
+#
+# mark nonterminals which derive the empty string
+# also, look for nonterminals which don't derive any token strings
+#
+cempty()
+{
+ i, p, np: int;
+ prd: array of int;
+
+ pempty = array[nnonter+1] of int;
+
+ # first, use the array pempty to detect productions that can never be reduced
+ # set pempty to WHONOWS
+ aryfil(pempty, nnonter+1, WHOKNOWS);
+
+ # now, look at productions, marking nonterminals which derive something
+more: for(;;){
+ for(i=0; i<nprod; i++) {
+ prd = prdptr[i];
+ if(pempty[prd[0] - NTBASE])
+ continue;
+ np = len prd - 1;
+ for(p = 1; p < np; p++)
+ if(prd[p] >= NTBASE && pempty[prd[p]-NTBASE] == WHOKNOWS)
+ break;
+ # production can be derived
+ if(p == np) {
+ pempty[prd[0]-NTBASE] = OK;
+ continue more;
+ }
+ }
+ break;
+ }
+
+ # now, look at the nonterminals, to see if they are all OK
+ for(i=0; i<=nnonter; i++) {
+ # the added production rises or falls as the start symbol ...
+ if(i == 0)
+ continue;
+ if(pempty[i] != OK) {
+ fatfl = 0;
+ error("nonterminal " + nontrst[i].name + " never derives any token string");
+ }
+ }
+
+ if(nerrors) {
+ summary();
+ raise "fail:error";
+ }
+
+ # now, compute the pempty array, to see which nonterminals derive the empty string
+ # set pempty to WHOKNOWS
+ aryfil(pempty, nnonter+1, WHOKNOWS);
+
+ # loop as long as we keep finding empty nonterminals
+
+again: for(;;){
+ next: for(i=1; i<nprod; i++) {
+ # not known to be empty
+ prd = prdptr[i];
+ if(pempty[prd[0]-NTBASE] != WHOKNOWS)
+ continue;
+ np = len prd - 1;
+ for(p = 1; p < np; p++)
+ if(prd[p] < NTBASE || pempty[prd[p]-NTBASE] != EMPTY)
+ continue next;
+
+ # we have a nontrivially empty nonterminal
+ pempty[prd[0]-NTBASE] = EMPTY;
+ # got one ... try for another
+ continue again;
+ }
+ return;
+ }
+}
+
+dumpempty()
+{
+ for(i := 0; i <= nnonter; i++)
+ if(pempty[i] == EMPTY)
+ print("non-term %d %s matches empty\n", i, symnam(i+NTBASE));
+}
+
+#
+# compute an array with the first of nonterminals
+#
+cpfir()
+{
+ s, n, p, np, ch: int;
+ curres: array of array of int;
+ prd: array of int;
+
+ wsets = array[nnonter+WSETINC] of Wset;
+ pfirst = array[nnonter+1] of Lkset;
+ for(i:=0; i<=nnonter; i++) {
+ wsets[i].ws = mkset();
+ pfirst[i] = mkset();
+ curres = pres[i];
+ n = len curres;
+ # initially fill the sets
+ for(s = 0; s < n; s++) {
+ prd = curres[s];
+ np = len prd - 1;
+ for(p = 0; p < np; p++) {
+ ch = prd[p];
+ if(ch < NTBASE) {
+ setbit(pfirst[i], ch);
+ break;
+ }
+ if(!pempty[ch-NTBASE])
+ break;
+ }
+ }
+ }
+
+ # now, reflect transitivity
+ changes := 1;
+ while(changes) {
+ changes = 0;
+ for(i=0; i<=nnonter; i++) {
+ curres = pres[i];
+ n = len curres;
+ for(s = 0; s < n; s++) {
+ prd = curres[s];
+ np = len prd - 1;
+ for(p = 0; p < np; p++) {
+ ch = prd[p] - NTBASE;
+ if(ch < 0)
+ break;
+ changes |= setunion(pfirst[i], pfirst[ch]);
+ if(!pempty[ch])
+ break;
+ }
+ }
+ }
+ }
+
+ if(!indebug)
+ return;
+ if(foutput != nil){
+ for(i=0; i<=nnonter; i++) {
+ foutput.putc('\n');
+ foutput.puts(nontrst[i].name);
+ foutput.puts(": ");
+ prlook(pfirst[i]);
+ foutput.putc(' ');
+ foutput.puts(string pempty[i]);
+ foutput.putc('\n');
+ }
+ }
+}
+
+#
+# generate the states
+#
+stagen()
+{
+ # initialize
+ nstate = 0;
+ tstates = array[ntokens+1] of {* => 0}; # states generated by terminal gotos
+ ntstates = array[nnonter+1] of {* => 0};# states generated by nonterminal gotos
+ amem = array[ACTSIZE] of {* => 0};
+ memp = 0;
+
+ clset = mkset();
+ pstate[0] = pstate[1] = 0;
+ aryfil(clset, tbitset, 0);
+ putitem(Pitem(prdptr[0], 0, 0, 0), clset);
+ tystate[0] = MUSTDO;
+ nstate = 1;
+ pstate[2] = pstate[1];
+
+ #
+ # now, the main state generation loop
+ # first pass generates all of the states
+ # later passes fix up lookahead
+ # could be sped up a lot by remembering
+ # results of the first pass rather than recomputing
+ #
+ first := 1;
+ for(more := 1; more; first = 0){
+ more = 0;
+ for(i:=0; i<nstate; i++) {
+ if(tystate[i] != MUSTDO)
+ continue;
+
+ tystate[i] = DONE;
+ aryfil(temp1, nnonter+1, 0);
+
+ # take state i, close it, and do gotos
+ closure(i);
+
+ # generate goto's
+ for(p:=0; p<cwp; p++) {
+ pi := wsets[p];
+ if(pi.flag)
+ continue;
+ wsets[p].flag = 1;
+ c := pi.pitem.first;
+ if(c <= 1) {
+ if(pstate[i+1]-pstate[i] <= p)
+ tystate[i] = MUSTLOOKAHEAD;
+ continue;
+ }
+ # do a goto on c
+ putitem(wsets[p].pitem, wsets[p].ws);
+ for(q:=p+1; q<cwp; q++) {
+ # this item contributes to the goto
+ if(c == wsets[q].pitem.first) {
+ putitem(wsets[q].pitem, wsets[q].ws);
+ wsets[q].flag = 1;
+ }
+ }
+
+ if(c < NTBASE)
+ state(c); # register new state
+ else
+ temp1[c-NTBASE] = state(c);
+ }
+
+ if(gsdebug && foutput != nil) {
+ foutput.puts(string i + ": ");
+ for(j:=0; j<=nnonter; j++)
+ if(temp1[j])
+ foutput.puts(nontrst[j].name + " " + string temp1[j] + ", ");
+ foutput.putc('\n');
+ }
+
+ if(first)
+ indgo[i] = apack(temp1[1:], nnonter-1) - 1;
+
+ more++;
+ }
+ }
+}
+
+#
+# generate the closure of state i
+#
+closure(i: int)
+{
+ zzclose++;
+
+ # first, copy kernel of state i to wsets
+ cwp = 0;
+ q := pstate[i+1];
+ for(p:=pstate[i]; p<q; p++) {
+ wsets[cwp].pitem = statemem[p].pitem;
+ wsets[cwp].flag = 1; # this item must get closed
+ wsets[cwp].ws[0:] = statemem[p].look;
+ cwp++;
+ }
+
+ # now, go through the loop, closing each item
+ work := 1;
+ while(work) {
+ work = 0;
+ for(u:=0; u<cwp; u++) {
+ if(wsets[u].flag == 0)
+ continue;
+ # dot is before c
+ c := wsets[u].pitem.first;
+ if(c < NTBASE) {
+ wsets[u].flag = 0;
+ # only interesting case is where . is before nonterminal
+ continue;
+ }
+
+ # compute the lookahead
+ aryfil(clset, tbitset, 0);
+
+ # find items involving c
+ for(v:=u; v<cwp; v++) {
+ if(wsets[v].flag != 1
+ || wsets[v].pitem.first != c)
+ continue;
+ pi := wsets[v].pitem.prod;
+ ipi := wsets[v].pitem.off + 1;
+
+ wsets[v].flag = 0;
+ if(nolook)
+ continue;
+ while((ch := pi[ipi++]) > 0) {
+ # terminal symbol
+ if(ch < NTBASE) {
+ setbit(clset, ch);
+ break;
+ }
+ # nonterminal symbol
+ setunion(clset, pfirst[ch-NTBASE]);
+ if(!pempty[ch-NTBASE])
+ break;
+ }
+ if(ch <= 0)
+ setunion(clset, wsets[v].ws);
+ }
+
+ #
+ # now loop over productions derived from c
+ #
+ curres := pres[c - NTBASE];
+ n := len curres;
+ # initially fill the sets
+ nexts: for(s := 0; s < n; s++) {
+ prd := curres[s];
+ #
+ # put these items into the closure
+ # is the item there
+ #
+ for(v=0; v<cwp; v++) {
+ # yes, it is there
+ if(wsets[v].pitem.off == 0
+ && wsets[v].pitem.prod == prd) {
+ if(!nolook && setunion(wsets[v].ws, clset))
+ wsets[v].flag = work = 1;
+ continue nexts;
+ }
+ }
+
+ # not there; make a new entry
+ if(cwp >= len wsets){
+ awsets := array[cwp + WSETINC] of Wset;
+ awsets[0:] = wsets;
+ wsets = awsets;
+ }
+ wsets[cwp].pitem = Pitem(prd, 0, prd[0], -prd[len prd-1]);
+ wsets[cwp].flag = 1;
+ wsets[cwp].ws = mkset();
+ if(!nolook) {
+ work = 1;
+ wsets[cwp].ws[0:] = clset;
+ }
+ cwp++;
+ }
+ }
+ }
+
+ # have computed closure; flags are reset; return
+ if(cldebug && foutput != nil) {
+ foutput.puts("\nState " + string i + ", nolook = " + string nolook + "\n");
+ for(u:=0; u<cwp; u++) {
+ if(wsets[u].flag)
+ foutput.puts("flag set!\n");
+ wsets[u].flag = 0;
+ foutput.putc('\t');
+ foutput.puts(writem(wsets[u].pitem));
+ prlook(wsets[u].ws);
+ foutput.putc('\n');
+ }
+ }
+}
+
+#
+# sorts last state,and sees if it equals earlier ones. returns state number
+#
+state(c: int): int
+{
+ zzstate++;
+ p1 := pstate[nstate];
+ p2 := pstate[nstate+1];
+ if(p1 == p2)
+ return 0; # null state
+ # sort the items
+ k, l: int;
+ for(k = p1+1; k < p2; k++) { # make k the biggest
+ for(l = k; l > p1; l--) {
+ if(statemem[l].pitem.prodno < statemem[l-1].pitem.prodno
+ || statemem[l].pitem.prodno == statemem[l-1].pitem.prodno
+ && statemem[l].pitem.off < statemem[l-1].pitem.off) {
+ s := statemem[l];
+ statemem[l] = statemem[l-1];
+ statemem[l-1] = s;
+ }else
+ break;
+ }
+ }
+
+ size1 := p2 - p1; # size of state
+
+ if(c >= NTBASE)
+ i := ntstates[c-NTBASE];
+ else
+ i = tstates[c];
+
+look: for(; i != 0; i = mstates[i]) {
+ # get ith state
+ q1 := pstate[i];
+ q2 := pstate[i+1];
+ size2 := q2 - q1;
+ if(size1 != size2)
+ continue;
+ k = p1;
+ for(l = q1; l < q2; l++) {
+ if(statemem[l].pitem.prod != statemem[k].pitem.prod
+ || statemem[l].pitem.off != statemem[k].pitem.off)
+ continue look;
+ k++;
+ }
+
+ # found it
+ pstate[nstate+1] = pstate[nstate]; # delete last state
+ # fix up lookaheads
+ if(nolook)
+ return i;
+ k = p1;
+ for(l = q1; l < q2; l++) {
+ if(setunion(statemem[l].look, statemem[k].look))
+ tystate[i] = MUSTDO;
+ k++;
+ }
+ return i;
+ }
+ # state is new
+ zznewstate++;
+ if(nolook)
+ error("yacc state/nolook error");
+ pstate[nstate+2] = p2;
+ if(nstate+1 >= NSTATES)
+ error("too many states");
+ if(c >= NTBASE) {
+ mstates[nstate] = ntstates[c-NTBASE];
+ ntstates[c-NTBASE] = nstate;
+ } else {
+ mstates[nstate] = tstates[c];
+ tstates[c] = nstate;
+ }
+ tystate[nstate] = MUSTDO;
+ return nstate++;
+}
+
+putitem(p: Pitem, set: Lkset)
+{
+ p.off++;
+ p.first = p.prod[p.off];
+
+ if(pidebug && foutput != nil)
+ foutput.puts("putitem(" + writem(p) + "), state " + string nstate + "\n");
+ j := pstate[nstate+1];
+ if(j >= len statemem){
+ asm := array[j + STATEINC] of Item;
+ asm[0:] = statemem;
+ statemem = asm;
+ }
+ statemem[j].pitem = p;
+ if(!nolook){
+ s := mkset();
+ s[0:] = set;
+ statemem[j].look = s;
+ }
+ j++;
+ pstate[nstate+1] = j;
+}
+
+#
+# creates output string for item pointed to by pp
+#
+writem(pp: Pitem): string
+{
+ i: int;
+ p := pp.prod;
+ q := chcopy(nontrst[prdptr[pp.prodno][0]-NTBASE].name) + ": ";
+ npi := pp.off;
+ pi := p == prdptr[pp.prodno];
+ for(;;){
+ c := ' ';
+ if(pi == npi)
+ c = '.';
+ q[len q] = c;
+ i = p[pi++];
+ if(i <= 0)
+ break;
+ q += chcopy(symnam(i));
+ }
+
+ # an item calling for a reduction
+ i = p[npi];
+ if(i < 0)
+ q += " (" + string -i + ")";
+ return q;
+}
+
+#
+# pack state i from temp1 into amem
+#
+apack(p: array of int, n: int): int
+{
+ #
+ # we don't need to worry about checking because
+ # we will only look at entries known to be there...
+ # eliminate leading and trailing 0's
+ #
+ off := 0;
+ for(pp := 0; pp <= n && p[pp] == 0; pp++)
+ off--;
+ # no actions
+ if(pp > n)
+ return 0;
+ for(; n > pp && p[n] == 0; n--)
+ ;
+ p = p[pp:n+1];
+
+ # now, find a place for the elements from p to q, inclusive
+ r := len amem - len p;
+nextk: for(rr := 0; rr <= r; rr++) {
+ qq := rr;
+ for(pp = 0; pp < len p; pp++) {
+ if(p[pp] != 0)
+ if(p[pp] != amem[qq] && amem[qq] != 0)
+ continue nextk;
+ qq++;
+ }
+
+ # we have found an acceptable k
+ if(pkdebug && foutput != nil)
+ foutput.puts("off = " + string(off+rr) + ", k = " + string rr + "\n");
+ qq = rr;
+ for(pp = 0; pp < len p; pp++) {
+ if(p[pp]) {
+ if(qq > memp)
+ memp = qq;
+ amem[qq] = p[pp];
+ }
+ qq++;
+ }
+ if(pkdebug && foutput != nil) {
+ for(pp = 0; pp <= memp; pp += 10) {
+ foutput.putc('\t');
+ for(qq = pp; qq <= pp+9; qq++)
+ foutput.puts(string amem[qq] + " ");
+ foutput.putc('\n');
+ }
+ }
+ return off + rr;
+ }
+ error("no space in action table");
+ return 0;
+}
+
+#
+# print the output for the states
+#
+output()
+{
+ c, u, v: int;
+
+ ftable.puts("yyexca := array[] of {");
+ if(fdebug != nil)
+ fdebug.puts("yystates = array [] of {\n");
+
+ noset := mkset();
+
+ # output the stuff for state i
+ for(i:=0; i<nstate; i++) {
+ nolook = tystate[i]!=MUSTLOOKAHEAD;
+ closure(i);
+
+ # output actions
+ nolook = 1;
+ aryfil(temp1, ntokens+nnonter+1, 0);
+ for(u=0; u<cwp; u++) {
+ c = wsets[u].pitem.first;
+ if(c > 1 && c < NTBASE && temp1[c] == 0) {
+ for(v=u; v<cwp; v++)
+ if(c == wsets[v].pitem.first)
+ putitem(wsets[v].pitem, noset);
+ temp1[c] = state(c);
+ } else
+ if(c > NTBASE && temp1[(c -= NTBASE) + ntokens] == 0)
+ temp1[c+ntokens] = amem[indgo[i]+c];
+ }
+ if(i == 1)
+ temp1[1] = ACCEPTCODE;
+
+ # now, we have the shifts; look at the reductions
+ lastred = 0;
+ for(u=0; u<cwp; u++) {
+ c = wsets[u].pitem.first;
+
+ # reduction
+ if(c > 0)
+ continue;
+ lastred = -c;
+ us := wsets[u].ws;
+ for(k:=0; k<=ntokens; k++) {
+ if(!bitset(us, k))
+ continue;
+ if(temp1[k] == 0)
+ temp1[k] = c;
+ else
+ if(temp1[k] < 0) { # reduce/reduce conflict
+ if(foutput != nil)
+ foutput.puts(
+ "\n" + string i + ": reduce/reduce conflict (red'ns "
+ + string -temp1[k] + " and " + string lastred + " ) on " + symnam(k));
+ if(-temp1[k] > lastred)
+ temp1[k] = -lastred;
+ zzrrconf++;
+ } else
+ # potential shift/reduce conflict
+ precftn(lastred, k, i);
+ }
+ }
+ wract(i);
+ }
+
+ if(fdebug != nil)
+ fdebug.puts("};\n");
+ ftable.puts("};\n");
+ ftable.puts("YYNPROD: con " + string nprod + ";\n");
+ ftable.puts("YYPRIVATE: con " + string PRIVATE + ";\n");
+ ftable.puts("yytoknames: array of string;\n");
+ ftable.puts("yystates: array of string;\n");
+ if(yydebug != nil){
+ ftable.puts("include \"y.debug\";\n");
+ ftable.puts("yydebug: con " + yydebug + ";\n");
+ }else{
+ ftable.puts("yydebug: con 0;\n");
+ }
+}
+
+#
+# decide a shift/reduce conflict by precedence.
+# r is a rule number, t a token number
+# the conflict is in state s
+# temp1[t] is changed to reflect the action
+#
+precftn(r, t, s: int)
+{
+ action: int;
+
+ lp := levprd[r];
+ lt := toklev[t];
+ if(PLEVEL(lt) == 0 || PLEVEL(lp) == 0) {
+
+ # conflict
+ if(foutput != nil)
+ foutput.puts(
+ "\n" + string s + ": shift/reduce conflict (shift "
+ + string temp1[t] + "(" + string PLEVEL(lt) + "), red'n "
+ + string r + "(" + string PLEVEL(lp) + ")) on " + symnam(t));
+ zzsrconf++;
+ return;
+ }
+ if(PLEVEL(lt) == PLEVEL(lp))
+ action = ASSOC(lt);
+ else if(PLEVEL(lt) > PLEVEL(lp))
+ action = RASC; # shift
+ else
+ action = LASC; # reduce
+ case action{
+ BASC => # error action
+ temp1[t] = ERRCODE;
+ LASC => # reduce
+ temp1[t] = -r;
+ }
+}
+
+#
+# output state i
+# temp1 has the actions, lastred the default
+#
+wract(i: int)
+{
+ p, p1: int;
+
+ # find the best choice for lastred
+ lastred = 0;
+ ntimes := 0;
+ for(j:=0; j<=ntokens; j++) {
+ if(temp1[j] >= 0)
+ continue;
+ if(temp1[j]+lastred == 0)
+ continue;
+ # count the number of appearances of temp1[j]
+ count := 0;
+ tred := -temp1[j];
+ levprd[tred] |= REDFLAG;
+ for(p=0; p<=ntokens; p++)
+ if(temp1[p]+tred == 0)
+ count++;
+ if(count > ntimes) {
+ lastred = tred;
+ ntimes = count;
+ }
+ }
+
+ #
+ # for error recovery, arrange that, if there is a shift on the
+ # error recovery token, `error', that the default be the error action
+ #
+ if(temp1[2] > 0)
+ lastred = 0;
+
+ # clear out entries in temp1 which equal lastred
+ # count entries in optst table
+ n := 0;
+ for(p=0; p<=ntokens; p++) {
+ p1 = temp1[p];
+ if(p1+lastred == 0)
+ temp1[p] = p1 = 0;
+ if(p1 > 0 && p1 != ACCEPTCODE && p1 != ERRCODE)
+ n++;
+ }
+
+ wrstate(i);
+ defact[i] = lastred;
+ flag := 0;
+ os := array[n*2] of int;
+ n = 0;
+ for(p=0; p<=ntokens; p++) {
+ if((p1=temp1[p]) != 0) {
+ if(p1 < 0) {
+ p1 = -p1;
+ } else if(p1 == ACCEPTCODE) {
+ p1 = -1;
+ } else if(p1 == ERRCODE) {
+ p1 = 0;
+ } else {
+ os[n++] = p;
+ os[n++] = p1;
+ zzacent++;
+ continue;
+ }
+ if(flag++ == 0)
+ ftable.puts("-1, " + string i + ",\n");
+ ftable.puts("\t" + string p + ", " + string p1 + ",\n");
+ zzexcp++;
+ }
+ }
+ if(flag) {
+ defact[i] = -2;
+ ftable.puts("\t-2, " + string lastred + ",\n");
+ }
+ optst[i] = os;
+}
+
+#
+# writes state i
+#
+wrstate(i: int)
+{
+ j0, j1, u: int;
+ pp, qq: int;
+
+ if(fdebug != nil) {
+ if(lastred) {
+ fdebug.puts(" nil, #" + string i + "\n");
+ } else {
+ fdebug.puts(" \"");
+ qq = pstate[i+1];
+ for(pp=pstate[i]; pp<qq; pp++){
+ fdebug.puts(writem(statemem[pp].pitem));
+ fdebug.puts("\\n");
+ }
+ if(tystate[i] == MUSTLOOKAHEAD)
+ for(u = pstate[i+1] - pstate[i]; u < cwp; u++)
+ if(wsets[u].pitem.first < 0){
+ fdebug.puts(writem(wsets[u].pitem));
+ fdebug.puts("\\n");
+ }
+ fdebug.puts("\", #" + string i + "/\n");
+ }
+ }
+ if(foutput == nil)
+ return;
+ foutput.puts("\nstate " + string i + "\n");
+ qq = pstate[i+1];
+ for(pp=pstate[i]; pp<qq; pp++){
+ foutput.putc('\t');
+ foutput.puts(writem(statemem[pp].pitem));
+ foutput.putc('\n');
+ }
+ if(tystate[i] == MUSTLOOKAHEAD) {
+ # print out empty productions in closure
+ for(u = pstate[i+1] - pstate[i]; u < cwp; u++) {
+ if(wsets[u].pitem.first < 0) {
+ foutput.putc('\t');
+ foutput.puts(writem(wsets[u].pitem));
+ foutput.putc('\n');
+ }
+ }
+ }
+
+ # check for state equal to another
+ for(j0=0; j0<=ntokens; j0++)
+ if((j1=temp1[j0]) != 0) {
+ foutput.puts("\n\t" + symnam(j0) + " ");
+ # shift, error, or accept
+ if(j1 > 0) {
+ if(j1 == ACCEPTCODE)
+ foutput.puts("accept");
+ else if(j1 == ERRCODE)
+ foutput.puts("error");
+ else
+ foutput.puts("shift "+string j1);
+ } else
+ foutput.puts("reduce " + string -j1 + " (src line " + string rlines[-j1] + ")");
+ }
+
+ # output the final production
+ if(lastred)
+ foutput.puts("\n\t. reduce " + string lastred + " (src line " + string rlines[lastred] + ")\n\n");
+ else
+ foutput.puts("\n\t. error\n\n");
+
+ # now, output nonterminal actions
+ j1 = ntokens;
+ for(j0 = 1; j0 <= nnonter; j0++) {
+ j1++;
+ if(temp1[j1])
+ foutput.puts("\t" + symnam(j0+NTBASE) + " goto " + string temp1[j1] + "\n");
+ }
+}
+
+#
+# output the gotos for the nontermninals
+#
+go2out()
+{
+ for(i := 1; i <= nnonter; i++) {
+ go2gen(i);
+
+ # find the best one to make default
+ best := -1;
+ times := 0;
+
+ # is j the most frequent
+ for(j := 0; j < nstate; j++) {
+ if(tystate[j] == 0)
+ continue;
+ if(tystate[j] == best)
+ continue;
+
+ # is tystate[j] the most frequent
+ count := 0;
+ cbest := tystate[j];
+ for(k := j; k < nstate; k++)
+ if(tystate[k] == cbest)
+ count++;
+ if(count > times) {
+ best = cbest;
+ times = count;
+ }
+ }
+
+ # best is now the default entry
+ zzgobest += times-1;
+ n := 0;
+ for(j = 0; j < nstate; j++)
+ if(tystate[j] != 0 && tystate[j] != best)
+ n++;
+ goent := array[2*n+1] of int;
+ n = 0;
+ for(j = 0; j < nstate; j++)
+ if(tystate[j] != 0 && tystate[j] != best) {
+ goent[n++] = j;
+ goent[n++] = tystate[j];
+ zzgoent++;
+ }
+
+ # now, the default
+ if(best == -1)
+ best = 0;
+ zzgoent++;
+ goent[n] = best;
+ yypgo[i] = goent;
+ }
+}
+
+#
+# output the gotos for nonterminal c
+#
+go2gen(c: int)
+{
+ i, cc, p, q: int;
+
+ # first, find nonterminals with gotos on c
+ aryfil(temp1, nnonter+1, 0);
+ temp1[c] = 1;
+ work := 1;
+ while(work) {
+ work = 0;
+ for(i=0; i<nprod; i++) {
+ # cc is a nonterminal with a goto on c
+ cc = prdptr[i][1]-NTBASE;
+ if(cc >= 0 && temp1[cc] != 0) {
+ # thus, the left side of production i does too
+ cc = prdptr[i][0]-NTBASE;
+ if(temp1[cc] == 0) {
+ work = 1;
+ temp1[cc] = 1;
+ }
+ }
+ }
+ }
+
+ # now, we have temp1[c] = 1 if a goto on c in closure of cc
+ if(g2debug && foutput != nil) {
+ foutput.puts(nontrst[c].name);
+ foutput.puts(": gotos on ");
+ for(i=0; i<=nnonter; i++)
+ if(temp1[i]){
+ foutput.puts(nontrst[i].name);
+ foutput.putc(' ');
+ }
+ foutput.putc('\n');
+ }
+
+ # now, go through and put gotos into tystate
+ aryfil(tystate, nstate, 0);
+ for(i=0; i<nstate; i++) {
+ q = pstate[i+1];
+ for(p=pstate[i]; p<q; p++) {
+ if((cc = statemem[p].pitem.first) >= NTBASE) {
+ # goto on c is possible
+ if(temp1[cc-NTBASE]) {
+ tystate[i] = amem[indgo[i]+c];
+ break;
+ }
+ }
+ }
+ }
+}
+
+#
+# in order to free up the mem and amem arrays for the optimizer,
+# and still be able to output yyr1, etc., after the sizes of
+# the action array is known, we hide the nonterminals
+# derived by productions in levprd.
+#
+hideprod()
+{
+ j := 0;
+ levprd[0] = 0;
+ for(i:=1; i<nprod; i++) {
+ if(!(levprd[i] & REDFLAG)) {
+ j++;
+ if(foutput != nil) {
+ foutput.puts("Rule not reduced: ");
+ foutput.puts(writem(Pitem(prdptr[i], 0, 0, i)));
+ foutput.putc('\n');
+ }
+ }
+ levprd[i] = prdptr[i][0] - NTBASE;
+ }
+ if(j)
+ print("%d rules never reduced\n", j);
+}
+
+callopt()
+{
+ j, k, p, q: int;
+ v: array of int;
+
+ pgo = array[nnonter+1] of int;
+ pgo[0] = 0;
+ maxoff = 0;
+ maxspr = 0;
+ for(i := 0; i < nstate; i++) {
+ k = 32000;
+ j = 0;
+ v = optst[i];
+ q = len v;
+ for(p = 0; p < q; p += 2) {
+ if(v[p] > j)
+ j = v[p];
+ if(v[p] < k)
+ k = v[p];
+ }
+ # nontrivial situation
+ if(k <= j) {
+ # j is now the range
+# j -= k; # call scj
+ if(k > maxoff)
+ maxoff = k;
+ }
+ tystate[i] = q + 2*j;
+ if(j > maxspr)
+ maxspr = j;
+ }
+
+ # initialize ggreed table
+ ggreed = array[nnonter+1] of int;
+ for(i = 1; i <= nnonter; i++) {
+ ggreed[i] = 1;
+ j = 0;
+
+ # minimum entry index is always 0
+ v = yypgo[i];
+ q = len v - 1;
+ for(p = 0; p < q ; p += 2) {
+ ggreed[i] += 2;
+ if(v[p] > j)
+ j = v[p];
+ }
+ ggreed[i] = ggreed[i] + 2*j;
+ if(j > maxoff)
+ maxoff = j;
+ }
+
+ # now, prepare to put the shift actions into the amem array
+ for(i = 0; i < ACTSIZE; i++)
+ amem[i] = 0;
+ maxa = 0;
+ for(i = 0; i < nstate; i++) {
+ if(tystate[i] == 0 && adb > 1)
+ ftable.puts("State " + string i + ": null\n");
+ indgo[i] = YYFLAG1;
+ }
+ while((i = nxti()) != NOMORE)
+ if(i >= 0)
+ stin(i);
+ else
+ gin(-i);
+
+ # print amem array
+ if(adb > 2)
+ for(p = 0; p <= maxa; p += 10) {
+ ftable.puts(string p + " ");
+ for(i = 0; i < 10; i++)
+ ftable.puts(string amem[p+i] + " ");
+ ftable.putc('\n');
+ }
+
+ aoutput();
+ osummary();
+}
+
+#
+# finds the next i
+#
+nxti(): int
+{
+ max := 0;
+ maxi := 0;
+ for(i := 1; i <= nnonter; i++)
+ if(ggreed[i] >= max) {
+ max = ggreed[i];
+ maxi = -i;
+ }
+ for(i = 0; i < nstate; i++)
+ if(tystate[i] >= max) {
+ max = tystate[i];
+ maxi = i;
+ }
+ if(max == 0)
+ return NOMORE;
+ return maxi;
+}
+
+gin(i: int)
+{
+ s: int;
+
+ # enter gotos on nonterminal i into array amem
+ ggreed[i] = 0;
+
+ q := yypgo[i];
+ nq := len q - 1;
+ # now, find amem place for it
+nextgp: for(p := 0; p < ACTSIZE; p++) {
+ if(amem[p])
+ continue;
+ for(r := 0; r < nq; r += 2) {
+ s = p + q[r] + 1;
+ if(s > maxa){
+ maxa = s;
+ if(maxa >= ACTSIZE)
+ error("a array overflow");
+ }
+ if(amem[s])
+ continue nextgp;
+ }
+ # we have found amem spot
+ amem[p] = q[nq];
+ if(p > maxa)
+ maxa = p;
+ for(r = 0; r < nq; r += 2) {
+ s = p + q[r] + 1;
+ amem[s] = q[r+1];
+ }
+ pgo[i] = p;
+ if(adb > 1)
+ ftable.puts("Nonterminal " + string i + ", entry at " + string pgo[i] + "\n");
+ return;
+ }
+ error("cannot place goto " + string i + "\n");
+}
+
+stin(i: int)
+{
+ s: int;
+
+ tystate[i] = 0;
+
+ # enter state i into the amem array
+ q := optst[i];
+ nq := len q;
+ # find an acceptable place
+nextn: for(n := -maxoff; n < ACTSIZE; n++) {
+ flag := 0;
+ for(r := 0; r < nq; r += 2) {
+ s = q[r] + n;
+ if(s < 0 || s > ACTSIZE)
+ continue nextn;
+ if(amem[s] == 0)
+ flag++;
+ else if(amem[s] != q[r+1])
+ continue nextn;
+ }
+
+ # check the position equals another only if the states are identical
+ for(j:=0; j<nstate; j++) {
+ if(indgo[j] == n) {
+
+ # we have some disagreement
+ if(flag)
+ continue nextn;
+ if(nq == len optst[j]) {
+
+ # states are equal
+ indgo[i] = n;
+ if(adb > 1)
+ ftable.puts("State " + string i + ": entry at "
+ + string n + " equals state " + string j + "\n");
+ return;
+ }
+
+ # we have some disagreement
+ continue nextn;
+ }
+ }
+
+ for(r = 0; r < nq; r += 2) {
+ s = q[r] + n;
+ if(s > maxa)
+ maxa = s;
+ if(amem[s] != 0 && amem[s] != q[r+1])
+ error("clobber of a array, pos'n " + string s + ", by " + string q[r+1] + "");
+ amem[s] = q[r+1];
+ }
+ indgo[i] = n;
+ if(adb > 1)
+ ftable.puts("State " + string i + ": entry at " + string indgo[i] + "\n");
+ return;
+ }
+ error("Error; failure to place state " + string i + "\n");
+}
+
+#
+# this version is for limbo
+# write out the optimized parser
+#
+aoutput()
+{
+ ftable.puts("YYLAST:\tcon "+string (maxa+1)+";\n");
+ arout("yyact", amem, maxa+1);
+ arout("yypact", indgo, nstate);
+ arout("yypgo", pgo, nnonter+1);
+}
+
+#
+# put out other arrays, copy the parsers
+#
+others()
+{
+ finput = bufio->open(parser, Bufio->OREAD);
+ if(finput == nil)
+ error("cannot find parser " + parser);
+ arout("yyr1", levprd, nprod);
+ aryfil(temp1, nprod, 0);
+
+ #
+ #yyr2 is the number of rules for each production
+ #
+ for(i:=1; i<nprod; i++)
+ temp1[i] = len prdptr[i] - 2;
+ arout("yyr2", temp1, nprod);
+
+ aryfil(temp1, nstate, -1000);
+ for(i=0; i<=ntokens; i++)
+ for(j:=tstates[i]; j!=0; j=mstates[j])
+ temp1[j] = i;
+ for(i=0; i<=nnonter; i++)
+ for(j=ntstates[i]; j!=0; j=mstates[j])
+ temp1[j] = -i;
+ arout("yychk", temp1, nstate);
+ arout("yydef", defact, nstate);
+
+ # put out token translation tables
+ # table 1 has 0-256
+ aryfil(temp1, 256, 0);
+ c := 0;
+ for(i=1; i<=ntokens; i++) {
+ j = tokset[i].value;
+ if(j >= 0 && j < 256) {
+ if(temp1[j]) {
+ print("yacc bug -- cant have 2 different Ts with same value\n");
+ print(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name);
+ nerrors++;
+ }
+ temp1[j] = i;
+ if(j > c)
+ c = j;
+ }
+ }
+ for(i = 0; i <= c; i++)
+ if(temp1[i] == 0)
+ temp1[i] = YYLEXUNK;
+ arout("yytok1", temp1, c+1);
+
+ # table 2 has PRIVATE-PRIVATE+256
+ aryfil(temp1, 256, 0);
+ c = 0;
+ for(i=1; i<=ntokens; i++) {
+ j = tokset[i].value - PRIVATE;
+ if(j >= 0 && j < 256) {
+ if(temp1[j]) {
+ print("yacc bug -- cant have 2 different Ts with same value\n");
+ print(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name);
+ nerrors++;
+ }
+ temp1[j] = i;
+ if(j > c)
+ c = j;
+ }
+ }
+ arout("yytok2", temp1, c+1);
+
+ # table 3 has everything else
+ ftable.puts("yytok3 := array[] of {\n");
+ c = 0;
+ for(i=1; i<=ntokens; i++) {
+ j = tokset[i].value;
+ if(j >= 0 && j < 256)
+ continue;
+ if(j >= PRIVATE && j < 256+PRIVATE)
+ continue;
+
+ ftable.puts(sprint("%4d,%4d,", j, i));
+ c++;
+ if(c%5 == 0)
+ ftable.putc('\n');
+ }
+ ftable.puts(sprint("%4d\n};\n", 0));
+
+ # copy parser text
+ while((c=finput.getc()) != Bufio->EOF) {
+ if(c == '$') {
+ if((c = finput.getc()) != 'A')
+ ftable.putc('$');
+ else { # copy actions
+ if(codehead == nil)
+ ftable.puts("* => ;");
+ else
+ dumpcode(-1);
+ c = finput.getc();
+ }
+ }
+ ftable.putc(c);
+ }
+ ftable.close();
+}
+
+arout(s: string, v: array of int, n: int)
+{
+ ftable.puts(s+" := array[] of {");
+ for(i := 0; i < n; i++) {
+ if(i%10 == 0)
+ ftable.putc('\n');
+ ftable.puts(sprint("%4d", v[i]));
+ ftable.putc(',');
+ }
+ ftable.puts("\n};\n");
+}
+
+#
+# output the summary on y.output
+#
+summary()
+{
+ if(foutput != nil) {
+ foutput.puts("\n" + string ntokens + " terminals, " + string(nnonter + 1) + " nonterminals\n");
+ foutput.puts("" + string nprod + " grammar rules, " + string nstate + "/" + string NSTATES + " states\n");
+ foutput.puts("" + string zzsrconf + " shift/reduce, " + string zzrrconf + " reduce/reduce conflicts reported\n");
+ foutput.puts("" + string len wsets + " working sets used\n");
+ foutput.puts("memory: parser " + string memp + "/" + string ACTSIZE + "\n");
+ foutput.puts(string (zzclose - 2*nstate) + " extra closures\n");
+ foutput.puts(string zzacent + " shift entries, " + string zzexcp + " exceptions\n");
+ foutput.puts(string zzgoent + " goto entries\n");
+ foutput.puts(string zzgobest + " entries saved by goto default\n");
+ }
+ if(zzsrconf != 0 || zzrrconf != 0) {
+ print("\nconflicts: ");
+ if(zzsrconf)
+ print("%d shift/reduce", zzsrconf);
+ if(zzsrconf && zzrrconf)
+ print(", ");
+ if(zzrrconf)
+ print("%d reduce/reduce", zzrrconf);
+ print("\n");
+ }
+ if(fdefine != nil)
+ fdefine.close();
+}
+
+#
+# write optimizer summary
+#
+osummary()
+{
+ if(foutput == nil)
+ return;
+ i := 0;
+ for(p := maxa; p >= 0; p--)
+ if(amem[p] == 0)
+ i++;
+
+ foutput.puts("Optimizer space used: output " + string (maxa+1) + "/" + string ACTSIZE + "\n");
+ foutput.puts(string(maxa+1) + " table entries, " + string i + " zero\n");
+ foutput.puts("maximum spread: " + string maxspr + ", maximum offset: " + string maxoff + "\n");
+}
+
+#
+# copies and protects "'s in q
+#
+chcopy(q: string): string
+{
+ s := "";
+ j := 0;
+ for(i := 0; i < len q; i++) {
+ if(q[i] == '"') {
+ s += q[j:i] + "\\";
+ j = i;
+ }
+ }
+ return s + q[j:i];
+}
+
+usage()
+{
+ fprint(stderr, "usage: yacc [-vdm] [-Dn] [-o output] [-s stem] file\n");
+ raise "fail:usage";
+}
+
+bitset(set: Lkset, bit: int): int
+{
+ return set[bit>>5] & (1<<(bit&31));
+}
+
+setbit(set: Lkset, bit: int): int
+{
+ return set[bit>>5] |= (1<<(bit&31));
+}
+
+mkset(): Lkset
+{
+ return array[tbitset] of {* => 0};
+}
+
+#
+# set a to the union of a and b
+# return 1 if b is not a subset of a, 0 otherwise
+#
+setunion(a, b: array of int): int
+{
+ sub := 0;
+ for(i:=0; i<tbitset; i++) {
+ x := a[i];
+ y := x | b[i];
+ a[i] = y;
+ if(y != x)
+ sub = 1;
+ }
+ return sub;
+}
+
+prlook(p: Lkset)
+{
+ if(p == nil){
+ foutput.puts("\tNULL");
+ return;
+ }
+ foutput.puts(" { ");
+ for(j:=0; j<=ntokens; j++){
+ if(bitset(p, j)){
+ foutput.puts(symnam(j));
+ foutput.putc(' ');
+ }
+ }
+ foutput.putc('}');
+}
+
+#
+# utility routines
+#
+isdigit(c: int): int
+{
+ return c >= '0' && c <= '9';
+}
+
+isword(c: int): int
+{
+ return c >= 16ra0 || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
+}
+
+mktemp(t: string): string
+{
+ return t;
+}
+
+#
+# arg processing
+#
+Arg.init(argv: list of string): ref Arg
+{
+ if(argv != nil)
+ argv = tl argv;
+ return ref Arg(argv, 0, "");
+}
+
+Arg.opt(arg: self ref Arg): int
+{
+ opts := arg.opts;
+ if(opts != ""){
+ arg.c = opts[0];
+ arg.opts = opts[1:];
+ return arg.c;
+ }
+ argv := arg.argv;
+ if(argv == nil)
+ return arg.c = 0;
+ opts = hd argv;
+ if(len opts < 2 || opts[0] != '-')
+ return arg.c = 0;
+ arg.argv = tl argv;
+ if(opts == "--")
+ return arg.c = 0;
+ arg.opts = opts[2:];
+ return arg.c = opts[1];
+}
+
+Arg.arg(arg: self ref Arg): string
+{
+ s := arg.opts;
+ arg.opts = "";
+ if(s != "")
+ return s;
+ argv := arg.argv;
+ if(argv == nil)
+ return "";
+ arg.argv = tl argv;
+ return hd argv;
+}
diff --git a/appl/cmd/zeros.b b/appl/cmd/zeros.b
new file mode 100644
index 00000000..9708fca3
--- /dev/null
+++ b/appl/cmd/zeros.b
@@ -0,0 +1,68 @@
+implement Zeros;
+
+include "sys.m";
+ sys: Sys;
+include "arg.m";
+ arg: Arg;
+include "string.m";
+ str: String;
+include "keyring.m";
+include "security.m";
+ random: Random;
+
+include "draw.m";
+
+Zeros: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ z: array of byte;
+ i: int;
+ sys = load Sys Sys->PATH;
+ arg = load Arg Arg->PATH;
+ str = load String String->PATH;
+
+ if(sys == nil || arg == nil)
+ return;
+
+ bs := 0;
+ n := 0;
+ val := 0;
+ rflag := 0;
+ arg->init(argv);
+ while ((c := arg->opt()) != 0)
+ case c {
+ 'r' => rflag = 1;
+ 'v' => (val, nil) = str->toint(arg->arg(), 16);
+ * => raise sys->sprint("fail:unknown option (%c)\n", c);
+ }
+ argv = arg->argv();
+ if(len argv >= 1)
+ bs = int hd argv;
+ else
+ bs = 1;
+ if (len argv >= 2)
+ n = int hd tl argv;
+ else
+ n = 1;
+ if(bs == 0 || n == 0) {
+ sys->fprint(sys->fildes(2), "usage: zeros [-r] [-v value] blocksize [number]\n");
+ raise "fail:usage";
+ }
+ if (rflag) {
+ random = load Random Random->PATH;
+ if (random == nil)
+ raise "fail:no security module\n";
+ z = random->randombuf(random->NotQuiteRandom, bs);
+ }
+ else {
+ z = array[bs] of byte;
+ for(i=0;i<bs;i++)
+ z[i] = byte val;
+ }
+ for(i=0;i<n;i++)
+ sys->write(sys->fildes(1), z, bs);
+}